[DRAFT] Runtime Shutdown Support - Runtime Thread Cleanup #276
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Add support for shutting down worker threads used by the runtime. For IL2CPP on the Mono classlibs we support shutting down all managed code and unloading. The dotnet/runtime classlibs were not built to support this.
For IL2CPP we have intercepted thread waits to make the interruptable so we can shutdown any responsive threads. (It is still up to the end user to ensure that they cleanly shutdown their code). What we largely need to be sure we handle is any threads that the runtime creates/manages.
These include:
1. Threadpool Threads
2. Threadpool Gate Thread
3. Timer Thread
4. Finalizer Thread
5. .NET SigHandler Thread (Non Windows Only)
6. .NET Sockets Thread (Non Windows Only)
For 1-3 our interruptable waits will work - these therads block on managed locks that we handle in the IL2CPP runtime so we can inject exceptions. The finalizer thread is part in the IL2CPP runtime so is under our control.
The .NET SigHandler thread currently appears to only be used to handle Console signals on desktop platforms. We do not support runtime unload on OSX or Linux - so this has not been handled.
The .NET Sockets thread however blocks in libSystemNative on on non- interruptable OS wait (kevent or epoll_wait). We are currently handling this in the IL2CPP runtime by, at shutdown, looking for a thread named ".NET Sockets" and doing a native thread abort. Native thread aborts are dangerous (could lead to crashes/memory leaks/deadlocks) and not all platforms support them.
To handle this this PR adds a new call to libSystemNative, SystemNative_WakeUpSocketEventThread, that will register an OS event (eventfd or pipe) into the events that the .NET Sockets thread waits on so we can wake it up. The .NET Sockets managed code on SocketAsyncEngine.Unix.cs does then need to be changed to check for a flag to determine if it should exit and cleanup its resources.
To try to make special shutdown handling reasonable general a new internal API was added to System.Thread -
RegisterShutdownHandler(Action). This registers a managed callback with the runtime that will be invoked when the runtime is shutting down. This is only implemented for our IL2CPP specific System.Private.CoreLib with an icall that we've added.
The workflow this new API is when starting a new thread that requires cooperative cleanup, call Thread.RegisterShutdownHandler(Action) will the shutdown steps. Then when the runtime shutdowns this action will be invoked and should invoke the shutdown and wait for the thread to exit.
This PR also adds shutdown support to the Timer thread and the Threadpool Gate Thread. As stated above we can still shutdown these threads but it is cleaner to do that cooperatively rather than injecting an Exception at a wait point. And for the Threadpool Gate thread we do want it to shutdown before we start shutting down any worker threads so it doesn't spin and try to create more worker threads to replace the ones that exited. (Note: We could also co-operatively exit the worker threads as well, but that hasn't be explored). The real motivation for adding the shutdown support here was the come up with an API/pattern that was more general and not just tied to the .NET Sockets thread.
TODOS: