[Haskell] Network accept loop with graceful shutdown implementation
Cat Dancer
haskell at catdancer.ws
Wed Dec 6 19:18:10 EST 2006
I have a prospective implementation of a network accept loop with
graceful shutdown.
This email builds upon the previous discussion "Help needed
interrupting accepting a network connection".
In this code, just the accept loop part has been factored out and put
into its own module. My hope is that if a fully correct version can
be written, it will be useful to other projects. The source is
available at http://code.catdancer.ws/acceptloop/ and is in the public
domain.
This AcceptLoop module is currently experimental, relying on an
"either-or" guarantee for interruptible operations... which I don't
know yet whether Haskell implementations provide -- or even intend to
provide.
Chris Kuklewicz provided several critical insights:
* The return value of the 'accept' call can be passed out of the
accept thread, allowing the code which implements accept with
graceful shutdown to be separated from the code which handles
the incoming client connection.
* Inside of a 'block', interruptible operations may not (will not?)
allow an asynchronous exception to be raised if the operation does
not block.
* Clarification for me of the desirable property that inside of a
'block', interruptible operations are either-or: either they allow
an asynchronous operation to be raised, or they perform their
operation, but not both.
I made the following design decisions:
In my original implementation, I used a custom datatype to throw a
dynamic asynchronous exception to the thread calling 'accept'. In
Chris' rewrite, he used 'killThread', which throws a 'ThreadKilled'
asynchronous exception. I choose to continue to throw (and catch)
only the specific, custom exception for the purpose of breaking out of
the 'accept'. A robust implementation may need to catch other
exceptions, however the desired behavior of the thread on receiving an
*unexpected* exception may be different, and so I choose to leave
handling of such an unexpected exception unimplemented for now.
Chris uses STM instead of MVar's for communication with the accept
thread. The challenge of writing code with STM or MVar's in the
presence of asynchronous exceptions is exactly the same: either a call
to 'atomically' or a call to an MVar operation such as 'putMVar' may
allow an asynchronous exception to be raised inside of a 'block', and
the code then needs to deal with that.
It seems to me that MVar's could be implemented in terms of STM, and
so the question is: are MVar's a more natural, higher level
description of the desired semantics in this case, and would
composable transactions be useful?
A key insight is that in this implementation, a separate thread is
used not for the purposes of concurrency, but solely to limit the
scope of the thrown asynchronous exception. Once a result is
available from the accept thread (either "Just" an incoming
connection, or "Nothing" to say that the accept loop has shutdown),
that result can then be used in a concurrent fashion, such as being
handled in a child thread, passed into an STM transaction or written
to a channel, etc. But there is no need to use composable
transactions *inside* of the AcceptLoop module.
Thus the only reason to use STM instead of MVar's inside the
implementation is if it turns out that STM has the desired "either-or"
behavior but MVar's don't.
To avoid the "unlock (return ())" issue that Chris discovered, this
implementation uses an additional MVar to indicate that a shutdown is
in process. Thus (if the implementation is correct) the accept loop
will shutdown either because of the MVar flag or by receiving the
asynchronous exception inside of the 'accept'.
To address the issue that Chris noticed of a race condition that new
threads cannot be started in a 'block' state, yet another MVar is set
by the accept thread to indicate that it is now inside of a 'block'
and is ready to receive the asynchronous exception.
More information about the Haskell
mailing list