1 package org.apache.jcs.engine;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.IOException;
23 import java.io.Serializable;
24 import java.util.ArrayList;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.apache.jcs.engine.behavior.ICacheElement;
29 import org.apache.jcs.engine.behavior.ICacheEventQueue;
30 import org.apache.jcs.engine.behavior.ICacheListener;
31 import org.apache.jcs.engine.stats.StatElement;
32 import org.apache.jcs.engine.stats.Stats;
33 import org.apache.jcs.engine.stats.behavior.IStatElement;
34 import org.apache.jcs.engine.stats.behavior.IStats;
35 import org.apache.jcs.utils.threadpool.ThreadPool;
36 import org.apache.jcs.utils.threadpool.ThreadPoolManager;
37
38 import EDU.oswego.cs.dl.util.concurrent.BoundedBuffer;
39
40 /***
41 * An event queue is used to propagate ordered cache events to one and only one target listener.
42 * <p>
43 * This is a modified version of the experimental version. It uses a PooledExecutor and a
44 * BoundedBuffer to queue up events and execute them as threads become available.
45 * <p>
46 * The PooledExecutor is static, because presumably these processes will be IO bound, so throwing
47 * more than a few threads at them will serve no purpose other than to saturate the IO interface. In
48 * light of this, having one thread per region seems unnecessary. This may prove to be false.
49 * <p>
50 * @author Aaron Smuts
51 * @author Travis Savo <tsavo@ifilm.com>
52 */
53 public class PooledCacheEventQueue
54 implements ICacheEventQueue
55 {
56 private static final int queueType = POOLED_QUEUE_TYPE;
57
58 private static final Log log = LogFactory.getLog( PooledCacheEventQueue.class );
59
60
61
62
63 private int waitToDieMillis = 10000;
64
65 private ICacheListener listener;
66
67 private long listenerId;
68
69 private String cacheName;
70
71 private int maxFailure;
72
73
74 private int waitBeforeRetry;
75
76 private boolean destroyed = true;
77
78 private boolean working = true;
79
80
81 private ThreadPool pool = null;
82
83 /***
84 * Constructor for the CacheEventQueue object
85 * <p>
86 * @param listener
87 * @param listenerId
88 * @param cacheName
89 * @param maxFailure
90 * @param waitBeforeRetry
91 * @param threadPoolName
92 */
93 public PooledCacheEventQueue( ICacheListener listener, long listenerId, String cacheName, int maxFailure,
94 int waitBeforeRetry, String threadPoolName )
95 {
96 if ( listener == null )
97 {
98 throw new IllegalArgumentException( "listener must not be null" );
99 }
100
101 this.listener = listener;
102 this.listenerId = listenerId;
103 this.cacheName = cacheName;
104 this.maxFailure = maxFailure <= 0 ? 3 : maxFailure;
105 this.waitBeforeRetry = waitBeforeRetry <= 0 ? 500 : waitBeforeRetry;
106
107
108 if ( threadPoolName == null )
109 {
110 threadPoolName = "cache_event_queue";
111 }
112 pool = ThreadPoolManager.getInstance().getPool( threadPoolName );
113
114 if ( log.isDebugEnabled() )
115 {
116 log.debug( "Constructed: " + this );
117 }
118 }
119
120
121
122
123
124 public int getQueueType()
125 {
126 return queueType;
127 }
128
129 /***
130 * Event Q is emtpy.
131 */
132 public synchronized void stopProcessing()
133 {
134 destroyed = true;
135 }
136
137 /***
138 * Returns the time to wait for events before killing the background thread.
139 * <p>
140 * @return the time to wait before shutting down in ms.
141 */
142 public int getWaitToDieMillis()
143 {
144 return waitToDieMillis;
145 }
146
147 /***
148 * Sets the time to wait for events before killing the background thread.
149 * <p>
150 * @param wtdm
151 */
152 public void setWaitToDieMillis( int wtdm )
153 {
154 waitToDieMillis = wtdm;
155 }
156
157 /***
158 * @return String info.
159 */
160 public String toString()
161 {
162 return "CacheEventQueue [listenerId=" + listenerId + ", cacheName=" + cacheName + "]";
163 }
164
165 /***
166 * @return true if not destroyed.
167 */
168 public boolean isAlive()
169 {
170 return ( !destroyed );
171 }
172
173 /***
174 * @param aState
175 */
176 public void setAlive( boolean aState )
177 {
178 destroyed = !aState;
179 }
180
181 /***
182 * @return The listenerId value
183 */
184 public long getListenerId()
185 {
186 return listenerId;
187 }
188
189 /***
190 * Destroy the queue. Interrupt all threads.
191 */
192 public synchronized void destroy()
193 {
194 if ( !destroyed )
195 {
196 destroyed = true;
197
198
199 pool.getPool().interruptAll();
200 if ( log.isInfoEnabled() )
201 {
202 log.info( "Cache event queue destroyed: " + this );
203 }
204 }
205 }
206
207 /***
208 * Constructs a PutEvent for the object and passes it to the event queue.
209 * <p>
210 * @param ce The feature to be added to the PutEvent attribute
211 * @exception IOException
212 */
213 public synchronized void addPutEvent( ICacheElement ce )
214 throws IOException
215 {
216 if ( isWorking() )
217 {
218 put( new PutEvent( ce ) );
219 }
220 else
221 {
222 if ( log.isWarnEnabled() )
223 {
224 log.warn( "Not enqueuing Put Event for [" + this + "] because it's non-functional." );
225 }
226 }
227 }
228
229 /***
230 * @param key The feature to be added to the RemoveEvent attribute
231 * @exception IOException
232 */
233 public synchronized void addRemoveEvent( Serializable key )
234 throws IOException
235 {
236 if ( isWorking() )
237 {
238 put( new RemoveEvent( key ) );
239 }
240 else
241 {
242 if ( log.isWarnEnabled() )
243 {
244 log.warn( "Not enqueuing Remove Event for [" + this + "] because it's non-functional." );
245 }
246 }
247 }
248
249 /***
250 * @exception IOException
251 */
252 public synchronized void addRemoveAllEvent()
253 throws IOException
254 {
255 if ( isWorking() )
256 {
257 put( new RemoveAllEvent() );
258 }
259 else
260 {
261 if ( log.isWarnEnabled() )
262 {
263 log.warn( "Not enqueuing RemoveAll Event for [" + this + "] because it's non-functional." );
264 }
265 }
266 }
267
268 /***
269 * @exception IOException
270 */
271 public synchronized void addDisposeEvent()
272 throws IOException
273 {
274 if ( isWorking() )
275 {
276 put( new DisposeEvent() );
277 }
278 else
279 {
280 if ( log.isWarnEnabled() )
281 {
282 log.warn( "Not enqueuing Dispose Event for [" + this + "] because it's non-functional." );
283 }
284 }
285 }
286
287 /***
288 * Adds an event to the queue.
289 * <p>
290 * @param event
291 */
292 private void put( AbstractCacheEvent event )
293 {
294 try
295 {
296 pool.execute( event );
297 }
298 catch ( InterruptedException e )
299 {
300 log.error( e );
301 }
302 }
303
304 /***
305 * @return Statistics info
306 */
307 public String getStats()
308 {
309 return getStatistics().toString();
310 }
311
312
313
314
315
316 public IStats getStatistics()
317 {
318 IStats stats = new Stats();
319 stats.setTypeName( "Pooled Cache Event Queue" );
320
321 ArrayList elems = new ArrayList();
322
323 IStatElement se = null;
324
325 se = new StatElement();
326 se.setName( "Working" );
327 se.setData( "" + this.working );
328 elems.add( se );
329
330 se = new StatElement();
331 se.setName( "Destroyed" );
332 se.setData( "" + this.isAlive() );
333 elems.add( se );
334
335 se = new StatElement();
336 se.setName( "Empty" );
337 se.setData( "" + this.isEmpty() );
338 elems.add( se );
339
340 if ( pool.getQueue() != null )
341 {
342 if ( pool.getQueue() instanceof BoundedBuffer )
343 {
344 BoundedBuffer bb = (BoundedBuffer) pool.getQueue();
345 se = new StatElement();
346 se.setName( "Queue Size" );
347 se.setData( "" + bb.size() );
348 elems.add( se );
349
350 se = new StatElement();
351 se.setName( "Queue Capacity" );
352 se.setData( "" + bb.capacity() );
353 elems.add( se );
354 }
355 }
356
357 se = new StatElement();
358 se.setName( "Pool Size" );
359 se.setData( "" + pool.getPool().getPoolSize() );
360 elems.add( se );
361
362 se = new StatElement();
363 se.setName( "Maximum Pool Size" );
364 se.setData( "" + pool.getPool().getMaximumPoolSize() );
365 elems.add( se );
366
367
368 IStatElement[] ses = (IStatElement[]) elems.toArray( new StatElement[elems.size()] );
369 stats.setStatElements( ses );
370
371 return stats;
372 }
373
374
375
376 /***
377 * Retries before declaring failure.
378 * <p>
379 * @author asmuts
380 * @created January 15, 2002
381 */
382 private abstract class AbstractCacheEvent
383 implements Runnable
384 {
385 int failures = 0;
386
387 boolean done = false;
388
389 /***
390 * Main processing method for the AbstractCacheEvent object. It calls the abstract doRun
391 * method that all concrete instances must implement.
392 */
393 public void run()
394 {
395 try
396 {
397 doRun();
398 }
399 catch ( IOException e )
400 {
401 if ( log.isWarnEnabled() )
402 {
403 log.warn( e );
404 }
405 if ( ++failures >= maxFailure )
406 {
407 if ( log.isWarnEnabled() )
408 {
409 log.warn( "Error while running event from Queue: " + this
410 + ". Dropping Event and marking Event Queue as non-functional." );
411 }
412 setWorking( false );
413 setAlive( false );
414 return;
415 }
416 if ( log.isInfoEnabled() )
417 {
418 log.info( "Error while running event from Queue: " + this + ". Retrying..." );
419 }
420 try
421 {
422 Thread.sleep( waitBeforeRetry );
423 run();
424 }
425 catch ( InterruptedException ie )
426 {
427 if ( log.isErrorEnabled() )
428 {
429 log.warn( "Interrupted while sleeping for retry on event " + this + "." );
430 }
431 setWorking( false );
432 setAlive( false );
433 }
434 }
435 }
436
437 /***
438 * @exception IOException
439 */
440 protected abstract void doRun()
441 throws IOException;
442 }
443
444 /***
445 * An event that puts an item to a ICacheListener
446 * <p>
447 * @author asmuts
448 * @created January 15, 2002
449 */
450 private class PutEvent
451 extends AbstractCacheEvent
452 {
453 private ICacheElement ice;
454
455 /***
456 * Constructor for the PutEvent object
457 * @param ice
458 * @exception IOException
459 */
460 PutEvent( ICacheElement ice )
461 throws IOException
462 {
463 this.ice = ice;
464 }
465
466 /***
467 * Tells the ICacheListener to handle the put.
468 * <p>
469 * @exception IOException
470 */
471 protected void doRun()
472 throws IOException
473 {
474 listener.handlePut( ice );
475 }
476
477 public String toString()
478 {
479 return new StringBuffer( "PutEvent for key: " ).append( ice.getKey() ).append( " value: " )
480 .append( ice.getVal() ).toString();
481 }
482
483 }
484
485 /***
486 * An event that knows how to call remove on an ICacheListener
487 * <p>
488 * @author asmuts
489 * @created January 15, 2002
490 */
491 private class RemoveEvent
492 extends AbstractCacheEvent
493 {
494 private Serializable key;
495
496 /***
497 * Constructor for the RemoveEvent object
498 * @param key
499 * @exception IOException
500 */
501 RemoveEvent( Serializable key )
502 throws IOException
503 {
504 this.key = key;
505 }
506
507 /***
508 * Calls remove on the listner.
509 * <p>
510 * @exception IOException
511 */
512 protected void doRun()
513 throws IOException
514 {
515 listener.handleRemove( cacheName, key );
516 }
517
518
519
520
521
522 public String toString()
523 {
524 return new StringBuffer( "RemoveEvent for " ).append( key ).toString();
525 }
526
527 }
528
529 /***
530 * An event that knows how to call remove all on an ICacheListener
531 * <p>
532 * @author asmuts
533 * @created January 15, 2002
534 */
535 private class RemoveAllEvent
536 extends AbstractCacheEvent
537 {
538 /***
539 * Call removeAll on the listener.
540 * <p>
541 * @exception IOException
542 */
543 protected void doRun()
544 throws IOException
545 {
546 listener.handleRemoveAll( cacheName );
547 }
548
549
550
551
552
553 public String toString()
554 {
555 return "RemoveAllEvent";
556 }
557
558 }
559
560 /***
561 * The Event put into the queue for dispose requests.
562 * <p>
563 * @author asmuts
564 * @created January 15, 2002
565 */
566 private class DisposeEvent
567 extends AbstractCacheEvent
568 {
569 /***
570 * Called when gets to the end of the queue
571 * <p>
572 * @exception IOException
573 */
574 protected void doRun()
575 throws IOException
576 {
577 listener.handleDispose( cacheName );
578 }
579
580 public String toString()
581 {
582 return "DisposeEvent";
583 }
584 }
585
586 /***
587 * @return whether or not the queue is functional
588 */
589 public boolean isWorking()
590 {
591 return working;
592 }
593
594 /***
595 * @param isWorkingArg whether the queue is functional
596 */
597 public void setWorking( boolean isWorkingArg )
598 {
599 working = isWorkingArg;
600 }
601
602 /***
603 * If the Queue is using a bounded channel we can determine the size. If it is zero or we can't
604 * determine the size, we return true.
605 * <p>
606 * @return whether or not there are items in the queue
607 */
608 public boolean isEmpty()
609 {
610 if ( pool.getQueue() == null )
611 {
612 return pool.getQueue().peek() == null;
613 }
614 else
615 {
616 if ( pool.getQueue() instanceof BoundedBuffer )
617 {
618 BoundedBuffer bb = (BoundedBuffer) pool.getQueue();
619 return bb.size() == 0;
620 }
621 else
622 {
623 return true;
624 }
625 }
626 }
627
628 /***
629 * Returns the number of elements in the queue. If the queue cannot determine the size
630 * accurately it will return 1.
631 * <p>
632 * @return number of items in the queue.
633 */
634 public int size()
635 {
636 if ( pool.getQueue() == null )
637 {
638 return pool.getQueue().peek() == null ? 0 : 1;
639 }
640 else
641 {
642 if ( pool.getQueue() instanceof BoundedBuffer )
643 {
644 BoundedBuffer bb = (BoundedBuffer) pool.getQueue();
645 return bb.size();
646 }
647 else
648 {
649 return 1;
650 }
651 }
652 }
653 }