private Thread exhaust() { Thread thread = new Thread(() -> { while (true) { try { limiters.acquireLimiterInternal(KEY).acquire().get(); } catch (ExecutionException | InterruptedException e) { throw new RuntimeException(e); } } }); thread.start(); // wait until the other thread blocks while (!thread.getState().equals(Thread.State.WAITING)) { Thread.yield(); } return thread; } }
synchronized ListenableFuture<Limiter.Listener> acquire() { SettableFuture<Limiter.Listener> future = SettableFuture.create(); addSlowAcquireMarker(future); waitingRequests.add(future); processQueue(); return future; }
synchronized void processQueue() { while (!waitingRequests.isEmpty()) { Optional<Limiter.Listener> maybeAcquired = limiter.acquire(NO_CONTEXT); if (!maybeAcquired.isPresent()) { if (!timeoutScheduled()) { timeoutCleanup = scheduledExecutorService.schedule( this::resetLimiter, timeout.toMillis(), TimeUnit.MILLISECONDS); } return; } Limiter.Listener acquired = maybeAcquired.get(); SettableFuture<Limiter.Listener> head = waitingRequests.remove(); head.set(wrap(acquired)); } if (timeoutScheduled()) { timeoutCleanup.cancel(true); } }
@Override public void run() { for (int i = 0; i < REQUESTS_PER_THREAD; ) { Limiter.Listener listener = Futures.getUnchecked(limiters.acquireLimiterInternal("").acquire()); boolean gotRateLimited = !rateLimiter.tryAcquire(100, TimeUnit.MILLISECONDS); if (!gotRateLimited) { meter.mark(); sleep(successDuration.toMillis()); listener.onSuccess(); avgRetries.update(numRetries); numRetries = 0; backoff = null; i++; } else { initializeBackoff(); Optional<Duration> sleep = backoff.nextBackoff(); numRetries++; if (!sleep.isPresent()) { listener.onIgnore(); throw new RuntimeException("Failed on request " + i); } else { sleep(1); listener.onDropped(); sleep(sleep.get().toMillis()); } } } }
synchronized void processQueue() { while (!waitingRequests.isEmpty()) { Optional<Limiter.Listener> maybeAcquired = limiter.acquire(NO_CONTEXT); if (!maybeAcquired.isPresent()) { if (!timeoutScheduled()) { timeoutCleanup = scheduledExecutorService.schedule( this::resetLimiter, timeout.toMillis(), TimeUnit.MILLISECONDS); } return; } Limiter.Listener acquired = maybeAcquired.get(); SettableFuture<Limiter.Listener> head = waitingRequests.remove(); head.set(wrap(acquired)); } if (timeoutScheduled()) { timeoutCleanup.cancel(true); } }
@Override public void enqueue(Callback callback) { ListenableFuture<Limiter.Listener> limiterListener = limiter.acquire(); request().tag(ConcurrencyLimiterListener.class).limiterListener().setFuture(limiterListener); Futures.addCallback(limiterListener, new FutureCallback<Limiter.Listener>() { @Override public void onSuccess(Limiter.Listener listener) { enqueueInternal(callback); } @Override public void onFailure(Throwable throwable) { callback.onFailure( RemotingOkHttpCall.this, new IOException(new AssertionError("This should never happen, since it implies " + "we failed when using the concurrency limiter", throwable))); } }, MoreExecutors.directExecutor()); }
@Override public void enqueue(Callback callback) { ListenableFuture<Limiter.Listener> limiterListener = limiter.acquire(); request().tag(ConcurrencyLimiterListener.class).limiterListener().setFuture(limiterListener); Futures.addCallback(limiterListener, new FutureCallback<Limiter.Listener>() { @Override public void onSuccess(Limiter.Listener listener) { enqueueInternal(callback); } @Override public void onFailure(Throwable throwable) { callback.onFailure( RemotingOkHttpCall.this, new IOException(new AssertionError("This should never happen, since it implies " + "we failed when using the concurrency limiter", throwable))); } }, MoreExecutors.directExecutor()); }
private synchronized void resetLimiter() { log.warn("Timed out waiting to get permits for concurrency. In most cases this would indicate some kind of " + "deadlock. We expect that either this is caused by not closing response bodies " + "(there should be OkHttp log lines indicating this), or service overloading.", SafeArg.of("serviceClass", serviceClass), SafeArg.of("limiterKey", limiterKey), SafeArg.of("timeout", timeout)); leakSuspected.mark(); limiter = limiterFactory.get(); processQueue(); }
private ConcurrencyLimiter newLimiter(String limiterKey) { Supplier<Limiter<Void>> limiter = () -> SimpleLimiter.newBuilder() .limit(new ConjureWindowedLimit(AIMDLimit.newBuilder().build())) .build(); return new ConcurrencyLimiter(limiterKey, limiter); }
@Test public void testTimeout() { Instant start = Instant.now(); Thread exhauster = exhaust(); Futures.getUnchecked(limiters.acquireLimiterInternal(KEY).acquire()); Instant end = Instant.now(); exhauster.interrupt(); assertThat(Duration.between(start, end)).isGreaterThanOrEqualTo(TIMEOUT); }
@Override public void onSuccess() { listener.onSuccess(); processQueue(); }
private synchronized void resetLimiter() { log.warn("Timed out waiting to get permits for concurrency. In most cases this would indicate some kind of " + "deadlock. We expect that either this is caused by not closing response bodies " + "(there should be OkHttp log lines indicating this), or service overloading.", SafeArg.of("serviceClass", serviceClass), SafeArg.of("limiterKey", limiterKey), SafeArg.of("timeout", timeout)); leakSuspected.mark(); limiter = limiterFactory.get(); processQueue(); }
@Override public void onIgnore() { listener.onIgnore(); processQueue(); }
@Override public void onSuccess() { listener.onSuccess(); processQueue(); }
synchronized ListenableFuture<Limiter.Listener> acquire() { SettableFuture<Limiter.Listener> future = SettableFuture.create(); addSlowAcquireMarker(future); waitingRequests.add(future); processQueue(); return future; }
@Override public void onIgnore() { listener.onIgnore(); processQueue(); }
@Override public void onDropped() { listener.onDropped(); processQueue(); } };
private ConcurrencyLimiter newLimiter(String limiterKey) { Supplier<Limiter<Void>> limiter = () -> SimpleLimiter.newBuilder() .limit(new ConjureWindowedLimit(AIMDLimit.newBuilder().build())) .build(); return new ConcurrencyLimiter(limiterKey, limiter); }
@Override public void onDropped() { listener.onDropped(); processQueue(); } };