4343 * <p>
4444 * This class is only constructed by {@link PersistentDataStoreBuilder}.
4545 */
46- final class PersistentDataStoreWrapper implements DataStore , SettableCache {
46+ final class PersistentDataStoreWrapper implements DataStore , SettableCache , DisableableCache {
4747 private final PersistentDataStore core ;
4848 private final LoadingCache <CacheKey , Optional <ItemDescriptor >> itemCache ;
4949 private final LoadingCache <DataKind , KeyedItems <ItemDescriptor >> allCache ;
@@ -54,9 +54,15 @@ final class PersistentDataStoreWrapper implements DataStore, SettableCache {
5454 private final AtomicBoolean inited = new AtomicBoolean (false );
5555 private final ListeningExecutorService cacheExecutor ;
5656 private final LDLogger logger ;
57-
57+
5858 private final Object externalStoreLock = new Object ();
5959 private volatile CacheExporter externalCache ;
60+
61+ // Once true, the cache is bypassed on reads and writes; entries already in
62+ // the cache have been invalidated by disableCache(). The cache instances
63+ // themselves remain alive until GC reclaims them; the LoadingCache loaders
64+ // are short-circuited because every touch site checks this flag first.
65+ private volatile boolean cacheDisabled ;
6066
6167 PersistentDataStoreWrapper (
6268 final PersistentDataStore core ,
@@ -151,14 +157,26 @@ public void close() throws IOException {
151157 core .close ();
152158 }
153159
160+ @ Override
161+ public void disableCache () {
162+ if (cacheDisabled ) return ;
163+ // Volatile write publishes the bypass flag before clearing cache contents.
164+ // Future readers observe cacheDisabled == true and skip the cache call
165+ // sites.
166+ cacheDisabled = true ;
167+ if (itemCache != null ) itemCache .invalidateAll ();
168+ if (allCache != null ) allCache .invalidateAll ();
169+ if (initCache != null ) initCache .invalidateAll ();
170+ }
171+
154172 @ Override
155173 public boolean isInitialized () {
156174 if (inited .get ()) {
157175 return true ;
158176 }
159177 boolean result ;
160178 try {
161- if (initCache != null ) {
179+ if (initCache != null && ! cacheDisabled ) {
162180 result = initCache .get ("" );
163181 } else {
164182 result = core .isInitialized ();
@@ -187,7 +205,7 @@ public void init(FullDataSet<ItemDescriptor> allData) {
187205 allBuilder .add (new AbstractMap .SimpleEntry <>(kind , items ));
188206 }
189207 RuntimeException failure = initCore (new FullDataSet <>(allBuilder .build (), allData .shouldPersist ()));
190- if (itemCache != null && allCache != null ) {
208+ if (itemCache != null && allCache != null && ! cacheDisabled ) {
191209 itemCache .invalidateAll ();
192210 allCache .invalidateAll ();
193211 if (failure != null && !cacheIndefinitely ) {
@@ -228,7 +246,7 @@ private RuntimeException initCore(FullDataSet<SerializedItemDescriptor> allData)
228246 @ Override
229247 public ItemDescriptor get (DataKind kind , String key ) {
230248 try {
231- ItemDescriptor ret = itemCache != null ? itemCache .get (CacheKey .forItem (kind , key )).orNull () :
249+ ItemDescriptor ret = ( itemCache != null && ! cacheDisabled ) ? itemCache .get (CacheKey .forItem (kind , key )).orNull () :
232250 getAndDeserializeItem (kind , key );
233251 processError (null );
234252 return ret ;
@@ -242,7 +260,7 @@ public ItemDescriptor get(DataKind kind, String key) {
242260 public KeyedItems <ItemDescriptor > getAll (DataKind kind ) {
243261 try {
244262 KeyedItems <ItemDescriptor > ret ;
245- ret = allCache != null ? allCache .get (kind ) : getAllAndDeserialize (kind );
263+ ret = ( allCache != null && ! cacheDisabled ) ? allCache .get (kind ) : getAllAndDeserialize (kind );
246264 processError (null );
247265 return ret ;
248266 } catch (Exception e ) {
@@ -281,7 +299,7 @@ public boolean upsert(DataKind kind, String key, ItemDescriptor item) {
281299 }
282300 failure = e ;
283301 }
284- if (itemCache != null ) {
302+ if (itemCache != null && ! cacheDisabled ) {
285303 CacheKey cacheKey = CacheKey .forItem (kind , key );
286304 if (failure == null ) {
287305 if (updated ) {
@@ -297,7 +315,7 @@ public boolean upsert(DataKind kind, String key, ItemDescriptor item) {
297315 }
298316 }
299317 }
300- if (allCache != null ) {
318+ if (allCache != null && ! cacheDisabled ) {
301319 // If the cache has a finite TTL, then we should remove the "all items" cache entry to force
302320 // a reread the next time All is called. However, if it's an infinite TTL, we need to just
303321 // update the item within the existing "all items" entry (since we want things to still work
@@ -340,7 +358,7 @@ public void setCacheExporter(CacheExporter externalDataSource) {
340358
341359 @ Override
342360 public CacheStats getCacheStats () {
343- if (itemCache == null || allCache == null ) {
361+ if (itemCache == null || allCache == null || cacheDisabled ) {
344362 return null ;
345363 }
346364 com .google .common .cache .CacheStats itemStats = itemCache .stats ();
@@ -443,8 +461,9 @@ private boolean pollAvailabilityAfterOutage() {
443461 }
444462
445463 // Fall back to cache-based recovery if external store is not available/initialized
446- // and we're in infinite cache mode
447- if (cacheIndefinitely && allCache != null ) {
464+ // and we're in infinite cache mode. Under FDv2 this branch is dead once
465+ // disableCache has run: the externalCache path above supersedes it.
466+ if (cacheIndefinitely && allCache != null && !cacheDisabled ) {
448467 // If we're in infinite cache mode, then we can assume the cache has a full set of current
449468 // flag data (since presumably the data source has still been running) and we can just
450469 // write the contents of the cache to the underlying data store.
0 commit comments