Crashes on Switch platform with broken pthread_detach() #149

Open
opened 2026-08-19 00:09:20 -05:00 by nyanpasu64 · 3 comments
nyanpasu64 commented 2026-08-19 00:09:20 -05:00 (Migrated from github.com)

I found that interrupting a connection to the server on the unofficial Switch client results in a crash when returning to launcher or reopening Moonlight. I traced this to a failed call to pthread_detach() leaving the thread handle open, which causes a crash after switching apps.

libnx does not contain an implementation of newlib::pthread_detach; the source says:

/* Unsupported
int __syscall_thread_detach(struct __pthread_t *thread)
{
}
*/

The ReSwitched server says it's not possible to implement thread detaching on the Switch's constrained (IMO anemic) runtime environment, especially when you need to support multiple applications chainloading in the same address space and OS process.

The solution (workaround) I came up with for my fork of the client was to call the termination callback synchronously, but it only sets a flag which gets read by the main thread loop. Obviously this is incompatible with the library's current programming model. Do you have any ideas on how to improve functionality?

I found that interrupting a connection to the server on the [unofficial Switch client](https://github.com/XITRIX/Moonlight-Switch) results in a crash when returning to launcher or reopening Moonlight. I traced this to a failed call to pthread_detach() leaving the thread handle open, which causes a crash after switching apps. libnx does not contain an implementation of newlib::pthread_detach; the source says: ```c /* Unsupported int __syscall_thread_detach(struct __pthread_t *thread) { } */ ``` The ReSwitched server says it's not possible to implement thread detaching on the Switch's constrained (IMO anemic) runtime environment, especially when you need to support multiple applications chainloading in the same address space and OS process. The solution (workaround) I came up with for my fork of the client was to call the termination callback synchronously, but it only sets a flag which gets read by the main thread loop. Obviously this is incompatible with the library's [current programming model](https://github.com/moonlight-stream/moonlight-common-c/blob/874ac9548f1bd6f095ef2b435c42cdde460e7821/src/Platform.c#L199-L209). Do you have any ideas on how to improve functionality?
nyanpasu64 commented 2026-08-19 15:48:42 -05:00 (Migrated from github.com)

theoretically you could add a global mutexed vector of threads to be joined upon session destructor or next startup
but i still think requiring shutdown in the main thread is a neater solution

3DS's "detach" is very much undefined behavior- the running thread has to deallocate its stack before it exits

Hmm it might be better to add a main-thread shutdown for the 3DS too.

Does this library have a way to pass events to the main thread event loop, or it depends on the user to handle that itself? I don't have the code in front of me right now.

theoretically you could add a global mutexed vector of threads to be joined upon session destructor or next startup but i still think requiring shutdown in the main thread is a neater solution > 3DS's "detach" is very much undefined behavior- the running thread has to deallocate its stack before it exits Hmm it might be better to add a main-thread shutdown for the 3DS too. Does this library have a way to pass events to the main thread event loop, or it depends on the user to handle that itself? I don't have the code in front of me right now.
cgutman commented 2026-08-19 16:26:35 -05:00 (Migrated from github.com)

For platforms that don't support detaching threads, we can just document that calling LiStopConnection() in the termination callback is not allowed and then implement detach as a join.

Hmm it might be better to add a main-thread shutdown for the 3DS too.

You should discuss this with @UselessMnemonic. I am hesistant to make a breaking behavior change like this for 3DS without seeing evidence of an actual bug or ensuring that no affected clients invoke LiStopConnection() in their termination callback.

Does this library have a way to pass events to the main thread event loop, or it depends on the user to handle that itself? I don't have the code in front of me right now.

Not natively, but the callback infrastructure lets you do whatever you want. Moonlight-Qt pushes SDL events to the main thread during several connection listener callbacks.

For platforms that don't support detaching threads, we can just document that calling `LiStopConnection()` in the termination callback is not allowed and then implement detach as a join. > Hmm it might be better to add a main-thread shutdown for the 3DS too. You should discuss this with @UselessMnemonic. I am hesistant to make a breaking behavior change like this for 3DS without seeing evidence of an actual bug or ensuring that no affected clients invoke `LiStopConnection()` in their termination callback. > Does this library have a way to pass events to the main thread event loop, or it depends on the user to handle that itself? I don't have the code in front of me right now. Not natively, but the callback infrastructure lets you do whatever you want. Moonlight-Qt [pushes SDL events to the main thread](https://github.com/moonlight-stream/moonlight-qt/blob/d2f6990be699197385a6458e1231d070da83e665/app/streaming/session.cpp#L159-L170) during several connection listener callbacks.
nyanpasu64 commented 2026-09-19 22:15:41 -05:00 (Migrated from github.com)

Hmm the downstream project hasn't chimed in with coordinating changes to these libraries. In the case of moonlight-common-c, I think invoking the user callback directly in ClInternalConnectionTerminated would be more efficient than spawning a thread and joining it immediately, and functionally identical.

I'm unsure (from a maintainer's view) if it's better to note down that termination is synchronous on Switch, in the function body of ClInternalConnectionTerminated or within PltDetachThread. I think the former better marks "the control flow is different" at the place it happens (to avoid a new library consumer thinking PltDetachThread detaches and being confused why the code deadlocks). Though it seems the existing code tries to keep platform-specific branching in PltDetachThread... but the changes suggested here are not merely platform-specific implementation, but contractural changes!

If we do place the change in Connection.c#ClInternalConnectionTerminated, we could choose to either #ifdef PltJoinThread rather than detaching, or cut out the thread entirely (more efficient but also more code divergence between Switch and other consoles).

As for the 3DS thread detach, given I haven't heard from UselessMnemonic, I'm not sure if he doesn't want to talk (or forgot to reply here), or I'd have to message off this issue tracker.

Hmm the downstream project hasn't chimed in with coordinating changes to these libraries. In the case of moonlight-common-c, I think invoking the user callback directly in `ClInternalConnectionTerminated` would be more efficient than spawning a thread and joining it immediately, and functionally identical. I'm unsure (from a maintainer's view) if it's better to note down that termination is synchronous on Switch, in the function body of `ClInternalConnectionTerminated` or within `PltDetachThread`. I think the former better marks "the control flow is different" at the place it happens (to avoid a new library consumer *thinking* PltDetachThread detaches and being confused why the code deadlocks). Though it seems the existing code tries to keep platform-specific branching in `PltDetachThread`... but the changes suggested here are not merely platform-specific implementation, but *contractural changes*! If we do place the change in Connection.c#ClInternalConnectionTerminated, we could choose to either #ifdef `PltJoinThread` rather than detaching, or cut out the thread entirely (more efficient but also more code divergence between Switch and other consoles). As for the 3DS thread detach, given I haven't heard from UselessMnemonic, I'm not sure if he doesn't want to talk (or forgot to reply here), or I'd have to message off this issue tracker.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-moonlight-stream/moonlight-common-c#149
No description provided.