[squeak-dev] Events in HydraVM

Igor Stasenko siguctua at gmail.com
Thu Feb 28 08:04:18 UTC 2008


John M McIntosh asked me to make some overview on HydraVM internals -
events subsystem.
So, here it is. :)

An event in HydraVM can be represented by any abstract structure, and
having only two mandatory fields:

typedef struct vmEvent {
	struct vmEvent * volatile next;
	eventFnPtr fn;
} vmEvent;

The first field - next, used to be able to put events into queue,
the second one is the function pointer of form:

typedef sqInt (*eventFnPtr)(struct Interpreter*, struct vmEvent*);

Any additional event payload can be implementation specific. For
instance, for channels i generating events which consisting of
information of destination channel + data buffer. So, event holds
everything in itself.

Each Interpreter instance having own event queue. Event queue
implementation belongs to 3 platform specific functions:

void ioInitEventQueue(struct vmEventQueue * queue);
void ioEnqueueEventInto(struct vmEvent * event , struct vmEventQueue * queue);
struct vmEvent * ioDequeueEventFrom(struct vmEventQueue * queue);

The requirements of enqueue/dequeue functions implementation is
simple: they should be atomic.
On windows i'm using InterlockedCompareExchange(), and on other
platforms, based on x86 architecture an equivalent is 'lock cmpxchg'
asm instruction.
To support other architectures, which may don't have atomic CAS
(compare and store) instructions, there can be a need in changing
vmEventQueue struct, to keep additional information, like mutex
handle, to ensure that enqueue/dequeue operations are thread safe.

If you still didn't catch how events working, here is some additional
information:
- since events are thread safe, you can generate an event from any
native thread, and don't need to take any additional steps for
synchronizing with VM/Interpreter instance. This, in particular, used
in SocketPlugin to signal semaphores when socket (which served by
separate native thread) changes it's state.

About event handling function:
this is the function which will be called when interpreter will
interrupt for handling events, so in this function you have
synchronized access to object memory, interpreter state e.t.c. and
don't have to worry about concurrency.
Also, function along with event payload are very convenient for
determining context and what event means and what it will do.
Instead of making dozens of event types, enumerating them.. then
writing a case statements, it's doing a simple dispatch
event->fn(interpreter, event); so, system are flexible and can handle
events of any kind doing anything you want.

As for example, suppose you wanna write a plugin which needs to post
events to interpreter, but with your own, custom handling code, and
with your event payload.
So, declare an event in form:

struct myEvent
{
  struct vmEvent header;
  int myField1;
  int myField2;
 ..
};

Now to post event we simply can do:

myEvent * event = malloc(sizeof(myEvent));
event->header->fn = myHandler;
event->myField1 = ...
....

Now, a handler function:

sqInt myHandler(struct Interpreter * intr, myEvent * evt)
{
   ... do something nasty here, knowing that you can't be trapped by
concurrency issues :)..

  free(evt); // release memory, allocated for event
}


-- 
Best regards,
Igor Stasenko AKA sig.



More information about the Squeak-dev mailing list