Skip to content

Commit 0461e33

Browse files
committed
MTP: fix event loop lock contention
- Upgrade to mtp-rs 0.7.0 (`MtpDevice` is now `Clone`, `next_event()` concurrency documented) - Clone `MtpDevice` at event loop startup and poll events on the clone without holding Cmdr's device mutex. `next_event()` reads the USB interrupt endpoint, which is independent from bulk endpoints used by file operations. - Previously the event loop held the device lock for up to 5s per poll cycle (98% duty cycle), starving copy/move/scan operations. Now file operations get uncontested lock access.
1 parent 4e1efab commit 0461e33

3 files changed

Lines changed: 25 additions & 36 deletions

File tree

Cargo.lock

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

apps/desktop/src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ mime_guess = "2"
107107

108108
[target.'cfg(any(target_os = "macos", target_os = "linux"))'.dependencies]
109109
# MTP (Android device) support via pure Rust implementation
110-
mtp-rs = "0.6.1"
110+
mtp-rs = "0.7.0"
111111
# USB hotplug detection for MTP device watcher
112112
nusb = "0.2.3"
113113
bytes = "1"

apps/desktop/src-tauri/src/mtp/connection/event_loop.rs

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,16 @@ impl MtpConnectionManager {
3838
tokio::spawn(async move {
3939
let mut shutdown_rx = shutdown_tx.subscribe();
4040

41+
// Clone the MtpDevice for event polling. MtpDevice is cheaply cloneable (Arc
42+
// internally) and next_event() reads from the USB interrupt endpoint, which is
43+
// independent from the bulk endpoints used by file operations. This lets us poll
44+
// for events WITHOUT holding Cmdr's device mutex, so file operations (copy, move,
45+
// scan) aren't blocked by event polling.
46+
let event_device: MtpDevice = device.lock().await.clone();
47+
4148
debug!("MTP event loop started for device: {}", device_id_clone);
4249

4350
loop {
44-
// Try to acquire the device lock with a short timeout to check for shutdown
4551
let poll_result = tokio::select! {
4652
biased;
4753

@@ -51,26 +57,9 @@ impl MtpConnectionManager {
5157
break;
5258
}
5359

54-
// Poll for next event (mtp-rs 0.4+ awaits indefinitely, so we
55-
// wrap in a timeout to release the device lock periodically)
56-
result = async {
57-
// Try to lock the device - use a timeout to prevent deadlocks
58-
match tokio::time::timeout(Duration::from_secs(5), device.lock()).await {
59-
Ok(guard) => {
60-
// Poll for event with timeout so the lock is released periodically,
61-
// allowing other MTP operations to proceed
62-
tokio::time::timeout(Duration::from_secs(5), guard.next_event())
63-
.await
64-
.unwrap_or(Err(mtp_rs::Error::Timeout))
65-
}
66-
Err(_) => {
67-
// Timeout acquiring lock - device might be busy with another operation
68-
// Return timeout to continue polling
69-
Err(mtp_rs::Error::Timeout)
70-
}
71-
}
72-
} => {
73-
result
60+
// Poll for next event — no device lock needed (interrupt endpoint is independent)
61+
result = tokio::time::timeout(Duration::from_secs(5), event_device.next_event()) => {
62+
result.unwrap_or(Err(mtp_rs::Error::Timeout))
7463
}
7564
};
7665

0 commit comments

Comments
 (0)