Skip to content

Commit caaddf3

Browse files
committed
Defer UMS device-list updates to the main thread
libusbhsfs's populate callback runs on its USB-event thread. The previous code mutated context.filesystems / cur_fs directly from that thread, racing with main-thread reads from UI code. Stash the new device list under a mutex; have the main loops (menu_loop, video_loop) drain and apply pending changes once per frame so context.filesystems stays single-threaded for readers. Also handle USB unplug during playback: when the filesystem hosting the currently-playing file disappears, async-stop mpv and break out of video_loop with EIO so the user falls back to the menu cleanly. (Note: this does not eliminate crashes when mpv's demuxer thread is mid-read at the moment of physical removal -- that race is inside libusbhsfs and would require upstream changes to fix.)
1 parent 79a3eb2 commit caaddf3

1 file changed

Lines changed: 36 additions & 1 deletion

File tree

src/main.cpp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
// You should have received a copy of the GNU General Public License
1616
// along with SwitchWave. If not, see <http://www.gnu.org/licenses/>.
1717

18+
#include <cerrno>
1819
#include <cstdio>
1920
#include <chrono>
2021
#include <filesystem>
22+
#include <mutex>
23+
#include <optional>
2124
#include <string_view>
2225
#include <thread>
2326

@@ -137,8 +140,26 @@ void mpv_presetup() {
137140
std::printf("Dumped standard font\n");
138141
}
139142

143+
// libusbhsfs invokes the populate callback on its USB-event thread.
144+
// Stash the new device list under a mutex; the main thread applies
145+
// the change so context.filesystems / cur_fs stay single-threaded.
146+
std::mutex g_ums_pending_mtx;
147+
std::optional<std::vector<sw::fs::UmsController::Device>> g_ums_pending;
148+
140149
void ums_devices_changed_cb(const std::vector<sw::fs::UmsController::Device> &devices, void *user) {
141-
auto &context = *static_cast<sw::Context *>(user);
150+
auto lk = std::scoped_lock(g_ums_pending_mtx);
151+
g_ums_pending = devices;
152+
}
153+
154+
void apply_pending_ums_changes(sw::Context &context) {
155+
std::vector<sw::fs::UmsController::Device> devices;
156+
{
157+
auto lk = std::scoped_lock(g_ums_pending_mtx);
158+
if (!g_ums_pending)
159+
return;
160+
devices = std::move(*g_ums_pending);
161+
g_ums_pending.reset();
162+
}
142163

143164
// Remove unmounted devices
144165
bool removed_cur = false;
@@ -187,6 +208,8 @@ int menu_loop(sw::Renderer &renderer, sw::Context &context) {
187208
break;
188209
}
189210

211+
apply_pending_ums_changes(context);
212+
190213
padUpdate(&g_pad);
191214
auto has_touches = hidGetTouchScreenStates(&g_touch_state, 1);
192215
ImGui::nx::newFrame(&g_pad, has_touches ? &g_touch_state : nullptr);
@@ -283,6 +306,18 @@ int video_loop(sw::Renderer &renderer, sw::Context &context) {
283306
break;
284307
}
285308

309+
apply_pending_ums_changes(context);
310+
311+
// If the filesystem hosting the playing file was unplugged
312+
// (e.g. USB key removed mid-playback), stop mpv and bail out
313+
// of the player loop so it can't keep reading a dead device.
314+
if (!context.cur_file.empty() &&
315+
!context.get_filesystem(sw::fs::Path::mountpoint(context.cur_file))) {
316+
lmpv.command_async("stop");
317+
context.set_error(-EIO);
318+
break;
319+
}
320+
286321
padUpdate(&g_pad);
287322
auto has_touches = hidGetTouchScreenStates(&g_touch_state, 1);
288323
ImGui::nx::newFrame(&g_pad, has_touches ? &g_touch_state : nullptr);

0 commit comments

Comments
 (0)