use of com.linkedin.r2.util.Cancellable in project rest.li by linkedin.
the class TestAsyncSharedPoolImpl method testGetItemCancelled.
@Test
public void testGetItemCancelled() throws Exception {
final LifecycleMock lifecycle = new LifecycleMock();
final CountDownLatch createLatch = new CountDownLatch(1);
lifecycle.setCreateConsumer(callback -> {
try {
createLatch.await();
callback.onSuccess(ITEM);
} catch (Exception e) {
callback.onError(e);
}
});
AsyncSharedPoolImpl<Object> pool = new AsyncSharedPoolImpl<>(POOL_NAME, lifecycle, SCHEDULER, LIMITER, NO_POOL_TIMEOUT, MAX_WAITERS);
pool.start();
// Only one thread will perform the actual item creation task and the rest
// will return immediately. Therefore we wait for GET_COUNT - 1 threads to complete.
final CountDownLatch getLatch = new CountDownLatch(GET_COUNT - 1);
final ConcurrentLinkedQueue<Cancellable> cancellables = new ConcurrentLinkedQueue<>();
for (int i = 0; i < GET_COUNT; i++) {
SCHEDULER.execute(() -> {
cancellables.add(pool.get(new FutureCallback<>()));
getLatch.countDown();
});
}
if (!getLatch.await(GET_TIMEOUT, TIME_UNIT)) {
Assert.fail("Timed out awaiting for get");
}
Assert.assertEquals(cancellables.size(), GET_COUNT - 1);
// Cancelling waiters should all succeed
cancellables.stream().forEach(cancellable -> Assert.assertTrue(cancellable.cancel()));
// Cancel the last waiter blocking item creation
Assert.assertEquals(pool.cancelWaiters().size(), 1);
createLatch.countDown();
FutureCallback<None> shutdownCallback = new FutureCallback<>();
pool.shutdown(shutdownCallback);
shutdownCallback.get(SHUTDOWN_TIMEOUT, TIME_UNIT);
}
use of com.linkedin.r2.util.Cancellable in project rest.li by linkedin.
the class AsyncPoolImpl method get.
@Override
public Cancellable get(final Callback<T> callback) {
// getter needs to add to wait queue atomically with check for empty pool
// putter needs to add to pool atomically with check for empty wait queue
boolean create = false;
boolean reject = false;
final LinkedDeque.Node<Callback<T>> node;
final Callback<T> callbackWithTracking = new TimeTrackingCallback<T>(callback);
for (; ; ) {
TimedObject<T> obj = null;
final State state;
synchronized (_lock) {
state = _state;
if (state == State.RUNNING) {
if (_strategy == Strategy.LRU) {
obj = _idle.pollFirst();
} else {
obj = _idle.pollLast();
}
if (obj == null) {
if (_waiters.size() < _maxWaiters) {
// No objects available and the waiter list is not full; add to waiter list and break out of loop
node = _waiters.addLastNode(callbackWithTracking);
create = shouldCreate();
} else {
reject = true;
node = null;
}
break;
}
}
}
if (state != State.RUNNING) {
// Defer execution of the callback until we are out of the synchronized block
callbackWithTracking.onError(new IllegalStateException(_poolName + " is " + _state));
return () -> false;
}
T rawObj = obj.get();
if (_lifecycle.validateGet(rawObj)) {
trc("dequeued an idle object");
// Valid object; done
synchronized (_lock) {
_checkedOut++;
_statsTracker.sampleMaxCheckedOut();
}
callbackWithTracking.onSuccess(rawObj);
return () -> false;
}
// Invalid object, discard it and keep trying
destroy(rawObj, true);
trc("dequeued and disposed an invalid idle object");
}
if (reject) {
// This is a recoverable exception. User can simply retry the failed get() operation.
callbackWithTracking.onError(new SizeLimitExceededException("AsyncPool " + _poolName + " reached maximum waiter size: " + _maxWaiters));
return null;
}
trc("enqueued a waiter");
if (create) {
create();
}
return new Cancellable() {
@Override
public boolean cancel() {
synchronized (_lock) {
return _waiters.removeNode(node) != null;
}
}
};
}
use of com.linkedin.r2.util.Cancellable in project rest.li by linkedin.
the class AsyncSharedPoolImpl method get.
@Override
public Cancellable get(Callback<T> callback) {
ArgumentUtil.notNull(callback, "callback");
final TimeTrackingCallback timeTrackingCallback = new TimeTrackingCallback(callback);
final LinkedDeque.Node<Callback<T>> node;
T item = null;
boolean create = false;
while (true) {
final State state;
synchronized (_lock) {
state = _state;
if (state == State.RUNNING) {
item = _item.get();
if (item == null) {
node = _waiters.size() < _maxWaiters ? _waiters.addLastNode(timeTrackingCallback) : null;
if (_isCreateInProgress) {
LOG.debug("{}: item creation is in progress", _name);
} else {
_isCreateInProgress = true;
create = true;
}
break;
}
_checkedOut++;
_statsTracker.sampleMaxCheckedOut();
}
}
if (state != State.RUNNING) {
// Defer execution of the callback until we are out of the synchronized block
timeTrackingCallback.onError(new IllegalStateException(_name + " is " + _state));
return () -> false;
}
// through the item lifecycle before passing back to user callback
if (_lifecycle.validateGet(item)) {
timeTrackingCallback.onSuccess(item);
return () -> false;
}
boolean disposed;
synchronized (_lock) {
// The connection has gone bad so we proceed to destroy it
disposed = doDispose(item);
}
if (disposed) {
doDestroy(item, BAD, () -> {
});
}
}
if (node == null) {
// This is a recoverable exception. User can simply retry the failed get() operation.
timeTrackingCallback.onError(new SizeLimitExceededException("AsyncPool " + _name + " reached maximum waiter size: " + _maxWaiters));
return () -> false;
}
// The pool is currently empty we need to construct a new item
if (create) {
doCreate();
}
return () -> {
synchronized (_lock) {
return _waiters.removeNode(node) != null;
}
};
}
Aggregations