It was time I did another Extra package. And I got an opportunity to.
Any real network programming requires that you deal with the network IO timeout problem. In C code, this basically means that you preface any read()/write()/send()/recv() with a poll()/select() call that has a non-zero timeout, and take appropriate action when your timeout expires.
In VisualWorks Smalltalk, this is not immediately obvious though, because we bury our IOAccessor objects (the equivalent of a C file descriptor) behind ExternalStreams, and Constructor (eww) thingies, and IOPositionableBuffers. For the most part this is a good thing. You get to use the same powerful streaming features on a socket that you would for a file, or an internal string, or whatever. But the timeout thing, it makes it tricky.
Our first attempt at this was to use a sort of a "transactional" timeout. Specify the time you expect to send your command, have it processed, and read the ack back. Many eons ago, I blogged about Finished Yet?, an extension to BlockClosure, so you could time them out. It was actually built originally for just this problem, though I've found it generally useful for other things too.
That worked fine for us for a while. It worked because it was run from a UI, and UIs don't normally do much. Other than interact asynchronously in short bursts. But then I added some monitoring processes which did long running computations, at a priority higher than some of the networking polling threads. Suddenly I started having timeout problems. Not because I was having legitimate network IO problems, but because I was starving the transactions from ever getting any work done. I tried a variant of the transaction timeout, that attempted to let the process run if it could as long as it wasn't idle after the timeout. This didn't really fix it.
SocketAccessor has the ability to do a select()/poll(), it's just not wired into the normal methods that streams and such use. Time for a new approach. Make timeout a first class part of SocketAccessor, rather than layering it on with other techniques.
ExtraSockets does this by adding a readTimeout and writeTimeout. By default, they are nil, and any methods which might use them, do the original thing. But if they're not nil, the two subclass overridden methods (readInto:startingAt:for: and writeFrom:startingAt:for:), first do the equivalent of the select(). And if they fail, raise a SocketIOTimeout (maybe we should have reused one of the existing signals, if you have suggestions let me know).
Results so far? Great. It works better than the old framework. With or without the longrunnning computations. And it made putting the timeout parameters much easier. Basically, the socket is given a read/write timeout that is indicative of what worst case network latencies are. The query proceeds. A transaction specific readWait is entered to allow the end processing time to vary per transaction. And then normal read timeouts apply for actually reading back the response.
I'm pretty convinced that network io timeouts are not something that should be "layered" on top of sockets. They should be first class integral parts of them. I'd love to see this code/approach integrated into VisualWorks in one way or another. Until then, feel free to grab ExtraSockets and see if it doesn't make your network IO timeout code easier and more robust.