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 Release 4.3 ALPHA1
------------------- -------------------

View File

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

View File

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

View File

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