66import com .codahale .metrics .Snapshot ;
77import java .io .IOException ;
88import java .lang .invoke .MethodHandles ;
9+ import java .util .Objects ;
910import java .util .concurrent .ScheduledExecutorService ;
1011import java .util .concurrent .TimeUnit ;
1112import java .util .concurrent .atomic .AtomicBoolean ;
1516import java .util .function .Supplier ;
1617import org .apache .lucene .index .Unloader ;
1718import org .apache .lucene .util .InfoStream ;
19+ import org .apache .solr .cloud .ZkController ;
1820import org .apache .solr .common .MapWriter ;
1921import org .apache .solr .handler .admin .MetricsHandler ;
2022import org .apache .solr .metrics .MetricSuppliers ;
@@ -41,7 +43,58 @@ final class UnloadHelper<T extends Unloader.UnloadHelper>
4143 private final Meter closed ;
4244 private final InfoStream infoStream = new UnloaderLoggingInfoStream ();
4345
46+ private volatile Object lastMinUnloadSpec ;
47+ private volatile long minUnloadNanos = Unloader .KEEP_ALIVE_NANOS ;
48+
4449 UnloadHelper (CoreContainer cc ) {
50+ ZkController zkController = cc .getZkController ();
51+ if (zkController != null ) {
52+ // NOTE: this is scoped to the CoreContainer lifecycle, so we don't have to worry about
53+ // removing the clusterProps listener.
54+ log .info ("adding clusterprops listener for minUnloadTime" );
55+ zkController
56+ .getZkStateReader ()
57+ .registerClusterPropertiesListener (
58+ (p ) -> {
59+ Object o = p .get ("minUnloadTime" );
60+ if (Objects .equals (lastMinUnloadSpec , o )) {
61+ // we get notified every time clusterprops changes; shortcircuit processing
62+ // unless it's actually _our_ value that's changed.
63+ return false ;
64+ }
65+ lastMinUnloadSpec = o ;
66+ long newVal ;
67+ if (o instanceof Number ) {
68+ // millis
69+ newVal = TimeUnit .MILLISECONDS .toNanos (((Number ) o ).longValue ());
70+ } else if (o instanceof String ) {
71+ try {
72+ newVal = Unloader .getNanos ((String ) o );
73+ } catch (Exception ex ) {
74+ log .warn ("problem parsing clusterprops `minUnloadTime` spec: {}" , o , ex );
75+ newVal = Unloader .KEEP_ALIVE_NANOS ;
76+ }
77+ } else {
78+ // either null, or unrecognized type
79+ newVal = Unloader .KEEP_ALIVE_NANOS ;
80+ if (o != null ) {
81+ log .warn (
82+ "unrecognized type for clusterprops `minUnloadTime`: {} ({})" ,
83+ o ,
84+ o .getClass ());
85+ }
86+ }
87+ if (newVal < 0 ) {
88+ log .warn (
89+ "clusterprops minUnloadTime should be >= 0; found {}, from {}" , newVal , o );
90+ minUnloadNanos = Unloader .KEEP_ALIVE_NANOS ;
91+ } else {
92+ log .info ("set `minUnloadNanos` from clusterprops {} ({} nanos)" , o , newVal );
93+ minUnloadNanos = newVal ;
94+ }
95+ return false ;
96+ });
97+ }
4598 this .exec = cc .getUnloaderExecutor ();
4699 MetricsHandler metricsHandler = cc .getMetricsHandler ();
47100 if (metricsHandler == null ) {
@@ -251,21 +304,30 @@ public void onClose() {
251304
252305 @ Override
253306 public long deferUnload (long nanosSinceLastAccess ) {
254- if (nanosSinceLastAccess >= MAX_THRESHOLD ) {
307+ long minUnloadNanos = UnloadHelper .this .minUnloadNanos ;
308+ if (nanosSinceLastAccess >= Math .max (MAX_THRESHOLD , minUnloadNanos )) {
255309 // we've waited the max amount of time; don't defer any longer
256310 return -1 ;
257311 }
258312 Snapshot s = h .getSnapshot ();
259- if (s .size () == 0 ) {
260- // no recent loads; establish a baseline, don't defer
261- return -1 ;
262- }
263313 // get a pessimistic estimate (based on recent history) of how long we anticipate
264314 // we might enjoy the benefits of unloading if we were to unload now.
265- long pessimisticExpectRemaining = (long ) s .getValue (0.25 ) - nanosSinceLastAccess ;
266- if (pessimisticExpectRemaining >= ACCEPTABLE_THRESHOLD ) {
267- // we expect it's worth unloading; don't defer.
268- return -1 ;
315+ @ SuppressWarnings ("unused" )
316+ long pessimisticExpectRemaining ;
317+ if (s .size () == 0
318+ || (pessimisticExpectRemaining = (long ) s .getValue (0.25 ) - nanosSinceLastAccess )
319+ >= ACCEPTABLE_THRESHOLD ) {
320+ // at this point we either expect it's worth unloading, or (size==0) we have no
321+ // context with which to make a decision, so allow to proceed (establish a baseline)
322+ if (nanosSinceLastAccess >= minUnloadNanos ) {
323+ // we meet the `minUnloadNanos` criterion; don't defer
324+ return -1 ;
325+ } else {
326+ // we wait until `minUnloadNanos` criterion may have been satisfied; but don't
327+ // wait longer than `MAX_THRESHOLD`, because we still want to check periodically
328+ // in case `minUnloadNanos` is reduced after being set very long.
329+ return Math .min (minUnloadNanos - nanosSinceLastAccess , MAX_THRESHOLD );
330+ }
269331 } else {
270332 // wait a generous amount of time, up to `MAX_THRESHOLD`, to give a chance to
271333 // be kept alive without being closed.
0 commit comments