зеркало из https://github.com/mozilla/gecko-dev.git
89 строки
4.7 KiB
ReStructuredText
89 строки
4.7 KiB
ReStructuredText
NSPR's position on abrupt thread termination
|
|
============================================
|
|
|
|
This memo describes my position on a facility that is currently under
|
|
discussion for inclusion in the NetScape Portable Runtime (NSPR); the
|
|
ability of a thread to abruptly exit. I resist including this function
|
|
in NSPR because it results in bad programming practice and unsupportable
|
|
programs.
|
|
|
|
*Threads are not processes.*
|
|
|
|
Abrupt termination has been available in the UNIX/C environment for some
|
|
time (``exit()``), and I assume that the basic semantics defined there
|
|
are applicable here. In that environment, ``exit()`` may be called and
|
|
any time, and results in the calling thread's immediate termination. In
|
|
the situation where it was defined (UNIX), which has only a single
|
|
thread of execution, that is equivalent to terminating the process. The
|
|
process abstraction is then responsible for closing all open files and
|
|
reclaiming all storage that may have been allocated during the process'
|
|
lifetime.
|
|
|
|
This practice does not extend to threads. Threads run within the
|
|
confines of a process (or similar abstractions in other environments).
|
|
Threads are lightweight because they do not maintain the full protection
|
|
domain provided by a process. So in a threaded environment, what is the
|
|
parallel to UNIX' ``exit()``?
|
|
|
|
NSPR has defined a function, callable by any thread within a process at
|
|
any time, called ``PR_ProcessExit()``. This is identical to UNIX
|
|
``exit()`` and was so named in an effort to make the obvious even more
|
|
so. When called, the process exits, closing files and reclaiming the
|
|
process' storage.
|
|
|
|
Certain people have been disappointed when NSPR did not provide a
|
|
functional equivalent to exit just a particular thread. Apparently they
|
|
have failed to consider the ramifications. If a thread was to abruptly
|
|
terminate, there is no recording of what resources it owns and should
|
|
therefore be reclaimed. Those resources are in fact, owned by the
|
|
process and shared by all the threads within the process.
|
|
|
|
In the general course of events when programming with threads, it is
|
|
very advantageous for a thread to have resources that it and only it
|
|
knows about. In the natural course of events, these resources will be
|
|
allocated by a thread, used for some period of time, and then freed as
|
|
the stack unwinds. In these cases, the presence of the data is recorded
|
|
only on the stack, known only to the single thread (normally referred to
|
|
as *encapsulated*).
|
|
|
|
The problem with abrupt termination is that it can happen at any time,
|
|
to a thread that is coded correctly to handle both normal and
|
|
exceptional situations, but will be unable to do so since it will be
|
|
denied the opportunity to complete execution. It can happen because it
|
|
called out of its own scope into some lazily implemented library.
|
|
|
|
NSPR's answer to this is that there is no abrupt thread termination. All
|
|
threads must unwind and return from their root function. If they cannot,
|
|
because of some state corruption, then they must assume that the
|
|
corruption, like the state, is shared, and their only resource is for
|
|
the process to terminate.
|
|
|
|
To make this solution work requires that a function that encounters an
|
|
error be designed such that it first repairs its immediate state, and
|
|
then reports that error to its caller. If the caller cannot deal with
|
|
the failure, it must do the same. This process continues until the
|
|
thread either recovers from the malady or returns from the root
|
|
function. This is not all that difficult (though having done it a number
|
|
of times to already existing code, I will admit it isn't much fun
|
|
either).
|
|
|
|
The implementation of either strategy within the NSPR runtime is not
|
|
difficult. That is not what this memo is about. This is about providing
|
|
an API that coaxes people to do the right thing in as many ways as
|
|
possible. The existence of ``exit()`` in the UNIX/C environment is a
|
|
perfect example of how programmers will employ the most expediant
|
|
solution available. The definition of the language C is such that
|
|
returning from ``main()`` is a perfectly fine thing to do. But what
|
|
percentage of C programs actually bother? In UNIX, with its complex
|
|
definition of a protection domain, it happens to work (one might even
|
|
say it's more efficient) to exit from anywhere. But threads are not
|
|
processes. If threads have to maintain the same type of resource
|
|
knowledge as a process, they loose all of their benefit.
|
|
|
|
Threads are an implementation strategy to provide the illusion of
|
|
concurrency within a process. They are alternatives to large state
|
|
machines with mostly non-blocking library functions. When the latter is
|
|
used to provide concurrency, calling ``exit()`` will terminate the
|
|
entire process. Why would anyone expect a thread to behave differently?
|
|
Threads are not processes.
|