New, simpler design: There is an 'Engine' which is both Executor and Mainloop. It uses an Epoll object to handle the polling. This epoll object does not do any locking by itself. Contexts can be created; they are like jobqueues, except they also allow you to add fd's and idles and timeouts. They can do that since they have access to the engine. These are completely unlocked; if you want to access a context from more than one thread, you must lock them yourself. It is guaranteed that only one callback will be active for a context at the same time. From within that callback it is then safe to remove other callbacks etc. In general such a context can act pretty much like one thread. A web server will consist of a listening context, which will create other contexts as necessary and add the fd's to them with the appropriate callback. Or maybe just call back with the new fd. I guess there should be a generic listener object that will create its own context and call back. Then an http module can just create a listener and do whatever it wants in response. One possible problem. The thread that creates a context and adds the fd not necessarily the one that will execute the first callback. Can these interfere? Maybe the solution is just that the creating thread should not do anything with the context after it adds the fd. A better idea may be to just have a context_dispatch() function that you must call after creating the context. Until this is called, nothing the context is not active. Beginning of this new design is implemented and can be built with build2.sh. Old design (referenced above) Highlevel design for a web server: - Executor takes callbacks and executes them, possibly in parallell or out of order. The callbacks are likely to be called in a different thread than the one that queued them. Should support both push_back() and push_front(). Push_front() is needed to do parallel requests with good latency. If a request can be parallellized, the parallel tasks should be put at the front of the queue. - EPoll Simple class that wraps epoll()/poll(). Note: must support oneshot polling and rearming. - JobQueue Maintain a queue of jobs, uses executor to execute them in order. Two jobs in the same JobQueue will never run at the same time. - Mainloop Uses executor, epoll. - Takes filedescriptors and callbacks. calls back when descriptor is readable/writable/etc. - The callbacks are put on a job queue, which is passed in - Each callback is a oneshot - ie., after calling, the filedescriptor is not polled again until it is rearmed. The mainloop will have a method to rearm filedescriptors. This ensures that the mainloop can start a new poll() whenever it wants without waiting for all the callbacks to finish. - Also takes timeouts that can be canceled. Timeouts also need to be put on a queue. Sketch of implementation: polljob() { timeout = compute min_timeout (); if (timeout > 0) poll_armed_describtors(timeout); call all timeouts (ie., put them on queues); call all callbacks (ie., put them on queues); schedule (polljob); } - MainContext - The thing clients will deal with - Uses the main loop - Filedescriptors maincontext is responsible for rearming the descriptor after the client callback has been called. - Timeouts - Idle handlers - Everything associated with a main context happens serialized - ie., as if only one thread executed it. - This means a client structure doesn't need to be locked. - Has a get_executor() method so that clients can parallelize if they want to. - Current thinking is that if we have main contexts, who really needs a main loop? The only thing you could do with it is to pass it to main contexts. OTOH that's true of several of the objects here. see notes at top of maincontext.c - Worth noting that stuff that has to be passed in to create a client object must be available to the listener callback. But see http_server.c for an example - Err, the epoll in itself is not enough for maincontext, since it needs to be shared between maincontexts. Ie., who would call epoll_wait(). We do really need a main loop that will call back. - Listener Listens on a port. Calls back with a file descriptor when someone connects. - Connection initialized with a MainContext and a filedescriptor. Creates events when something happens - Http: Has a Connection parses http, emits events such as "get hostname pagename query" "post etc etc etc" - ContentProvider