001 /*
002 Copyright (c) 1996-2011, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029 package org.hd.d.pg2k.webSvr.exhibit;
030
031 import java.io.IOException;
032 import java.io.Serializable;
033 import java.lang.ref.SoftReference;
034 import java.lang.ref.WeakReference;
035 import java.util.ArrayList;
036 import java.util.Arrays;
037 import java.util.Collection;
038 import java.util.Collections;
039 import java.util.Date;
040 import java.util.List;
041 import java.util.Observable;
042 import java.util.Observer;
043 import java.util.Timer;
044 import java.util.TimerTask;
045
046 import org.hd.d.pg2k.svrCore.AllExhibitImmutableData;
047 import org.hd.d.pg2k.svrCore.GenUtils;
048 import org.hd.d.pg2k.svrCore.MemoryTools;
049 import org.hd.d.pg2k.svrCore.Name;
050 import org.hd.d.pg2k.svrCore.Name.ExhibitFull;
051 import org.hd.d.pg2k.svrCore.Rnd;
052
053 import ORG.hd.d.IsDebug;
054
055 /**
056 * Created by IntelliJ IDEA.
057 * User: Damon Hart-Davis
058 * Date: 24-May-2003
059 * Time: 17:42:56
060 */
061
062 /**JavaBean that can filter/sort and cache selections of exhibits.
063 * Designed to be used as a bean in a JSP or servlet, and can cache
064 * filter/sort results, for example, at application level or in a
065 * user's session.
066 * <p>
067 * This applies its filter to the input List (or entire exhibit set)
068 * that it is provided with; deriving classes may make all or part
069 * of the result directly or indirectly available.
070 * <p>
071 * This silently recomputes its results if they may have been affected by
072 * any update in the exhibit data (or when select() is called differently
073 * from the last call to the same instance). The cached state is not persisted
074 * if the bean is serialised, but the filter description is. This is
075 * only meant to be persisted, for example, to be passed between
076 * multiple instances of a distributable WAR and not to be stored for lengthy
077 * periods on disc, so does not use some of the fancier support for
078 * serialisation.
079 * <p>
080 * This uses but does not cache a DataSourceBean which can be supplied
081 * explicitly or recovered implicitly from a servlet context
082 * (at application scope), and can take the whole set of exhibits as
083 * input or can use the output of another filter. In all cases the output
084 * is only recomputed if the DataSourceBean.exhibitDataVersionHash()
085 * method result changes (so any randomised or conditional element had better be the
086 * last stage of the chain and only have (say) page or request scope),
087 * or if select() is called with different args to the last call on this
088 * instance (so, if for example, a different List argument is passed;
089 * select does a test on the hash code of the select() List arg).
090 * <p>
091 * This is thread-safe and as concurrent as possible, with the intention
092 * that it may be involved in many users' activity simultaneously.
093 * <p>
094 * The expression must be set (ideally exactly once) before any attempt
095 * is made to fetch results. For JSPs this set can be done in
096 * the body of the JSP so that it is done only on creation of the bean
097 * as a small efficiency gain.
098 * <p>
099 * If the expression is changed and is not equals() to the previous value,
100 * the cache is cleared.
101 * <p>
102 * The result returned by any select() operation is an immutable, thread-safe,
103 * RandomAccess List (eg like ArrayList). (This cannot be enforced on
104 * derived classes, but is part of the general contract.)
105 * <p>
106 * The expression is of the form:
107 * <pre>
108 * expr = expr sortExpr
109 * | expr filterExpr
110 * | ALL
111 * sortExpr = sort(args)
112 * filterExpr = filter(args)
113 * | filterExpr filterExpr &&
114 * | filterExpr filterExpr ||
115 * | filterExpr !
116 * </pre>
117 * ie a filter on its own is applied to all the available exhibits,
118 * or can be negated or combined (ANDed or ORed) with other filter
119 * expressions to make more complex filter expressions.
120 * A complete expression is a series of filter expressions and/or
121 * sorts applied in a pipeline to the result of some filtering.
122 * <p>
123 * The expression syntax is reverse Polish amd dataflow is from left
124 * to right.
125 * <p>
126 * This class implements the Observer interface and when it receives
127 * a notification its immediately clears its cache by default.
128 * This is in addition to the normal automatic cache discard in select()
129 * when the imput has changed, and is an optimisation; behvaiour is correct
130 * without it.
131 * This mechanism helps discard stale data quickly and thus make better use of memory.
132 * <p>
133 * This can also be set to expire automatically from time to time,
134 * for example to allow for slowly mutating/changing data
135 * such as ExhibitPropsComputableMutable items.
136 * The default is not to expire periodically.
137 * <p>
138 * This Serializable so as to be able to be stored in
139 * a servlet session; nothing especially long-lived or sensitive.
140 * <p>
141 * TODO: Should do validation on deserialisation.
142 */
143 public abstract class AbstractFilterBean implements Serializable
144 {
145 /**If true, become an Observer of the DataSourceBean so that we can eagerly discard a stale cache.
146 * Observing the DataSourceBean should not cause a deadlock
147 * as we do so only indirectly/asynchronously.
148 */
149 private static final boolean OBSERVE_DATASOURCEBEAN = true;
150
151 /**The (filter/sorter) expression, or null if not yet set.
152 * Accessed under the instance lock.
153 * <p>
154 * This is persisted if the object is serialised.
155 */
156 private Expr expr;
157
158 /**If true then led cached value go if we run short of memory, eg hold it via a SoftReference.
159 * This is useful for infrequently-accessed and not-too-expensive
160 * filters.
161 * <p>
162 * This can be set or reset at any time but is only effective when
163 * the cache is filled, so is probably done once, before the first
164 * set of values is retrieved.
165 * <p>
166 * By default this value is true and the cache is memory sensitive,
167 * ie won't cause the system to run out of memory.
168 * <p>
169 * Accessed under the instance lock.
170 * <p>
171 * This is persisted if the object is serialised.
172 */
173 private boolean memorySensitiveCache = true;
174
175
176
177 /**Expiry interval in ms, zero (the default) means no automatic expiry; non-negative.
178 * The data may expire in a little less than this.
179 * <p>
180 * Is volatile so as to allow access without a lock.
181 */
182 private int expiryInterval;
183
184 /**Get expiry interval in ms, zero (the default) means no automatic expiry; non-negative.
185 * The data may expire in a little less than this.
186 */
187 public int getExpiryInterval()
188 { return(expiryInterval); }
189
190 /**Set expiry interval in ms, zero means no automatic expiry; non-negative.
191 * The data may expire in a little less than this
192 * at random to spread out expiries and recalculations,
193 * or may expire more slowly if the system is short of power
194 * or otherwise under stress to help reduce system load.
195 */
196 public void setExpiryInterval(final int expiryInterval)
197 {
198 if(expiryInterval < 0) { throw new IllegalArgumentException(); }
199 this.expiryInterval = expiryInterval;
200 }
201
202
203
204
205
206 /**Get exhibitDataHash. */
207 private int getExhibitDataHash()
208 { return(exhibitDataHash); }
209
210 /**Get exhibitDataHash. */
211 private void setExhibitDataHash(final int exhibitDataHash)
212 { this.exhibitDataHash = exhibitDataHash; }
213
214 /**The exhibit data hash when the cache was computed.
215 * If this changes we should regard the cache as invalid.
216 * <p>
217 * Is volatile so that it can be accessed from multiple
218 * threads without taking a lock.
219 * <p>
220 * Should be accessed only by getExhibitDataHash() and
221 * setExhibitDataHash().
222 * <p>
223 * Zero when the cache is clear/invalid (the initial state).
224 */
225 private transient volatile int exhibitDataHash;
226
227 /**The hashCode of the last List arg to select or 0 if none.
228 * This is used to try to cheaply ensure that we discard our
229 * cache if our calling pattern changes (which should not
230 * happen, but this may save us from poor usage).
231 * <p>
232 * Accessed under the instance lock and is private to
233 * _select() and clearCache().
234 * <p>
235 * Zero when the cache is clear.
236 */
237 private transient int lastListArgHashCode;
238
239 /**The cached results (or null if not yet computed).
240 * This consists of an immutable RandomAccess List,
241 * not sorted unless the expression sorts it,
242 * of the accepted results;
243 * no slots in the List are duplicates nor nulls.
244 * <p>
245 * If the exhibit data hash (or select()-arg hash)
246 * changes then we should regard the cache as invalid.
247 * <p>
248 * This is null only if not yet computed; it is zero length
249 * if the result set was empty.
250 * <p>
251 * Accessed under the instance lock and is private to
252 * _select() and clearCache().
253 * <p>
254 * Is either a List or a SoftReference wrapper around one.
255 */
256 private transient Object _basicCache;
257
258 /**Embedded observer object.
259 * Designed to break link between DataSourceBean and our class
260 * to allow us to be GCed even if we are recorded as an observer.
261 * <p>
262 * This inner object has only a SoftReference to us.
263 */
264 private final MyObserver observer = new MyObserver(this);
265
266
267 /**Callback for 'emergency free' from MemoryTools; never null after registration.
268 * We create and register this when we first call _select()
269 * and re-register it when it is called back.
270 * <p>
271 * When it is called it unguards any SoftReferences
272 * allowing memory to be freed rather than an OutOfMemoryException
273 * getting thrown somewhere in the application.
274 * <p>
275 * It is assumed that MemoryTools holds this via a WeakReference
276 * so that it will not inhibit this instance being GCed.
277 */
278 private Runnable emergencyFreeHook;
279
280
281 /**Name for this instance; by default null. */
282 private String name;
283
284 /**Get the name for this instance; by default null. */
285 public synchronized String getName() { return(name); }
286
287 /**Set the name for this instance; by default null. */
288 public synchronized void setName(final String n) { name = n; }
289
290 /**Get human-readable summary; never null. */
291 @Override public synchronized String toString()
292 {
293 final StringBuilder sb = new StringBuilder(64);
294 final String className = getClass().getSimpleName();
295 sb.append("".equals(className) ? "AbstractFilterBean" : className);
296 final String n = name;
297 if(null != n) { sb.append(':').append(n); }
298 return(sb.toString());
299 }
300
301
302 /**Set the `memory-sensitive' flag for the cache.
303 * This is useful for infrequently-accessed and not-too-expensive
304 * filters; if set true then the cache is automatically discarded
305 * if we run short of heap space.
306 * <p>
307 * This can be set or reset at any time but is only effective when
308 * the cache is filled, so is probably best done once, before the first
309 * set of values is retrieved.
310 * <p>
311 * By default this value is true and the cache is not memory sensitive.
312 * <p>
313 * This is persisted if the object is serialised.
314 */
315 public synchronized void setMemorySensitiveCache(final boolean f)
316 { memorySensitiveCache = f; }
317
318 /**Get the `memory-sensitive' flag for the cache.
319 */
320 public synchronized boolean getMemorySensitiveCache()
321 { return(memorySensitiveCache); }
322
323 /**Set the simple single (input) filter/sort expression directly.
324 * If the expression is set to a new value not equals() to the old,
325 * then the cache is cleared.
326 *
327 * @throws java.lang.IllegalArgumentException if the argument is null
328 */
329 public synchronized void setExpr(final Expr _expr)
330 throws IllegalArgumentException, IllegalStateException
331 {
332 if(_expr == null) { throw new IllegalArgumentException(); }
333 if(!_expr.equals(expr)) { clearCache(); }
334 expr = _expr;
335 }
336
337 /**Get the simple single (input) filter/sort expression directly.
338 * Has protected scope to prevent every Tom Dick and Harry fiddling with
339 * a possibly-mutable expression.
340 * <p>
341 * This can be overridden to provide some extra filtering or sorting, for example,
342 * in derived classes.
343 *
344 * @return the expression or null if none yet set
345 */
346 protected synchronized Expr getExpr()
347 {
348 return(expr);
349 }
350
351 /**Set the filter/sorter expression as a textual expression.
352 * Will clear the cache if the expression changes.
353 *
354 * @throws java.lang.IllegalArgumentException if the argument is null or unparseable
355 */
356 public synchronized void setExpr(final String _expr)
357 throws IllegalArgumentException, IllegalStateException
358 {
359 if(_expr == null) { throw new IllegalArgumentException(); }
360
361 // PARSE THE EXPESSION.
362 throw new RuntimeException("NOT IMPLEMENTED");
363 }
364
365 /**Clears the cache of the basic set of filtered/sorted items.
366 * Replaces any cached item with null and
367 * resets the internal hashes of the data and argument to null.
368 * <p>
369 * A deriving class can override this to clear any extra state it maintains,
370 * but must chain to this first in order to clear the underlying caches
371 * and free other resources.
372 * <p>
373 * This routine is synchronized and overriding methods
374 * almost certainly should be so too.
375 */
376 protected synchronized void clearCache()
377 {
378 _basicCache = null;
379 setExhibitDataHash(0);
380 lastListArgHashCode = 0;
381
382 // Clear retained references entirely, if any.
383 _retainedCacheRefs = null;
384
385 // Kill off any timer task(s) associated with this cache.
386 final TimerTask ttExpiry = _ttExpiry;
387 if(ttExpiry != null)
388 {
389 _ttExpiry = null;
390 ttExpiry.cancel();
391 }
392 final TimerTask ttPurgeRetainedRefs = _ttPurgeRetainedRefs;
393 if(ttPurgeRetainedRefs != null)
394 {
395 _ttPurgeRetainedRefs = null;
396 ttPurgeRetainedRefs.cancel();
397 }
398 }
399
400 /**Invalidate the cache.
401 * Can be called by anyone;
402 * may force the cache to be invalidated immediately or before next use.
403 * <p>
404 * May need to grab locks.
405 */
406 public void invalidate()
407 { clearCache(); }
408
409 /**Timer task if any, to eagerly force expiry and release of resources, associated with this cache.
410 * Marked transient since we couldn't persist this
411 * but in any case after deserialisation would expect it to be set up again if needed.
412 * <p>
413 * Accessed under the instance lock.
414 */
415 private transient TimerTask _ttExpiry;
416
417 /**Expiry time of the cache (ie its content is to be considered invalid after this).
418 * Private to _isValid and accessed in the scope of its lock (instance lock).
419 * May also be peeked at by the ExpiryTask under the lock.
420 * <p>
421 * Transient to force an immediate recomputation if deserialised.
422 */
423 private transient long expiryTime;
424
425 /**Returns cache reference if the cache still seems to be valid.
426 * If null, then the cache needs to be rebuilt.
427 * As a side-effect, this will clear an invalid cache.
428 * <p>
429 * Parameters as for _select(),
430 * return value as for _select() or null if invalid.
431 * <p>
432 * Cheaper than calling _select() to force the cache to be invalidated
433 * since it does not attempt to rebuild the cache.
434 */
435 @SuppressWarnings("unchecked")
436 protected synchronized List<Name.ExhibitFull> _isValid(final DataSourceBean ds,
437 final List<Name.ExhibitFull> inputSet)
438 throws IOException
439 {
440 // Cache is (1) null or (2) a List or (3) a SoftReference to a List.
441 final Object _bc = _basicCache;
442 final List<Name.ExhibitFull> currentCache = (_bc instanceof SoftReference)
443 ? ((List<Name.ExhibitFull>) (((SoftReference) _bc).get()))
444 : ((List<Name.ExhibitFull>) _bc);
445
446 final long now;
447 final boolean isInvalid =
448 (currentCache == null) ||
449 ((expiryInterval != 0) && (expiryTime < (now = System.currentTimeMillis())) &&
450 // Postpone expiry somewhat when the system (temporarily) needs to conserve resources...
451 ((!GenUtils.mustConservePower()) || (expiryTime+expiryInterval < now))) ||
452 (lastListArgHashCode != _computeListArgHashCode(inputSet)) ||
453 (getExhibitDataHash() != ds.exhibitDataVersionHash());
454
455 if(isInvalid)
456 {
457 // Abolish old cached values.
458 clearCache();
459
460 return(null);
461 }
462
463 return(currentCache);
464 }
465
466 /**Compute the hash over our List argument, used by _isValid() and _select().
467 * Note that the identityHashCode() is used.
468 */
469 private static int _computeListArgHashCode(final List<Name.ExhibitFull> inputSet)
470 { return((inputSet == null) ? 0 : System.identityHashCode(inputSet)); }
471
472 /**Records the time at which _select() was last started; 0 if not yet called (or if deserialised).
473 * This effectively (conservatively) notes when the cached value was last requested/used,
474 * as well as providing a baseline to measure how long value (re)generation took.
475 * <p>
476 * Only written to by _select(), under the instance lock.
477 */
478 private transient long _select_startTime;
479
480 /**Records the time taken to complete _select() and descendants; non-negative and 0 if not yet called (or if deserialised).
481 * This is based on when the last request was made
482 * to hold a strong reference to internal state.
483 * <p>
484 * May not be maintained if the cache is not memory sensitive.
485 * <p>
486 * TODO: May be used to give the cached state a new lease of life on each access.
487 * <p>
488 * Only written to by _retainCacheRef(), under the instance lock.
489 */
490 private transient long _select_runTime;
491
492 /**Internal method that computes the result of accepted exhibits.
493 * The result is an immutable list of full exhibit names.
494 * <p>
495 * The ds argument must not be null.
496 * <p>
497 * If the inputSet argument is null the whole set of
498 * exhibits is used, else the supplied set is used.
499 * This must be a List of String items with no nulls nor
500 * duplicates.
501 * <p>
502 * This will (re)compute its results if necessary, ie:
503 * <ul>
504 * <li>there are no cached results,
505 * <li>the exhibit hash has changed,
506 * <li>the List argument identity hashCode has changed.
507 * </ul>
508 * otherwise this routine is relatively fast and returns the cached List.
509 * <p>
510 * This never returns null.
511 * <p>
512 * Null names and names that do not represent items currently
513 * in the exhibit set are silently dropped.
514 * <p>
515 * This routine may register this instance with the data source
516 * as an Observer of exhibit set changes in order to be able to quickly
517 * invalidate/release our (then-stale) cache upon any changes.
518 *
519 * @param inputSet null, or a list of full exhibit names
520 *
521 * @throws IllegalStateException if the filter has not been set
522 */
523 protected synchronized List<ExhibitFull> _select(final DataSourceBean ds,
524 final List<ExhibitFull> inputSet)
525 throws IOException, IllegalStateException
526 {
527 if(expr == null) { throw new IllegalStateException(); }
528
529 // Note when we started this call (and any computation work).
530 _select_startTime = System.currentTimeMillis();
531
532 // Cache is (1) null or (2) a List or (3) a SoftReference to a List.
533 List<Name.ExhibitFull> currentCache = _isValid(ds, inputSet);
534
535 // Hash over all exhibits...
536 final int currentHash = ds.exhibitDataVersionHash();
537
538 // If the cache is invalid, (re)build it.
539 // The _isValid() call will have cleared the cache already if invalid.
540 if(currentCache == null)
541 {
542 // Warn against possible misuse of a filter, dumping a stack trace.
543 if(lastListArgHashCode != 0)
544 {
545 final Error e = new Error("WARNING: FilterBean List argument changed between non-null input List items which is probably a programming error; please check");
546 e.printStackTrace();
547 }
548
549 // Recompute the results...
550 //if(ORG.hd.d.IsDebug.isDebug) { System.err.println("[Recomputing AbstractFilterBean result for: " + this + ".]"); }
551
552 final AllExhibitImmutableData aeid = ds.getAllExhibitImmutableData(-1);
553
554 // Create a new (empty) retained-references collection.
555 // This must dumbly add all the values given without examining them,
556 // eg like a List rather than a Set.
557 _retainedCacheRefs = new ArrayList<Object>();
558 // Kill any pending task to clear the refs (from a previous run).
559 final TimerTask ttPurgeRetainedRefs = _ttPurgeRetainedRefs;
560 if(ttPurgeRetainedRefs != null)
561 {
562 _ttPurgeRetainedRefs = null;
563 ttPurgeRetainedRefs.cancel();
564 }
565
566 // Use the input exhibit set if supplied,
567 // else examine the entire extant exhibit set...
568 List<Name.ExhibitFull> inputNames;
569 if(inputSet == null) // No input set; start with all names.
570 { inputNames = aeid.getAllExhibitNamesSorted(); }
571 else
572 {
573 // Extract String[] copy of input List.
574 //
575 // We should consider verifying no duplicate inputs
576 // in debug mode...
577 inputNames = new ArrayList<Name.ExhibitFull>(inputSet);
578 // Quick check for gross problems in copy...
579 if((inputNames.size() > 0) && (inputNames.get(0) == null))
580 { throw new IllegalArgumentException("problem copying inputSet"); }
581 }
582
583 // We can optionally Observe the DataSourceBean
584 // now that we've taken some state from it
585 // so as to discard our cache when the AEP changes.
586 // We avoid a potential deadlock hazard by
587 // registering our observer and having it call back invalidate()
588 // immediately but asynchronously via our timer.
589 if(OBSERVE_DATASOURCEBEAN)
590 {
591 lifetimeManager.schedule((new TimerTask() {
592 @Override public void run() { ds.addObserver(observer); }
593 }), 0); // ASAP.
594 }
595
596 // Apply the expression to the input set...
597 final Name.ExhibitFull[] rawResult = getExpr().eval(ds.getAllExhibitProperties(-1L), inputNames.toArray(new Name.ExhibitFull[inputNames.size()])); // FIXME: avoid expensive copying
598 // ...and prepare to cache an unmodifiable (and thread-safe) random-access copy.
599 currentCache = Collections.unmodifiableList(Arrays.asList(rawResult));
600
601 // Cache the data, with a strong or soft reference as appropriate.
602 if(memorySensitiveCache)
603 {
604 _basicCache = new SoftReference<List<Name.ExhibitFull>>(currentCache);
605
606 // Allow premature/emergency expiry of cache
607 // (though only effective for memory-sensitive instances)
608 // if called back critically short of memory.
609 if(null == emergencyFreeHook)
610 {
611 final MemoryTools.RecurrentEmergencyFreeHandle r = new MemoryTools.RecurrentEmergencyFreeHandle() {
612 public void run() { _clearRetainedRefs(); }// Allow retained refs to be freed, without blocking.
613 };
614 emergencyFreeHook = r;
615 MemoryTools.registerRecurrentEmergencyFreeHandle(r); // Initial registration.
616 }
617 }
618 else
619 { _basicCache = currentCache; }
620 // Always force basic computation-time calculation.
621 // If memory-sensitive, protect this from premature expiry.
622 _retainCacheRef(currentCache);
623
624 // We use the hash we got before recomputing the results
625 // so that we recompute again later if the cached
626 // data changed while we were in this function, ie
627 // we err on the side of caution.
628 setExhibitDataHash(currentHash);
629
630 // We store the hash of the current arg list,
631 // so if it changes we are likely to notice and
632 // will recompute the cached value.
633 lastListArgHashCode = _computeListArgHashCode(inputSet);
634
635 // Set new expiry time if on automatic expiry.
636 // No more than specified expiry interval;
637 // may be as little as half that.
638 if(expiryInterval > 0)
639 {
640 expiryTime = System.currentTimeMillis() +
641 ((expiryInterval/2)|1) + Rnd.fastRnd.nextInt((expiryInterval/2)|1);
642
643 // Use our timer to help us eagerly free up resources soon after expiry.
644 // We do this regardless of general memory sensitivity of this cache.
645 assert(null == _ttExpiry) : "we don't expect there to be an extant expiry task at this point";
646 lifetimeManager.schedule(_ttExpiry = (new ExpiryTask(this)), new Date(expiryTime));
647 }
648 }
649
650 // Refresh the cache to give it a little more life if memory sensitive.
651 _retainCacheRef(null);
652
653 // Return immutable list...
654 return(currentCache);
655 }
656
657 /**Unique Serialisation class ID generated by http://random.hd.org/. */
658 private static final long serialVersionUID = -4409526925504499328L;
659
660
661 /**Task to actively expire a filter and release its memory.
662 * Only retains a weak reference to the filter bean
663 * so as not to prevent the bean being GCed.
664 */
665 private static final class ExpiryTask extends TimerTask
666 {
667 /**Weak ref to the bean; never null though the referent may be. */
668 private final WeakReference<AbstractFilterBean> beanWR;
669 ExpiryTask(final AbstractFilterBean afb) { beanWR = new WeakReference<AbstractFilterBean>(afb); }
670 @Override public void run()
671 {
672 final AbstractFilterBean bean = beanWR.get();
673 if(null == bean)
674 {
675 if(IsDebug.isDebug) { System.out.println("AbstractFilterBean expired before we got there..."); }
676 return;
677 }
678
679 synchronized(bean)
680 {
681 // Reduce the possibility of a race with other invalidation/recreation
682 // by vetoing the invalidation if the instance seems no longer 'expired'.
683 final long expiryTime = bean.expiryTime;
684 if(expiryTime == 0) { return; }
685 if(expiryTime > System.currentTimeMillis()) { return; }
686 // OK, release the resources.
687 bean.invalidate();
688 }
689 if(IsDebug.isDebug) { System.out.println("INFO: eagerly expired FilterBean cache... "+bean); }
690 }
691 }
692
693 /**Task to weaken retained references to soft from strong.
694 * Only retains a weak reference to the filter bean
695 * so as not to prevent the bean being GCed.
696 */
697 private static final class WeakenerTask extends TimerTask
698 {
699 /**Weak ref to the bean; never null though the referent may be. */
700 private final WeakReference<AbstractFilterBean> beanWR;
701
702 /**Size of the set of retained refs when we were scheduled; non-negative. */
703 private final int sizeWhenScheduled;
704
705 private WeakenerTask(final AbstractFilterBean afb, final int sizeWhenScheduled)
706 {
707 beanWR = new WeakReference<AbstractFilterBean>(afb);
708 this.sizeWhenScheduled = sizeWhenScheduled;
709 }
710
711 /**Weaken retained refs to soft ref under instance lock, eg so as to prevent clear before select() finished. */
712 @Override public void run()
713 {
714 final AbstractFilterBean bean = beanWR.get();
715 if(null == bean)
716 {
717 if(IsDebug.isDebug) { System.out.println("AbstractFilterBean expired while in the ref-weakening queue..."); }
718 return;
719 }
720
721 synchronized(bean)
722 {
723 final Object rrLater = bean._retainedCacheRefs;
724 if(rrLater == null) { return; } // Nothing to do.
725 if(rrLater instanceof SoftReference) { return; } // Nothing to do.
726 final Collection<Object> rr = (Collection<Object>) rrLater;
727 if(rr.size() != sizeWhenScheduled) { return; } // We were superseded.
728 // Weaken refs collection to soft reference itself...
729 bean._retainedCacheRefs = new SoftReference<Collection<Object>>(rr);
730 if(IsDebug.isDebug && bean.memorySensitiveCache) { System.out.println("INFO: "+toString()+": weakened retained references, count="+sizeWhenScheduled); }
731 }
732
733 // Take this opportunity to do a tiny bit of housekeeping
734 // and clear up any cancelled tasks on the timer queue.
735 // May in incur O(n^2) cost total for n live AFB instances.
736 lifetimeManager.purge(); // Potentially help along GC a little...
737 }
738 }
739
740 /**This object has only a WeakReference to the outer class to allow GC.
741 * This way it remains fairly safe for us to register with a DataSourceBean
742 * even if the outer class may be short-lived and churned in huge numbers.
743 * <p>
744 * FIXME: should find way to stop ourself from observing any DataSourceBean
745 * (there can be at most one)
746 * when the outer object is GCed or maybe when the cache is cleared.
747 * Else, lots of redundant (if small) MyObserver objects may
748 * accumulate in the DataSourceBean until the exhibit set changes.
749 */
750 private static final class MyObserver implements Observer
751 {
752 /**Capture a SoftReference to the outer class.
753 * @param outer non-null reference to outer class.
754 */
755 MyObserver(final AbstractFilterBean outer)
756 { beanWR = new WeakReference<AbstractFilterBean>(outer); }
757
758 /**WeakReference to outer class; never null. */
759 private final WeakReference<AbstractFilterBean> beanWR;
760
761 /**This causes the cache to be cleared immediately.
762 * If [Object] arg is an Integer whose value is same as the value that
763 * we hold for the DataSourceBean.ds.exhibitDataVersionHash()
764 * then we return immediately on the assumption that this indicates
765 * that we still hold an up-to-date cache.
766 * This avoids extra builds and clears of the cache when the FilterBean
767 * is accessed frequently, and in particular between the underlying
768 * data changing and a call being made sometime later to this routine
769 * to clear all potentially-expired caches.
770 * <p>
771 * We use [Observable] o to deregister ourself from the Observable
772 * on the grounds that our next select() operation will (re-)register
773 * us with whatever DataSourceBean we are then using.
774 * This removal avoids leaving references to us
775 * from multiple DataSourceBeans or after we have otherwise expired,
776 * which would otherwise make GC harder.
777 * <p>
778 * The cache-clearing code is synchronized so the caller may be briefly
779 * blocked and there may be a small risk of deadlocks if clearCache()
780 * does something foolish.
781 */
782 public final void update(final Observable o, final Object arg)
783 {
784 final AbstractFilterBean bean = beanWR.get();
785 if(bean == null)
786 {
787 // Outer class has been GCed,
788 // so we don't want to be an Observer any more!
789 o.deleteObserver(this);
790 return;
791 }
792
793 // We take a lock only to perform the clearCache()
794 // at a slight risk of a race with a resulting
795 // extra or missed cache clear.
796
797 // If the argument indicates that we are in fact still up to date
798 // then return immediately.
799 if((arg instanceof Integer) &&
800 (((Integer) arg).intValue() == bean.getExhibitDataHash()))
801 { return; }
802
803 o.deleteObserver(this); // No more updates until the next select().
804 // Avoid any chance of deadlock by doing the invalidation asynchronously.
805 lifetimeManager.schedule(new TimerTask(){
806 @Override public void run() { bean.invalidate(); } // FIXME: small possibility of race with other invalidation.
807 }, 0); // Schedule ASAP.
808 }
809 }
810
811
812 /**Collection of all references to internal cache objects requested to be retained; null when none present.
813 * This is used to prevent otherwise-soft cache references being cleared too soon.
814 * <p>
815 * Intended primarily for memory-sensitive caches
816 * though will do no harm will occur if references to internal cached state are always posted here.
817 * <p>
818 * Erased/dropped entirely when the cache is invalidated.
819 * <p>
820 * Cleared or switched to being themselves held via a soft reference
821 * whenever the initial lifetime is over,
822 * but not until _select() and its descendants have finished their computation.
823 * May then be switched back to being a strong reference for a while
824 * on each read access of not already expired, to provide another memory-insensitive period.
825 * <p>
826 * Created in _select() when a new value is computed.
827 * Each derived class should post its recomputed cached values (under the instance lock)
828 * after (re)computing them, to prevent them expiring too soon.
829 * <p>
830 * Accessed under the instance lock
831 * except marked volatile so that it may safely be cleared without a lock by _clearRetainedRefs().
832 */
833 private transient volatile Object _retainedCacheRefs;
834
835
836 /**Fixed dead SoftReference to use to clear _retainedCacheRefs without allocating.
837 * May be null during class initialisation.
838 */
839 private static final SoftReference<Collection<Object>> NO_REFS = new SoftReference<Collection<Object>>(null);
840
841 /**Clears retained references without taking any locks.
842 * Suitable for use in an emergency, eg when memory is very low.
843 */
844 private void _clearRetainedRefs()
845 {
846 SoftReference<Collection<Object>> nr = NO_REFS;
847 if(null == nr) { nr = new SoftReference<Collection<Object>>(null); }
848 _retainedCacheRefs = nr;
849 }
850
851 /**Add to the retained refs an cached internal object that must be maintained to avoid the cache expiring.
852 * This should only be called by descendants/callers of _select()
853 * and under the same instance lock.
854 * <p>
855 * Automatically creates/replaces the timer task to clear this.
856 * <p>
857 * We go through the motions even if not currently a memory-sensitive cache
858 * since that status may be changed at any time.
859 * <p>
860 * @param newRef reference to retain, or null to revive existing references if possible
861 */
862 protected synchronized void _retainCacheRef(final Object newRef)
863 {
864 final long started = _select_startTime;
865 if(started == 0) { throw new IllegalStateException("Called from wrong place?"); }
866
867 final Object rrRaw = _retainedCacheRefs;
868 final boolean isSoftRef = (rrRaw instanceof SoftReference);
869 final Collection<Object> rr = (!isSoftRef) ? (Collection<Object>) rrRaw :
870 ((SoftReference<Collection<Object>>)rrRaw).get();
871 if(rr == null)
872 {
873 // Note that GC races may get some cached state cleared separately from the rest
874 // and thus trying to repost its references here with a cleared reference holder....
875 if((newRef != null) && !isSoftRef)
876 { throw new IllegalStateException("Called from the wrong place?"); }
877 if(IsDebug.isDebug) { System.out.println("INFO: "+toString()+": refs already gone"); }
878 return; // References expired harmlessly?
879 }
880 if(IsDebug.isDebug && memorySensitiveCache && (newRef == null) && isSoftRef) { System.out.println("INFO: "+toString()+": revived references: "+rr.size()); }
881 // In case currently held via a soft reference, we force it back to a strong reference now.
882 _retainedCacheRefs = rr; // Force back strong ref.
883
884 // Add any new reference provided.
885 if(newRef != null) { rr.add(newRef); }
886
887 // Purge any existing task...
888 final TimerTask ttPurgeRetainedRefs = _ttPurgeRetainedRefs;
889 if(ttPurgeRetainedRefs != null)
890 {
891 _ttPurgeRetainedRefs = null;
892 ttPurgeRetainedRefs.cancel();
893 }
894 // If this is a new reference being posted
895 // then continue to compute/extend our notion of how long the computation is taking
896 // else base the computation on the previous run, if any.
897 // If (temporarily) conserving energy then attempt to avoid premature timer wakeup and timer firing.
898 final int minRetentionMs = GenUtils.mustConservePower() ? 1000 : 100;
899 final long computeTime;
900 if(newRef != null)
901 {
902 computeTime = System.currentTimeMillis() - started;
903 _select_runTime = computeTime; // Save the computed run time.
904 }
905 else
906 {
907 computeTime = _select_runTime;
908 }
909 // Create a new task delayed enough to allow a minimum life for this state
910 // that also helps ensure that a maximum fraction of wall-clock time is spent (re)building it.
911 // Will only weaken/clear the refs if the retained set has not grown since this this task ran,
912 // ensuring that one or more premature expiring tasks from inner parts of the select()
913 // piled up waiting to grab the lock and get in are simply ignored.
914 // Only get the longer lifetime if there's lots of spare memory.
915 // Maximum strong-retention life time is capped to limit task queue lifetime, etc.
916 long minimumRetentionMs = Math.min(MAX_STRONG_RETENTION_MS,
917 Math.max(minRetentionMs, computeTime *
918 (!MemoryTools.lotsFree() ? BUILD_TIME_LIVE_RATIO_MEM_STRESS : BUILD_TIME_LIVE_RATIO)));
919 // Note that if there's an expiryTime then we cap the retention time to that.
920 if(expiryInterval > 0) { minimumRetentionMs = Math.min(minimumRetentionMs, expiryInterval+1); }
921 final int sizeWhenScheduled = rr.size();
922 lifetimeManager.schedule(_ttPurgeRetainedRefs = (new WeakenerTask(AbstractFilterBean.this, sizeWhenScheduled)), minimumRetentionMs);
923 if(IsDebug.isDebug && memorySensitiveCache && (minimumRetentionMs > 30000)) { System.out.println("INFO: "+toString()+": compute time ms="+computeTime+", minimum retention ms="+minimumRetentionMs+", retained ref count="+sizeWhenScheduled+", lifetime extension: "+(newRef==null)); }
924 }
925
926 /**A task for clearing the _retainedCacheRefs; ephemeral and usually null.
927 * Cleared when computing a new value in _select() and descendants,
928 * also when the cache is invalidated,
929 * and when this task itself completes,
930 * and killed and recreated as each new item is added to _retainedCacheRefs
931 * to reflect the time taken to compute the values so far on this run.
932 * <p>
933 * Helps retain the cache for a minimum lifetime.
934 */
935 private transient TimerTask _ttPurgeRetainedRefs;
936
937 /**Minimum multiple of build time for which we attempt to keep a memory-sensitive cache live; strictly positive.
938 * Increasing this value reduces the fraction of wall-clock time
939 * that we may have to spend rebuilding,
940 * but increases the risk of causing memory starvation elsewhere.
941 * <p>
942 * TODO: may replace with dynamic calculation of ratio large enough to keep amortised build time to <~100ms per _select().
943 * <p>
944 * A reasonable value is probably in the range 5--1000;
945 * a power of two may prove marginally more efficient.
946 */
947 protected static final int BUILD_TIME_LIVE_RATIO = 256;
948
949 /**Minimum multiple of build time for which we attempt to keep a memory-sensitive cache live when memory may be short; strictly positive.
950 * Should be (much) smaller than BUILD_TIME_LIVE_RATIO.
951 * <p>
952 * A reasonable value is probably in the range 2--32;
953 * a power of two may prove marginally more efficient.
954 */
955 protected static final int BUILD_TIME_LIVE_RATIO_MEM_STRESS = 16;
956
957 /**Maximum strong-ref retention time in ms; strictly positive.
958 * This helps bound the length of the task queues, etc.
959 * <p>
960 * Should be a very long time in computing terms, eg minutes through to hours.
961 */
962 protected static final int MAX_STRONG_RETENTION_MS = 3636001 + Rnd.fastRnd.nextInt(1800*1000);
963
964 /**Timer used to help manage the cache lifetimes for memory-sensitive caches.
965 * Is a daemon to avoid keeping the JVM alive.
966 * <p>
967 * Should never be cancelled (will be GCed in due course if this class is).
968 * <p>
969 * It is very important that any tasks not due to run immediately
970 * should only hold onto AFB references weakly
971 * (and thus must be static classed for example)
972 * to avoid retaining garbage and compounding the problem that is intended to solve!
973 */
974 private static final Timer lifetimeManager = new Timer("AbstractFilterBean lifetimeManager", true);
975 }