[dmd-concurrency] Thread termination protocol (shutdown protocol evolved)
Robert Jacques
sandford at jhu.edu
Thu Jan 21 10:36:08 PST 2010
On Thu, 21 Jan 2010 13:11:17 -0500, Michel Fortin
<michel.fortin at michelf.com> wrote:
> Here is another idea for the "shutdown protocol". I'm changing the name
> to better reflect what the proposal is. Also take note that I've renamed
> the "Shutdown" exception to "Terminated".
>
> It includes ideas from my previous proposal as well as from how Erlang
> handles linked processes. Linked processes in Erlang define an error
> handling mechanism, much like the one I'm proposing here. I was mistaken
> before about how it worked and what it did. This time I've integrated
> the concept correctly.
>
> Thank you for reading! This might take a while. :-)
>
> - - -
>
> The thread termination protocol has two goals:
>
> * Establish a generic way of expressing when you want a thread to
> terminate that can cover a majority of cases. But it's important that
> cases not supported by it can still be handled by user-constructed
> termination protocols.
>
> * Establish a generic way to handle thrown exceptions in spawned threads.
>
> So the thread termination protocol relies on four important points:
>
> 1. When spawning a thread, the parent thread is set as the owner of the
> new one.
>
> 2. The owner link with the child thread can be broken by choosing
> another thread as the owner. Setting the owner to the main thread means
> that you don't want the child to be terminated until the program itself
> terminates.
>
> 3. When a thread terminates, it sends a Terminated exception message to
> each of the threads it owns.
>
> 4. When a child thread receives a Terminated exception message, the
> thread can handle it and even ignore it if it wants. But in the absence
> of corresponding message handlers and exception handlers, the thrown
> exception will stop the thread.
>
> 5. When a thread terminates via an exception other than Terminated, the
> exception is sent back as a message to the owner thread. In the absence
> of corresponding message handlers and exception handlers, the thrown
> exception will stop the owner thread and thus again send the exception
> as a message to the owner's owner, until it reaches the main thread
> (which has no owner).
>
> Here is an important thing: sending a Terminated exception must not
> prevent the thread from receiving more messages afterwards. If the child
> thread chooses to ignore the Terminated message then nothing prevents it
> to continue receiving messages normally afterward. One reason for this
> is that it might want to postpone termination to perform a closing
> handshake with something else it is currently communicating with.
>
> Also important is that you can at any time send manually a Terminated
> message to a thread when you want it to terminate.
>
> And we might want to add a Tid field to the Exception class to identify
> the thread it originated from.
>
> - - -
>
> Now, let's see how it works with various use cases. (This first case one
> is pretty much a repetition of the one that came along with my previous
> shutdown protocol proposal.)
>
> For the file copy example with an intermediate processing step, it's a
> simple ownership graph:
>
> main -owns- read thread -owns- processing thread -owns- writer thread
>
> When main terminates, it sends Terminated to the read thread, which
> ignores it because it's reading from a file. When the read threads
> finish reading, it terminates and send a Terminated to the processing
> thread which will receive it as its last message. When the processing
> thread receives Terminated it terminates which automatically sends a
> Terminated message to the writer thread. The writer threads then
> terminates after writing the last part. At this moment the program
> closes.
>
> What happens if the writer thread throws an exception (other than
> Terminated)? The exception will terminate the writer thread, be sent
> back as a message to the processing thread, which will terminate and
> send the exception to the reader thread, which will terminate and send
> the exception to the main thread, which will terminate the program. If
> any of those threads in the middle of the chain is already terminated
> when the exception is thrown, the exception is sent directly to the
> owner's owner.
>
> Of course, any thread in the graph might catch the exception, preventing
> it from percolating to other threads.
>
> So this simple case works well out of the box. That's because the graph
> is a simple tree. If you have a thread spawning a child thread only to
> then give it to another thread, then you'll probably want to decide
> yourself when you want to terminate it and who should handle exceptions.
> Here is how that should work:
>
> 1. Create your thread, setting ownership to the main thread.
> 2. Give the Tid to whoever you want.
> 3. ...
> 4. Send the thread a Terminated exception when you're done with it.
>
> Here the owner thread just acts as a safeguard in case you forget to
> send a Terminated message manually. You can set the owner to any thread
> that lives longer than the spawned thread, not necessarily the main
> thread. When you know you want to terminate the thread, just send it a
> Terminated exception.
>
> You might want to setup a special "monitoring" thread as the owner of
> such child threads. This thread could catch exceptions leaking from
> child threads and do some error handling.
>
> - - -
>
> For the API, I propose this:
>
> spawn(function, args...)
> // creates a new thread having the spawning thread as the owner.
>
> spawnOwned(ownerTid, function, args...)
> // creates a new thread with a specific owner.
>
> tid.owner = ownerTid
> // Changes the owner of a thread.
> // Note 1: this needs to be protected against circular ownerships.
>
> terminate(tid);
> // Sends a Terminated exception to the thread. This only works for
> // threads listening for messages.
>
> This makes only two notable differences with Erlang:
>
> 1. You cannot have unlinked threads. This ensures that all threads
> receive a Terminated message eventually (if they don't terminate by
> themselves before that). This also make sure that uncaught exceptions
> will always be propagated back to somewhere, right up to the main thread
> if you don't catch them.
>
> 2. Sending a Terminated exception is a standard way to tell a thread to
> just stop. I don't think there is such a thing in Erlang. Fortunately,
> you don't have to obey the Terminated message if you don't want to, but
> most likely you'll just want to postpone termination while you clean
> things up.
Looks okay at first glance. To reduce namespace pollution: terminate(tid)
-> tid.terminate. Also, overloading spawn and spawnOwned should also be
considered. To clarify, the exception/terminate message passing are passed
with the same priority as normal messages, so they only get re-thrown
after prior messages are sent / received / etc. Correct?
More information about the dmd-concurrency
mailing list