Skip to content

Commit 6f6cd04

Browse files
authored
refactor: remove parking_lot dependency and refine feature selections (#1543)
- refine dependency features and versions in Cargo.toml files - switch from parking_lot to std sync primitives - remove dashmap dependency and use DefaultKeyedStateStore - update crates Replace parking_lot with std::sync::{Mutex, RwLock, Condvar} throughout the codebase. Update dependencies and code to use poisoning-aware locks, adding explicit panic messages where necessary. Update governor to use DashMapStateStore for rate limiting.
1 parent df5f957 commit 6f6cd04

File tree

16 files changed

+637
-340
lines changed

16 files changed

+637
-340
lines changed

Cargo.lock

Lines changed: 290 additions & 224 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ tokio = { version = "1", features = [
179179
"macros",
180180
"signal",
181181
"sync",
182-
"parking_lot",
183182
"process",
184183
] }
185184
url = "2.2"

audio/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,11 @@ librespot-core = { version = "0.7.1", path = "../core", default-features = false
2323
aes = "0.8"
2424
bytes = "1"
2525
ctr = "0.9"
26-
futures-util = "0.3"
26+
futures-util = { version = "0.3", default-features = false, features = ["std"] }
2727
http-body-util = "0.1"
2828
hyper = { version = "1.6", features = ["http1", "http2"] }
2929
hyper-util = { version = "0.1", features = ["client", "http2"] }
3030
log = "0.4"
31-
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
3231
tempfile = "3"
3332
thiserror = "2"
34-
tokio = { version = "1", features = ["macros", "parking_lot", "sync"] }
33+
tokio = { version = "1", features = ["macros", "sync"] }

audio/src/fetch/mod.rs

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ use std::{
88
Arc, OnceLock,
99
atomic::{AtomicBool, AtomicUsize, Ordering},
1010
},
11+
sync::{Condvar, Mutex},
1112
time::Duration,
1213
};
1314

1415
use futures_util::{StreamExt, TryFutureExt, future::IntoStream};
1516
use hyper::{Response, StatusCode, body::Incoming, header::CONTENT_RANGE};
1617
use hyper_util::client::legacy::ResponseFuture;
17-
use parking_lot::{Condvar, Mutex};
18+
1819
use tempfile::NamedTempFile;
1920
use thiserror::Error;
2021
use tokio::sync::{Semaphore, mpsc, oneshot};
@@ -27,6 +28,8 @@ use crate::range_set::{Range, RangeSet};
2728

2829
pub type AudioFileResult = Result<(), librespot_core::Error>;
2930

31+
const DOWNLOAD_STATUS_POISON_MSG: &str = "audio download status mutex should not be poisoned";
32+
3033
#[derive(Error, Debug)]
3134
pub enum AudioFileError {
3235
#[error("other end of channel disconnected")]
@@ -163,7 +166,10 @@ impl StreamLoaderController {
163166

164167
pub fn range_available(&self, range: Range) -> bool {
165168
if let Some(ref shared) = self.stream_shared {
166-
let download_status = shared.download_status.lock();
169+
let download_status = shared
170+
.download_status
171+
.lock()
172+
.expect(DOWNLOAD_STATUS_POISON_MSG);
167173

168174
range.length
169175
<= download_status
@@ -214,19 +220,24 @@ impl StreamLoaderController {
214220
self.fetch(range);
215221

216222
if let Some(ref shared) = self.stream_shared {
217-
let mut download_status = shared.download_status.lock();
223+
let mut download_status = shared
224+
.download_status
225+
.lock()
226+
.expect(DOWNLOAD_STATUS_POISON_MSG);
218227
let download_timeout = AudioFetchParams::get().download_timeout;
219228

220229
while range.length
221230
> download_status
222231
.downloaded
223232
.contained_length_from_value(range.start)
224233
{
225-
if shared
234+
let (new_download_status, wait_result) = shared
226235
.cond
227-
.wait_for(&mut download_status, download_timeout)
228-
.timed_out()
229-
{
236+
.wait_timeout(download_status, download_timeout)
237+
.expect(DOWNLOAD_STATUS_POISON_MSG);
238+
239+
download_status = new_download_status;
240+
if wait_result.timed_out() {
230241
return Err(AudioFileError::WaitTimeout.into());
231242
}
232243

@@ -558,7 +569,11 @@ impl Read for AudioFileStreaming {
558569
let mut ranges_to_request = RangeSet::new();
559570
ranges_to_request.add_range(&Range::new(offset, length_to_request));
560571

561-
let mut download_status = self.shared.download_status.lock();
572+
let mut download_status = self
573+
.shared
574+
.download_status
575+
.lock()
576+
.expect(DOWNLOAD_STATUS_POISON_MSG);
562577

563578
ranges_to_request.subtract_range_set(&download_status.downloaded);
564579
ranges_to_request.subtract_range_set(&download_status.requested);
@@ -571,12 +586,14 @@ impl Read for AudioFileStreaming {
571586

572587
let download_timeout = AudioFetchParams::get().download_timeout;
573588
while !download_status.downloaded.contains(offset) {
574-
if self
589+
let (new_download_status, wait_result) = self
575590
.shared
576591
.cond
577-
.wait_for(&mut download_status, download_timeout)
578-
.timed_out()
579-
{
592+
.wait_timeout(download_status, download_timeout)
593+
.expect(DOWNLOAD_STATUS_POISON_MSG);
594+
595+
download_status = new_download_status;
596+
if wait_result.timed_out() {
580597
return Err(io::Error::new(
581598
io::ErrorKind::TimedOut,
582599
Error::deadline_exceeded(AudioFileError::WaitTimeout),
@@ -619,6 +636,7 @@ impl Seek for AudioFileStreaming {
619636
.shared
620637
.download_status
621638
.lock()
639+
.expect(DOWNLOAD_STATUS_POISON_MSG)
622640
.downloaded
623641
.contains(requested_pos as usize);
624642

audio/src/fetch/receive.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ enum ReceivedData {
3333
}
3434

3535
const ONE_SECOND: Duration = Duration::from_secs(1);
36+
const DOWNLOAD_STATUS_POISON_MSG: &str = "audio download status mutex should not be poisoned";
3637

3738
async fn receive_data(
3839
shared: Arc<AudioFileShared>,
@@ -124,7 +125,10 @@ async fn receive_data(
124125
if bytes_remaining > 0 {
125126
{
126127
let missing_range = Range::new(offset, bytes_remaining);
127-
let mut download_status = shared.download_status.lock();
128+
let mut download_status = shared
129+
.download_status
130+
.lock()
131+
.expect(DOWNLOAD_STATUS_POISON_MSG);
128132
download_status.requested.subtract_range(&missing_range);
129133
shared.cond.notify_all();
130134
}
@@ -189,7 +193,11 @@ impl AudioFileFetch {
189193
// The iteration that follows spawns streamers fast, without awaiting them,
190194
// so holding the lock for the entire scope of this function should be faster
191195
// then locking and unlocking multiple times.
192-
let mut download_status = self.shared.download_status.lock();
196+
let mut download_status = self
197+
.shared
198+
.download_status
199+
.lock()
200+
.expect(DOWNLOAD_STATUS_POISON_MSG);
193201

194202
ranges_to_request.subtract_range_set(&download_status.downloaded);
195203
ranges_to_request.subtract_range_set(&download_status.requested);
@@ -227,7 +235,11 @@ impl AudioFileFetch {
227235
let mut missing_data = RangeSet::new();
228236
missing_data.add_range(&Range::new(0, self.shared.file_size));
229237
{
230-
let download_status = self.shared.download_status.lock();
238+
let download_status = self
239+
.shared
240+
.download_status
241+
.lock()
242+
.expect(DOWNLOAD_STATUS_POISON_MSG);
231243
missing_data.subtract_range_set(&download_status.downloaded);
232244
missing_data.subtract_range_set(&download_status.requested);
233245
}
@@ -349,7 +361,11 @@ impl AudioFileFetch {
349361
let received_range = Range::new(data.offset, data.data.len());
350362

351363
let full = {
352-
let mut download_status = self.shared.download_status.lock();
364+
let mut download_status = self
365+
.shared
366+
.download_status
367+
.lock()
368+
.expect(DOWNLOAD_STATUS_POISON_MSG);
353369
download_status.downloaded.add_range(&received_range);
354370
self.shared.cond.notify_all();
355371

@@ -415,7 +431,10 @@ pub(super) async fn audio_file_fetch(
415431
initial_request.offset + initial_request.length,
416432
);
417433

418-
let mut download_status = shared.download_status.lock();
434+
let mut download_status = shared
435+
.download_status
436+
.lock()
437+
.expect(DOWNLOAD_STATUS_POISON_MSG);
419438
download_status.requested.add_range(&requested_range);
420439
}
421440

@@ -466,7 +485,11 @@ pub(super) async fn audio_file_fetch(
466485

467486
if fetch.shared.is_download_streaming() && fetch.has_download_slots_available() {
468487
let bytes_pending: usize = {
469-
let download_status = fetch.shared.download_status.lock();
488+
let download_status = fetch
489+
.shared
490+
.download_status
491+
.lock()
492+
.expect(DOWNLOAD_STATUS_POISON_MSG);
470493

471494
download_status
472495
.requested

connect/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ librespot-core = { version = "0.7.1", path = "../core", default-features = false
2222
librespot-playback = { version = "0.7.1", path = "../playback", default-features = false }
2323
librespot-protocol = { version = "0.7.1", path = "../protocol", default-features = false }
2424

25-
futures-util = "0.3"
25+
futures-util = { version = "0.3", default-features = false, features = ["std"] }
2626
log = "0.4"
2727
protobuf = "3.7"
2828
rand = { version = "0.9", default-features = false, features = ["small_rng"] }
2929
serde_json = "1.0"
3030
thiserror = "2"
31-
tokio = { version = "1", features = ["macros", "parking_lot", "sync"] }
32-
tokio-stream = "0.1"
33-
uuid = { version = "1.18", features = ["v4"] }
31+
tokio = { version = "1", features = ["macros", "sync"] }
32+
tokio-stream = { version = "0.1", default-features = false }
33+
uuid = { version = "1.18", default-features = false, features = ["v4"] }

core/Cargo.toml

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,12 @@ data-encoding = "2.9"
5151
flate2 = "1.1"
5252
form_urlencoded = "1.2"
5353
futures-core = "0.3"
54-
futures-util = { version = "0.3", features = [
54+
futures-util = { version = "0.3", default-features = false, features = [
5555
"alloc",
5656
"bilock",
57-
"sink",
5857
"unstable",
5958
] }
60-
governor = { version = "0.10", default-features = false, features = [
61-
"std",
62-
"jitter",
63-
] }
59+
governor = { version = "0.10", default-features = false, features = ["std"] }
6460
hmac = "0.12"
6561
httparse = "1.10"
6662
http = "1.3"
@@ -84,14 +80,13 @@ num-bigint = "0.4"
8480
num-derive = "0.4"
8581
num-integer = "0.1"
8682
num-traits = "0.2"
87-
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
8883
pbkdf2 = { version = "0.12", default-features = false, features = ["hmac"] }
8984
pin-project-lite = "0.2"
9085
priority-queue = "2.5"
9186
protobuf = "3.7"
9287
protobuf-json-mapping = "3.7"
9388
quick-xml = { version = "0.38", features = ["serialize"] }
94-
rand = "0.9"
89+
rand = { version = "0.9", default-features = false, features = ["thread_rng"] }
9590
rsa = "0.9"
9691
serde = { version = "1.0", features = ["derive"] }
9792
serde_json = "1.0"
@@ -104,23 +99,22 @@ tokio = { version = "1", features = [
10499
"io-util",
105100
"macros",
106101
"net",
107-
"parking_lot",
108102
"rt",
109103
"sync",
110104
"time",
111105
] }
112-
tokio-stream = "0.1"
106+
tokio-stream = { version = "0.1", default-features = false }
113107
tokio-tungstenite = { version = "0.27", default-features = false }
114-
tokio-util = { version = "0.7", features = ["codec"] }
108+
tokio-util = { version = "0.7", default-features = false }
115109
url = "2"
116110
uuid = { version = "1", default-features = false, features = ["v4"] }
117111

118112
[build-dependencies]
119-
rand = "0.9"
113+
rand = { version = "0.9", default-features = false, features = ["thread_rng"] }
120114
rand_distr = "0.5"
121115
vergen-gitcl = { version = "1.0", default-features = false, features = [
122116
"build",
123117
] }
124118

125119
[dev-dependencies]
126-
tokio = { version = "1", features = ["macros", "parking_lot"] }
120+
tokio = { version = "1", features = ["macros"] }

core/src/cache.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@ use std::{
44
fs::{self, File},
55
io::{self, Read, Write},
66
path::{Path, PathBuf},
7-
sync::Arc,
7+
sync::{Arc, Mutex},
88
time::SystemTime,
99
};
1010

11-
use parking_lot::Mutex;
1211
use priority_queue::PriorityQueue;
1312
use thiserror::Error;
1413

1514
use crate::{Error, FileId, authentication::Credentials, error::ErrorKind};
1615

16+
const CACHE_LIMITER_POISON_MSG: &str = "cache limiter mutex should not be poisoned";
17+
1718
#[derive(Debug, Error)]
1819
pub enum CacheError {
1920
#[error("audio cache location is not configured")]
@@ -189,15 +190,24 @@ impl FsSizeLimiter {
189190
}
190191

191192
fn add(&self, file: &Path, size: u64) {
192-
self.limiter.lock().add(file, size, SystemTime::now())
193+
self.limiter
194+
.lock()
195+
.expect(CACHE_LIMITER_POISON_MSG)
196+
.add(file, size, SystemTime::now())
193197
}
194198

195199
fn touch(&self, file: &Path) -> bool {
196-
self.limiter.lock().update(file, SystemTime::now())
200+
self.limiter
201+
.lock()
202+
.expect(CACHE_LIMITER_POISON_MSG)
203+
.update(file, SystemTime::now())
197204
}
198205

199206
fn remove(&self, file: &Path) -> bool {
200-
self.limiter.lock().remove(file)
207+
self.limiter
208+
.lock()
209+
.expect(CACHE_LIMITER_POISON_MSG)
210+
.remove(file)
201211
}
202212

203213
fn prune_internal<F: FnMut() -> Option<PathBuf>>(mut pop: F) -> Result<(), Error> {
@@ -232,7 +242,7 @@ impl FsSizeLimiter {
232242
}
233243

234244
fn prune(&self) -> Result<(), Error> {
235-
Self::prune_internal(|| self.limiter.lock().pop())
245+
Self::prune_internal(|| self.limiter.lock().expect(CACHE_LIMITER_POISON_MSG).pop())
236246
}
237247

238248
fn new(path: &Path, limit: u64) -> Result<Self, Error> {

core/src/component.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1+
pub(crate) const COMPONENT_POISON_MSG: &str = "component mutex should not be poisoned";
2+
13
macro_rules! component {
24
($name:ident : $inner:ident { $($key:ident : $ty:ty = $value:expr,)* }) => {
35
#[derive(Clone)]
4-
pub struct $name(::std::sync::Arc<($crate::session::SessionWeak, ::parking_lot::Mutex<$inner>)>);
6+
pub struct $name(::std::sync::Arc<($crate::session::SessionWeak, ::std::sync::Mutex<$inner>)>);
57
impl $name {
68
#[allow(dead_code)]
79
pub(crate) fn new(session: $crate::session::SessionWeak) -> $name {
810
debug!(target:"librespot::component", "new {}", stringify!($name));
911

10-
$name(::std::sync::Arc::new((session, ::parking_lot::Mutex::new($inner {
12+
$name(::std::sync::Arc::new((session, ::std::sync::Mutex::new($inner {
1113
$($key : $value,)*
1214
}))))
1315
}
1416

1517
#[allow(dead_code)]
1618
fn lock<F: FnOnce(&mut $inner) -> R, R>(&self, f: F) -> R {
17-
let mut inner = (self.0).1.lock();
19+
let mut inner = (self.0).1.lock()
20+
.expect($crate::component::COMPONENT_POISON_MSG);
1821
f(&mut inner)
1922
}
2023

0 commit comments

Comments
 (0)