wasCSharpSQLite – Blame information for rev

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 /*
2 * Notifier.java --
3 *
4 * Implements the Jacl version of the Notifier class.
5 *
6 * Copyright (c) 1997 Sun Microsystems, Inc.
7 *
8 * See the file "license.terms" for information on usage and
9 * redistribution of this file, and for a DISCLAIMER OF ALL
10 * WARRANTIES.
11 *
12 * Included in SQLite3 port to C# for use in testharness only; 2008 Noah B Hart
13 *
14 * RCS @(#) $Id: Notifier.java,v 1.8 2003/03/11 02:21:14 mdejong Exp $
15 *
16 */
17 using System;
18 using System.Collections;
19  
20 namespace tcl.lang
21 {
22  
23 // Implements the Jacl version of the Notifier class. The Notifier is
24 // the lowest-level part of the event system. It is used by
25 // higher-level event sources such as file, JavaBean and timer
26 // events. The Notifier manages an event queue that holds TclEvent
27 // objects.
28 //
29 // The Jacl notifier is designed to run in a multi-threaded
30 // environment. Each notifier instance is associated with a primary
31 // thread. Any thread can queue (or dequeue) events using the
32 // queueEvent (or deleteEvents) call. However, only the primary thread
33 // may process events in the queue using the doOneEvent()
34 // call. Attepmts to call doOneEvent from a non-primary thread will
35 // cause a TclRuntimeError.
36 //
37 // This class does not have a public constructor and thus cannot be
38 // instantiated. The only way to for a Tcl extension to get an
39 // Notifier is to call Interp.getNotifier() (or
40 // Notifier.getNotifierForThread() ), which returns the Notifier for that
41 // interpreter (thread).
42  
43 public class Notifier : EventDeleter
44 {
45  
46 // First pending event, or null if none.
47  
48 private TclEvent firstEvent;
49  
50 // Last pending event, or null if none.
51  
52 private TclEvent lastEvent;
53  
54 // Last high-priority event in queue, or null if none.
55  
56 private TclEvent markerEvent;
57  
58 // Event that was just processed by serviceEvent
59  
60 private TclEvent servicedEvent = null;
61  
62 // The primary thread of this notifier. Only this thread should process
63 // events from the event queue.
64  
65 internal System.Threading.Thread primaryThread;
66  
67 // Stores the Notifier for each thread.
68  
69 private static Hashtable notifierTable;
70  
71 // List of registered timer handlers.
72  
73 internal ArrayList timerList;
74  
75 // Used to distinguish older timer handlers from recently-created ones.
76  
77 internal int timerGeneration;
78  
79 // True if there is a pending timer event in the event queue, false
80 // otherwise.
81  
82 internal bool timerPending;
83  
84 // List of registered idle handlers.
85  
86 internal ArrayList idleList;
87  
88 // Used to distinguish older idle handlers from recently-created ones.
89  
90 internal int idleGeneration;
91  
92 // Reference count of the notifier. It's used to tell when a notifier
93 // is no longer needed.
94  
95 internal int refCount;
96  
97 private Notifier( System.Threading.Thread primaryTh )
98 {
99 primaryThread = primaryTh;
100 firstEvent = null;
101 lastEvent = null;
102 markerEvent = null;
103  
104 timerList = new ArrayList( 10 );
105 timerGeneration = 0;
106 idleList = new ArrayList( 10 );
107 idleGeneration = 0;
108 timerPending = false;
109 refCount = 0;
110 }
111 public static Notifier getNotifierForThread( System.Threading.Thread thread )
112 // The thread that owns this Notifier.
113 {
114 lock ( typeof( tcl.lang.Notifier ) )
115 {
116 Notifier notifier = (Notifier)notifierTable[thread];
117 if ( notifier == null )
118 {
119 notifier = new Notifier( thread );
120 SupportClass.PutElement( notifierTable, thread, notifier );
121 }
122  
123 return notifier;
124 }
125 }
126 public void preserve()
127 {
128 lock ( this )
129 {
130 if ( refCount < 0 )
131 {
132 throw new TclRuntimeError( "Attempting to preserve a freed Notifier" );
133 }
134 ++refCount;
135 }
136 }
137 public void release()
138 {
139 lock ( this )
140 {
141 if ( ( refCount == 0 ) && ( primaryThread != null ) )
142 {
143 throw new TclRuntimeError( "Attempting to release a Notifier before it's preserved" );
144 }
145 if ( refCount <= 0 )
146 {
147 throw new TclRuntimeError( "Attempting to release a freed Notifier" );
148 }
149 --refCount;
150 if ( refCount == 0 )
151 {
152 SupportClass.HashtableRemove( notifierTable, primaryThread );
153 primaryThread = null;
154 }
155 }
156 }
157 public void queueEvent( TclEvent evt, int position )
158 // One of TCL.QUEUE_TAIL,
159 // TCL.QUEUE_HEAD or TCL.QUEUE_MARK.
160 {
161 lock ( this )
162 {
163 evt.notifier = this;
164  
165 if ( position == TCL.QUEUE_TAIL )
166 {
167 // Append the event on the end of the queue.
168  
169 evt.next = null;
170  
171 if ( firstEvent == null )
172 {
173 firstEvent = evt;
174 }
175 else
176 {
177 lastEvent.next = evt;
178 }
179 lastEvent = evt;
180 }
181 else if ( position == TCL.QUEUE_HEAD )
182 {
183 // Push the event on the head of the queue.
184  
185 evt.next = firstEvent;
186 if ( firstEvent == null )
187 {
188 lastEvent = evt;
189 }
190 firstEvent = evt;
191 }
192 else if ( position == TCL.QUEUE_MARK )
193 {
194 // Insert the event after the current marker event and advance
195 // the marker to the new event.
196  
197 if ( markerEvent == null )
198 {
199 evt.next = firstEvent;
200 firstEvent = evt;
201 }
202 else
203 {
204 evt.next = markerEvent.next;
205 markerEvent.next = evt;
206 }
207 markerEvent = evt;
208 if ( evt.next == null )
209 {
210 lastEvent = evt;
211 }
212 }
213 else
214 {
215 // Wrong flag.
216  
217 throw new TclRuntimeError( "wrong position \"" + position + "\", must be TCL.QUEUE_HEAD, TCL.QUEUE_TAIL or TCL.QUEUE_MARK" );
218 }
219  
220 if ( System.Threading.Thread.CurrentThread != primaryThread )
221 {
222 System.Threading.Monitor.PulseAll( this );
223 }
224 }
225 }
226 public void deleteEvents( EventDeleter deleter )
227 // The deleter that checks whether an event
228 // should be removed.
229 {
230 lock ( this )
231 {
232 TclEvent evt, prev;
233 TclEvent servicedEvent = null;
234  
235 // Handle the special case of deletion of a single event that was just
236 // processed by the serviceEvent() method.
237  
238 if ( deleter == this )
239 {
240 servicedEvent = this.servicedEvent;
241 if ( servicedEvent == null )
242 throw new TclRuntimeError( "servicedEvent was not set by serviceEvent()" );
243 this.servicedEvent = null;
244 }
245  
246 for ( prev = null, evt = firstEvent; evt != null; evt = evt.next )
247 {
248 if ( ( ( servicedEvent == null ) && ( deleter.deleteEvent( evt ) == 1 ) ) || ( evt == servicedEvent ) )
249 {
250 if ( evt == firstEvent )
251 {
252 firstEvent = evt.next;
253 }
254 else
255 {
256 prev.next = evt.next;
257 }
258 if ( evt.next == null )
259 {
260 lastEvent = prev;
261 }
262 if ( evt == markerEvent )
263 {
264 markerEvent = prev;
265 }
266 if ( evt == servicedEvent )
267 {
268 servicedEvent = null;
269 break; // Just service this one event in the special case
270 }
271 }
272 else
273 {
274 prev = evt;
275 }
276 }
277 if ( servicedEvent != null )
278 {
279 throw new TclRuntimeError( "servicedEvent was not removed from the queue" );
280 }
281 }
282 }
283 public int deleteEvent( TclEvent evt )
284 {
285 throw new TclRuntimeError( "The Notifier.deleteEvent() method should not be called" );
286 }
287 internal int serviceEvent( int flags )
288 // Indicates what events should be processed.
289 // May be any combination of TCL.WINDOW_EVENTS
290 // TCL.FILE_EVENTS, TCL.TIMER_EVENTS, or other
291 // flags defined elsewhere. Events not
292 // matching this will be skipped for processing
293 // later.
294 {
295 TclEvent evt;
296  
297 // No event flags is equivalent to TCL_ALL_EVENTS.
298  
299 if ( ( flags & TCL.ALL_EVENTS ) == 0 )
300 {
301 flags |= TCL.ALL_EVENTS;
302 }
303  
304 // Loop through all the events in the queue until we find one
305 // that can actually be handled.
306  
307 evt = null;
308 while ( ( evt = getAvailableEvent( evt ) ) != null )
309 {
310 // Call the handler for the event. If it actually handles the
311 // event then free the storage for the event. There are two
312 // tricky things here, both stemming from the fact that the event
313 // code may be re-entered while servicing the event:
314 //
315 // 1. Set the "isProcessing" field to true. This is a signal to
316 // ourselves that we shouldn't reexecute the handler if the
317 // event loop is re-entered.
318 // 2. When freeing the event, must search the queue again from the
319 // front to find it. This is because the event queue could
320 // change almost arbitrarily while handling the event, so we
321 // can't depend on pointers found now still being valid when
322 // the handler returns.
323  
324 evt.isProcessing = true;
325  
326 if ( evt.processEvent( flags ) != 0 )
327 {
328 evt.isProcessed = true;
329 // Don't allocate/grab the monitor for the event unless sync()
330 // has been called in another thread. This is thread safe
331 // since sync() checks the isProcessed flag before calling wait.
332 if ( evt.needsNotify )
333 {
334 lock ( evt )
335 {
336 System.Threading.Monitor.PulseAll( evt );
337 }
338 }
339 // Remove this specific event from the queue
340 servicedEvent = evt;
341 deleteEvents( this );
342 return 1;
343 }
344 else
345 {
346 // The event wasn't actually handled, so we have to
347 // restore the isProcessing field to allow the event to be
348 // attempted again.
349  
350 evt.isProcessing = false;
351 }
352  
353 // The handler for this event asked to defer it. Just go on to
354 // the next event.
355  
356 continue;
357 }
358 return 0;
359 }
360 private TclEvent getAvailableEvent( TclEvent skipEvent )
361 // Indicates that the given event should not
362 // be returned. This argument can be null.
363 {
364 lock ( this )
365 {
366 TclEvent evt;
367  
368 for ( evt = firstEvent; evt != null; evt = evt.next )
369 {
370 if ( ( evt.isProcessing == false ) && ( evt.isProcessed == false ) && ( evt != skipEvent ) )
371 {
372 return evt;
373 }
374 }
375 return null;
376 }
377 }
378 public int doOneEvent( int flags )
379 // Miscellaneous flag values: may be any
380 // combination of TCL.DONT_WAIT,
381 // TCL.WINDOW_EVENTS, TCL.FILE_EVENTS,
382 // TCL.TIMER_EVENTS, TCL.IDLE_EVENTS,
383 // or others defined by event sources.
384 {
385 int result = 0;
386  
387 // No event flags is equivalent to TCL_ALL_EVENTS.
388  
389 if ( ( flags & TCL.ALL_EVENTS ) == 0 )
390 {
391 flags |= TCL.ALL_EVENTS;
392 }
393  
394 // The core of this procedure is an infinite loop, even though
395 // we only service one event. The reason for this is that we
396 // may be processing events that don't do anything inside of Tcl.
397  
398 while ( true )
399 {
400 // If idle events are the only things to service, skip the
401 // main part of the loop and go directly to handle idle
402 // events (i.e. don't wait even if TCL_DONT_WAIT isn't set).
403  
404 if ( ( flags & TCL.ALL_EVENTS ) == TCL.IDLE_EVENTS )
405 {
406 return serviceIdle();
407 }
408  
409 long sysTime = ( System.DateTime.Now.Ticks - 621355968000000000 ) / 10000;
410  
411 // If some timers have been expired, queue them into the
412 // event queue. We can't process expired times right away,
413 // because there may already be other events on the queue.
414  
415 if ( !timerPending && ( timerList.Count > 0 ) )
416 {
417 TimerHandler h = (TimerHandler)timerList[0];
418  
419 if ( h.atTime <= sysTime )
420 {
421 TimerEvent Tevent = new TimerEvent();
422 Tevent.notifier = this;
423 queueEvent( Tevent, TCL.QUEUE_TAIL );
424 timerPending = true;
425 }
426 }
427  
428 // Service a queued event, if there are any.
429  
430 if ( serviceEvent( flags ) != 0 )
431 {
432 result = 1;
433 break;
434 }
435  
436 // There is no event on the queue. Check for idle events.
437  
438 if ( ( flags & TCL.IDLE_EVENTS ) != 0 )
439 {
440 if ( serviceIdle() != 0 )
441 {
442 result = 1;
443 break;
444 }
445 }
446  
447 if ( ( flags & TCL.DONT_WAIT ) != 0 )
448 {
449 break;
450 }
451  
452 // We don't have any event to service. We'll wait if
453 // TCL.DONT_WAIT. When the following wait() call returns,
454 // one of the following things may happen:
455 //
456 // (1) waitTime milliseconds has elasped (if waitTime != 0);
457 //
458 // (2) The primary notifier has been notify()'ed by other threads:
459 // (a) an event is queued by queueEvent().
460 // (b) a timer handler was created by new TimerHandler();
461 // (c) an idle handler was created by new IdleHandler();
462 // (3) We receive an InterruptedException.
463 //
464  
465 try
466 {
467 // Don't acquire the monitor until we are about to wait
468 // for notification from another thread. It is critical
469 // that this entire method not be synchronized since
470 // a call to processEvent via serviceEvent could take
471 // a very long time. We don't want the monitor held
472 // during that time since that would force calls to
473 // queueEvent in other threads to wait.
474  
475 lock ( this )
476 {
477 if ( timerList.Count > 0 )
478 {
479 TimerHandler h = (TimerHandler)timerList[0];
480 long waitTime = h.atTime - sysTime;
481 if ( waitTime > 0 )
482 {
483 System.Threading.Monitor.Wait( this, TimeSpan.FromMilliseconds( waitTime ) );
484 }
485 }
486 else
487 {
488 System.Threading.Monitor.Wait( this );
489 }
490 } // synchronized (this)
491 }
492 catch ( System.Threading.ThreadInterruptedException e )
493 {
494 // We ignore any InterruptedException and loop continuously
495 // until we receive an event.
496 }
497 }
498  
499 return result;
500 }
501 private int serviceIdle()
502 {
503 int result = 0;
504 int gen = idleGeneration;
505 idleGeneration++;
506  
507 // The code below is trickier than it may look, for the following
508 // reasons:
509 //
510 // 1. New handlers can get added to the list while the current
511 // one is being processed. If new ones get added, we don't
512 // want to process them during this pass through the list (want
513 // to check for other work to do first). This is implemented
514 // using the generation number in the handler: new handlers
515 // will have a different generation than any of the ones currently
516 // on the list.
517 // 2. The handler can call doOneEvent, so we have to remove
518 // the handler from the list before calling it. Otherwise an
519 // infinite loop could result.
520  
521 while ( idleList.Count > 0 )
522 {
523 IdleHandler h = (IdleHandler)idleList[0];
524 if ( h.generation > gen )
525 {
526 break;
527 }
528 idleList.RemoveAt( 0 );
529 if ( h.invoke() != 0 )
530 {
531 result = 1;
532 }
533 }
534  
535 return result;
536 }
537 static Notifier()
538 {
539 notifierTable = new Hashtable();
540 }
541 } // end Notifier
542  
543 class TimerEvent : TclEvent
544 {
545  
546 // The notifier what owns this TimerEvent.
547  
548 new internal Notifier notifier;
549  
550 public override int processEvent( int flags )
551 // Same as flags passed to Notifier.doOneEvent.
552 {
553 if ( ( flags & TCL.TIMER_EVENTS ) == 0 )
554 {
555 return 0;
556 }
557  
558 long sysTime = ( System.DateTime.Now.Ticks - 621355968000000000 ) / 10000;
559 int gen = notifier.timerGeneration;
560 notifier.timerGeneration++;
561  
562 // The code below is trickier than it may look, for the following
563 // reasons:
564 //
565 // 1. New handlers can get added to the list while the current
566 // one is being processed. If new ones get added, we don't
567 // want to process them during this pass through the list to
568 // avoid starving other event sources. This is implemented
569 // using the timer generation number: new handlers will have
570 // a newer generation number than any of the ones currently on
571 // the list.
572 // 2. The handler can call doOneEvent, so we have to remove
573 // the handler from the list before calling it. Otherwise an
574 // infinite loop could result.
575 // 3. Because we only fetch the current time before entering the loop,
576 // the only way a new timer will even be considered runnable is if
577 // its expiration time is within the same millisecond as the
578 // current time. This is fairly likely on Windows, since it has
579 // a course granularity clock. Since timers are placed
580 // on the queue in time order with the most recently created
581 // handler appearing after earlier ones with the same expiration
582 // time, we don't have to worry about newer generation timers
583 // appearing before later ones.
584  
585 while ( notifier.timerList.Count > 0 )
586 {
587 TimerHandler h = (TimerHandler)notifier.timerList[0];
588 if ( h.generation > gen )
589 {
590 break;
591 }
592 if ( h.atTime > sysTime )
593 {
594 break;
595 }
596 notifier.timerList.RemoveAt( 0 );
597 h.invoke();
598 }
599  
600 notifier.timerPending = false;
601 return 1;
602 }
603 } // end TimerEvent
604 }