Waiting on input with `hWaitForInput' or `threadWaitRead'

I would like to use evented I/O for a proxying application. My present thinking is to fork a thread for each new connection and then to wait for data on either socket in this thread, writing to one or the other socket as needed. There are two API functions I've found for waiting and they each raise some questions: System.IO.hWaitForInput :: Handle -> Int -> IO Bool This function dovetails well with the high-level networking libraries but introduces difficulties because it decodes the stream to check for full characters. If the handle is set to binary mode, are decoding errors still a possibility? Control.Concurrent.threadWaitRead :: Fd -> IO () This function would seem to be closer to the ideal in terms of performance and semantics: it just waits until there are bytes available. However, converting the handle produced by the high-level networking libraries to a file descriptor closes the handle (!). This makes some sense: plucking the descriptor out of a handle doubtless interacts with other users of that handle in an unsafe way. It seems I need to through a very different set of libraries, one based on file descriptors and not handles, to be able to use this function. Ideally, I'd get something like select() on handles, just saying whether there are bytes or not. However, I haven't managed to find anything like that in the standard libraries. -- Jason Dusek () ascii ribbon campaign - against html e-mail /\ www.asciiribbon.org - against proprietary attachments

Jason Dusek
I would like to use evented I/O for a proxying application. My present thinking is to fork a thread for each new connection and then to wait for data on either socket in this thread, writing to one or the other socket as needed.
[...]
Ideally, I'd get something like select() on handles, just saying whether there are bytes or not. However, I haven't managed to find anything like that in the standard libraries.
I don't think you want either of the functions you mentioned. What you probably want instead is to do concurrent programming by creating Haskell threads. A hundred Haskell threads reading from Handles are translated to one or more OS threads using whatever polling mechanism (select(), poll(), epoll) your operating system supports. I have uploaded a simple concurrent echo server implementation to hpaste [1]. It uses one thread for the stdout logger, one thread for the server, one thread for each client and finally a main thread waiting for you to hit enter to quit the application. [1] http://hpaste.org/52742 - Concurrent echo server with logger Greets, Ertugrul -- nightmare = unsafePerformIO (getWrongWife >>= sex) http://ertes.de/

On 10/17/11 04:58, Ertugrul Soeylemez wrote:
I have uploaded a simple concurrent echo server implementation to hpaste [1]. It uses one thread for the stdout logger, one thread for the server, one thread for each client and finally a main thread waiting for you to hit enter to quit the application.
[1] http://hpaste.org/52742 - Concurrent echo server with logger
This is a good example; you should stick it on the wiki somewhere so it isn't lost.

Michael Orlitzky
I have uploaded a simple concurrent echo server implementation to hpaste [1]. It uses one thread for the stdout logger, one thread for the server, one thread for each client and finally a main thread waiting for you to hit enter to quit the application.
[1] http://hpaste.org/52742 - Concurrent echo server with logger
This is a good example; you should stick it on the wiki somewhere so it isn't lost.
It is a good example for concurrent programming, but not a good example for server programming. By putting it into the wiki I would discourage some programmers from using more suitable I/O abstractions/mechanisms. Better let's keep it away from the wiki. Greets, Ertugrul -- nightmare = unsafePerformIO (getWrongWife >>= sex) http://ertes.de/

2011/10/17 Ertugrul Soeylemez
Jason Dusek
wrote: I would like to use evented I/O for a proxying application. My present thinking is to fork a thread for each new connection and then to wait for data on either socket in this thread, writing to one or the other socket as needed.
[...]
Ideally, I'd get something like select() on handles, just saying whether there are bytes or not. However, I haven't managed to find anything like that in the standard libraries.
I don't think you want either of the functions you mentioned. What you probably want instead is to do concurrent programming by creating Haskell threads. A hundred Haskell threads reading from Handles are translated to one or more OS threads using whatever polling mechanism (select(), poll(), epoll) your operating system supports.
I have uploaded a simple concurrent echo server implementation to hpaste [1]. It uses one thread for the stdout logger, one thread for the server, one thread for each client and finally a main thread waiting for you to hit enter to quit the application.
[1] http://hpaste.org/52742 - Concurrent echo server with logger
I am not sure how to apply the principle you mention to a proxy, which must read from and write to both handles in turn (or, ideally, as needed). Here's a little demo that uses hWaitForInput and strict ByteStrings as well as plain hGetContents with lazy ByteStrings: http://hpaste.org/52777 You can load it in GHC and try out the strict/hWaitForInput version like this:
proxy (PortNumber 9001) (PortNumber 9000) strictBridge
Then run, in this order, in two terminals: :; nc -l -k 9000 # The proxied backend server. :; nc localhost 9001 # The nominal client. Now you can type text on the client side, hit return and see it on the server side and then vice versa. The lazy bridging code, `lazyBridge', blocks (unsurprisingly) and does not allow packets to go back and forth. I think I need explicit selects/waits here to get the back and forth traffic. Maybe there is a some way to leverage GHC's internal async I/O but I'm not sure how to do it. -- Jason Dusek () ascii ribbon campaign - against html e-mail /\ www.asciiribbon.org - against proprietary attachments

On Tue, Oct 18, 2011 at 3:18 AM, Jason Dusek
The lazy bridging code, `lazyBridge', blocks (unsurprisingly) and does not allow packets to go back and forth. I think I need explicit selects/waits here to get the back and forth traffic. Maybe there is a some way to leverage GHC's internal async I/O but I'm not sure how to do it.
Maybe: forkIO two threads, one for the read end, one for the write
end? I would use a loop over lazy I/O, also.
G
--
Gregory Collins

2011/10/18 Gregory Collins
On Tue, Oct 18, 2011 at 3:18 AM, Jason Dusek
wrote: The lazy bridging code, `lazyBridge', blocks (unsurprisingly) and does not allow packets to go back and forth. I think I need explicit selects/waits here to get the back and forth traffic. Maybe there is a some way to leverage GHC's internal async I/O but I'm not sure how to do it.
Maybe: forkIO two threads, one for the read end, one for the write end? I would use a loop over lazy I/O, also.
This does work, thanks; the new version of lazyBridge is: lazyBridge :: Handle -> Handle -> IO () lazyBridge a b = do forkIO (flush a b) forkIO (flush b a) return () where flush a b = LazyB.hGetContents a >>= LazyB.hPut b -- http://hpaste.org/52814 I am kind of surprised that this works at all, actually. The strict version has this problem where it lets each socket takes turns sending and receiving, if you try to send and it's not your turn, it waits for the other one to send before sending your data. The lazy version just sends bytes as they become available, the desired behaviour. I guess if I wanted to instrument the proxying, to keep a tally of how much traffic there was (to GC little used connections, for example), I would need to move up to enumerators? -- Jason Dusek () ascii ribbon campaign - against html e-mail /\ www.asciiribbon.org - against proprietary attachments

Jason Dusek
I don't think you want either of the functions you mentioned. What you probably want instead is to do concurrent programming by creating Haskell threads. A hundred Haskell threads reading from Handles are translated to one or more OS threads using whatever polling mechanism (select(), poll(), epoll) your operating system supports.
I have uploaded a simple concurrent echo server implementation to hpaste [1]. It uses one thread for the stdout logger, one thread for the server, one thread for each client and finally a main thread waiting for you to hit enter to quit the application.
[1] http://hpaste.org/52742 - Concurrent echo server with logger
I am not sure how to apply the principle you mention to a proxy, which must read from and write to both handles in turn (or, ideally, as needed).
A proxy server acts a lot like an echo server. The difference is that usually before the actual proxying starts you have a negotiation phase, and instead of echoing back to the same socket, you just write it to a different one. Here is an (untested) example: (clientH, clientHost, clientPort) <- accept serverSock destH <- negotiate clientH doneVar <- newEmptyMVar forkIO (hGetContents clientH >>= hPutStr destH >>= putMVar doneVar) forkIO (hGetContents destH >>= hPutStr clientH >>= putMVar doneVar) replicateM_ 2 (takeMVar doneVar) mapM_ hClose [clientH, destH] Of course this code is going to bite you in production for two reasons: First of all it has no error handling. If the 'negotiate' function throws an exception, then nobody will close the client handle. So view this is a highly simplified example! The second reason is that in this lazy I/O framework it is extraordinarily difficult to write the 'negotiate' function in the first place, unless you allow yourself to put stuff back into the handle or process only one byte at a time. Both options are bad. A better option is to use a proper I/O abstraction suitable for protocol processing. Iteratees [1] come to mind. They solve this problem elegantly and let you really just use the parser style "destH <- negotiate". My usage of the MVar is actually kind of an abuse. I just use it to allow the two forwarder threads to signal their completion. The main thread just waits for the two to complete and then closes both handles. The word "abuse" is perhaps too strong, because there is essentially nothing wrong with the approach. The standard concurrency library doesn't provide an event primitive, so the more general MVar is often used for this. [1] http://www.yesodweb.com/book/enumerator Greets, Ertugrul -- nightmare = unsafePerformIO (getWrongWife >>= sex) http://ertes.de/

2011/10/18 Ertugrul Soeylemez
A proxy server acts a lot like an echo server. The difference is that usually before the actual proxying starts you have a negotiation phase, and instead of echoing back to the same socket, you just write it to a different one. Here is an (untested) example:
(clientH, clientHost, clientPort) <- accept serverSock destH <- negotiate clientH doneVar <- newEmptyMVar
forkIO (hGetContents clientH >>= hPutStr destH >>= putMVar doneVar) forkIO (hGetContents destH >>= hPutStr clientH >>= putMVar doneVar) replicateM_ 2 (takeMVar doneVar) mapM_ hClose [clientH, destH]
This code seems like it says: Allow the client to write to the server one time. Allow the server to write to the client one time. Teardown both sides of the connection. Am I reading this correctly? This is, indeed, a proxy; but I'm not sure it could support a wide range of protocols. -- Jason Dusek () ascii ribbon campaign - against html e-mail /\ www.asciiribbon.org - against proprietary attachments

2011/10/18 Jason Dusek
2011/10/18 Ertugrul Soeylemez
: A proxy server acts a lot like an echo server. The difference is that usually before the actual proxying starts you have a negotiation phase, and instead of echoing back to the same socket, you just write it to a different one. Here is an (untested) example:
(clientH, clientHost, clientPort) <- accept serverSock destH <- negotiate clientH doneVar <- newEmptyMVar
forkIO (hGetContents clientH >>= hPutStr destH >>= putMVar doneVar) forkIO (hGetContents destH >>= hPutStr clientH >>= putMVar doneVar) replicateM_ 2 (takeMVar doneVar) mapM_ hClose [clientH, destH]
This code seems like it says: [...]
After working through Gregory Collins suggestion, above, I see that I was not reading your code correctly. -- Jason Dusek () ascii ribbon campaign - against html e-mail /\ www.asciiribbon.org - against proprietary attachments
participants (4)
-
Ertugrul Soeylemez
-
Gregory Collins
-
Jason Dusek
-
Michael Orlitzky