001 package org.hd.d.pg2k.ai.scorer;
002
003
004 import java.io.IOException;
005 import java.util.Set;
006
007 import org.hd.d.pg2k.svrCore.AllExhibitImmutableData;
008 import org.hd.d.pg2k.svrCore.ExhibitName;
009 import org.hd.d.pg2k.svrCore.ExhibitStaticAttr;
010 import org.hd.d.pg2k.svrCore.ExhibitThumbnails;
011 import org.hd.d.pg2k.svrCore.MemoryTools;
012 import org.hd.d.pg2k.svrCore.Name;
013 import org.hd.d.pg2k.svrCore.SimpleLoggerIF;
014 import org.hd.d.pg2k.svrCore.Name.ExhibitFull;
015 import org.hd.d.pg2k.svrCore.Tuple.Pair;
016 import org.hd.d.pg2k.svrCore.datasource.SimpleExhibitPipelineIF;
017 import org.hd.d.pg2k.svrCore.vars.EventPeriod;
018
019 /**Shared abstract base to handle common Scorer cache tasks.
020 * Package-visible only for now to support initial cache implementations.
021 *
022 * @author dhd
023 */
024 abstract class AbstractScorerCache implements ScorerCacheIF
025 {
026 protected AbstractScorerCache(final ScorerPopulation population,
027 final SimpleExhibitPipelineIF dataSource,
028 final SimpleLoggerIF log)
029 {
030 if((population == null) || (log == null) || (dataSource == null))
031 { throw new IllegalArgumentException(); }
032 this.log = log;
033 this.population = population;
034 this.dataSource = dataSource;
035
036 // We set up internal caches with capped sizes
037 // so as to concentrate memory usage on the population;
038 // the internal caches are scale in proportion.
039
040 // Limit the the raw score cache in proportion to the population size.
041 final int rMax = Math.max(2 * population.maxSize(), 2 * ScorerCreator.SUGGESTED_CALIB_SET_SIZE);
042 // Have a reduced lower limit to be whittled down to when memory is short.
043 final int rMin = Math.max(1+ScorerCreator.MIN_SUGGESTED_CALIB_SET_SIZE, rMax/4);
044 _rawScores = MemoryTools.SimpleLRUMapAutoSizeForHitRate.<Pair<String,Name.ExhibitFull>, Pair<Long, ScoreAndConf>>create(rMin, rMax, "_rawScores");
045 }
046
047 /**Logger; never null. */
048 protected final SimpleLoggerIF log;
049 /**Scorer population (and cache of Scorer "goodness"); never null. */
050 protected final ScorerPopulation population;
051 /**Live data source for variables, exhibit data and metadata, etc; never null. */
052 protected final SimpleExhibitPipelineIF dataSource;
053
054 /**Make internal dataSource available to classes in the same package only; never null. */
055 SimpleExhibitPipelineIF getDataSource() { return(dataSource); }
056
057 /**Get current population size; non-negative. */
058 public int size() { return(population.size()); }
059
060 /**Make population available to classes in the same package only; never null. */
061 ScorerPopulation getPopulation() { return(population); }
062
063 /**Save work-in-progress if possible, and free up resources, ASAP.
064 * This may enable us to reduce work lost during a graceful system shutdown,
065 * but many shutdowns may not be graceful and so we should incrementally save/checkpoint too.
066 * <p>
067 * By default does nothing.
068 */
069 public void destroy() { }
070
071 /**Current set of best-available Scorers with their parameters; never null but may be empty.
072 * This operation should always be relatively fast.
073 */
074 public Set<String> getCurrentScorersWithParameters(final boolean allowStale)
075 { return(population.getCurrentScorersWithParameters(allowStale)); }
076
077 /**Size-limited cache of raw exhibit scores (and when computed); never null.
078 * Map from scorer name-and-parameters and exhibit name to ScoreAndConfidence and time computed.
079 * <p>
080 * This is a thread-safe map, with automatic LRU discarding of stale/unused values.
081 * <p>
082 * A lock can be held on this map to make compound operations atomic,
083 * though the lock should be held for as little time as possible
084 * so as to maximise available concurrency.
085 */
086 private final MemoryTools.SimpleLRUMapAutoSizeForHitRate<Pair<String,Name.ExhibitFull>, Pair<Long, ScoreAndConf>> _rawScores;
087
088 /* Inherit javadoc. */
089 public ScoreAndConf computeUnweightedScoreAndConfidence(final ExhibitFull exhibitName, final ScorerIF scorer, final boolean allowStale)
090 throws IOException
091 {
092 if(scorer == null) { throw new IllegalArgumentException(); }
093 if(!ExhibitName.validNameSyntax(exhibitName)) { throw new IllegalArgumentException("exhibit name must be full, not short"); }
094
095 final long startTime = System.currentTimeMillis();
096
097 // Get vote events for all retained full/complete (VLONG) slots.
098 final long currentPeriod = EventPeriod.VLONG.getIntervalNumber(startTime);
099
100 // If the exhibit is not valid/current then return the "no opinion" result.
101 final AllExhibitImmutableData aeid = dataSource.getAllExhibitImmutableData(-1);
102 final ExhibitStaticAttr esa = aeid.getStaticAttr(exhibitName);
103 if(esa == null) { return(ScoreAndConf.NO_OPINION); }
104
105 // We can use a value from cache if the following conditions hold:
106 // 1) The cached value is from the current period
107 // (or the previous period if allowStale is true).
108 // 2) The cached value is newer than the exhibit file date
109 // (and any thumbnails that might be used).
110 // Since we expect thumbnails only to change rarely for a given exhibit,
111 // ie after a thumbnail-generation algorithm change,
112 // we certainly do not need to force thumbnail creation just to get a timestamp.
113 // We DO NOT intern() the name-and-parameters value because it would stress the PermGen.
114 // We assume that the exhibitName is already an intern()ed value.
115 final Pair<String, Name.ExhibitFull> cacheKey = new Pair<String, Name.ExhibitFull>(scorer.getNameAndParameters(), exhibitName);
116 // See what, if anything, is currently in cache.
117 final Pair<Long, ScoreAndConf> cachedValue = _rawScores.get(cacheKey);
118 final ExhibitThumbnails tns;
119 if((cachedValue != null) &&
120 ((EventPeriod.VLONG.getIntervalNumber(cachedValue.first) >= (allowStale ? currentPeriod-1 : currentPeriod)) &&
121 (cachedValue.first >= esa.timestamp) &&
122 (allowStale || (null == (tns = dataSource.getThumbnails(exhibitName, false))) || (cachedValue.first >= tns.created))))
123 { return(cachedValue.second); }
124
125 // Compute value from scratch.
126 final ScoreAndConf result = scorer.computeScoreAndConfidence(dataSource, exhibitName);
127
128 // Atomically replace the value in cache
129 // making sure that we never replace a newer value.
130 final Pair<Long, ScoreAndConf> newValue = new Pair<Long, ScoreAndConf>(startTime, result);
131 synchronized(_rawScores)
132 {
133 final Pair<Long, ScoreAndConf> curValue = _rawScores.get(cacheKey);
134 if((curValue == null) || (curValue.first < newValue.first))
135 { _rawScores.put(cacheKey, newValue); }
136 }
137
138 return(result);
139 }
140
141 /* (non-Javadoc)
142 * @see org.hd.d.pg2k.ai.scorer.ScorerCacheIF#computeScorerWeighting(org.hd.d.pg2k.ai.scorer.ScorerIF, boolean, java.lang.String)
143 */
144 public ScoreAndConf computeScorerWeighting(final String scorerNameAndParameters, final boolean allowStale, final String source) throws IOException
145 {
146 // Check that the scorer requested is available/valid.
147 // If not, return the "no opinion" result.
148 final ScorerIF scorer = getScorerInstance(scorerNameAndParameters);
149 if(scorer == null) { return(ScoreAndConf.NO_OPINION); }
150 return(computeScorerWeighting(scorer, allowStale, source));
151 }
152
153 /* (non-Javadoc)
154 * @see org.hd.d.pg2k.ai.scorer.ScorerCacheIF#getScorerInstance(java.lang.String)
155 */
156 public ScorerIF getScorerInstance(final String nameAndParameters)
157 {
158 // Use the available functionality in the population instance.
159 return(population.getScorerInstance(nameAndParameters));
160 }
161
162 /**Non-blocking attempt to queue an externally-supplied Scorer value; returns true if accepted.
163 * This default implementation always returns false.
164 */
165 public boolean offerExternalScorer(final String externalScorerNameAndParameters) { return(false); }
166
167 /**Returns true if this cache can accept (many) more external-supplied Scorer values.
168 * This default implementation always returns false.
169 */
170 public boolean canAcceptMoreExternalScorers() { return(false); }
171
172 /**Returns true if at least once external Scorer is queued waiting to be processed.
173 * This default implementation always returns false.
174 */
175 public boolean hasQueuedExternalScorer() { return(false); }
176
177 }