hartcov.gif (20840 bytes)

Win32 System Programming

Return to JMH Associates Home Page  

AUTHOR: Johnson M. Hart, jmhart@world.std.com

© JMH Associates, 2000. This material  is supplied as a supplement to
Win32 System Programming. You can purchase a copy at amazon.com.In Association with Amazon.com


Batons

I recently coauthored paper with Andrew Tucker - "Batons: A Sequential Synchronization Object" by Andrew Tucker and
Johnson M Hart. (Windows Developer’s Journal, July, 2001, pp24 ff. www.wdj.com. A copy is available
from http://www.halcyon.com/ast/dload/batons.pdf. Source code is in the latest CD download. In the paper, we explored the
various way to use events and mutexes, and compared Win32 events to Pthreads condition variables.

We asked readers to send us any solutions that could avoid our time out loop using a pulsed manual-reset event;
the only requirement was that there be a bounded number of synchronization oblects.

Two readers responded with solutions. Both used QueueUserAPC(), which allows a function to be executed
by a specified thread when the thread awakes from an "alertable wait." The ability to specify an action in a specific
target thread overcomes the problem with events, which are not (and should not be) thread specific. The solutions
also require maintenance of a searchable data structure that associates thread handles with sequence numbers.
Among the other benefits of using QueueUserAPC() is the fact that you will gain performance since only a
specified thread is signaled.

The first solution, from Doug Currie (e@flavors.com; Flavors Technology, Inc., Londonderry, NH), is listed below
as an extension to our solution. All that is required is to define a preprocessor macro.

The second solution is from William Brooks (phideaux69@hotmail.com) and is written in Pascal.

Both solutions are reproduced below, with permission form Doug and William. We really appreciate receiving these
two solutions, which illustrate the power of QueueUserAPC().

First, here is the augmented C file and header file as sent by Doug Currie.


/* batons.c */
/* Based upon "Batons: A Sequential Synchronization Object" 
 * by Andrew Tucker and Johnson M Hart. 
 * Windows Developer's Journal, July, 2001, pp24 ff.
 * Additions are by Doug Currie (e@flavors.com) to use QueueUserAPC()
 * so that a specific thread can be "signaled"
 * The QueueUserAPC version is enabled by defining BATON_WITH_MHEAP
/*
    Implementation of batons API
*/
#ifdef BATON_WITH_SOAW
#define _WIN32_WINNT  0x0400
#endif 
#include <windows.h>
#include "batons.h"
#define EVENT_TIMEOUT 50  /* Tuneable ms timeout in the wait loop */
 
#ifdef BATON_WITH_MHEAP
// PRIORITY QUEUE WITH A MUTEX -- NO LOOPS!
// these should be args to CreateBaton...
#define MAX_PQ_BATONS 0x10000
#define INI_PQ_BATONS 1024
HBATON CreateBaton(VOID)
{
    DWORD maxPqBatons = MAX_PQ_BATONS;
    DWORD iniPqBatons = INI_PQ_BATONS;
	HBATON hBaton = (HBATON)malloc(sizeof(struct BATON));
    if (hBaton != NULL)
    {
        hBaton->pMheap = (DWORD *)calloc (iniPqBatons, sizeof(DWORD));
		if (hBaton->pMheap == NULL)
		{
			free (hBaton);
			hBaton = NULL;
		}
		else
		{
			hBaton->hMutex = CreateMutex (NULL, TRUE, NULL);
			hBaton->dwSeqLastOwner = 0;
			hBaton->hBatonHolder   = 0;
			hBaton->dwMaxMheapSize = maxPqBatons;
			hBaton->dwCurMheapSize = iniPqBatons;
			ReleaseMutex (hBaton->hMutex);
		}
    }
    return hBaton;
}
BOOL DestroyBaton(HBATON hBaton)
{
    HANDLE hMutex;
    BOOL Result = FALSE;
    
	if (hBaton == NULL) return FALSE;
	hMutex = hBaton->hMutex;
    // Do not destroy an owned baton
    WaitForSingleObject (hMutex, INFINITE);
	if ( hBaton->hBatonHolder == 0 )
	{
		if (hBaton->pMheap != NULL) free ((void *)hBaton->pMheap);
		free (hBaton);
		Result = TRUE;
	}
	ReleaseMutex (hMutex);
    CloseHandle (hMutex);
    return Result;
}
BOOL AcquireBaton(HBATON hBaton, DWORD dwSequence)
{
    BOOL Result = TRUE;
    BOOL Wait = FALSE;
	DWORD diff;
	/* To accommodate very long running applications, dwSequence can
	   be any value. The first task run will be that with sequence
	   number one, the second task two, etc. Eventually, dwSequence
	   will wrap around to zero, one, etc.; this is handled using 
	   modulo arithmetic and is guaranteed to work reliably 
	   so long as hBaton->dwMaxMheapSize is less than 0x80000000. */
	WaitForSingleObject (hBaton->hMutex, INFINITE);
	/* To be legitimate, i.e., to fit in the Mheap, dwSequence must be
	   between dwSeqLastOwner+1 and dwSeqLastOwner+dwMaxMheapSize-1 incl.
	   using unsigned modulo arithmetic...
	   (note that DWORDs are unsigned 32 bit Qs) */
	diff = dwSequence - hBaton->dwSeqLastOwner - 1;
	if ( diff >= hBaton->dwMaxMheapSize )
	{
		/* illegitimate */
		Result = FALSE;
	}
	else if ( diff == 0 )
	{
		/* verify that we aren't a duplicate */
		if( hBaton->pMheap [dwSequence % hBaton->dwCurMheapSize] != 0 )
		{
			Result = FALSE; /* duplicate sequence number */
		}
		else if ( hBaton->hBatonHolder == 0 )
		{
			hBaton->dwSeqLastOwner = dwSequence;	/* we're on! */
			hBaton->hBatonHolder = GetCurrentThreadId();
			//printf( "Immediately Running %d\n", dwSequence );
		}
		else
		{
			Wait = TRUE; /* wait */
		}
	}
	else if ( diff >= hBaton->dwCurMheapSize )
	{
		/* in well designed apps, this case
		   should happen extremely rarely, if at all */
		DWORD *p;
		DWORD cur = hBaton->dwCurMheapSize;
		/* resize the Mheap; diff is between Cur and Max
		   to avoid thrashing here, double the size of the mheap
		   until the new sequence number is accommodated */
		do{ cur += cur; } while (diff >= cur );
		if( cur > hBaton->dwMaxMheapSize )
			cur = hBaton->dwMaxMheapSize;
		p = (DWORD *)calloc( cur, sizeof(DWORD) );
		if( p == NULL )
		{
			Result = FALSE; /* no memory */
		}
		else
		{
			DWORD next = (hBaton->dwSeqLastOwner + 1) % hBaton->dwCurMheapSize;
			DWORD chunk1 = (hBaton->dwCurMheapSize - next) * sizeof( DWORD );
			DWORD chunk2 = next * sizeof( DWORD );
			if (chunk1)
				memcpy( &p[next], (void *)&hBaton->pMheap[next], chunk1 );
			if (chunk2)
				memcpy( &p[hBaton->dwCurMheapSize], (void *)&hBaton->pMheap[0], chunk2 );
			hBaton->pMheap = p;
			hBaton->dwCurMheapSize = cur;
			Wait = TRUE; /* now wait */
		}
	}
	else if( hBaton->pMheap [dwSequence % hBaton->dwCurMheapSize] != 0 )
	{
		Result = FALSE; /* duplicate sequence number */
	}
	else
	{
		Wait = TRUE; /* wait */
	}
	if( Wait == TRUE )
	{
		/* enter mheap */
		hBaton->pMheap [dwSequence % hBaton->dwCurMheapSize]
			= GetCurrentThreadId();
		//printf( "Waiting %d\n", dwSequence );
#ifdef BATON_WITH_SOAW
        switch( SignalObjectAndWait (hBaton->hMutex, /* release Mutex */
									GetCurrentProcess(), 
									INFINITE, TRUE) ) /* wait for APC */
#else
        ReleaseMutex (hBaton->hMutex);
        switch( WaitForSingleObjectEx( GetCurrentProcess(),
									INFINITE, TRUE) ) /* wait for APC */
#endif
		{
		case WAIT_IO_COMPLETION:
			/* One or more I/O completion routines or user-mode APCs 
			are queued for execution. -- we win! */
			//printf( "Got completion %d\n", hBaton->dwSeqLastOwner );
			break;
		/* case WAIT_TIMEOUT:
			/* The time-out interval elapsed, and the object's state is nonsignaled. */
		/* case WAIT_ABANDONED:
			/* The specified object is a mutex object that was
			not released by the thread that owned the mutex object
			before the owning thread terminated. Ownership of the 
			mutex object is granted to the calling thread, and the 
			mutex is set to nonsignaled. */
		/* case WAIT_OBJECT_0:
			/*  The state of the specified object is signaled. 
			This means my process is terminated !? */
		/* case -1:
			/* error */
		default:
			//printf( "Got bad completion %d\n", hBaton->dwSeqLastOwner );
			Result = FALSE;
			break;
		}
	}
	else /* Wait != TRUE */
	{
		ReleaseMutex (hBaton->hMutex);
	}
	return Result;
}
static VOID CALLBACK fun( ULONG_PTR dwParam )
{
	/* doesn't do anything! */
}
BOOL ReleaseBaton(HBATON hBaton)
{
	DWORD candidate;
	DWORD next;
	WaitForSingleObject (hBaton->hMutex, INFINITE);
	/* could check that hBaton->hBatonHolder == GetCurrentThreadId() */
	//printf( "Completing %d\n", hBaton->dwSeqLastOwner );
	hBaton->hBatonHolder = 0;				/* release ownership */
	next = (hBaton->dwSeqLastOwner + 1) % hBaton->dwCurMheapSize;
	candidate = hBaton->pMheap [next];
	if ( candidate != 0 )
	{
		HANDLE h = OpenThread( THREAD_ALL_ACCESS, FALSE, candidate );
		hBaton->pMheap [next] = 0;			/* free candiate's queue slot */
		hBaton->dwSeqLastOwner++;			/* bump seq number */
		hBaton->hBatonHolder = candidate;	/* grab ownership */
		//printf( "Firing %d 0x%x\n", hBaton->dwSeqLastOwner, candidate );
		//next =
		QueueUserAPC ( fun, h, 0 );			/* start candidate */
		//printf( "QueueUserAPC returned %d\n", next );
		CloseHandle( h );
	}
    ReleaseMutex (hBaton->hMutex);
    return TRUE;
}
#endif  // BATON_WITH_MHEAP
/************************************************************/
#ifdef BATON_WITH_MUTEX
// CONDITION VARIABLE MODEL WITH A MUTEX
HBATON CreateBaton(VOID)
{
    HBATON hBaton = (HBATON)malloc(sizeof(struct BATON));
    if (hBaton != NULL)
    {
        hBaton->hMutex = CreateMutex (NULL, TRUE, NULL);
        hBaton->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        hBaton->dwSeqLastOwner = 0;
        ReleaseMutex (hBaton->hMutex);
    }
    return hBaton;
}
BOOL DestroyBaton(HBATON hBaton)
{
    HANDLE hMutex = hBaton->hMutex;
    BOOL Result = FALSE;
    // Do not destroy an owned baton
    WaitForSingleObject (hMutex, INFINITE);
    CloseHandle(hBaton->hEvent);
    free (hBaton);
    ReleaseMutex (hMutex);
    CloseHandle (hMutex);
    Result = TRUE;
    return Result;
}
BOOL AcquireBaton(HBATON hBaton, DWORD dwSequence)
{
    BOOL Result = TRUE;
    if (dwSequence == 0) return FALSE; /* Sequence starts at 1 */
    WaitForSingleObject (hBaton->hMutex, INFINITE);
    while (hBaton->dwSeqLastOwner < dwSequence-1) {
        /* The baton is owned, or the baton has not yet
         * been acquired with the preceding sequence */
#ifdef BATON_WITH_SOAW
        SignalObjectAndWait (hBaton->hMutex, 
                             hBaton->hEvent, 
                             INFINITE, FALSE);
#else
        ReleaseMutex (hBaton->hMutex);
        WaitForSingleObject (hBaton->hEvent, EVENT_TIMEOUT);
#endif
        WaitForSingleObject (hBaton->hMutex, INFINITE);
    }
    /* hBaton->dwOnwerId == 0 && 
     * hBaton->dwSeqLastOwner >= dwSequence-1 
     * That is: The baton is unowned and the baton has been
     * acquired with all sequence numbers [1 .. dwSequence-1]
     */
    Result = (hBaton->dwSeqLastOwner == dwSequence-1);
    /* The sequence is one more than that of the last owner */ 
    if (Result)  
        hBaton->dwSeqLastOwner = dwSequence;
    else 
    {
        /* Duplicate sequence number */ 
        ReleaseMutex (hBaton->hMutex); 
    }
    return Result;
}
BOOL ReleaseBaton(HBATON hBaton)
{
    /* PulseEvent on a manual reset event guarantees that all
     * waiting threads are released and that the event is reset 
     */
    PulseEvent(hBaton->hEvent); /* Baton state change */
    ReleaseMutex (hBaton->hMutex);
    return TRUE;
}
#endif  // BATON_WITH_MUTEX
/************************************************************/
#ifdef BATON_WITH_TWO_EVENTS
// EVENT PAIR TO SIGNAL BETWEEN RELEASING THREAD AND WAITING THREADS
// Alternative solution proposed by Ron Burk. This solution, while
// more complex, does not require a time out on the event wait.
HBATON CreateBaton(VOID)
{
    HBATON hBaton = (HBATON)malloc(sizeof(struct BATON));
    if (hBaton != NULL)
    {
        hBaton->hMutex = CreateMutex (NULL, TRUE, NULL);
        hBaton->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        hBaton->hEventReleased = CreateEvent (NULL, 
                                              TRUE, 
                                              FALSE, 
                                              NULL);
        hBaton->dwSeqLastOwner = 0;
        hBaton->dwWaitCount = 0;
        ReleaseMutex (hBaton->hMutex);
    }
    return hBaton;
}
BOOL DestroyBaton(HBATON hBaton)
{
    HANDLE hMutex = hBaton->hMutex;
    WaitForSingleObject (hMutex, INFINITE);
    CloseHandle(hBaton->hEvent);
    CloseHandle(hBaton->hEventReleased);
    free (hBaton);
    ReleaseMutex (hMutex);
    CloseHandle (hMutex);
    return TRUE;
}
BOOL AcquireBaton(HBATON hBaton, DWORD dwSequence)
{
    BOOL Result = FALSE;
    if (dwSequence == 0) return FALSE; /* Sequence starts at 1 */
    
    WaitForSingleObject (hBaton->hMutex, INFINITE);
    while (hBaton->dwSeqLastOwner < dwSequence-1) {
        hBaton->dwWaitCount++;
        ReleaseMutex (hBaton->hMutex);
        /* No timeout */ 
        WaitForSingleObject (hBaton->hEvent, INFINITE); 
        /* When wait count is 0, inform the ReleaseBaton thread */
        /* Note that the mutex is not locked, making */
        /* InterlockedDecrement useful */
        if (InterlockedDecrement (&hBaton->dwWaitCount) == 0)
            SetEvent(hBaton->hEventReleased);
        WaitForSingleObject (hBaton->hMutex, INFINITE);
    }
    Result = (hBaton->dwSeqLastOwner == dwSequence-1);
    /* The sequence is one more than that of the last owner */ 
    if (Result)  
        hBaton->dwSeqLastOwner = dwSequence;
    else ReleaseMutex (hBaton->hMutex);
    return Result;
}
BOOL ReleaseBaton(HBATON hBaton)
{
    ResetEvent (hBaton->hEventReleased);
    SetEvent (hBaton->hEvent);
    if (hBaton->dwWaitCount != 0) 
        WaitForSingleObject (hBaton->hEventReleased, INFINITE);
    /* Reset the event only after all threads waiting on hEvent */
    /* have been released. This prevents missed signals */
    ResetEvent (hBaton->hEvent);
    ReleaseMutex (hBaton->hMutex);
    return TRUE;
}
#endif  // BATON_WITH_TWO_EVENTS
/************************************************************/
#ifdef BATON_WITH_CS
// CONDITION VARIABLE MODEL WITH CRITICAL_SECTION 
// IN PLACE OF THE MUTEX. 
HBATON CreateBaton(VOID)
{
    HBATON hBaton = (HBATON)malloc(sizeof(struct BATON));
    if (hBaton != NULL)
    {
        InitializeCriticalSection (&hBaton->hMutex);
        hBaton->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        hBaton->dwSeqLastOwner = 0;
    }
    return hBaton;
}
BOOL DestroyBaton(HBATON hBaton)
{
    CRITICAL_SECTION hMutex = hBaton->hMutex;
    // Do not destroy an owned baton
    EnterCriticalSection (&hMutex);
    CloseHandle(hBaton->hEvent);
    free (hBaton);
    LeaveCriticalSection (&hMutex);
    DeleteCriticalSection (&hMutex);
    return TRUE;
}
BOOL AcquireBaton(HBATON hBaton, DWORD dwSequence)
{
    BOOL Result = TRUE;
    if (dwSequence == 0) return FALSE; /* Sequence starts at 1 */
    EnterCriticalSection (&hBaton->hMutex);
    while (hBaton->dwSeqLastOwner < dwSequence-1) {
        LeaveCriticalSection (&hBaton->hMutex);
        WaitForSingleObject (hBaton->hEvent, EVENT_TIMEOUT);
        EnterCriticalSection (&hBaton->hMutex);
    }
    Result = (hBaton->dwSeqLastOwner == dwSequence-1);
    /* The sequence is one more than that of the last owner */ 
    if (Result) { 
        hBaton->dwSeqLastOwner = dwSequence;
    } /* Otherwise, this is a duplicate sequence number */
    return Result;
}
BOOL ReleaseBaton(HBATON hBaton)
{
    PulseEvent(hBaton->hEvent); /* Baton state change */
    LeaveCriticalSection (&hBaton->hMutex);
    return TRUE;
}
#endif // BATON_WITH_CS
Here is the associated header file
/*
 *    batons.h. Header file for batons API. 
 * 	With additions by Doug Currie
*/
#ifndef __BATONS_H__
#define __BATONS_H__
#ifdef BATON_WITH_MHEAP
struct BATON
{
    /* Guard the baton structure */
    HANDLE hMutex;
    /* Last owned sequence. Init: 0 */
    volatile DWORD  dwSeqLastOwner;
	/* the current baton holder; 0 if none */
	volatile DWORD  hBatonHolder;
	/* maximum size (in HANDLEs) of *pMheap */
    volatile DWORD  dwMaxMheapSize;
	/* current size (in HANDLEs) of *pMheap */
    volatile DWORD  dwCurMheapSize;
	/* a monotone heap priority queue */
    volatile DWORD * volatile pMheap; /* actually a pointer to an array */
};
typedef struct BATON *HBATON;
#endif BATON_WITH_MHEAP
#ifdef BATON_WITH_MUTEX
struct BATON
{
    /* Guard the baton structure */
    HANDLE hMutex;            
    /* Signaled when released */
    HANDLE hEvent;            
    /* Last owned sequence. Init: 0 */
    volatile DWORD  dwSeqLastOwner;    
};
typedef struct BATON *HBATON;
#endif BATON_WITH_MUTEX
#ifdef BATON_WITH_CS
struct BATON
{
    /* Guard the baton structure */
    CRITICAL_SECTION hMutex;
    /* Signaled when released */
    HANDLE hEvent;            
    /* Last owned sequence. Init: 0 */
    volatile DWORD  dwSeqLastOwner;    
};
typedef struct BATON *HBATON;
#endif // BATON_WITH_CS
#ifdef BATON_WITH_TWO_EVENTS
// TWO EVENTS SOLUTION
struct BATON
{
    /* Guard the baton structure */
    HANDLE hMutex;            
    /* Signaled when baton is released */
    HANDLE hEvent;            
    /* AR event signaled when all waiting threads released */
    HANDLE hEventReleased;  
    /* Last owned sequence. Init: 0 */
    volatile DWORD  dwSeqLastOwner;    
    /* Number of waiting threads */
    DWORD  dwWaitCount;    
};
typedef struct BATON *HBATON;
#endif //BATON_WITH_TWO_EVENTS
#ifdef __cplusplus
extern "C" {
#endif
HBATON CreateBaton(VOID);
BOOL DestroyBaton(HBATON hBaton);
BOOL AcquireBaton(HBATON hBaton, DWORD dwSequence);
BOOL ReleaseBaton(HBATON hBaton);
#ifdef __cplusplus
}
#endif


#endif

Here is the Pascal solution from William Brooks:


unit Batons;
interface
uses
  Windows,
  SysUtils;
type
  HBATON = THANDLE;
function CreateBaton ( dwInitial: DWORD ): HBATON;
function AcquireBaton ( hBaton: HBATON; dwSequence: DWORD ): BOOLEAN;
function ReleaseBaton ( hBaton: HBATON ): BOOLEAN;
function DestroyBaton ( hBaton: HBATON ): BOOLEAN;
implementation
const
  // this constant is missing from the Windows unit..
  THREAD_SET_CONTEXT = $0010;
const
  THREAD_BATONACQUIRED  = 1;
  THREAD_BATONDESTROYED = 2;
type
  PLISTTHREAD = ^TLISTTHREAD;
  TLISTTHREAD = record                  // pNext must be the first member of this structure
    pNext      : PLISTTHREAD;           // pointer to next Listd thread
    hThread    : THANDLE;               // handle of Listd thread
    dwSequence : DWORD;                 // Listd thread sequence number
    dwResult   : DWORD;
  end;
type
  PBATON = ^TBATON;
  TBATON = record                       // this structure must be aligned to a DWORD boundary
    csList      : TRTLCRITICALSECTION;  // critical section to protect pList
    pList       : PLISTTHREAD;          // List of threads waiting for ownership of the baton
    dwCurrent   : DWORD;                // sequence number of the owning thread
    dwCurrentId : DWORD;                // TID of the owning thread
    dwSequence  : DWORD;                // number of the next sequence
  end;
function GenericInterlockedCompareExchange ( var iTarget: INTEGER; iExchange, iComperand: INTEGER ): INTEGER; assembler;
asm
         xchg       eax, ecx
  lock   cmpxchg    [ecx], edx          // perform compare-exchange
end;
procedure APCAcquired ( pData: PLISTTHREAD ); stdcall;
begin
  // the current thread has acquired the baton, inform it of this state
  // by setting dwResult appropriately and returning.
  pData.dwResult := THREAD_BATONACQUIRED;
end;
procedure APCDestroyed ( pData: PLISTTHREAD ); stdcall;
begin
  // the baton has been (or is being) destroyed, inform the waiting thread
  // of this state change.
  pData.dwResult := THREAD_BATONDESTROYED;
end;  
function CreateBaton ( dwInitial: DWORD ): HBATON;
var pData : PBATON;
begin
  Result := 0;
  try
    // allocate memory for the baton data structure, AllocMem zero fills.
    pData := AllocMem(sizeOf(TBATON));
    try
      // set the initial sequence number ..
      pData.dwSequence := dwInitial;
      // initialize the critical section..
      InitializeCriticalSection(pData.csList);
      // return the baton handle..
      Result := HBATON(pData);
    except
      FreeMem(pData);
      raise;
    end;
  except
    on EOutOfMemory do
      SetLastError(ERROR_OUTOFMEMORY);
    on EAccessViolation do
      SetLastError(ERROR_INVALID_PARAMETER);
  else
    SetLastError(ERROR_GEN_FAILURE);
  end;
end;
function AcquireBaton ( hBaton: HBATON; dwSequence: DWORD ): BOOLEAN;
var pData   : PBATON absolute hBaton;
    qtData  : TLISTTHREAD;
begin
  Result := FALSE;
  try
    // get a handle to this thread, this handle may not actually be required, but it
    // is easier to clean it up if it is created here..
    if (not(DuplicateHandle(GetCurrentProcess,
                            GetCurrentThread,
                            GetCurrentProcess,
                            @qtData.hThread,
                            THREAD_SET_CONTEXT,
                            FALSE,
                            0))) then
      exit;
    try
      // quick check to determine if this thread is the next thread in the
      // sequence..
      if (GenericInterlockedCompareExchange(INTEGER(pData.dwSequence), INTEGER(-1), INTEGER(dwSequence)) = INTEGER(dwSequence)) then
      begin
        pData.dwCurrent := dwSequence;
        pData.dwCurrentId := GetCurrentThreadId;
        // at this point, this thread has ownership of the baton.
        Result := TRUE;
        exit;
      end;
      // the baton is either currently owned or this thread is not next in the
      // sequence. In either case, this thread is placed on to the waiting List. First
      // step is to enter the wait List's critical section.
      EnterCriticalSection(pData.csList);
      try
        // after entering the critical section, the code must check for the case where
        // this thread is next in sequence and the baton is no longer owned. Basically,
        // there is a race between entering the critical section and ReleaseBaton releasing
        // baton ownership.
        if (GenericInterlockedCompareExchange(INTEGER(pData.dwSequence), INTEGER(-1), INTEGER(dwSequence)) = INTEGER(dwSequence)) then
        begin
          pData.dwCurrent := dwSequence;
          pData.dwCurrentId := GetCurrentThreadId;
          // this thread now has ownership of the baton.. return
          Result := TRUE;
          exit;
        end;
        // add the thread to wait List..
        qtData.dwSequence := dwSequence;
        qtData.dwResult := 0;
        qtData.pNext := pData.pList;
        pData.pList := @qtData;
      finally
        LeaveCriticalSection(pData.csList);
      end;
      // at this point, the thread is on the waiting list and must wait until its
      // sequence number is reached. SleepEx will cause this thread to sleep until
      // an APC is posted to the thread to wake it up.
      while (TRUE) do
      begin
        // NOTE: If SleepEx returns without executing a callback, this routine
        // should handle the error more gracefully. As it is now, the thread
        // will leave its entry in the waiting list, likely causing some other
        // thread to raise an exception when it attempts to wake this thread.
        if (SleepEx(INFINITE, TRUE) <> WAIT_IO_COMPLETION) then
          exit;
        // check the result of the APC callback, this is done in case the thread
        // has APCs posted to it that are not from ReleaseBaton.
        if (qtData.dwResult = THREAD_BATONDESTROYED) then
          exit
        else if (qtData.dwResult = THREAD_BATONACQUIRED) then
          break; 
      end;
      // take ownership of the baton..
      pData.dwCurrent := dwSequence;
      pData.dwCurrentId := GetCurrentThreadId;
      Result := TRUE;
    finally
      CloseHandle(qtData.hThread);
    end;
  except
    on EOutOfMemory do
      SetLastError(ERROR_OUTOFMEMORY);
    on EAccessViolation do
      SetLastError(ERROR_INVALID_HANDLE);
  else
    SetLastError(ERROR_GEN_FAILURE);
  end;
end;
function ReleaseBaton ( hBaton: HBATON ): BOOLEAN;
var pData   : PBATON absolute hBaton;
    pqtData : PLISTTHREAD;
    pqtPrev : PLISTTHREAD;
    dwNext  : DWORD;
begin
  try
    Result := FALSE;
    // this thread must be the current owner of the baton.. this check is
    // not protected because reads (or writes) on DWORD boundaries are atomic
    // on Intel processors.
    if (pData.dwCurrentId <> GetCurrentThreadId) then
    begin
      SetLastError(ERROR_NOT_OWNER);
      exit;
    end;
    // reset the current id, this ensures that another call to ReleaseBaton on this
    // thread will fail (unless it has reacquired the baton)
    pData.dwCurrentId := 0;
    // determine the next sequence number..
    dwNext := pData.dwCurrent + 1;
    // this thread is the owner of the baton, check the waiting List to
    // determine if the next thread in the sequence is waiting.
    EnterCriticalSection(pData.csList);
    try
      pqtPrev := @pData.pList;
      pqtData := pData.pList;
      while (pqtData <> NIL) do
      begin
        if (pqtData.dwSequence = dwNext) then
        begin
          // the pqtData structure is for the next thread in the sequence. Remove
          // the entry from the waiting List and attempt to pass ownership of the
          // baton to the waiting thread.
          // remove this entry from the List..
          pqtPrev.pNext := pqtData.pNext;
          // post an APC to the waiting thread.
          QueueUserAPC(@APCAcquired, pqtData.hThread, DWORD(pqtData));
		  // When a user-mode APC is queued, the thread is not directed to 
		  // call the APC function unless it is in an alertable state. 
		  // After the thread is in an alertable state, the thread handles 
		  // all pending APCs in first in, first out (FIFO) order, 
          // at this point, the waiting thread will take ownership of the baton and
          // this routine is complete.
          Result := TRUE;
          exit;
        end;
        pqtPrev := pqtData;
        pqtData := pqtData.pNext;
      end;
      // if this point is reached, no thread is currently waiting for the next sequence
      // number. The next sequence number is placed into the dwSequence variable. This
      // must be done while holding the pData.csList lock, otherwise a race between
      // ReleaseBaton and AcquireBaton occurs.
      pData.dwSequence := dwNext;
      Result := TRUE;
    finally
      LeaveCriticalSection(pData.csList);
    end;
  except
    on EOutOfMemory do
      SetLastError(ERROR_OUTOFMEMORY);
    on EAccessViolation do
      SetLastError(ERROR_INVALID_HANDLE);
  else
    SetLastError(ERROR_GEN_FAILURE);
  end;
end;
function DestroyBaton ( hBaton: HBATON ): BOOLEAN;
var pData : PBATON absolute hBaton;
begin
  DeleteCriticalSection(pData.csList);
  FreeMem(pData);
end;
end.