wasCSharpSQLite – Rev 1
?pathlinks?
/*
* Notifier.java --
*
* Implements the Jacl version of the Notifier class.
*
* Copyright (c) 1997 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and
* redistribution of this file, and for a DISCLAIMER OF ALL
* WARRANTIES.
*
* Included in SQLite3 port to C# for use in testharness only; 2008 Noah B Hart
*
* RCS @(#) $Id: Notifier.java,v 1.8 2003/03/11 02:21:14 mdejong Exp $
*
*/
using System;
using System.Collections;
namespace tcl.lang
{
// Implements the Jacl version of the Notifier class. The Notifier is
// the lowest-level part of the event system. It is used by
// higher-level event sources such as file, JavaBean and timer
// events. The Notifier manages an event queue that holds TclEvent
// objects.
//
// The Jacl notifier is designed to run in a multi-threaded
// environment. Each notifier instance is associated with a primary
// thread. Any thread can queue (or dequeue) events using the
// queueEvent (or deleteEvents) call. However, only the primary thread
// may process events in the queue using the doOneEvent()
// call. Attepmts to call doOneEvent from a non-primary thread will
// cause a TclRuntimeError.
//
// This class does not have a public constructor and thus cannot be
// instantiated. The only way to for a Tcl extension to get an
// Notifier is to call Interp.getNotifier() (or
// Notifier.getNotifierForThread() ), which returns the Notifier for that
// interpreter (thread).
public class Notifier : EventDeleter
{
// First pending event, or null if none.
private TclEvent firstEvent;
// Last pending event, or null if none.
private TclEvent lastEvent;
// Last high-priority event in queue, or null if none.
private TclEvent markerEvent;
// Event that was just processed by serviceEvent
private TclEvent servicedEvent = null;
// The primary thread of this notifier. Only this thread should process
// events from the event queue.
internal System.Threading.Thread primaryThread;
// Stores the Notifier for each thread.
private static Hashtable notifierTable;
// List of registered timer handlers.
internal ArrayList timerList;
// Used to distinguish older timer handlers from recently-created ones.
internal int timerGeneration;
// True if there is a pending timer event in the event queue, false
// otherwise.
internal bool timerPending;
// List of registered idle handlers.
internal ArrayList idleList;
// Used to distinguish older idle handlers from recently-created ones.
internal int idleGeneration;
// Reference count of the notifier. It's used to tell when a notifier
// is no longer needed.
internal int refCount;
private Notifier( System.Threading.Thread primaryTh )
{
primaryThread = primaryTh;
firstEvent = null;
lastEvent = null;
markerEvent = null;
timerList = new ArrayList( 10 );
timerGeneration = 0;
idleList = new ArrayList( 10 );
idleGeneration = 0;
timerPending = false;
refCount = 0;
}
public static Notifier getNotifierForThread( System.Threading.Thread thread )
// The thread that owns this Notifier.
{
lock ( typeof( tcl.lang.Notifier ) )
{
Notifier notifier = (Notifier)notifierTable[thread];
if ( notifier == null )
{
notifier = new Notifier( thread );
SupportClass.PutElement( notifierTable, thread, notifier );
}
return notifier;
}
}
public void preserve()
{
lock ( this )
{
if ( refCount < 0 )
{
throw new TclRuntimeError( "Attempting to preserve a freed Notifier" );
}
++refCount;
}
}
public void release()
{
lock ( this )
{
if ( ( refCount == 0 ) && ( primaryThread != null ) )
{
throw new TclRuntimeError( "Attempting to release a Notifier before it's preserved" );
}
if ( refCount <= 0 )
{
throw new TclRuntimeError( "Attempting to release a freed Notifier" );
}
--refCount;
if ( refCount == 0 )
{
SupportClass.HashtableRemove( notifierTable, primaryThread );
primaryThread = null;
}
}
}
public void queueEvent( TclEvent evt, int position )
// One of TCL.QUEUE_TAIL,
// TCL.QUEUE_HEAD or TCL.QUEUE_MARK.
{
lock ( this )
{
evt.notifier = this;
if ( position == TCL.QUEUE_TAIL )
{
// Append the event on the end of the queue.
evt.next = null;
if ( firstEvent == null )
{
firstEvent = evt;
}
else
{
lastEvent.next = evt;
}
lastEvent = evt;
}
else if ( position == TCL.QUEUE_HEAD )
{
// Push the event on the head of the queue.
evt.next = firstEvent;
if ( firstEvent == null )
{
lastEvent = evt;
}
firstEvent = evt;
}
else if ( position == TCL.QUEUE_MARK )
{
// Insert the event after the current marker event and advance
// the marker to the new event.
if ( markerEvent == null )
{
evt.next = firstEvent;
firstEvent = evt;
}
else
{
evt.next = markerEvent.next;
markerEvent.next = evt;
}
markerEvent = evt;
if ( evt.next == null )
{
lastEvent = evt;
}
}
else
{
// Wrong flag.
throw new TclRuntimeError( "wrong position \"" + position + "\", must be TCL.QUEUE_HEAD, TCL.QUEUE_TAIL or TCL.QUEUE_MARK" );
}
if ( System.Threading.Thread.CurrentThread != primaryThread )
{
System.Threading.Monitor.PulseAll( this );
}
}
}
public void deleteEvents( EventDeleter deleter )
// The deleter that checks whether an event
// should be removed.
{
lock ( this )
{
TclEvent evt, prev;
TclEvent servicedEvent = null;
// Handle the special case of deletion of a single event that was just
// processed by the serviceEvent() method.
if ( deleter == this )
{
servicedEvent = this.servicedEvent;
if ( servicedEvent == null )
throw new TclRuntimeError( "servicedEvent was not set by serviceEvent()" );
this.servicedEvent = null;
}
for ( prev = null, evt = firstEvent; evt != null; evt = evt.next )
{
if ( ( ( servicedEvent == null ) && ( deleter.deleteEvent( evt ) == 1 ) ) || ( evt == servicedEvent ) )
{
if ( evt == firstEvent )
{
firstEvent = evt.next;
}
else
{
prev.next = evt.next;
}
if ( evt.next == null )
{
lastEvent = prev;
}
if ( evt == markerEvent )
{
markerEvent = prev;
}
if ( evt == servicedEvent )
{
servicedEvent = null;
break; // Just service this one event in the special case
}
}
else
{
prev = evt;
}
}
if ( servicedEvent != null )
{
throw new TclRuntimeError( "servicedEvent was not removed from the queue" );
}
}
}
public int deleteEvent( TclEvent evt )
{
throw new TclRuntimeError( "The Notifier.deleteEvent() method should not be called" );
}
internal int serviceEvent( int flags )
// Indicates what events should be processed.
// May be any combination of TCL.WINDOW_EVENTS
// TCL.FILE_EVENTS, TCL.TIMER_EVENTS, or other
// flags defined elsewhere. Events not
// matching this will be skipped for processing
// later.
{
TclEvent evt;
// No event flags is equivalent to TCL_ALL_EVENTS.
if ( ( flags & TCL.ALL_EVENTS ) == 0 )
{
flags |= TCL.ALL_EVENTS;
}
// Loop through all the events in the queue until we find one
// that can actually be handled.
evt = null;
while ( ( evt = getAvailableEvent( evt ) ) != null )
{
// Call the handler for the event. If it actually handles the
// event then free the storage for the event. There are two
// tricky things here, both stemming from the fact that the event
// code may be re-entered while servicing the event:
//
// 1. Set the "isProcessing" field to true. This is a signal to
// ourselves that we shouldn't reexecute the handler if the
// event loop is re-entered.
// 2. When freeing the event, must search the queue again from the
// front to find it. This is because the event queue could
// change almost arbitrarily while handling the event, so we
// can't depend on pointers found now still being valid when
// the handler returns.
evt.isProcessing = true;
if ( evt.processEvent( flags ) != 0 )
{
evt.isProcessed = true;
// Don't allocate/grab the monitor for the event unless sync()
// has been called in another thread. This is thread safe
// since sync() checks the isProcessed flag before calling wait.
if ( evt.needsNotify )
{
lock ( evt )
{
System.Threading.Monitor.PulseAll( evt );
}
}
// Remove this specific event from the queue
servicedEvent = evt;
deleteEvents( this );
return 1;
}
else
{
// The event wasn't actually handled, so we have to
// restore the isProcessing field to allow the event to be
// attempted again.
evt.isProcessing = false;
}
// The handler for this event asked to defer it. Just go on to
// the next event.
continue;
}
return 0;
}
private TclEvent getAvailableEvent( TclEvent skipEvent )
// Indicates that the given event should not
// be returned. This argument can be null.
{
lock ( this )
{
TclEvent evt;
for ( evt = firstEvent; evt != null; evt = evt.next )
{
if ( ( evt.isProcessing == false ) && ( evt.isProcessed == false ) && ( evt != skipEvent ) )
{
return evt;
}
}
return null;
}
}
public int doOneEvent( int flags )
// Miscellaneous flag values: may be any
// combination of TCL.DONT_WAIT,
// TCL.WINDOW_EVENTS, TCL.FILE_EVENTS,
// TCL.TIMER_EVENTS, TCL.IDLE_EVENTS,
// or others defined by event sources.
{
int result = 0;
// No event flags is equivalent to TCL_ALL_EVENTS.
if ( ( flags & TCL.ALL_EVENTS ) == 0 )
{
flags |= TCL.ALL_EVENTS;
}
// The core of this procedure is an infinite loop, even though
// we only service one event. The reason for this is that we
// may be processing events that don't do anything inside of Tcl.
while ( true )
{
// If idle events are the only things to service, skip the
// main part of the loop and go directly to handle idle
// events (i.e. don't wait even if TCL_DONT_WAIT isn't set).
if ( ( flags & TCL.ALL_EVENTS ) == TCL.IDLE_EVENTS )
{
return serviceIdle();
}
long sysTime = ( System.DateTime.Now.Ticks - 621355968000000000 ) / 10000;
// If some timers have been expired, queue them into the
// event queue. We can't process expired times right away,
// because there may already be other events on the queue.
if ( !timerPending && ( timerList.Count > 0 ) )
{
TimerHandler h = (TimerHandler)timerList[0];
if ( h.atTime <= sysTime )
{
TimerEvent Tevent = new TimerEvent();
Tevent.notifier = this;
queueEvent( Tevent, TCL.QUEUE_TAIL );
timerPending = true;
}
}
// Service a queued event, if there are any.
if ( serviceEvent( flags ) != 0 )
{
result = 1;
break;
}
// There is no event on the queue. Check for idle events.
if ( ( flags & TCL.IDLE_EVENTS ) != 0 )
{
if ( serviceIdle() != 0 )
{
result = 1;
break;
}
}
if ( ( flags & TCL.DONT_WAIT ) != 0 )
{
break;
}
// We don't have any event to service. We'll wait if
// TCL.DONT_WAIT. When the following wait() call returns,
// one of the following things may happen:
//
// (1) waitTime milliseconds has elasped (if waitTime != 0);
//
// (2) The primary notifier has been notify()'ed by other threads:
// (a) an event is queued by queueEvent().
// (b) a timer handler was created by new TimerHandler();
// (c) an idle handler was created by new IdleHandler();
// (3) We receive an InterruptedException.
//
try
{
// Don't acquire the monitor until we are about to wait
// for notification from another thread. It is critical
// that this entire method not be synchronized since
// a call to processEvent via serviceEvent could take
// a very long time. We don't want the monitor held
// during that time since that would force calls to
// queueEvent in other threads to wait.
lock ( this )
{
if ( timerList.Count > 0 )
{
TimerHandler h = (TimerHandler)timerList[0];
long waitTime = h.atTime - sysTime;
if ( waitTime > 0 )
{
System.Threading.Monitor.Wait( this, TimeSpan.FromMilliseconds( waitTime ) );
}
}
else
{
System.Threading.Monitor.Wait( this );
}
} // synchronized (this)
}
catch ( System.Threading.ThreadInterruptedException e )
{
// We ignore any InterruptedException and loop continuously
// until we receive an event.
}
}
return result;
}
private int serviceIdle()
{
int result = 0;
int gen = idleGeneration;
idleGeneration++;
// The code below is trickier than it may look, for the following
// reasons:
//
// 1. New handlers can get added to the list while the current
// one is being processed. If new ones get added, we don't
// want to process them during this pass through the list (want
// to check for other work to do first). This is implemented
// using the generation number in the handler: new handlers
// will have a different generation than any of the ones currently
// on the list.
// 2. The handler can call doOneEvent, so we have to remove
// the handler from the list before calling it. Otherwise an
// infinite loop could result.
while ( idleList.Count > 0 )
{
IdleHandler h = (IdleHandler)idleList[0];
if ( h.generation > gen )
{
break;
}
idleList.RemoveAt( 0 );
if ( h.invoke() != 0 )
{
result = 1;
}
}
return result;
}
static Notifier()
{
notifierTable = new Hashtable();
}
} // end Notifier
class TimerEvent : TclEvent
{
// The notifier what owns this TimerEvent.
new internal Notifier notifier;
public override int processEvent( int flags )
// Same as flags passed to Notifier.doOneEvent.
{
if ( ( flags & TCL.TIMER_EVENTS ) == 0 )
{
return 0;
}
long sysTime = ( System.DateTime.Now.Ticks - 621355968000000000 ) / 10000;
int gen = notifier.timerGeneration;
notifier.timerGeneration++;
// The code below is trickier than it may look, for the following
// reasons:
//
// 1. New handlers can get added to the list while the current
// one is being processed. If new ones get added, we don't
// want to process them during this pass through the list to
// avoid starving other event sources. This is implemented
// using the timer generation number: new handlers will have
// a newer generation number than any of the ones currently on
// the list.
// 2. The handler can call doOneEvent, so we have to remove
// the handler from the list before calling it. Otherwise an
// infinite loop could result.
// 3. Because we only fetch the current time before entering the loop,
// the only way a new timer will even be considered runnable is if
// its expiration time is within the same millisecond as the
// current time. This is fairly likely on Windows, since it has
// a course granularity clock. Since timers are placed
// on the queue in time order with the most recently created
// handler appearing after earlier ones with the same expiration
// time, we don't have to worry about newer generation timers
// appearing before later ones.
while ( notifier.timerList.Count > 0 )
{
TimerHandler h = (TimerHandler)notifier.timerList[0];
if ( h.generation > gen )
{
break;
}
if ( h.atTime > sysTime )
{
break;
}
notifier.timerList.RemoveAt( 0 );
h.invoke();
}
notifier.timerPending = false;
return 1;
}
} // end TimerEvent
}