package com.chaegeon.niro; /** Pure-Java admission gate for stock WeatherActivity refresh requests. */ public final class WeatherRefreshGate { public enum Admission { FETCH, REPLAY, COALESCE, BACKOFF } private final long cooldownMillis; private final long failureRetryMillis; private final long replaySpacingMillis; private boolean running; private Admission active; private long nextFetchAt; private long nextRetryAt; private long nextReplayAt; public WeatherRefreshGate(long cooldownMillis, long failureRetryMillis, long replaySpacingMillis) { if (cooldownMillis < 0L || failureRetryMillis < 0L || replaySpacingMillis < 0L) { throw new IllegalArgumentException("invalid gate interval"); } this.cooldownMillis = cooldownMillis; this.failureRetryMillis = failureRetryMillis; this.replaySpacingMillis = replaySpacingMillis; } public synchronized Admission admit(long elapsedMillis) { if (running) { // The in-flight fetch/replay will broadcast to all active listeners. return Admission.COALESCE; } if (elapsedMillis < nextRetryAt) { // A fast-failing privileged sender must not hammer the root shell // or online service. There is no successful response to replay. return Admission.BACKOFF; } running = true; active = elapsedMillis < nextFetchAt ? Admission.REPLAY : Admission.FETCH; return active; } public synchronized void finish(long elapsedMillis, Admission admission, boolean success) { if (!running) { throw new IllegalStateException("not running"); } if (admission == Admission.COALESCE || admission == Admission.BACKOFF) { throw new IllegalArgumentException("request did not start a worker"); } if (admission != active) { throw new IllegalArgumentException("wrong active request"); } running = false; active = null; if (!success) { // Never replay a response after a failed fetch or failed replay. nextFetchAt = 0L; nextRetryAt = elapsedMillis + failureRetryMillis; nextReplayAt = 0L; } else if (admission == Admission.FETCH) { // Replays do not extend freshness. A new fetch follows 60 s later. nextFetchAt = elapsedMillis + cooldownMillis; nextRetryAt = 0L; nextReplayAt = elapsedMillis + replaySpacingMillis; } else { nextRetryAt = 0L; nextReplayAt = elapsedMillis + replaySpacingMillis; } } public synchronized boolean isRunning() { return running; } /** Delay one admitted replay; new arrivals coalesce while its worker waits. */ public synchronized long replayDelayMillis(long elapsedMillis) { if (active != Admission.REPLAY) { throw new IllegalStateException("no admitted replay"); } return elapsedMillis < nextReplayAt ? nextReplayAt - elapsedMillis : 0L; } }