diff --git a/common/src/main/java/me/lucko/luckperms/common/buffers/BufferedRequest.java b/common/src/main/java/me/lucko/luckperms/common/buffers/BufferedRequest.java index 2e95da8c..1b14fa9b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/buffers/BufferedRequest.java +++ b/common/src/main/java/me/lucko/luckperms/common/buffers/BufferedRequest.java @@ -101,19 +101,21 @@ public abstract class BufferedRequest { */ protected abstract T perform(); - private static class Processor implements Runnable { + private static class Processor { private Supplier supplier; private final long delay; private final TimeUnit unit; private final SchedulerAdapter schedulerAdapter; - private SchedulerTask task; private final Object[] mutex = new Object[0]; private CompletableFuture future = new CompletableFuture<>(); private boolean usable = true; + private SchedulerTask scheduledTask; + private BoundTask boundTask = null; + Processor(Supplier supplier, long delay, TimeUnit unit, SchedulerAdapter schedulerAdapter) { this.supplier = supplier; this.delay = delay; @@ -128,36 +130,14 @@ public abstract class BufferedRequest { if (!this.usable) { throw new IllegalStateException("Processor not usable"); } - if (this.task != null) { - this.task.cancel(); + if (this.scheduledTask != null) { + this.scheduledTask.cancel(); } - this.task = this.schedulerAdapter.asyncLater(this, this.delay, this.unit); + this.boundTask = new BoundTask(); + this.scheduledTask = this.schedulerAdapter.asyncLater(this.boundTask, this.delay, this.unit); } } - @Override - public void run() { - synchronized (this.mutex) { - if (!this.usable) { - throw new IllegalStateException("Task has already ran"); - } - this.usable = false; - } - - // compute result - try { - R result = this.supplier.get(); - this.future.complete(result); - } catch (Exception e) { - this.future.completeExceptionally(e); - } - - // allow supplier and future to be GCed - this.task = null; - this.supplier = null; - this.future = null; - } - CompletableFuture getFuture() { return this.future; } @@ -167,6 +147,39 @@ public abstract class BufferedRequest { return this.future; } + private final class BoundTask implements Runnable { + @Override + public void run() { + synchronized (Processor.this.mutex) { + if (!Processor.this.usable) { + throw new IllegalStateException("Task has already ran"); + } + + // check that we're still the bound task. + // prevents a race condition between #run and #rescheduleTask + if (Processor.this.boundTask != this) { + return; + } + + Processor.this.usable = false; + } + + // compute result + try { + R result = Processor.this.supplier.get(); + Processor.this.future.complete(result); + } catch (Exception e) { + Processor.this.future.completeExceptionally(e); + } + + // allow supplier and future to be GCed + Processor.this.supplier = null; + Processor.this.future = null; + Processor.this.scheduledTask = null; + Processor.this.boundTask = null; + } + } + } }