Events are processed whenever the VMM is about to transfer control from ring 0 to ring 3. One common example of this is returning from ring 0 back to an application running inside a virtual machine. A second example is when a nested execution service like Resume_Exec or Exec_Int is about to transfer control to ring 3 code..
Events are also processed when a thread is blocked on a synchronization object, provided that the Blk_Svc_Ints flag was passed to the corresponding synchronization service. (Such a thread is known as 'blocked servicing events'.) That a thread which is ostensibly blocked on a synchronization object can be called upon to do work while waiting for that synchronization object is a common source of confusion (or horror) for developers familiar with other operating systems. Failure to consider this is a common source of system deadlocks, so this document will attempt to highlight frequently-encountered scenarios where particular caution must be exercised.
Note also that event processing is secondary to the scheduler. An event scheduled for a thread or virtual machine may meet all its restrictions, but will nevertheless not be processed if the scheduler does not choose to run that thread or virtual machine. In simplified terms, the scheduler chooses the the highest-priority thread in the system which satisfies the following criterion:
(Not suspended) and ((Is not blocked) or ((Is blocked servicing events) and (contains events which have met the restrictions)))
This has some subtle consequences.
Spinning in a Resume_Exec loop is not the same as being blocked on a synchronization object, although it does process events. This has not been an uncommon source of deadlocks. Thread A initiates an asynchronous operation, then goes into a Resume_Exec loop, processing events and checking for the asynchronous operation to complete, which breaks the loop. Meanwhile, the code that completes the operation resides in an event callback procedure whose event was scheduled for thread B. If the priority of thread B does not exceed that of thread A, the event will never be processed, because the scheduler only pays attention to the highest-priority unblocked thread.
Note also that unless otherwise explicitly noted, there is no guarantee on the order in which events are processed.