From: Zed Shaw Date: 2006-01-24T12:51:04+09:00 Subject: Re: Mongrel 0.1.1 -- A Fast Ruby Web Server (It Works Now, M On Jan 23, 2006, at 9:27 PM, Toby DiPasquale wrote: > Zed Shaw wrote: >> You're going to laugh but right now it's down to a bit of Ruby and a >> nifty C extension. Seriously. No need yet of much more than some >> threads that crank on output, a parser (in C) that makes a hash, and >> a way to quickly lookup URI mappings. The rest is done with handlers >> that process the result of this. > > That's a PATRICIA trie for URL lookup, a finite state machine compiled > Ragel->C->binary for HTTP protocol parsing and an implicit use of > select(2) (via Thread), for the even-more-curious out there ;) (first > hit on Google for "Ragel" will tell you what you need to know about > that) > Ooohh, *that's* what people want to know. You're right. Here's the main gear involved in the process: 1) Basic Ruby TCPServer is used to create the server socket. No magic here. A thread then just runs in a loop accepting connections. 2) When a client is accepted it's passed to a "client processor". This processor is a single function that runs in a loop doing a readpartial on the socket to get a chunk of data. 3) That chunk's passed to a HTTP parser which makes a Ruby Hash with the CGI vars in it. The parser is written with Ragel 5.2 (which has problems compiling on some systems). This parser is the first key to Mongrel's speed. 4) With a completed HTTP parse, and the body of the request waiting to be processed, Mongrel tries to find the handler for the URI. It does this with a modified trie that returns the handler as well as break the prefix and postfix of the URI into SCRIPT_INFO and PATH_INFO components. 5) Once I've got the handler, the request hash variables, and a request object I just call the "process" method and it does it's work. Unhandled issues are: * The trie was written in ruby and isn't all that fast. A trie might also be overkill for what will typically be a few URIs. I was thinking though that the trie would be great for storing cached results and looking them up really fast. * The thread handling has limitations that make it not quite as efficient as I'd like. For example, I read 2k chunks off the wire and parse them. If the request doesn't fit in the 2k then I have to reset the parser, keep the data, and parse it again. I'd really much rather use a nice ring buffer for this. * The threads create a ton of objects which can make the GC cause large pauses. I've tried a group of threads waiting on a queue of requests, but that's not much faster or better. So far the fastest is using IO.select (see below). >> It may get a bit larger than this, >> but this core will probably be more than enough to at least service >> basic requests. I'm currently testing out a way to drop the threads >> in favor of IO.select, but it looks like that messes with threads in >> some weird ways. > > Ok, so here's where I fell off your train. On your Ruby/Event page, > you > said that you killed the project b/c Ruby's Thread class > multiplexes via > the use of select(2), which undermines libevent's ability to > effectively > manage events (which I had discovered while writing some extensions a > while back and thought "how unfortunate"). But I have some questions > about the above: > Yes, that's still true since Ruby and libevent don't know about the other. They fight like twenty rabid cats in a pillow case. The main difference is that IO.select knows about Ruby's threads, so it's supposed to be safe to use. > 1. As above, the Thread class uses select(2) (or poll(2)) internally; > what would be the difference in using IO::select explicitly besides > more > code to write to manage it all? > It does use select transparently, but it seems to add a bunch of overhead to the select processing it uses. I'm sorting out the IO.select and thread relationship. > 2. What are these "weird ways" you keep referring to? I got the > select-hogging-the-event-party thing, but what else? > Basically select hogs the party, threads just kind of stop for no reason, select just stops, etc. I really which they'd just use pth so I could get on with my life. :-) I've been playing with it, and I think I have something that might work. > I am interested b/c I am currently trying to write a microthreading > library for Ruby based on some of the more performing event > multiplexing > techniques (kqueue, port_create, epoll, etc) so I can use it for other > stuff I want to write (^_^) > You know, having tried this, I have to say you'll be fighting a losing battle. Ruby's thread implementation just isn't able to work with external multiplexing methods. I couldn't figure it out, so if you do then let me know. >> Once I figure out all the nooks and crannies of the thing then I'll >> do a more formal design, but even then it's going to be ruthlessly >> simplistic. > > Simple is good, m'kay? ;-) Great show in any case! I know I'll be > using > this for my next internal Rails app. > Thanks! Zed A. Shaw http://www.zedshaw.com/