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. _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell