I've been working on a new project and evaluating the Twisted python framework which relies on asynchIO rather than threading to achieve speed. The resulting idioms feel like they would be much more elegant in Haskell. So my question at this point is: Is there a reasonably efficient Haskell httpd implementation around that uses poll/select? -Alex- ___________________________________________________________________ S. Alexander Jacobson Check out my new blog!!! 1-212-787-1914 voice http://alexjacobson.com
S Alexander Jacobson writes:
Is there a reasonably efficient Haskell httpd implementation around that uses poll/select?
There is a web server written in Haskell: HWS-WP -- or "Haskell Web Server with Plug-ins". You'll find it at: http://sourceforge.net/forum/forum.php?forum_id=253134 http://cvs.sourceforge.net/viewcvs.py/haskell-libs/libs/hws-wp/ The following paper from Simon Marlow provides a detailed description of the server's architecture: Writing High-Performance Server Applications in Haskell, Case Study: A Haskell Web Server http://www.haskell.org/~simonmar/bib.html Note that HWS-WP does not use poll(2) directly. Instead, it relies an Haskell's forkIO function to spawn concurrent threads of execution. These are _not_ necessarily system threads, though. GHC, for instance, does implement IO threads with poll() internally. Other compilers or run-time systems may use other techniques. I have experimented with direct poll()-based scheduling in Haskell (using a CSP monad to implement co-routines) and honestly didn't find it to be worth the extra effort. Eventually, I threw all the code away and just used forkIO, like everybody else does. Peter P. S.: If you're interested in a poll()-based web server written in C++, though, let me know. I have some code I'm more than willing to share. The server speaks HTTP/1.1 and can deliver static pages. No dynamic content, though. But it _is_ fast. :-)
If anyone ports this to work with GHC6.0 please let us know. Tom On Wed, 5 Nov 2003 09:58 am, Peter Simons wrote:
S Alexander Jacobson writes:
Is there a reasonably efficient Haskell httpd implementation around that uses poll/select?
There is a web server written in Haskell: HWS-WP -- or "Haskell Web Server with Plug-ins". You'll find it at:
http://sourceforge.net/forum/forum.php?forum_id=253134 http://cvs.sourceforge.net/viewcvs.py/haskell-libs/libs/hws-wp/
The following paper from Simon Marlow provides a detailed description of the server's architecture:
Writing High-Performance Server Applications in Haskell, Case Study: A Haskell Web Server
http://www.haskell.org/~simonmar/bib.html
Note that HWS-WP does not use poll(2) directly. Instead, it relies an Haskell's forkIO function to spawn concurrent threads of execution. These are _not_ necessarily system threads, though. GHC, for instance, does implement IO threads with poll() internally. Other compilers or run-time systems may use other techniques.
I have experimented with direct poll()-based scheduling in Haskell (using a CSP monad to implement co-routines) and honestly didn't find it to be worth the extra effort. Eventually, I threw all the code away and just used forkIO, like everybody else does.
Peter
P. S.: If you're interested in a poll()-based web server written in C++, though, let me know. I have some code I'm more than willing to share. The server speaks HTTP/1.1 and can deliver static pages. No dynamic content, though. But it _is_ fast. :-)
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
-- Uncontrolled power will turn even saints into savages. And we can all be counted on to live down to our lowest impulses. -- Parmen, "Plato's Stepchildren", stardate 5784.3
"TLB" == Thomas L Bevan <thomas_bevan@toll.com.au> writes:
TLB> If anyone ports this to work with GHC6.0 please let TLB> us know. Tom The patch below allows me to build hws with ghc-6.0.1 and run it without plugins. However when trying to run it with loading modules I see: hws-wp% sudo ./src/hws -d . /home/petersen/haskell/haskell-libs/hws-wp/hws-wp/plugins/DirPlugin.o: unknown symbol `SystemziPosixziFiles_isDirectory_closure' Fail: user error Reason: resolveFunctions failed?False and similarly for the other modules. Perhaps the linking isn't quite, right? Jens Index: hws-wp/src/AccessLogger.hs =================================================================== RCS file: /cvsroot/haskell-libs/libs/hws-wp/hws-wp/src/AccessLogger.hs,v retrieving revision 1.1 diff -u -r1.1 AccessLogger.hs --- hws-wp/src/AccessLogger.hs 16 Feb 2003 14:55:28 -0000 1.1 +++ hws-wp/src/AccessLogger.hs 5 Nov 2003 04:09:34 -0000 @@ -45,7 +45,7 @@ import IO import Char (toLower) import IOExts hiding (trace) -import Time +import System.Time import Network.BSD import Network.Socket import Exception Index: hws-wp/src/Core.hs =================================================================== RCS file: /cvsroot/haskell-libs/libs/hws-wp/hws-wp/src/Core.hs,v retrieving revision 1.1 diff -u -r1.1 Core.hs --- hws-wp/src/Core.hs 16 Feb 2003 14:55:28 -0000 1.1 +++ hws-wp/src/Core.hs 5 Nov 2003 04:09:34 -0000 @@ -15,6 +15,7 @@ fileAccess) import Exception (tryJust, ioErrors) import System.IO +import Control.Exception (bracket) import Control.Monad import Control.Monad.Trans Index: hws-wp/src/Main.hs =================================================================== RCS file: /cvsroot/haskell-libs/libs/hws-wp/hws-wp/src/Main.hs,v retrieving revision 1.1 diff -u -r1.1 Main.hs --- hws-wp/src/Main.hs 16 Feb 2003 14:55:28 -0000 1.1 +++ hws-wp/src/Main.hs 5 Nov 2003 04:09:34 -0000 @@ -51,7 +51,7 @@ import Posix import Network.BSD import IO hiding (bracket) -import Exception +import Control.Exception import Monad import IOExts hiding (trace) import Control.Concurrent @@ -212,7 +212,7 @@ -- server. If we receive a restart signal (from a SIGHUP), then we -- re-read the configuration file. topServer conf ps = do - catchError + Control.Exception.catch (do unBlockSignals sigsToBlock unblock $ do server conf ps) @@ -233,7 +233,7 @@ -- open the server socket and start accepting connections server conf ps = do proto <- getProtocolNumber "tcp" - Exception.bracket + bracket (socket AF_INET Stream proto) (\sock -> sClose sock) (\sock -> do @@ -247,7 +247,7 @@ acceptConnections conf ps sock = do (h, SockAddrInet port haddr) <- accept sock forkIO ( (talk conf ps h haddr `finally` (hClose h)) - `Exception.catch` + `Control.Exception.catch` (\e -> trace ("servlet died: " ++ show e) (return ())) ) acceptConnections conf ps sock @@ -277,11 +277,7 @@ else return ()) return Nothing) ) -#if __GLASGOW_HASKELL__ < 409 (\e@io -> -#else - (\e@(IOException io) -> -#endif if isEOFError e then trace "EOF from client" $ return Nothing else do logError ("request: " ++ showIOError io) Index: hws-wp/src/Makefile =================================================================== RCS file: /cvsroot/haskell-libs/libs/hws-wp/hws-wp/src/Makefile,v retrieving revision 1.1 diff -u -r1.1 Makefile --- Makefile 16 Feb 2003 14:55:28 -0000 1.1 +++ Makefile 5 Nov 2003 06:14:47 -0000 @@ -14,13 +14,13 @@ # Change this to wherever yours are, I don't know what the variable # is called in the fptools mk stuff -GHCDIR = /usr/lib/ghc-5.04.2 +GHCDIR = /usr/lib/ghc-6.0.1 LD_OPTS += -optl-export-dynamic -L$(RTLDIR) HS_OBJS += -ldl -lHSrts -lHSlang -lHSposix -lHSnetwork # Link in the objects. Yes it takes ages to compile, but that's better # than it taking ages to start the program! HS_OBJS += $(GHCDIR)/HSbase.o $(GHCDIR)/HSlang.o $(GHCDIR)/HSposix.o $(GHCDIR)/HSnetwork.o -HS_OBJS += $(RTLDIR)/libRuntimeLoader.a - +#HS_OBJS += $(RTLDIR)/libRuntimeLoader.a +HS_OBJS += $(RTLDIR)/*.o Index: hws-wp/src/Util.hs =================================================================== RCS file: /cvsroot/haskell-libs/libs/hws-wp/hws-wp/src/Util.hs,v retrieving revision 1.1 diff -u -r1.1 Util.hs --- hws-wp/src/Util.hs 16 Feb 2003 14:55:28 -0000 1.1 +++ hws-wp/src/Util.hs 5 Nov 2003 04:09:34 -0000 @@ -38,7 +38,7 @@ import Network.Socket hiding (accept) import qualified Network.Socket -import Time +import System.Time import Locale import Char import IO @@ -51,6 +51,7 @@ #else import GHC.Base import GHC.Conc +import GHC.Exception import GHC.IOBase #endif @@ -148,12 +149,12 @@ killThread timeout return result ) - `catch` + `catchException` ( \exception -> case exception of ErrorCall "__timeout" -> on_timeout _other -> do killThread timeout - throw exception ) + throwIO exception ) forkIOIgnoreExceptions :: IO () -> IO ThreadId forkIOIgnoreExceptions action = IO $ \ s -> Index: runtime_loader/GHCLibraryPath.hs =================================================================== RCS file: /cvsroot/haskell-libs/libs/hws-wp/runtime_loader/GHCLibraryPath.hs,v retrieving revision 1.1 diff -u -r1.1 GHCLibraryPath.hs --- runtime_loader/GHCLibraryPath.hs 16 Feb 2003 14:55:29 -0000 1.1 +++ runtime_loader/GHCLibraryPath.hs 5 Nov 2003 04:09:34 -0000 @@ -1,4 +1,4 @@ module GHCLibraryPath (ghcLibraryPath) where -ghcLibraryPath = "/usr/lib/ghc-5.04/" +ghcLibraryPath = "/usr/lib/ghc-6.0.1/"
Brian Demsky's master's thesis compares servers written in direct style (using a thread per connection) to event driven servers (which are supposedly oh so much faster.) He shows how you can CPS the thread per connection server and end up with the event driven server. That way you don't need to manually mangle your code in an ad hoc way. Of course, as Peter points out, you can build cooperative multitasking in Haskell using monads rather that face the pain of using Java like Demsky. If I recall correctly, the main performance advantages came from avoiding - thread creation overhead - excessive context switching - synchronization due to potential interruptions at inconvenient moments I suspect Haskell threads are much lighter than Java threads, so there may not be as large of a payoff. I'm curious about the difficulties with the monadic cooperative threads. (I assume Peter meant cooperative threads where a scheduler chooses the next bit-o-code to run rather than coroutines where the yielder explicitly calls some other coroutine.) Were the asynchronous IO primitives too ugly to deal with cleanly, or was the performance gain too small to be worth while? Regards, Paul On Tuesday, November 4, 2003, at 02:58 PM, Peter Simons wrote:
S Alexander Jacobson writes:
Is there a reasonably efficient Haskell httpd implementation around that uses poll/select?
There is a web server written in Haskell: HWS-WP -- or "Haskell Web Server with Plug-ins". You'll find it at:
http://sourceforge.net/forum/forum.php?forum_id=253134 http://cvs.sourceforge.net/viewcvs.py/haskell-libs/libs/hws-wp/
The following paper from Simon Marlow provides a detailed description of the server's architecture:
Writing High-Performance Server Applications in Haskell, Case Study: A Haskell Web Server
http://www.haskell.org/~simonmar/bib.html
Note that HWS-WP does not use poll(2) directly. Instead, it relies an Haskell's forkIO function to spawn concurrent threads of execution. These are _not_ necessarily system threads, though. GHC, for instance, does implement IO threads with poll() internally. Other compilers or run-time systems may use other techniques.
I have experimented with direct poll()-based scheduling in Haskell (using a CSP monad to implement co-routines) and honestly didn't find it to be worth the extra effort. Eventually, I threw all the code away and just used forkIO, like everybody else does.
Peter
P. S.: If you're interested in a poll()-based web server written in C++, though, let me know. I have some code I'm more than willing to share. The server speaks HTTP/1.1 and can deliver static pages. No dynamic content, though. But it _is_ fast. :-)
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Paul Graunke writes:
[...] event driven servers (which are supposedly oh so much faster.)
At least in my experience, multiplexing servers _are_ significantly faster than those relying on the OS (or whatever library) to do the scheduling. They also tend to be much more efficient in terms of memory consumption, thus allowing for more simultaneous connections than a fork()ing or pthread_xxx()ing server would. (Not a big surprise, if you think about it.)
I assume Peter meant cooperative threads where a scheduler chooses the next bit-o-code to run rather than coroutines where the yielder explicitly calls some other coroutine.
Yes, you're right. I like to think of it as "coroutines", because there _is_ a call to "yield", even though it is hidden from the programmer. (The monad switched contexts every time an I/O primitive would block.) But cooperative multi-tasking is probably the more fitting term.
Were the asynchronous IO primitives too ugly to deal with cleanly, or was the performance gain too small to be worth while?
It has been a mixture of both. To actually do asynchronous I/O in Haskell, you'll need hGetBufNonBlocking :: Handle -> Ptr a -> Int -> IO Int hPutBufNonBlocking :: Handle -> Ptr a -> Int -> IO Int which aren't available in any of the released GHC versions yet. Thus, you'll have to use the most current GHC version from CVS. Good luck. :-) Aside from that, it appears to be impossible to combine poll()-scheduled Haskell code with the "traditionally" scheduled Haskell code in the same program -- unless you count busy polling as a viable solution. GHC's new forkOS function _might_ remedy this, but I have no first-hand experience with it yet. In the end, I had lots of pretty fast code, which compiled only with an unstable development version of the compiler and didn't mix with anybody else's code -- only to implement something GHC's run-time system implemented already anyway. So I gave up. Peter
G'day all. Quoting Peter Simons <simons@cryp.to>:
At least in my experience, multiplexing servers _are_ significantly faster than those relying on the OS (or whatever library) to do the scheduling. They also tend to be much more efficient in terms of memory consumption, thus allowing for more simultaneous connections than a fork()ing or pthread_xxx()ing server would. (Not a big surprise, if you think about it.)
In my experience, it depends highly on the application. I currently hack database servers for a living. These have properties which are almost ideal for multithreading. Requests tend to require a lot of work to satisfy, different requests often don't need the same resources (so the server isn't effectively sequential), many operations are I/O bound and so on. Being able to exploit SMP concurrency and being able to get useful work done during iowait result in a net win. Yes, you can use asynchronous I/O, but I usually find this harder to understand than the equivalent multithreaded code. select() and poll() may help, but they are hard to use in conjunction with other kinds of events which don't map to file descriptors, such as SysV semaphores, Unix signals, condition variables, GUI events etc.
Aside from that, it appears to be impossible to combine poll()-scheduled Haskell code with the "traditionally" scheduled Haskell code in the same program -- unless you count busy polling as a viable solution.
I don't. I don't count cycles as a rule (unless it really, REALLY matters, which it does on rare occasions), but burning as many as you can while doing precisely nothing is just plain wrong. There's a lot to be said for Win32's unified model of events. Or it would if WaitForMultipleObjects() wasn't limited to an insanely small number of objects. What I think I want is QNX-like pulses. In the Haskell world, we come pretty close with channels and ports. If there was a way to deliver system events via a Channel-like structure without busy polling, that would be really nice. Cheers, Andrew Bromage
On Wednesday, November 5, 2003, at 08:46 PM, ajb@spamcop.net wrote:
G'day all.
Quoting Peter Simons <simons@cryp.to>:
At least in my experience, multiplexing servers _are_ significantly faster than those relying on the OS (or whatever library) to do the scheduling. They also tend to be much more efficient in terms of memory consumption, thus allowing for more simultaneous connections than a fork()ing or pthread_xxx()ing server would. (Not a big surprise, if you think about it.)
Right, cooperative multitasking is faster than preemptive multitasking.
In my experience, it depends highly on the application. I currently hack database servers for a living. These have properties which are almost ideal for multithreading. Requests tend to require a lot of work to satisfy, different requests often don't need the same resources (so the server isn't effectively sequential), many operations are I/O bound and so on. Being able to exploit SMP concurrency and being able to get useful work done during iowait result in a net win.
Right, the more CPUs the merrier (for parallelizable tasks). One technique is to have several OS threads to make use of CPUs and do your own scheduling inside that.
Yes, you can use asynchronous I/O, but I usually find this harder to understand than the equivalent multithreaded code. select() and poll() may help, but they are hard to use in conjunction with other kinds of events which don't map to file descriptors, such as SysV semaphores, Unix signals, condition variables, GUI events etc.
Most event/select/poll implementations built in C by systems guys look really messy. Writing a single threaded program and then slapping a forkIO around the code for a connection is convenient. If all the IO is monadic anyway, you might as well use a different monad instead. Then IO can capture the continuation, store it in a run queue, and yield to another coroutine. (I think of cooperative threads as coroutines + scheduling.) All the do-it-yourself threading code is in one place, leaving the structure of the rest of the code (fairly) unmolested.
Aside from that, it appears to be impossible to combine poll()-scheduled Haskell code with the "traditionally" scheduled Haskell code in the same program -- unless you count busy polling as a viable solution.
You do have the problem that any remaining blocking IO blocks the whole bundle of cooperative threads. I think the Portable Common Runtime hands off blocking operations to a pool of preemptive system threads to avoid this problem.
I don't. I don't count cycles as a rule (unless it really, REALLY matters, which it does on rare occasions), but burning as many as you can while doing precisely nothing is just plain wrong.
There's a lot to be said for Win32's unified model of events. Or it would if WaitForMultipleObjects() wasn't limited to an insanely small number of objects. What I think I want is QNX-like pulses. In the Haskell world, we come pretty close with channels and ports. If there was a way to deliver system events via a Channel-like structure without busy polling, that would be really nice.
Cheers, Andrew Bromage
Is there a problem with having a system thread do the IO based on pulling events from a channel and pushing the results back to a channel? You can push the result channel/mvar through the request channel along with the request. This is the Erlang style, no? Regards, Paul
G'day all. Quoting Paul Graunke <ptg@ccs.neu.edu>:
Right, cooperative multitasking is faster than preemptive multitasking.
That's often the case, but it depends. Some OSes have very, very fast thread primitives. It also depends on the application, as I noted, because what you lose in system call overhead you can win back elsewhere.
One technique is to have several OS threads to make use of CPUs and do your own scheduling inside that.
That certainly works. The "thread pool" approach is the simplest incarnation of this.
Is there a problem with having a system thread do the IO based on pulling events from a channel and pushing the results back to a channel? You can push the result channel/mvar through the request channel along with the request.
Unfortunately, I can't think of a way to make this work with memory mapped files, which are pretty important these days. Cheers, Andrew Bromage
Peter Simons <simons@cryp.to> replies:
Paul Graunke writes:
[...] event driven servers (which are supposedly oh so much faster.)
At least in my experience, multiplexing servers _are_ significantly faster than those relying on the OS (or whatever library) to do the scheduling. They also tend to be much more efficient in terms of memory consumption, thus allowing for more simultaneous connections than a fork()ing or pthread_xxx()ing server would. (Not a big surprise, if you think about it.)
For a somewhat-contrary opinion, see the recent SOSP paper "Capriccio: Scalable Threads for Internet Services" by Rob von Behren, Jeremy Condit, Feng Zhou, George Necula, and Eric Brewer: http://www.cs.berkeley.edu/~jcondit/capriccio-sosp-2003.pdf In a nutshell: they wrote a user-level threads package that wrapped the usual library functions and did them all using asynchronous I/O. All the scheduling takes places at user level. The whole system is currently uniprocessor. The threads package they describe is virtually identical to the way threading is implemented in GHC. The overheads? Virtually none compared to writing the program explicitly in an event-driven style (sometimes they're even faster). That said, they were quite a bit more careful about tracking resource demands of various sorts and using them to make clever scheduling decisions. GHC is a bit more naive, but there's nothing ruling out a more clever implementation, and GHC programs written using forkIO would certainly benefit from such optimizations. -Jan-Willem Maessen Resource-aware scheduling freak jmaessen@alum.mit.edu
Thank you for the discussion, but let me ask some more questions: Simple questions: 1. Is there ssl support for the haskell httpd somewhere? 2. Does this httpd actually build w/ modern GHC? 3. Why doesn't haskell.org run this httpd? More complex question: Assumptions: * This httpd can do >1000 req./sec. on modern CPUs (enough for me) * I write-ahead log all PUT/POST/DELETE requests before executing * I can reproduce server state at a given time by replaying the log * I checkpoint periodically so I don't have to replay the whole log * I treat each HTTP PUT/POST/DELETE as a discrete state transition Question: Can I make sure that I have concurrency only w/r/t client communication? I don't want the thread of control to change during state transitions. What happens if state is too big to fit in memory? * Does forkIO switch control if a thread writes to the disk but write-caching is enabled or if the thread access some part of memory that is swapped to disk? Optimization question: If I am using RAID, can I allow control to switch if I am handling multiple GET requests but lock for PUT/POST/DELETE requests? Notes: * Write-caching means the app is not slowed by disk writes * Write-ahead logs mean not worrying about crashes during writes * If necessary, I can scale up GET performance using caching and multiple CPUs each doing log recovery. -Alex- ___________________________________________________________________ S. Alexander Jacobson Check out my new blog!!! 1-212-787-1914 voice http://alexjacobson.com
Thank you for the discussion, but let me ask some more questions: Simple questions: 1. Is there ssl support for the haskell httpd somewhere? 2. Does this httpd actually build w/ modern GHC? 3. Why doesn't haskell.org run this httpd? More complex question: Assumptions: * This httpd can do >1000 req./sec. on modern CPUs (enough for me) * I write-ahead log all PUT/POST/DELETE requests before executing * I can reproduce server state at a given time by replaying the log * I checkpoint periodically so I don't have to replay the whole log * I treat each HTTP PUT/POST/DELETE as a discrete state transition Question: Can I make sure that I have concurrency only w/r/t client communication? I don't want the thread of control to change during state transitions. What happens if state is too big to fit in memory? * Does forkIO switch control if a thread writes to the disk but write-caching is enabled or if the thread access some part of memory that is swapped to disk? Optimization question: If I am using RAID, can I allow control to switch if I am handling multiple GET requests but lock for PUT/POST/DELETE requests? Notes: * Write-caching means the app is not slowed by disk writes * Write-ahead logs mean not worrying about crashes during writes * If necessary, I can scale up GET performance using caching and multiple CPUs each doing log recovery. -Alex- ___________________________________________________________________ S. Alexander Jacobson Check out my new blog!!! 1-212-787-1914 voice http://alexjacobson.com
S Alexander Jacobson writes:
1. Is there ssl support for the haskell httpd somewhere?
Not that I'd know.
2. Does this httpd actually build w/ modern GHC?
It probably will, but not out-of-the-box. The code hasn't been actively maintained for a while.
More complex question: [...]
HWS-WP is an _experimental_ web server; it is by no means ready for the kind of production set-up you apparently need. Peter
<rant> Frustrating. Its been 4 years since I seriously looked at Haskell. I would have expected that over this time, someone here would have consolidated it into a language useful for real world applications. The Haskell in Practice page is shockingly short and many of the examples are not actually Haskell in practice. Does anyone here eat the Haskell dog food or is this all just fooling around (basic reasearch)? Simon, why did you write the Haskell web server? </rant> Ok. Is anyone running this web server at all? What is changing in Haskell that makes 3 year old code so uncompilable? -Alex- ___________________________________________________________________ S. Alexander Jacobson Check out my new blog!!! 1-212-787-1914 voice http://alexjacobson.com On Tue, 11 Nov 2003, Peter Simons wrote:
S Alexander Jacobson writes:
1. Is there ssl support for the haskell httpd somewhere?
Not that I'd know.
2. Does this httpd actually build w/ modern GHC?
It probably will, but not out-of-the-box. The code hasn't been actively maintained for a while.
More complex question: [...]
HWS-WP is an _experimental_ web server; it is by no means ready for the kind of production set-up you apparently need.
Peter
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
participants (10)
-
ajb@spamcop.net -
Jan-Willem Maessen -
Jens Petersen -
Paul Graunke -
Paul Graunke -
Peter Simons -
S. Alexander Jacobson -
S. Alexander Jacobson -
S. Alexander Jacobson -
Thomas L. Bevan