use of com.linkedin.r2.transport.http.client.ObjectCreationTimeoutException in project rest.li by linkedin.
the class TestAsyncPool method testCreationTimeout.
@Test(dataProvider = "creationTimeoutDataProvider")
public void testCreationTimeout(int poolSize, int concurrency) throws Exception {
// this object creation life cycle simulate the creation limbo state
ObjectCreatorThatNeverCreates objectCreatorThatNeverCreates = new ObjectCreatorThatNeverCreates();
ClockedExecutor clockedExecutor = new ClockedExecutor();
ExponentialBackOffRateLimiter rateLimiter = new ExponentialBackOffRateLimiter(0, 5000, 10, clockedExecutor, concurrency);
final AsyncPool<Object> pool = new AsyncPoolImpl<>("object pool", objectCreatorThatNeverCreates, poolSize, Integer.MAX_VALUE, Integer.MAX_VALUE, clockedExecutor, Integer.MAX_VALUE, AsyncPoolImpl.Strategy.MRU, 0, rateLimiter, clockedExecutor, new LongTracking());
pool.start();
List<FutureCallback<Object>> checkoutCallbacks = new ArrayList<>();
// Lets try to checkout more than the max Pool Size times when the object creator is in limbo state
for (int i = 0; i < poolSize * 2; i++) {
FutureCallback<Object> cb = new FutureCallback<>();
checkoutCallbacks.add(cb);
// Reset the exponential back off due to creation timeout error
rateLimiter.setPeriod(0);
pool.get(cb);
// run for the duration of default creation timeout
// TODO: parameterize the creation duration when the default creation gets parameterized
clockedExecutor.runFor(AsyncPoolImpl.DEFAULT_OBJECT_CREATION_TIMEOUT);
}
// drain all the pending tasks
clockedExecutor.runFor(AsyncPoolImpl.DEFAULT_OBJECT_CREATION_TIMEOUT);
// since the object creator went to limbo state
for (FutureCallback<Object> cb : checkoutCallbacks) {
try {
cb.get(100, TimeUnit.MILLISECONDS);
} catch (Exception ex) {
Assert.assertTrue(ex.getCause() instanceof ObjectCreationTimeoutException);
}
}
// Lets make sure the channel pool stats are at expected state
PoolStats stats = pool.getStats();
// Lets make sure all the limbo creations are timed out as expected
Assert.assertEquals(stats.getTotalCreateErrors(), poolSize * 2);
// No checkout should have happened due to object creator in limbo
Assert.assertEquals(stats.getCheckedOut(), 0);
// No Idle objects in the pool
Assert.assertEquals(stats.getIdleCount(), 0);
// Lets make sure that all the slots in the pool are reclaimed even if the object creation is in limbo
Assert.assertEquals(stats.getPoolSize(), 0);
// Since the max pending creation request reached the max pool size,
// we should have reached the maPool Size at least once
Assert.assertEquals(stats.getMaxPoolSize(), poolSize);
// Since no object is successfully created, expecting idle objects to be zero
Assert.assertEquals(stats.getIdleCount(), 0);
}
use of com.linkedin.r2.transport.http.client.ObjectCreationTimeoutException in project rest.li by linkedin.
the class AsyncPoolImpl method create.
/**
* DO NOT call this method while holding the lock! It invokes user code.
*/
private void create() {
trc("initiating object creation");
_rateLimiter.submit(new Task() {
@Override
public void run(final SimpleCallback callback) {
boolean shouldIgnore;
synchronized (_lock) {
// Ignore the object creation if no one is waiting for the object and the pool already has _minSize objects
int totalObjects = _checkedOut + _idle.size();
shouldIgnore = _waiters.size() == 0 && totalObjects >= _minSize;
if (shouldIgnore) {
_statsTracker.incrementIgnoredCreation();
if (_poolSize >= 1) {
// _poolSize also include the count of creation requests pending. So we have to make sure the pool size
// count is updated when we ignore the creation request.
_poolSize--;
}
}
}
if (shouldIgnore) {
callback.onDone();
return;
}
// Lets not trust the _lifecycle to timely return a response here.
// Embedding the callback inside a timeout callback (ObjectCreationTimeoutCallback)
// to force a response within creationTimeout deadline to reclaim the object slot in the pool
_lifecycle.create(new TimeoutCallback<>(_timeoutExecutor, _creationTimeout, TimeUnit.MILLISECONDS, new Callback<T>() {
@Override
public void onSuccess(T t) {
synchronized (_lock) {
_statsTracker.incrementCreated();
_lastCreateError = null;
}
add(t);
callback.onDone();
}
@Override
public void onError(final Throwable e) {
// Note we drain all waiters and cancel all pending creates if a create fails.
// When a create fails, rate-limiting logic will be applied. In this case,
// we may be initiating creations at a lower rate than incoming requests. While
// creations are suppressed, it is better to deny all waiters and let them see
// the real reason (this exception) rather than keep them around to eventually
// get an unhelpful timeout error
final Collection<Callback<T>> waitersDenied;
final Collection<Task> cancelledCreate = _rateLimiter.cancelPendingTasks();
boolean create;
synchronized (_lock) {
_statsTracker.incrementCreateErrors();
_lastCreateError = e;
// Cancel all waiters in the rate limiter
if (!_waiters.isEmpty()) {
waitersDenied = cancelWaiters();
} else {
waitersDenied = Collections.<Callback<T>>emptyList();
}
// reclaim the slot in the pool
create = objectDestroyed(1 + cancelledCreate.size());
}
// lets fail all the waiters with the object creation error
for (Callback<T> denied : waitersDenied) {
try {
denied.onError(e);
} catch (Exception ex) {
LOG.error("Encountered error while invoking error waiter callback", ex);
}
}
// Now after cancelling all the pending tasks, lets make sure to back off on the creation
_rateLimiter.incrementPeriod();
// the min poolSize
if (create) {
create();
}
LOG.debug(_poolName + ": object creation failed", e);
callback.onDone();
}
}, () -> new ObjectCreationTimeoutException("Exceeded creation timeout of " + _creationTimeout + "ms: in Pool: " + _poolName)));
}
});
}
Aggregations