HTTPCLIENT-1298: Add AsynchronousValidator in HttpClientBuilder's list of closeable objects

Contributed by Martin Meinhold <mmeinhold at atlassian.com>

* Resolve constructor dependency between CachingExec and AsynchronousValidator
* AsynchronousValidator implements Closeable interface

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1437952 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2013-01-24 11:05:36 +00:00
parent 7f6198fe46
commit 508497a8be
5 changed files with 66 additions and 30 deletions

View File

@ -1,3 +1,11 @@
Changes since 4.3 ALPHA1
-------------------
* [HTTPCLIENT-1298] Add AsynchronousValidator in HttpClientBuilder's list of closeable objects.
Contributed by Martin Meinhold <mmeinhold at atlassian.com>
Release 4.3 ALPHA1
-------------------

View File

@ -26,6 +26,8 @@
*/
package org.apache.http.impl.client.cache;
import java.io.Closeable;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@ -47,8 +49,7 @@ import org.apache.http.conn.routing.HttpRoute;
* Class used for asynchronous revalidations to be used when the "stale-
* while-revalidate" directive is present
*/
class AsynchronousValidator {
private final CachingExec cachingExec;
class AsynchronousValidator implements Closeable {
private final ExecutorService executor;
private final Set<String> queued;
private final CacheKeyGenerator cacheKeyGenerator;
@ -57,20 +58,16 @@ class AsynchronousValidator {
/**
* Create AsynchronousValidator which will make revalidation requests
* using the supplied {@link CachingHttpClient}, and
* a {@link ThreadPoolExecutor} generated according to the thread
* using a {@link ThreadPoolExecutor} generated according to the thread
* pool settings provided in the given {@link CacheConfig}.
* @param cachingExect used to execute asynchronous requests
* @param config specifies thread pool settings. See
* {@link CacheConfig#getAsynchronousWorkersMax()},
* {@link CacheConfig#getAsynchronousWorkersCore()},
* {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()},
* and {@link CacheConfig#getRevalidationQueueSize()}.
*/
public AsynchronousValidator(final CachingExec cachingExect,
final CacheConfig config) {
this(cachingExect,
new ThreadPoolExecutor(config.getAsynchronousWorkersCore(),
public AsynchronousValidator(final CacheConfig config) {
this(new ThreadPoolExecutor(config.getAsynchronousWorkersCore(),
config.getAsynchronousWorkersMax(),
config.getAsynchronousWorkerIdleLifetimeSecs(),
TimeUnit.SECONDS,
@ -82,20 +79,24 @@ class AsynchronousValidator {
* Create AsynchronousValidator which will make revalidation requests
* using the supplied {@link CachingHttpClient} and
* {@link ExecutorService}.
* @param cachingExect used to execute asynchronous requests
* @param executor used to manage a thread pool of revalidation workers
*/
AsynchronousValidator(final CachingExec cachingExec, final ExecutorService executor) {
this.cachingExec = cachingExec;
AsynchronousValidator(final ExecutorService executor) {
this.executor = executor;
this.queued = new HashSet<String>();
this.cacheKeyGenerator = new CacheKeyGenerator();
}
@Override
public void close() throws IOException {
executor.shutdown();
}
/**
* Schedules an asynchronous revalidation
*/
public synchronized void revalidateCacheEntry(
final CachingExec cachingExec,
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext context,

View File

@ -119,6 +119,14 @@ public class CachingExec implements ClientExecChain {
final ClientExecChain backend,
final HttpCache cache,
final CacheConfig config) {
this(backend, cache, config, null);
}
public CachingExec(
final ClientExecChain backend,
final HttpCache cache,
final CacheConfig config,
final AsynchronousValidator asynchRevalidator) {
super();
Args.notNull(backend, "HTTP backend");
Args.notNull(cache, "HttpCache");
@ -135,7 +143,7 @@ public class CachingExec implements ClientExecChain {
this.responseCachingPolicy = new ResponseCachingPolicy(
this.cacheConfig.getMaxObjectSize(), this.cacheConfig.isSharedCache(),
this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery());
this.asynchRevalidator = makeAsynchronousValidator(config);
this.asynchRevalidator = asynchRevalidator != null ? asynchRevalidator : makeAsynchronousValidator(config);
}
public CachingExec(
@ -179,7 +187,7 @@ public class CachingExec implements ClientExecChain {
private AsynchronousValidator makeAsynchronousValidator(
final CacheConfig config) {
if (config.getAsynchronousWorkersMax() > 0) {
return new AsynchronousValidator(this, config);
return new AsynchronousValidator(config);
}
return null;
}
@ -312,7 +320,7 @@ public class CachingExec implements ClientExecChain {
&& validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) {
log.trace("Serving stale with asynchronous revalidation");
final HttpResponse resp = generateCachedResponse(request, context, entry, now);
asynchRevalidator.revalidateCacheEntry(route, request, context, execAware, entry);
asynchRevalidator.revalidateCacheEntry(this, route, request, context, execAware, entry);
return Proxies.enhanceResponse(resp);
}
return revalidateCacheEntry(route, request, context, execAware, entry);

View File

@ -97,8 +97,15 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
}
storage = new BasicHttpCacheStorage(cacheConfig);
}
final AsynchronousValidator revalidator = createAsynchronousRevalidator(config);
return new CachingExec(mainExec,
new BasicHttpCache(resourceFactory, storage, config), config);
new BasicHttpCache(resourceFactory, storage, config), config, revalidator);
}
private AsynchronousValidator createAsynchronousRevalidator(final CacheConfig config) {
final AsynchronousValidator revalidator = new AsynchronousValidator(config);
addCloseable(revalidator);
return revalidator;
}
}

View File

@ -26,6 +26,7 @@
*/
package org.apache.http.impl.client.cache;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
@ -74,13 +75,13 @@ public class TestAsynchronousValidator {
@Test
public void testRevalidateCacheEntrySchedulesExecutionAndPopulatesIdentifier() {
impl = new AsynchronousValidator(mockClient, mockExecutor);
impl = new AsynchronousValidator(mockExecutor);
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
mockExecutor.execute(EasyMock.isA(AsynchronousValidationRequest.class));
replayMocks();
impl.revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
impl.revalidateCacheEntry(mockClient, route, request, context, mockExecAware, mockCacheEntry);
verifyMocks();
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
@ -88,14 +89,14 @@ public class TestAsynchronousValidator {
@Test
public void testMarkCompleteRemovesIdentifier() {
impl = new AsynchronousValidator(mockClient, mockExecutor);
impl = new AsynchronousValidator(mockExecutor);
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
final Capture<AsynchronousValidationRequest> cap = new Capture<AsynchronousValidationRequest>();
mockExecutor.execute(EasyMock.capture(cap));
replayMocks();
impl.revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
impl.revalidateCacheEntry(mockClient, route, request, context, mockExecAware, mockCacheEntry);
verifyMocks();
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
@ -107,14 +108,14 @@ public class TestAsynchronousValidator {
@Test
public void testRevalidateCacheEntryDoesNotPopulateIdentifierOnRejectedExecutionException() {
impl = new AsynchronousValidator(mockClient, mockExecutor);
impl = new AsynchronousValidator(mockExecutor);
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
mockExecutor.execute(EasyMock.isA(AsynchronousValidationRequest.class));
EasyMock.expectLastCall().andThrow(new RejectedExecutionException());
replayMocks();
impl.revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
impl.revalidateCacheEntry(mockClient, route, request, context, mockExecAware, mockCacheEntry);
verifyMocks();
Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
@ -122,7 +123,7 @@ public class TestAsynchronousValidator {
@Test
public void testRevalidateCacheEntryProperlyCollapsesRequest() {
impl = new AsynchronousValidator(mockClient, mockExecutor);
impl = new AsynchronousValidator(mockExecutor);
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
mockExecutor.execute(EasyMock.isA(AsynchronousValidationRequest.class));
@ -130,8 +131,8 @@ public class TestAsynchronousValidator {
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
replayMocks();
impl.revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
impl.revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
impl.revalidateCacheEntry(mockClient, route, request, context, mockExecAware, mockCacheEntry);
impl.revalidateCacheEntry(mockClient, route, request, context, mockExecAware, mockCacheEntry);
verifyMocks();
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
@ -139,7 +140,7 @@ public class TestAsynchronousValidator {
@Test
public void testVariantsBothRevalidated() {
impl = new AsynchronousValidator(mockClient, mockExecutor);
impl = new AsynchronousValidator(mockExecutor);
final HttpRequest req1 = new HttpGet("/");
req1.addHeader(new BasicHeader("Accept-Encoding", "identity"));
@ -157,8 +158,8 @@ public class TestAsynchronousValidator {
EasyMock.expectLastCall().times(2);
replayMocks();
impl.revalidateCacheEntry(route, HttpRequestWrapper.wrap(req1), context, mockExecAware, mockCacheEntry);
impl.revalidateCacheEntry(route, HttpRequestWrapper.wrap(req2), context, mockExecAware, mockCacheEntry);
impl.revalidateCacheEntry(mockClient, route, HttpRequestWrapper.wrap(req1), context, mockExecAware, mockCacheEntry);
impl.revalidateCacheEntry(mockClient, route, HttpRequestWrapper.wrap(req2), context, mockExecAware, mockCacheEntry);
verifyMocks();
Assert.assertEquals(2, impl.getScheduledIdentifiers().size());
@ -171,14 +172,14 @@ public class TestAsynchronousValidator {
.setAsynchronousWorkersMax(1)
.setAsynchronousWorkersCore(1)
.build();
impl = new AsynchronousValidator(mockClient, config);
impl = new AsynchronousValidator(config);
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
EasyMock.expect(mockClient.revalidateCacheEntry(
route, request, context, mockExecAware, mockCacheEntry)).andReturn(null);
replayMocks();
impl.revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
impl.revalidateCacheEntry(mockClient, route, request, context, mockExecAware, mockCacheEntry);
try {
// shut down backend executor and make sure all finishes properly, 1 second should be sufficient
@ -194,6 +195,17 @@ public class TestAsynchronousValidator {
}
}
@Test
public void testExecutorShutdownOnClose() throws IOException {
impl = new AsynchronousValidator(mockExecutor);
mockExecutor.shutdown();
replayMocks();
impl.close();
verifyMocks();
}
public void replayMocks() {
EasyMock.replay(mockExecutor);
EasyMock.replay(mockClient);