Hi Biping,

On Mon, Jun 15, 2009 at 10:11:39AM +0800, Biping MENG wrote:
> I found it's really easy to make the userland switch work. We only
> need to add a swapcontext() statement after
> drizzleclient_vio_read/write in the
> my_real_read/drizzleclient_net_real_write call, so the running session
> can switch out when I/O fails. So we don't need too much changes in
> this way.
> But this way of implementation will do no good to the proposal of
> architecture (cool thing!) mentioned on your blog. So I think choosing
> the not so easy way will be more valuable, that is, making the
> execution engine stateful.

Yeah, I think going down the stateful engine route may be more useful,
I'm not sure if we want to get into the business of writing our own
userland threading libraries. :)  Also, making the engine stateful
is a good way to clean some of that up.

> For making the execution engine stateful, libdrizzle is a good
> reference. We need something new:
> 1. _class EventListener_ used by the scheduler to register events like
> data available event on sockets.

We can get away without this for now just by using the libevent
interface in pool_of_threads for now. At some point we will want to
abstract that though for other modules to use.

> 2. A new nonblocking Protocol implementation such as class
> NonblockingProtocol : public Protocol

I'd like to make Protocol all non-blocking in the server, so rather
than create a new class, we just need to change the Protocol behavior.

> 3. A state function stack set as member of the Session class which is
> used to remember the state.
> 4. A serial of state functions which have the same signature and will
> be pushed into the state stack.

This is where the real work will come in, and is the first step. Before
we even look at non-blocking I/O or different schedulers, we can start
cleaning up this code by storing all state in Session (or sub classes)
and making all functions just take this as an argument. Once those
engines functions are reworked in this manner, we can then do the
stack, and then look at the non-blocking I/O.

> How these new things work together:
> If we turn on the pool of threads scheduler, the ProtocolFactory is
> set to produce NonblockingProtocol objects when the scheduler plugin
> gets initialized. Once a new client connection is accepted, a Session
> object is created and is appended to the Wait for I/O list. The
> scheduler registers I/O events of the new session to EventListener
> telling "call me when data is available". The session is moved to
> Ready for I/O list when notified by EventListener. State functions are
> a little special (in what ways? explained later) since they may be
> called a second time when a internal block point is no longer
> blocking. I found it's always very easy for I/O read, since read
> operation always exists at the head part of the execution call, that
> means, before going further, we will check if enough data have already
> been read. So another check will work well for the second time call.
> But it's not the same for write, since I/O writes appear all over the
> execution path. Who knows which write chocks up the buffer, causes a
> real flush and maybe then causes a blocking.
> For example, if we have an execution call:
> exec_A (Session* session)
> {
>   if(!enough_have_been_read(session))
>   {
>     PUSH_STATE_FN(session, drizzle_state_read);
>     retuen DRIZZLE_RETURN_OK;
>   }
>   foreach unit in to_be_executed_units
>   {
>     exec(unit);
>     write_result(unit);
>   }
> }
> 
> Say, this exec_A is pushed into the stack and is called by
> drizzle_state_loop(). If enough_have_been_read(session) is not
> satisfied, we'll need to read in more data. So push a
> drizzle_state_read into the stack. The next time we may get enough
> data in the buffer, and then is able to go forward to the execution
> statement. That's fine.
> If we got blocked at some write_result(unit) call, yes, we know how
> many units have already been executed, but how can we remember this
> information for the second call of exec_A? It'll surely cause an error
> if we do the execution for the same unit for more than once. Isn't it?
> I'm thinking if only a callback stack is enough to remember these
> information (including the local variables of the execution function).
> 
> In what ways are these state functions special?
> - They have the same signature.
> - If and only if one execution method has potential block points, it
> needs a "state_fn" version.
> - They always know where to continue when called for the another time.
> - And more?

This looks good, and you bring up some good questions. We need to
not only keep state as far as what function we should be in (on the
function stack), but also any state within the function. That should
be kept to a minimum, and it may make sense to split a function into
more functions if we find there are a lot of places it may need to
block. This also helps us keep functions small and concise, not trying
to do too much at once. Ideally, the state functions will:

* Only have one potential blocking point at the top of the function.
* Will NOT have any enums/state variable within it (create more
  functions if so).
* Will do one and only one thing, don't overload them with too
  much logic.

I know this may not be easy, or even possible in all cases due to
existing code base, but it's something we can work towards.

-Eric

_______________________________________________
Mailing list: https://launchpad.net/~drizzle-discuss
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~drizzle-discuss
More help   : https://help.launchpad.net/ListHelp

Reply via email to