From 7bc6bc14fe0428cd63251f8f3afdbfb7eddcd74e Mon Sep 17 00:00:00 2001 From: Cry_Legende Date: Fri, 6 Nov 2026 23:14:17 +0100 Subject: [PATCH] Run throttled fetches later instead of dropping them A fetch requested less than 5 seconds after the previous request was ignored. When it came from the website's instant commands request, the commands stayed queued until the next scheduled fetch, up to one minute later, even though the website had been told the request succeeded. Schedule a single fetch for the end of the interval instead, so the rate limit is unchanged and instant commands wait 5 seconds at most. --- .../azlink/common/tasks/FetcherTask.java | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/com/azuriom/azlink/common/tasks/FetcherTask.java b/common/src/main/java/com/azuriom/azlink/common/tasks/FetcherTask.java index 3ea805b..0857dca 100644 --- a/common/src/main/java/com/azuriom/azlink/common/tasks/FetcherTask.java +++ b/common/src/main/java/com/azuriom/azlink/common/tasks/FetcherTask.java @@ -5,6 +5,7 @@ import com.azuriom.azlink.common.data.UserInfo; import com.azuriom.azlink.common.data.WebsiteResponse; +import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.util.List; @@ -12,14 +13,18 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; public class FetcherTask implements Runnable { + private static final Duration MIN_REQUEST_INTERVAL = Duration.ofSeconds(5); + private final AzLinkPlugin plugin; private Instant lastFullDataSent = Instant.MIN; private Instant lastRequest = Instant.MIN; + private CompletableFuture scheduledFetch; public FetcherTask(AzLinkPlugin plugin) { this.plugin = plugin; @@ -27,6 +32,10 @@ public FetcherTask(AzLinkPlugin plugin) { @Override public void run() { + if (Instant.now().isBefore(nextRequestTime())) { + return; + } + fetch().exceptionally(ex -> { this.plugin.getLogger().error("Unable to send data to the website: " + ex.getMessage()); @@ -34,14 +43,21 @@ public void run() { }); } - public CompletableFuture fetch() { + public synchronized CompletableFuture fetch() { Instant now = Instant.now(); - if (!this.plugin.getConfig().isValid() - || this.lastRequest.isAfter(now.minusSeconds(5))) { + if (!this.plugin.getConfig().isValid()) { return CompletableFuture.completedFuture(null); } + Instant nextRequest = nextRequestTime(); + + if (now.isBefore(nextRequest)) { + // The request in progress may already have missed what the caller is waiting for, + // like commands the website just queued, so send another one as soon as possible + return scheduleFetch(Duration.between(now, nextRequest).toMillis() + 1); + } + this.plugin.getPlatform().prepareDataAsync(); this.lastRequest = now; @@ -57,6 +73,35 @@ public CompletableFuture fetch() { .thenAcceptAsync(res -> handleResponse(res, sendFullData), sync); } + private Instant nextRequestTime() { + return this.lastRequest.plus(MIN_REQUEST_INTERVAL); + } + + private CompletableFuture scheduleFetch(long delayMillis) { + if (this.scheduledFetch != null) { + return this.scheduledFetch; + } + + CompletableFuture future = new CompletableFuture<>(); + this.scheduledFetch = future; + + this.plugin.getScheduler().scheduleAsyncLater(() -> { + synchronized (this) { + this.scheduledFetch = null; + } + + fetch().whenComplete((result, ex) -> { + if (ex != null) { + future.completeExceptionally(ex); + } else { + future.complete(null); + } + }); + }, delayMillis, TimeUnit.MILLISECONDS); + + return future; + } + private void handleResponse(WebsiteResponse response, boolean sendFullData) { if (response == null) { return;