Skip to content

Commit

Permalink
Tolerate non-blocking exceptions from linker
Browse files Browse the repository at this point in the history
  • Loading branch information
robertvazan committed Jan 20, 2024
1 parent 7574bc2 commit 7147762
Showing 1 changed file with 68 additions and 62 deletions.
130 changes: 68 additions & 62 deletions src/main/java/com/machinezoo/foxcache/CacheThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,66 +9,72 @@
* Automatic refresh scheduling. There's one thread per cache except for manual caches.
*/
class CacheThread extends ReactiveThread {
private final CacheOwner owner;
CacheThread(CacheOwner owner) {
this.owner = owner;
OwnerTrace.of(this).parent(owner);
}
@Override
protected void run() {
var worker = owner.worker;
/*
* Refresh already in progress.
*/
if (worker.progress() != null)
return;
/*
* This may throw. That's okay. We don't want to refresh the cache if its linker is failing.
*/
var input = owner.input.get();
/*
* Blocking linker.
*/
if (CurrentReactiveScope.blocked())
return;
var snapshot = owner.snapshot.get();
/*
* Unstable or failing dependency.
*/
if (input.stability() != CacheStability.READY)
return;
boolean dirty = false;
/*
* Empty cache. No need to check for null snapshot hash as that would imply either exception or cancellation.
*/
if (snapshot == null)
dirty = true;
var policy = owner.cache.caching();
if (policy.mode() == CacheRefreshMode.AUTOMATIC && snapshot != null) {
/*
* Stale cache.
*
* This will not cause looping of refreshes even if the cache is failed or cancelled,
* because stored input hash reflects last refresh, successful or not,
* instead of the input used to generate last valid cache content.
* If there's an exception, it is stored along with an up-to-date hash, preventing further refreshes.
*/
if (!input.hash().equals(snapshot.input()))
dirty = true;
/*
* Expired cache.
*/
if (policy.period() != null && ReactiveInstant.now().isAfter(snapshot.refreshed().plus(policy.period())))
dirty = true;
}
/*
* No reason to refresh.
*/
if (!dirty)
return;
/*
* All conditions have been met. Start refresh.
*/
worker.schedule();
}
private final CacheOwner owner;
CacheThread(CacheOwner owner) {
this.owner = owner;
OwnerTrace.of(this).parent(owner);
}
@Override protected void run() {
var worker = owner.worker;
/*
* Refresh already in progress.
*/
if (worker.progress() != null)
return;
/*
* This may throw. That's okay. We don't want to refresh the cache if its linker is failing.
* This applies also to non-blocking exceptions and to exceptions other than EmptyCacheException,
* because caches can be stale and out of sync with each other temporarily, which can derail logic in the linker.
*/
CacheInput input;
try {
input = owner.input.get();
} catch (Throwable ex) {
return;
}
/*
* Blocking linker.
*/
if (CurrentReactiveScope.blocked())
return;
var snapshot = owner.snapshot.get();
/*
* Unstable or failing dependency.
*/
if (input.stability() != CacheStability.READY)
return;
boolean dirty = false;
/*
* Empty cache. No need to check for null snapshot hash as that would imply either exception or cancellation.
*/
if (snapshot == null)
dirty = true;
var policy = owner.cache.caching();
if (policy.mode() == CacheRefreshMode.AUTOMATIC && snapshot != null) {
/*
* Stale cache.
*
* This will not cause looping of refreshes even if the cache is failed or cancelled,
* because stored input hash reflects last refresh, successful or not,
* instead of the input used to generate last valid cache content.
* If there's an exception, it is stored along with an up-to-date hash, preventing further refreshes.
*/
if (!input.hash().equals(snapshot.input()))
dirty = true;
/*
* Expired cache.
*/
if (policy.period() != null && ReactiveInstant.now().isAfter(snapshot.refreshed().plus(policy.period())))
dirty = true;
}
/*
* No reason to refresh.
*/
if (!dirty)
return;
/*
* All conditions have been met. Start refresh.
*/
worker.schedule();
}
}

0 comments on commit 7147762

Please sign in to comment.