wasCSharpSQLite – Blame information for rev
?pathlinks?
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 | } |