You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2018/01/07 11:54:02 UTC
[1/3] httpcomponents-client git commit: Decoupled SchedulingStrategy
and its implementations from HTTP cache specific logic
Repository: httpcomponents-client
Updated Branches:
refs/heads/master 1c7328098 -> 7cf4240b3
Decoupled SchedulingStrategy and its implementations from HTTP cache specific logic
Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/commit/0561bacc
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/tree/0561bacc
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/diff/0561bacc
Branch: refs/heads/master
Commit: 0561bacc6602f001d97eac6c67d009d67e9ede62
Parents: 1c73280
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Fri Jan 5 10:18:17 2018 +0100
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Fri Jan 5 12:29:14 2018 +0100
----------------------------------------------------------------------
.../http/impl/cache/AsynchronousValidator.java | 44 +++--
.../ExponentialBackOffSchedulingStrategy.java | 166 ------------------
.../impl/cache/ImmediateSchedulingStrategy.java | 89 ----------
.../http/impl/cache/SchedulingStrategy.java | 45 -----
.../ExponentialBackOffSchedulingStrategy.java | 118 +++++++++++++
.../schedule/ImmediateSchedulingStrategy.java | 49 ++++++
.../http/schedule/SchedulingStrategy.java | 43 +++++
.../impl/cache/TestAsynchronousValidator.java | 121 ++++++-------
...ExponentialBackingOffSchedulingStrategy.java | 175 -------------------
.../cache/TestImmediateSchedulingStrategy.java | 55 ------
...ExponentialBackingOffSchedulingStrategy.java | 59 +++++++
.../TestImmediateSchedulingStrategy.java | 51 ++++++
12 files changed, 399 insertions(+), 616 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/0561bacc/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java
index c17928a..c635747 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java
@@ -32,11 +32,18 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.classic.ExecChain;
+import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy;
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -45,6 +52,8 @@ import org.apache.logging.log4j.Logger;
* while-revalidate" directive is present
*/
class AsynchronousValidator implements Closeable {
+
+ private final ScheduledExecutorService executorService;
private final SchedulingStrategy schedulingStrategy;
private final Set<String> queued;
private final CacheKeyGenerator cacheKeyGenerator;
@@ -63,7 +72,7 @@ class AsynchronousValidator implements Closeable {
* and {@link CacheConfig#getRevalidationQueueSize()}.
*/
public AsynchronousValidator(final CacheConfig config) {
- this(new ImmediateSchedulingStrategy(config));
+ this(new ScheduledThreadPoolExecutor(config.getAsynchronousWorkersCore()), new ImmediateSchedulingStrategy());
}
/**
@@ -73,18 +82,14 @@ class AsynchronousValidator implements Closeable {
* @param schedulingStrategy used to maintain a pool of worker threads and
* schedules when requests are executed
*/
- AsynchronousValidator(final SchedulingStrategy schedulingStrategy) {
+ AsynchronousValidator(final ScheduledExecutorService executorService, final SchedulingStrategy schedulingStrategy) {
+ this.executorService = executorService;
this.schedulingStrategy = schedulingStrategy;
this.queued = new HashSet<>();
this.cacheKeyGenerator = CacheKeyGenerator.INSTANCE;
this.failureCache = new DefaultFailureCache();
}
- @Override
- public void close() throws IOException {
- schedulingStrategy.close();
- }
-
/**
* Schedules an asynchronous revalidation
*/
@@ -96,23 +101,34 @@ class AsynchronousValidator implements Closeable {
final ExecChain chain,
final HttpCacheEntry entry) {
// getVariantURI will fall back on getURI if no variants exist
- final String uri = cacheKeyGenerator.generateVariantURI(target, request, entry);
+ final String cacheKey = cacheKeyGenerator.generateVariantURI(target, request, entry);
- if (!queued.contains(uri)) {
- final int consecutiveFailedAttempts = failureCache.getErrorCount(uri);
+ if (!queued.contains(cacheKey)) {
+ final int consecutiveFailedAttempts = failureCache.getErrorCount(cacheKey);
final AsynchronousValidationRequest revalidationRequest =
new AsynchronousValidationRequest(
- this, cachingExec, target, request, scope, chain, entry, uri, consecutiveFailedAttempts);
+ this, cachingExec, target, request, scope, chain, entry, cacheKey, consecutiveFailedAttempts);
+ final TimeValue executionTime = schedulingStrategy.schedule(consecutiveFailedAttempts);
try {
- schedulingStrategy.schedule(revalidationRequest);
- queued.add(uri);
+ executorService.schedule(revalidationRequest, executionTime.getDuration(), executionTime.getTimeUnit());
+ queued.add(cacheKey);
} catch (final RejectedExecutionException ree) {
- log.debug("Revalidation for [" + uri + "] not scheduled: " + ree);
+ log.debug("Revalidation for [" + cacheKey + "] not scheduled: " + ree);
}
}
}
+ @Override
+ public void close() throws IOException {
+ executorService.shutdown();
+ }
+
+ public void awaitTermination(final Timeout timeout) throws InterruptedException {
+ Args.notNull(timeout, "Timeout");
+ executorService.awaitTermination(timeout.getDuration(), timeout.getTimeUnit());
+ }
+
/**
* Removes an identifier from the internal list of revalidation jobs in
* progress. This is meant to be called by
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/0561bacc/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ExponentialBackOffSchedulingStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ExponentialBackOffSchedulingStrategy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ExponentialBackOffSchedulingStrategy.java
deleted file mode 100644
index 921d49e..0000000
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ExponentialBackOffSchedulingStrategy.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.hc.core5.annotation.Contract;
-import org.apache.hc.core5.annotation.ThreadingBehavior;
-import org.apache.hc.core5.util.Args;
-
-/**
- * An implementation that backs off exponentially based on the number of
- * consecutive failed attempts stored in the
- * {@link AsynchronousValidationRequest}. It uses the following defaults:
- * <pre>
- * no delay in case it was never tried or didn't fail so far
- * 6 secs delay for one failed attempt (= {@link #getInitialExpiryInMillis()})
- * 60 secs delay for two failed attempts
- * 10 mins delay for three failed attempts
- * 100 mins delay for four failed attempts
- * ~16 hours delay for five failed attempts
- * 24 hours delay for six or more failed attempts (= {@link #getMaxExpiryInMillis()})
- * </pre>
- *
- * The following equation is used to calculate the delay for a specific revalidation request:
- * <pre>
- * delay = {@link #getInitialExpiryInMillis()} * Math.pow({@link #getBackOffRate()},
- * {@link AsynchronousValidationRequest#getConsecutiveFailedAttempts()} - 1))
- * </pre>
- * The resulting delay won't exceed {@link #getMaxExpiryInMillis()}.
- *
- * @since 4.3
- */
-@Contract(threading = ThreadingBehavior.SAFE)
-public class ExponentialBackOffSchedulingStrategy implements SchedulingStrategy {
-
- public static final long DEFAULT_BACK_OFF_RATE = 10;
- public static final long DEFAULT_INITIAL_EXPIRY_IN_MILLIS = TimeUnit.SECONDS.toMillis(6);
- public static final long DEFAULT_MAX_EXPIRY_IN_MILLIS = TimeUnit.SECONDS.toMillis(86400);
-
- private final long backOffRate;
- private final long initialExpiryInMillis;
- private final long maxExpiryInMillis;
-
- private final ScheduledExecutorService executor;
-
- /**
- * Create a new scheduling strategy using a fixed pool of worker threads.
- * @param cacheConfig the thread pool configuration to be used; not {@code null}
- * @see CacheConfig#getAsynchronousWorkersMax()
- * @see #DEFAULT_BACK_OFF_RATE
- * @see #DEFAULT_INITIAL_EXPIRY_IN_MILLIS
- * @see #DEFAULT_MAX_EXPIRY_IN_MILLIS
- */
- public ExponentialBackOffSchedulingStrategy(final CacheConfig cacheConfig) {
- this(cacheConfig,
- DEFAULT_BACK_OFF_RATE,
- DEFAULT_INITIAL_EXPIRY_IN_MILLIS,
- DEFAULT_MAX_EXPIRY_IN_MILLIS);
- }
-
- /**
- * Create a new scheduling strategy by using a fixed pool of worker threads and the
- * given parameters to calculated the delay.
- *
- * @param cacheConfig the thread pool configuration to be used; not {@code null}
- * @param backOffRate the back off rate to be used; not negative
- * @param initialExpiryInMillis the initial expiry in milli seconds; not negative
- * @param maxExpiryInMillis the upper limit of the delay in milli seconds; not negative
- * @see CacheConfig#getAsynchronousWorkersMax()
- * @see ExponentialBackOffSchedulingStrategy
- */
- public ExponentialBackOffSchedulingStrategy(
- final CacheConfig cacheConfig,
- final long backOffRate,
- final long initialExpiryInMillis,
- final long maxExpiryInMillis) {
- this(createThreadPoolFromCacheConfig(cacheConfig),
- backOffRate,
- initialExpiryInMillis,
- maxExpiryInMillis);
- }
-
- private static ScheduledThreadPoolExecutor createThreadPoolFromCacheConfig(
- final CacheConfig cacheConfig) {
- final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(
- cacheConfig.getAsynchronousWorkersMax());
- scheduledThreadPoolExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
- return scheduledThreadPoolExecutor;
- }
-
- ExponentialBackOffSchedulingStrategy(
- final ScheduledExecutorService executor,
- final long backOffRate,
- final long initialExpiryInMillis,
- final long maxExpiryInMillis) {
- this.executor = Args.notNull(executor, "Executor");
- this.backOffRate = Args.notNegative(backOffRate, "BackOffRate");
- this.initialExpiryInMillis = Args.notNegative(initialExpiryInMillis, "InitialExpiryInMillis");
- this.maxExpiryInMillis = Args.notNegative(maxExpiryInMillis, "MaxExpiryInMillis");
- }
-
- @Override
- public void schedule(
- final AsynchronousValidationRequest revalidationRequest) {
- Args.notNull(revalidationRequest, "RevalidationRequest");
- final int consecutiveFailedAttempts = revalidationRequest.getConsecutiveFailedAttempts();
- final long delayInMillis = calculateDelayInMillis(consecutiveFailedAttempts);
- executor.schedule(revalidationRequest, delayInMillis, TimeUnit.MILLISECONDS);
- }
-
- @Override
- public void close() {
- executor.shutdown();
- }
-
- public long getBackOffRate() {
- return backOffRate;
- }
-
- public long getInitialExpiryInMillis() {
- return initialExpiryInMillis;
- }
-
- public long getMaxExpiryInMillis() {
- return maxExpiryInMillis;
- }
-
- protected long calculateDelayInMillis(final int consecutiveFailedAttempts) {
- if (consecutiveFailedAttempts > 0) {
- final long delayInSeconds = (long) (initialExpiryInMillis *
- Math.pow(backOffRate, consecutiveFailedAttempts - 1));
- return Math.min(delayInSeconds, maxExpiryInMillis);
- }
- else {
- return 0;
- }
- }
-
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/0561bacc/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ImmediateSchedulingStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ImmediateSchedulingStrategy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ImmediateSchedulingStrategy.java
deleted file mode 100644
index f38b061..0000000
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ImmediateSchedulingStrategy.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.hc.core5.annotation.Contract;
-import org.apache.hc.core5.annotation.ThreadingBehavior;
-import org.apache.hc.core5.util.Args;
-
-/**
- * Immediately schedules any incoming validation request. Relies on
- * {@link CacheConfig} to configure the used {@link java.util.concurrent.ThreadPoolExecutor}.
- *
- * @since 4.3
- */
-@Contract(threading = ThreadingBehavior.SAFE)
-public class ImmediateSchedulingStrategy implements SchedulingStrategy {
-
- private final ExecutorService executor;
-
- /**
- * Uses a {@link java.util.concurrent.ThreadPoolExecutor} which is configured according to the
- * given {@link CacheConfig}.
- * @param cacheConfig specifies thread pool settings. See
- * {@link CacheConfig#getAsynchronousWorkersMax()},
- * {@link CacheConfig#getAsynchronousWorkersCore()},
- * {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()},
- * and {@link CacheConfig#getRevalidationQueueSize()}.
- */
- public ImmediateSchedulingStrategy(final CacheConfig cacheConfig) {
- this(new ThreadPoolExecutor(
- cacheConfig.getAsynchronousWorkersCore(),
- cacheConfig.getAsynchronousWorkersMax(),
- cacheConfig.getAsynchronousWorkerIdleLifetimeSecs(),
- TimeUnit.SECONDS,
- new ArrayBlockingQueue<Runnable>(cacheConfig.getRevalidationQueueSize()))
- );
- }
-
- ImmediateSchedulingStrategy(final ExecutorService executor) {
- this.executor = executor;
- }
-
- @Override
- public void schedule(final AsynchronousValidationRequest revalidationRequest) {
- Args.notNull(revalidationRequest, "AsynchronousValidationRequest");
- executor.execute(revalidationRequest);
- }
-
- @Override
- public void close() {
- executor.shutdown();
- }
-
- /**
- * Visible for testing.
- */
- void awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException {
- executor.awaitTermination(timeout, unit);
- }
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/0561bacc/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/SchedulingStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/SchedulingStrategy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/SchedulingStrategy.java
deleted file mode 100644
index d9cbd55..0000000
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/SchedulingStrategy.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import java.io.Closeable;
-
-/**
- * Specifies when revalidation requests are scheduled.
- *
- * @since 4.3
- */
-public interface SchedulingStrategy extends Closeable
-{
- /**
- * Schedule an {@link AsynchronousValidationRequest} to be executed.
- *
- * @param revalidationRequest the request to be executed; not {@code null}
- * @throws java.util.concurrent.RejectedExecutionException if the request could not be scheduled for execution
- */
- void schedule(AsynchronousValidationRequest revalidationRequest);
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/0561bacc/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ExponentialBackOffSchedulingStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ExponentialBackOffSchedulingStrategy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ExponentialBackOffSchedulingStrategy.java
new file mode 100644
index 0000000..2bccc9c
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ExponentialBackOffSchedulingStrategy.java
@@ -0,0 +1,118 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.schedule;
+
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.TimeValue;
+
+/**
+ * An implementation that backs off exponentially based on the number of
+ * consecutive failed attempts. It uses the following defaults:
+ * <pre>
+ * no delay in case it was never tried or didn't fail so far
+ * 6 secs delay for one failed attempt (= {@link #getInitialExpiry()})
+ * 60 secs delay for two failed attempts
+ * 10 mins delay for three failed attempts
+ * 100 mins delay for four failed attempts
+ * ~16 hours delay for five failed attempts
+ * 24 hours delay for six or more failed attempts (= {@link #getMaxExpiry()})
+ * </pre>
+ *
+ * The following equation is used to calculate the delay for a specific pending operation:
+ * <pre>
+ * delay = {@link #getInitialExpiry()} * Math.pow({@link #getBackOffRate()},
+ * {@code consecutiveFailedAttempts} - 1))
+ * </pre>
+ * The resulting delay won't exceed {@link #getMaxExpiry()}.
+ *
+ * @since 4.3
+ */
+@Contract(threading = ThreadingBehavior.SAFE)
+public class ExponentialBackOffSchedulingStrategy implements SchedulingStrategy {
+
+ public static final long DEFAULT_BACK_OFF_RATE = 10;
+ public static final TimeValue DEFAULT_INITIAL_EXPIRY = TimeValue.ofSeconds(6);
+ public static final TimeValue DEFAULT_MAX_EXPIRY = TimeValue.ofSeconds(86400);
+
+ private static final ExponentialBackOffSchedulingStrategy INSTANCE = new ExponentialBackOffSchedulingStrategy(
+ DEFAULT_BACK_OFF_RATE, DEFAULT_INITIAL_EXPIRY, DEFAULT_MAX_EXPIRY);
+
+ private final long backOffRate;
+ private final TimeValue initialExpiry;
+ private final TimeValue maxExpiry;
+
+ public ExponentialBackOffSchedulingStrategy(
+ final long backOffRate,
+ final TimeValue initialExpiry,
+ final TimeValue maxExpiry) {
+ this.backOffRate = Args.notNegative(backOffRate, "BackOff rate");
+ this.initialExpiry = Args.notNull(initialExpiry, "Initial expiry");
+ this.maxExpiry = Args.notNull(maxExpiry, "Max expiry");
+ }
+
+ public ExponentialBackOffSchedulingStrategy(final long backOffRate, final TimeValue initialExpiry) {
+ this(backOffRate, initialExpiry, DEFAULT_MAX_EXPIRY);
+ }
+
+ public ExponentialBackOffSchedulingStrategy(final long backOffRate) {
+ this(backOffRate, DEFAULT_INITIAL_EXPIRY, DEFAULT_MAX_EXPIRY);
+ }
+
+ public ExponentialBackOffSchedulingStrategy() {
+ this(DEFAULT_BACK_OFF_RATE, DEFAULT_INITIAL_EXPIRY, DEFAULT_MAX_EXPIRY);
+ }
+
+ @Override
+ public TimeValue schedule(final int attemptNumber) {
+ return TimeValue.ofMillis(calculateDelayInMillis(attemptNumber));
+ }
+
+ public long getBackOffRate() {
+ return backOffRate;
+ }
+
+ public TimeValue getInitialExpiry() {
+ return initialExpiry;
+ }
+
+ public TimeValue getMaxExpiry() {
+ return maxExpiry;
+ }
+
+ protected long calculateDelayInMillis(final int consecutiveFailedAttempts) {
+ if (consecutiveFailedAttempts > 0) {
+ final long delay = (long) (initialExpiry.toMillis() * Math.pow(backOffRate, consecutiveFailedAttempts - 1));
+ return Math.min(delay, maxExpiry.toMillis());
+ } else {
+ return 0;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/0561bacc/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ImmediateSchedulingStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ImmediateSchedulingStrategy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ImmediateSchedulingStrategy.java
new file mode 100644
index 0000000..bb498f6
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ImmediateSchedulingStrategy.java
@@ -0,0 +1,49 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.schedule;
+
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.util.TimeValue;
+
+/**
+ * Immediately schedules any operation.
+ *
+ * @since 5.0
+ */
+@Contract(threading = ThreadingBehavior.STATELESS)
+public class ImmediateSchedulingStrategy implements SchedulingStrategy {
+
+ private final static ImmediateSchedulingStrategy INSTANCE = new ImmediateSchedulingStrategy();
+
+ @Override
+ public TimeValue schedule(final int attemptNumber) {
+ return TimeValue.ZERO_MILLISECONDS;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/0561bacc/httpclient5-cache/src/main/java/org/apache/hc/client5/http/schedule/SchedulingStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/schedule/SchedulingStrategy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/schedule/SchedulingStrategy.java
new file mode 100644
index 0000000..bffed1d
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/schedule/SchedulingStrategy.java
@@ -0,0 +1,43 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.schedule;
+
+import org.apache.hc.core5.util.TimeValue;
+
+/**
+ * Strategy to determine an execution time (schedule) for an operation.
+ *
+ * @since 5.0
+ */
+public interface SchedulingStrategy {
+
+ /**
+ * Schedules execution time for an operation.
+ */
+ TimeValue schedule(int attemptNumber);
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/0561bacc/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidator.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidator.java
index 06c42ac..8450fa3 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidator.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidator.java
@@ -26,15 +26,12 @@
*/
package org.apache.hc.client5.http.impl.cache;
-import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import java.io.IOException;
import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.hc.client5.http.HttpRoute;
@@ -44,114 +41,121 @@ import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecRuntime;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.BasicHeaderIterator;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
-@SuppressWarnings({"boxing","static-access"}) // test code
+@RunWith(MockitoJUnitRunner.class)
public class TestAsynchronousValidator {
- private AsynchronousValidator impl;
-
+ @Mock
private CachingExec mockClient;
+ @Mock
+ private ExecChain mockExecChain;
+ @Mock
+ private ExecRuntime mockEndpoint;
+ @Mock
+ private HttpCacheEntry mockCacheEntry;
+ @Mock
+ private SchedulingStrategy mockSchedulingStrategy;
+ @Mock
+ ScheduledExecutorService mockExecutorService;
+
private HttpHost host;
private HttpRoute route;
private ClassicHttpRequest request;
private HttpClientContext context;
private ExecChain.Scope scope;
- private ExecChain mockExecChain;
- private ExecRuntime mockEndpoint;
- private HttpCacheEntry mockCacheEntry;
+ private AsynchronousValidator impl;
- private SchedulingStrategy mockSchedulingStrategy;
@Before
public void setUp() {
- mockClient = mock(CachingExec.class);
host = new HttpHost("foo.example.com", 80);
route = new HttpRoute(host);
request = new HttpGet("/");
context = HttpClientContext.create();
- mockExecChain = mock(ExecChain.class);
- mockEndpoint = mock(ExecRuntime.class);
- mockCacheEntry = mock(HttpCacheEntry.class);
- mockSchedulingStrategy = mock(SchedulingStrategy.class);
scope = new ExecChain.Scope("test", route, request, mockEndpoint, context);
+ impl = new AsynchronousValidator(mockExecutorService, mockSchedulingStrategy);
}
@Test
public void testRevalidateCacheEntrySchedulesExecutionAndPopulatesIdentifier() {
- impl = new AsynchronousValidator(mockSchedulingStrategy);
-
when(mockCacheEntry.hasVariants()).thenReturn(false);
+ when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(1));
impl.revalidateCacheEntry(mockClient, host, request, scope, mockExecChain, mockCacheEntry);
verify(mockCacheEntry).hasVariants();
- verify(mockSchedulingStrategy).schedule(isA(AsynchronousValidationRequest.class));
+ verify(mockSchedulingStrategy).schedule(0);
+ verify(mockExecutorService).schedule(Mockito.<Runnable>any(), Mockito.eq(1L), Mockito.eq(TimeUnit.SECONDS));
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
}
@Test
public void testMarkCompleteRemovesIdentifier() {
- impl = new AsynchronousValidator(mockSchedulingStrategy);
-
when(mockCacheEntry.hasVariants()).thenReturn(false);
+ when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(3));
impl.revalidateCacheEntry(mockClient, host, request, scope, mockExecChain, mockCacheEntry);
- final ArgumentCaptor<AsynchronousValidationRequest> cap = ArgumentCaptor.forClass(AsynchronousValidationRequest.class);
verify(mockCacheEntry).hasVariants();
- verify(mockSchedulingStrategy).schedule(cap.capture());
+ verify(mockSchedulingStrategy).schedule(0);
+ verify(mockExecutorService).schedule(Mockito.<Runnable>any(), Mockito.eq(3L), Mockito.eq(TimeUnit.SECONDS));
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
+ final String cacheKey = CacheKeyGenerator.INSTANCE.generateVariantURI(host, request, mockCacheEntry);
+ Assert.assertTrue(impl.getScheduledIdentifiers().contains(cacheKey));
- impl.markComplete(cap.getValue().getIdentifier());
+ impl.markComplete(cacheKey);
Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
}
@Test
public void testRevalidateCacheEntryDoesNotPopulateIdentifierOnRejectedExecutionException() {
- impl = new AsynchronousValidator(mockSchedulingStrategy);
-
when(mockCacheEntry.hasVariants()).thenReturn(false);
- doThrow(new RejectedExecutionException()).when(mockSchedulingStrategy).schedule(isA(AsynchronousValidationRequest.class));
+ when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(2));
+ doThrow(new RejectedExecutionException()).when(mockExecutorService).schedule(Mockito.<Runnable>any(), Mockito.anyLong(), Mockito.<TimeUnit>any());
impl.revalidateCacheEntry(mockClient, host, request, scope, mockExecChain, mockCacheEntry);
verify(mockCacheEntry).hasVariants();
Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
- verify(mockSchedulingStrategy).schedule(isA(AsynchronousValidationRequest.class));
+ verify(mockExecutorService).schedule(Mockito.<Runnable>any(), Mockito.eq(2L), Mockito.eq(TimeUnit.SECONDS));
}
@Test
public void testRevalidateCacheEntryProperlyCollapsesRequest() {
- impl = new AsynchronousValidator(mockSchedulingStrategy);
-
when(mockCacheEntry.hasVariants()).thenReturn(false);
+ when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(2));
impl.revalidateCacheEntry(mockClient, host, request, scope, mockExecChain, mockCacheEntry);
impl.revalidateCacheEntry(mockClient, host, request, scope, mockExecChain, mockCacheEntry);
- verify(mockCacheEntry, times(2)).hasVariants();
- verify(mockSchedulingStrategy).schedule(isA(AsynchronousValidationRequest.class));
+ verify(mockCacheEntry, Mockito.times(2)).hasVariants();
+ verify(mockSchedulingStrategy).schedule(Mockito.anyInt());
+ verify(mockExecutorService).schedule(Mockito.<Runnable>any(), Mockito.eq(2L), Mockito.eq(TimeUnit.SECONDS));
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
}
@Test
public void testVariantsBothRevalidated() {
- impl = new AsynchronousValidator(mockSchedulingStrategy);
-
final ClassicHttpRequest req1 = new HttpGet("/");
req1.addHeader(new BasicHeader("Accept-Encoding", "identity"));
@@ -165,55 +169,28 @@ public class TestAsynchronousValidator {
when(mockCacheEntry.hasVariants()).thenReturn(true);
when(mockCacheEntry.headerIterator(HeaderConstants.VARY)).thenReturn(
new BasicHeaderIterator(variantHeaders, HeaderConstants.VARY));
- mockSchedulingStrategy.schedule(isA(AsynchronousValidationRequest.class));
+ when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(2));
impl.revalidateCacheEntry(mockClient, host, req1, new ExecChain.Scope("test", route, req1, mockEndpoint, context),
mockExecChain, mockCacheEntry);
impl.revalidateCacheEntry(mockClient, host, req2, new ExecChain.Scope("test", route, req2, mockEndpoint, context),
mockExecChain, mockCacheEntry);
- verify(mockCacheEntry, times(2)).hasVariants();
- verify(mockCacheEntry, times(2)).headerIterator(HeaderConstants.VARY);
- verify(mockSchedulingStrategy, times(2)).schedule(isA(AsynchronousValidationRequest.class));
+ verify(mockCacheEntry, Mockito.times(2)).hasVariants();
+ verify(mockCacheEntry, Mockito.times(2)).headerIterator(HeaderConstants.VARY);
+ verify(mockSchedulingStrategy, Mockito.times(2)).schedule(Mockito.anyInt());
+ verify(mockExecutorService, Mockito.times(2)).schedule(Mockito.<Runnable>any(), Mockito.eq(2L), Mockito.eq(TimeUnit.SECONDS));
Assert.assertEquals(2, impl.getScheduledIdentifiers().size());
}
@Test
- public void testRevalidateCacheEntryEndToEnd() throws Exception {
- final CacheConfig config = CacheConfig.custom()
- .setAsynchronousWorkersMax(1)
- .setAsynchronousWorkersCore(1)
- .build();
- final ImmediateSchedulingStrategy schedulingStrategy = new ImmediateSchedulingStrategy(config);
- impl = new AsynchronousValidator(schedulingStrategy);
-
- when(mockCacheEntry.hasVariants()).thenReturn(false);
- when(mockClient.revalidateCacheEntry(
- host, request, scope, mockExecChain, mockCacheEntry)).thenReturn(null);
-
- impl.revalidateCacheEntry(mockClient, host, request, scope, mockExecChain, mockCacheEntry);
-
- try {
- // shut down backend executor and make sure all finishes properly, 1 second should be sufficient
- schedulingStrategy.close();
- schedulingStrategy.awaitTermination(1, TimeUnit.SECONDS);
- } catch (final InterruptedException ie) {
-
- } finally {
- verify(mockCacheEntry).hasVariants();
- verify(mockClient).revalidateCacheEntry(host, request, scope, mockExecChain, mockCacheEntry);
-
- Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
- }
- }
-
- @Test
- public void testSchedulingStrategyShutdownOnClose() throws IOException {
- impl = new AsynchronousValidator(mockSchedulingStrategy);
-
+ public void testShutdown() throws Exception {
impl.close();
+ impl.awaitTermination(Timeout.ofMinutes(2));
- verify(mockSchedulingStrategy).close();
+ verify(mockExecutorService).shutdown();
+ verify(mockExecutorService).awaitTermination(2L, TimeUnit.MINUTES);
}
+
}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/0561bacc/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestExponentialBackingOffSchedulingStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestExponentialBackingOffSchedulingStrategy.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestExponentialBackingOffSchedulingStrategy.java
deleted file mode 100644
index 277e82f..0000000
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestExponentialBackingOffSchedulingStrategy.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.hc.client5.http.HttpRoute;
-import org.apache.hc.client5.http.classic.ExecChain;
-import org.apache.hc.client5.http.classic.ExecRuntime;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.core5.http.ClassicHttpRequest;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
-import org.junit.Before;
-import org.junit.Test;
-
-public class TestExponentialBackingOffSchedulingStrategy {
-
- private ScheduledExecutorService mockExecutor;
- private ExecRuntime mockEndpoint;
- private ExponentialBackOffSchedulingStrategy impl;
-
- @Before
- public void setUp() {
- mockExecutor = mock(ScheduledExecutorService.class);
-
- impl = new ExponentialBackOffSchedulingStrategy(
- mockExecutor,
- ExponentialBackOffSchedulingStrategy.DEFAULT_BACK_OFF_RATE,
- ExponentialBackOffSchedulingStrategy.DEFAULT_INITIAL_EXPIRY_IN_MILLIS,
- ExponentialBackOffSchedulingStrategy.DEFAULT_MAX_EXPIRY_IN_MILLIS
- );
- }
-
- @Test
- public void testScheduleWithoutPreviousError() {
- final AsynchronousValidationRequest request = createAsynchronousValidationRequest(withErrorCount(0));
-
- expectRequestScheduledWithoutDelay(request);
-
- impl.schedule(request);
-
- verify(mockExecutor).schedule(request, 0, TimeUnit.MILLISECONDS);
- }
-
- @Test
- public void testScheduleWithOneFailedAttempt() {
- final AsynchronousValidationRequest request = createAsynchronousValidationRequest(withErrorCount(1));
-
- expectRequestScheduledWithDelay(request, TimeUnit.SECONDS.toMillis(6));
-
- impl.schedule(request);
-
- verify(mockExecutor).schedule(request, 6000, TimeUnit.MILLISECONDS);
- }
-
- @Test
- public void testScheduleWithTwoFailedAttempts() {
- final AsynchronousValidationRequest request = createAsynchronousValidationRequest(withErrorCount(2));
-
- expectRequestScheduledWithDelay(request, TimeUnit.SECONDS.toMillis(60));
-
- impl.schedule(request);
-
- verify(mockExecutor).schedule(request, 60000, TimeUnit.MILLISECONDS);
- }
-
- @Test
- public void testScheduleWithThreeFailedAttempts() {
- final AsynchronousValidationRequest request = createAsynchronousValidationRequest(withErrorCount(3));
-
- expectRequestScheduledWithDelay(request, TimeUnit.SECONDS.toMillis(600));
-
- impl.schedule(request);
-
- verify(mockExecutor).schedule(request, 600000, TimeUnit.MILLISECONDS);
- }
-
- @Test
- public void testScheduleWithFourFailedAttempts() {
- final AsynchronousValidationRequest request = createAsynchronousValidationRequest(withErrorCount(4));
-
- expectRequestScheduledWithDelay(request, TimeUnit.SECONDS.toMillis(6000));
-
- impl.schedule(request);
-
- verify(mockExecutor).schedule(request, 6000000, TimeUnit.MILLISECONDS);
- }
-
- @Test
- public void testScheduleWithFiveFailedAttempts() {
- final AsynchronousValidationRequest request = createAsynchronousValidationRequest(withErrorCount(5));
-
- expectRequestScheduledWithDelay(request, TimeUnit.SECONDS.toMillis(60000));
-
- impl.schedule(request);
-
- verify(mockExecutor).schedule(request, 60000000, TimeUnit.MILLISECONDS);
- }
-
- @Test
- public void testScheduleWithSixFailedAttempts() {
- final AsynchronousValidationRequest request = createAsynchronousValidationRequest(withErrorCount(6));
-
- expectRequestScheduledWithDelay(request, TimeUnit.SECONDS.toMillis(86400));
-
- impl.schedule(request);
-
- verify(mockExecutor).schedule(request, 86400000, TimeUnit.MILLISECONDS);
- }
-
- @Test
- public void testScheduleWithMaxNumberOfFailedAttempts() {
- final AsynchronousValidationRequest request = createAsynchronousValidationRequest(withErrorCount(Integer.MAX_VALUE));
-
- expectRequestScheduledWithDelay(request, TimeUnit.SECONDS.toMillis(86400));
-
- impl.schedule(request);
-
- verify(mockExecutor).schedule(request, 86400000, TimeUnit.MILLISECONDS);
- }
-
- private void expectRequestScheduledWithoutDelay(final AsynchronousValidationRequest request) {
- expectRequestScheduledWithDelay(request, 0);
- }
-
- private void expectRequestScheduledWithDelay(final AsynchronousValidationRequest request, final long delayInMillis) {
- when(mockExecutor.schedule(request, delayInMillis, TimeUnit.MILLISECONDS)).thenReturn(null);
- }
-
- private AsynchronousValidationRequest createAsynchronousValidationRequest(final int errorCount) {
- final CachingExec cachingHttpClient = new CachingExec();
- final AsynchronousValidator mockValidator = new AsynchronousValidator(impl);
- final HttpHost host = new HttpHost("foo.example.com", 80);
- final HttpRoute route = new HttpRoute(host);
- final ClassicHttpRequest request = new BasicClassicHttpRequest("GET", "/");
- final HttpClientContext context = new HttpClientContext();
- final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, mock(ExecRuntime.class), context);
- return new AsynchronousValidationRequest(mockValidator, cachingHttpClient, host, request,
- scope, mock(ExecChain.class), null, "identifier", errorCount);
- }
-
- private static int withErrorCount(final int errorCount) {
- return errorCount;
- }
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/0561bacc/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestImmediateSchedulingStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestImmediateSchedulingStrategy.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestImmediateSchedulingStrategy.java
deleted file mode 100644
index 645dbe0..0000000
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestImmediateSchedulingStrategy.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import static org.mockito.Mockito.mock;
-
-import java.util.concurrent.ExecutorService;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class TestImmediateSchedulingStrategy {
-
- private ExecutorService mockExecutor;
- private AsynchronousValidationRequest mockRevalidationRequest;
- private SchedulingStrategy schedulingStrategy;
-
- @Before
- public void setUp() {
- mockExecutor = mock(ExecutorService.class);
- mockRevalidationRequest = mock(AsynchronousValidationRequest.class);
- schedulingStrategy = new ImmediateSchedulingStrategy(mockExecutor);
- }
-
- @Test
- public void testRequestScheduledImmediately() {
- mockExecutor.execute(mockRevalidationRequest);
-
- schedulingStrategy.schedule(mockRevalidationRequest);
- }
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/0561bacc/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/schedule/TestExponentialBackingOffSchedulingStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/schedule/TestExponentialBackingOffSchedulingStrategy.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/schedule/TestExponentialBackingOffSchedulingStrategy.java
new file mode 100644
index 0000000..65181c9
--- /dev/null
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/schedule/TestExponentialBackingOffSchedulingStrategy.java
@@ -0,0 +1,59 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.schedule;
+
+import org.apache.hc.core5.util.TimeValue;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestExponentialBackingOffSchedulingStrategy {
+
+ private ExponentialBackOffSchedulingStrategy impl;
+
+ @Before
+ public void setUp() {
+ impl = new ExponentialBackOffSchedulingStrategy(
+ ExponentialBackOffSchedulingStrategy.DEFAULT_BACK_OFF_RATE,
+ ExponentialBackOffSchedulingStrategy.DEFAULT_INITIAL_EXPIRY,
+ ExponentialBackOffSchedulingStrategy.DEFAULT_MAX_EXPIRY
+ );
+ }
+
+ @Test
+ public void testSchedule() {
+ Assert.assertEquals(TimeValue.ofMillis(0), impl.schedule(0));
+ Assert.assertEquals(TimeValue.ofMillis(6000), impl.schedule(1));
+ Assert.assertEquals(TimeValue.ofMillis(60000), impl.schedule(2));
+ Assert.assertEquals(TimeValue.ofMillis(600000), impl.schedule(3));
+ Assert.assertEquals(TimeValue.ofMillis(6000000), impl.schedule(4));
+ Assert.assertEquals(TimeValue.ofMillis(60000000), impl.schedule(5));
+ Assert.assertEquals(TimeValue.ofMillis(86400000), impl.schedule(6));
+ Assert.assertEquals(TimeValue.ofMillis(86400000), impl.schedule(Integer.MAX_VALUE));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/0561bacc/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/schedule/TestImmediateSchedulingStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/schedule/TestImmediateSchedulingStrategy.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/schedule/TestImmediateSchedulingStrategy.java
new file mode 100644
index 0000000..97f1ac9
--- /dev/null
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/schedule/TestImmediateSchedulingStrategy.java
@@ -0,0 +1,51 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.schedule;
+
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
+import org.apache.hc.core5.util.TimeValue;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestImmediateSchedulingStrategy {
+
+ private SchedulingStrategy impl;
+
+ @Before
+ public void setUp() {
+ impl = new ImmediateSchedulingStrategy();
+ }
+
+ @Test
+ public void testSchedule() {
+ Assert.assertEquals(TimeValue.ZERO_MILLISECONDS, impl.schedule(0));
+ Assert.assertEquals(TimeValue.ZERO_MILLISECONDS, impl.schedule(1));
+ Assert.assertEquals(TimeValue.ZERO_MILLISECONDS, impl.schedule(Integer.MAX_VALUE));
+ }
+
+}
[2/3] httpcomponents-client git commit: Replaced FailureCache by
generic ConcurrentCountMap
Posted by ol...@apache.org.
Replaced FailureCache by generic ConcurrentCountMap
Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/commit/79b76030
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/tree/79b76030
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/diff/79b76030
Branch: refs/heads/master
Commit: 79b76030fd0844f5cd3f71f67d9fc93d5e2b6e57
Parents: 0561bac
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Fri Jan 5 15:09:05 2018 +0100
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Fri Jan 5 15:09:05 2018 +0100
----------------------------------------------------------------------
.../http/impl/cache/AsynchronousValidator.java | 12 +-
.../http/impl/cache/DefaultFailureCache.java | 147 -------------------
.../client5/http/impl/cache/FailureCache.java | 57 -------
.../http/impl/cache/FailureCacheValue.java | 68 ---------
.../http/schedule/ConcurrentCountMap.java | 75 ++++++++++
.../impl/cache/TestDefaultFailureCache.java | 69 ---------
.../http/schedule/TestConcurrentCountMap.java | 50 +++++++
7 files changed, 132 insertions(+), 346 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/79b76030/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java
index c635747..b5c90ca 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java
@@ -38,6 +38,7 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy;
+import org.apache.hc.client5.http.schedule.ConcurrentCountMap;
import org.apache.hc.client5.http.schedule.SchedulingStrategy;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.HttpHost;
@@ -57,7 +58,7 @@ class AsynchronousValidator implements Closeable {
private final SchedulingStrategy schedulingStrategy;
private final Set<String> queued;
private final CacheKeyGenerator cacheKeyGenerator;
- private final FailureCache failureCache;
+ private final ConcurrentCountMap<String> failureCache;
private final Logger log = LogManager.getLogger(getClass());
@@ -87,7 +88,7 @@ class AsynchronousValidator implements Closeable {
this.schedulingStrategy = schedulingStrategy;
this.queued = new HashSet<>();
this.cacheKeyGenerator = CacheKeyGenerator.INSTANCE;
- this.failureCache = new DefaultFailureCache();
+ this.failureCache = new ConcurrentCountMap<>();
}
/**
@@ -104,7 +105,7 @@ class AsynchronousValidator implements Closeable {
final String cacheKey = cacheKeyGenerator.generateVariantURI(target, request, entry);
if (!queued.contains(cacheKey)) {
- final int consecutiveFailedAttempts = failureCache.getErrorCount(cacheKey);
+ final int consecutiveFailedAttempts = failureCache.getCount(cacheKey);
final AsynchronousValidationRequest revalidationRequest =
new AsynchronousValidationRequest(
this, cachingExec, target, request, scope, chain, entry, cacheKey, consecutiveFailedAttempts);
@@ -147,7 +148,7 @@ class AsynchronousValidator implements Closeable {
* @param identifier the revalidation job's unique identifier
*/
void jobSuccessful(final String identifier) {
- failureCache.resetErrorCount(identifier);
+ failureCache.resetCount(identifier);
}
/**
@@ -157,10 +158,11 @@ class AsynchronousValidator implements Closeable {
* @param identifier the revalidation job's unique identifier
*/
void jobFailed(final String identifier) {
- failureCache.increaseErrorCount(identifier);
+ failureCache.increaseCount(identifier);
}
Set<String> getScheduledIdentifiers() {
return Collections.unmodifiableSet(queued);
}
+
}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/79b76030/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultFailureCache.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultFailureCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultFailureCache.java
deleted file mode 100644
index 7170d99..0000000
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultFailureCache.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.hc.core5.annotation.Contract;
-import org.apache.hc.core5.annotation.ThreadingBehavior;
-
-/**
- * Implements a bounded failure cache. The oldest entries are discarded when
- * the maximum size is exceeded.
- *
- * @since 4.3
- */
-@Contract(threading = ThreadingBehavior.SAFE)
-public class DefaultFailureCache implements FailureCache {
-
- static final int DEFAULT_MAX_SIZE = 1000;
- static final int MAX_UPDATE_TRIES = 10;
-
- private final int maxSize;
- private final ConcurrentMap<String, FailureCacheValue> storage;
-
- /**
- * Create a new failure cache with the maximum size of
- * {@link #DEFAULT_MAX_SIZE}.
- */
- public DefaultFailureCache() {
- this(DEFAULT_MAX_SIZE);
- }
-
- /**
- * Creates a new failure cache with the specified maximum size.
- * @param maxSize the maximum number of entries the cache should store
- */
- public DefaultFailureCache(final int maxSize) {
- this.maxSize = maxSize;
- this.storage = new ConcurrentHashMap<>();
- }
-
- @Override
- public int getErrorCount(final String identifier) {
- if (identifier == null) {
- throw new IllegalArgumentException("identifier may not be null");
- }
- final FailureCacheValue storedErrorCode = storage.get(identifier);
- return storedErrorCode != null ? storedErrorCode.getErrorCount() : 0;
- }
-
- @Override
- public void resetErrorCount(final String identifier) {
- if (identifier == null) {
- throw new IllegalArgumentException("identifier may not be null");
- }
- storage.remove(identifier);
- }
-
- @Override
- public void increaseErrorCount(final String identifier) {
- if (identifier == null) {
- throw new IllegalArgumentException("identifier may not be null");
- }
- updateValue(identifier);
- removeOldestEntryIfMapSizeExceeded();
- }
-
- private void updateValue(final String identifier) {
- /**
- * Due to concurrency it is possible that someone else is modifying an
- * entry before we could write back our updated value. So we keep
- * trying until it is our turn.
- *
- * In case there is a lot of contention on that identifier, a thread
- * might starve. Thus it gives up after a certain number of failed
- * processChallenge tries.
- */
- for (int i = 0; i < MAX_UPDATE_TRIES; i++) {
- final FailureCacheValue oldValue = storage.get(identifier);
- if (oldValue == null) {
- final FailureCacheValue newValue = new FailureCacheValue(identifier, 1);
- if (storage.putIfAbsent(identifier, newValue) == null) {
- return;
- }
- }
- else {
- final int errorCount = oldValue.getErrorCount();
- if (errorCount == Integer.MAX_VALUE) {
- return;
- }
- final FailureCacheValue newValue = new FailureCacheValue(identifier, errorCount + 1);
- if (storage.replace(identifier, oldValue, newValue)) {
- return;
- }
- }
- }
- }
-
- private void removeOldestEntryIfMapSizeExceeded() {
- if (storage.size() > maxSize) {
- final FailureCacheValue valueWithOldestTimestamp = findValueWithOldestTimestamp();
- if (valueWithOldestTimestamp != null) {
- storage.remove(valueWithOldestTimestamp.getKey(), valueWithOldestTimestamp);
- }
- }
- }
-
- private FailureCacheValue findValueWithOldestTimestamp() {
- long oldestTimestamp = Long.MAX_VALUE;
- FailureCacheValue oldestValue = null;
- for (final Map.Entry<String, FailureCacheValue> storageEntry : storage.entrySet()) {
- final FailureCacheValue value = storageEntry.getValue();
- final long creationTimeInNanos = value.getCreationTimeInNanos();
- if (creationTimeInNanos < oldestTimestamp) {
- oldestTimestamp = creationTimeInNanos;
- oldestValue = storageEntry.getValue();
- }
- }
- return oldestValue;
- }
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/79b76030/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/FailureCache.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/FailureCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/FailureCache.java
deleted file mode 100644
index 19c941d..0000000
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/FailureCache.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-/**
- * Increase and reset the number of errors associated with a specific
- * identifier.
- *
- * @since 4.3
- */
-public interface FailureCache {
-
- /**
- * Get the current error count.
- * @param identifier the identifier for which the error count is requested
- * @return the currently known error count or zero if there is no record
- */
- int getErrorCount(String identifier);
-
- /**
- * Reset the error count back to zero.
- * @param identifier the identifier for which the error count should be
- * reset
- */
- void resetErrorCount(String identifier);
-
- /**
- * Increases the error count by one.
- * @param identifier the identifier for which the error count should be
- * increased
- */
- void increaseErrorCount(String identifier);
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/79b76030/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/FailureCacheValue.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/FailureCacheValue.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/FailureCacheValue.java
deleted file mode 100644
index 32f7702..0000000
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/FailureCacheValue.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import org.apache.hc.core5.annotation.Contract;
-import org.apache.hc.core5.annotation.ThreadingBehavior;
-
-/**
- * The error count with a creation timestamp and its associated key.
- *
- * @since 4.3
- */
-@Contract(threading = ThreadingBehavior.IMMUTABLE)
-public class FailureCacheValue {
-
- private final long creationTimeInNanos;
- private final String key;
- private final int errorCount;
-
- public FailureCacheValue(final String key, final int errorCount) {
- this.creationTimeInNanos = System.nanoTime();
- this.key = key;
- this.errorCount = errorCount;
- }
-
- public long getCreationTimeInNanos() {
- return creationTimeInNanos;
- }
-
- public String getKey()
- {
- return key;
- }
-
- public int getErrorCount() {
- return errorCount;
- }
-
- @Override
- public String toString() {
- return "[entry creationTimeInNanos=" + creationTimeInNanos + "; " +
- "key=" + key + "; errorCount=" + errorCount + ']';
- }
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/79b76030/httpclient5-cache/src/main/java/org/apache/hc/client5/http/schedule/ConcurrentCountMap.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/schedule/ConcurrentCountMap.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/schedule/ConcurrentCountMap.java
new file mode 100644
index 0000000..60eb84e
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/schedule/ConcurrentCountMap.java
@@ -0,0 +1,75 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.schedule;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.util.Args;
+
+@Contract(threading = ThreadingBehavior.SAFE)
+public final class ConcurrentCountMap<T> {
+
+ private final ConcurrentMap<T, AtomicInteger> map;
+
+ public ConcurrentCountMap() {
+ this.map = new ConcurrentHashMap<>();
+ }
+
+ public int getCount(final T identifier) {
+ Args.notNull(identifier, "Identifier");
+ final AtomicInteger count = map.get(identifier);
+ return count != null ? count.get() : 0;
+ }
+
+ public void resetCount(final T identifier) {
+ Args.notNull(identifier, "Identifier");
+ map.remove(identifier);
+ }
+
+ public int increaseCount(final T identifier) {
+ Args.notNull(identifier, "Identifier");
+ final AtomicInteger count = get(identifier);
+ return count.incrementAndGet();
+ }
+
+ private AtomicInteger get(final T identifier) {
+ AtomicInteger entry = map.get(identifier);
+ if (entry == null) {
+ final AtomicInteger newEntry = new AtomicInteger();
+ entry = map.putIfAbsent(identifier, newEntry);
+ if (entry == null) {
+ entry = newEntry;
+ }
+ }
+ return entry;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/79b76030/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultFailureCache.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultFailureCache.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultFailureCache.java
deleted file mode 100644
index 729694e..0000000
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultFailureCache.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import org.junit.Assert;
-import org.junit.Test;
-
-public class TestDefaultFailureCache
-{
-
- private static final String IDENTIFIER = "some-identifier";
-
- private FailureCache failureCache = new DefaultFailureCache();
-
- @Test
- public void testResetErrorCount() {
- failureCache.increaseErrorCount(IDENTIFIER);
- failureCache.resetErrorCount(IDENTIFIER);
-
- final int errorCount = failureCache.getErrorCount(IDENTIFIER);
- Assert.assertEquals(0, errorCount);
- }
-
- @Test
- public void testIncrementErrorCount() {
- failureCache.increaseErrorCount(IDENTIFIER);
- failureCache.increaseErrorCount(IDENTIFIER);
- failureCache.increaseErrorCount(IDENTIFIER);
-
- final int errorCount = failureCache.getErrorCount(IDENTIFIER);
- Assert.assertEquals(3, errorCount);
- }
-
- @Test
- public void testMaxSize() {
- failureCache = new DefaultFailureCache(3);
- failureCache.increaseErrorCount("a");
- failureCache.increaseErrorCount("b");
- failureCache.increaseErrorCount("c");
- failureCache.increaseErrorCount("d");
-
- final int errorCount = failureCache.getErrorCount("a");
- Assert.assertEquals(0, errorCount);
- }
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/79b76030/httpclient5-cache/src/test/java/org/apache/hc/client5/http/schedule/TestConcurrentCountMap.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/schedule/TestConcurrentCountMap.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/schedule/TestConcurrentCountMap.java
new file mode 100644
index 0000000..50b3832
--- /dev/null
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/schedule/TestConcurrentCountMap.java
@@ -0,0 +1,50 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.schedule;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestConcurrentCountMap
+{
+
+ private static final String IDENTIFIER = "some-identifier";
+
+ private ConcurrentCountMap<String> map = new ConcurrentCountMap<>();
+
+ @Test
+ public void testBasics() {
+ map.increaseCount(IDENTIFIER);
+ map.increaseCount(IDENTIFIER);
+ Assert.assertThat(map.getCount(IDENTIFIER), CoreMatchers.equalTo(2));
+
+ map.resetCount(IDENTIFIER);
+ Assert.assertThat(map.getCount(IDENTIFIER), CoreMatchers.equalTo(0));
+ }
+
+}
[3/3] httpcomponents-client git commit: Redesigned cache entry
re-validation logic;
added classic and async implementations of cache re-validators
Posted by ol...@apache.org.
Redesigned cache entry re-validation logic; added classic and async implementations of cache re-validators
Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/commit/7cf4240b
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/tree/7cf4240b
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/diff/7cf4240b
Branch: refs/heads/master
Commit: 7cf4240b3f580ecf5ff67eeb71b3e91bf4a100fd
Parents: 79b7603
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Sat Jan 6 11:45:13 2018 +0100
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Sun Jan 7 12:48:59 2018 +0100
----------------------------------------------------------------------
.../cache/AsynchronousValidationRequest.java | 169 ----------------
.../http/impl/cache/AsynchronousValidator.java | 168 ----------------
.../http/impl/cache/CacheInvalidatorBase.java | 4 +-
.../http/impl/cache/CacheRevalidatorBase.java | 190 ++++++++++++++++++
.../cache/DefaultAsyncCacheRevalidator.java | 160 +++++++++++++++
.../impl/cache/DefaultCacheRevalidator.java | 113 +++++++++++
.../TestAsynchronousValidationRequest.java | 193 ------------------
.../impl/cache/TestAsynchronousValidator.java | 196 -------------------
.../impl/cache/TestCacheRevalidatorBase.java | 154 +++++++++++++++
9 files changed, 619 insertions(+), 728 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/7cf4240b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidationRequest.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidationRequest.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidationRequest.java
deleted file mode 100644
index 01a8e6b..0000000
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidationRequest.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import java.io.IOException;
-
-import org.apache.hc.client5.http.cache.HeaderConstants;
-import org.apache.hc.client5.http.cache.HttpCacheEntry;
-import org.apache.hc.client5.http.classic.ExecChain;
-import org.apache.hc.core5.http.ClassicHttpRequest;
-import org.apache.hc.core5.http.ClassicHttpResponse;
-import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpResponse;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-/**
- * Class used to represent an asynchronous revalidation event, such as with
- * "stale-while-revalidate"
- */
-public class AsynchronousValidationRequest implements Runnable {
- private final AsynchronousValidator parent;
- private final CachingExec cachingExec;
- private final HttpHost target;
- private final ClassicHttpRequest request;
- private final ExecChain.Scope scope;
- private final ExecChain chain;
- private final HttpCacheEntry cacheEntry;
- private final String identifier;
- private final int consecutiveFailedAttempts;
-
- private final Logger log = LogManager.getLogger(getClass());
-
- /**
- * Used internally by {@link AsynchronousValidator} to schedule a
- */
- AsynchronousValidationRequest(
- final AsynchronousValidator parent,
- final CachingExec cachingExec,
- final HttpHost target,
- final ClassicHttpRequest request,
- final ExecChain.Scope scope,
- final ExecChain chain,
- final HttpCacheEntry cacheEntry,
- final String identifier,
- final int consecutiveFailedAttempts) {
- this.parent = parent;
- this.cachingExec = cachingExec;
- this.target = target;
- this.request = request;
- this.scope = scope;
- this.chain = chain;
- this.cacheEntry = cacheEntry;
- this.identifier = identifier;
- this.consecutiveFailedAttempts = consecutiveFailedAttempts;
- }
-
- @Override
- public void run() {
- try {
- if (revalidateCacheEntry()) {
- parent.jobSuccessful(identifier);
- } else {
- parent.jobFailed(identifier);
- }
- } finally {
- parent.markComplete(identifier);
- }
- }
-
- /**
- * Revalidate the cache entry and return if the operation was successful.
- * Success means a connection to the server was established and replay did
- * not indicate a server error.
- * @return {@code true} if the cache entry was successfully validated;
- * otherwise {@code false}
- */
- private boolean revalidateCacheEntry() {
- try {
- try (ClassicHttpResponse httpResponse = cachingExec.revalidateCacheEntry(target, request, scope, chain, cacheEntry)) {
- final int statusCode = httpResponse.getCode();
- return isNotServerError(statusCode) && isNotStale(httpResponse);
- }
- } catch (final IOException ioe) {
- log.debug("Asynchronous revalidation failed due to I/O error", ioe);
- return false;
- } catch (final HttpException pe) {
- log.error("HTTP protocol exception during asynchronous revalidation", pe);
- return false;
- } catch (final RuntimeException re) {
- log.error("RuntimeException thrown during asynchronous revalidation: " + re);
- return false;
- }
- }
-
- /**
- * Return whether the status code indicates a server error or not.
- * @param statusCode the status code to be checked
- * @return if the status code indicates a server error or not
- */
- private boolean isNotServerError(final int statusCode) {
- return statusCode < 500;
- }
-
- /**
- * Try to detect if the returned response is generated from a stale cache entry.
- * @param httpResponse the response to be checked
- * @return whether the response is stale or not
- */
- private boolean isNotStale(final HttpResponse httpResponse) {
- final Header[] warnings = httpResponse.getHeaders(HeaderConstants.WARNING);
- if (warnings != null)
- {
- for (final Header warning : warnings)
- {
- /**
- * warn-codes
- * 110 = Response is stale
- * 111 = Revalidation failed
- */
- final String warningValue = warning.getValue();
- if (warningValue.startsWith("110") || warningValue.startsWith("111"))
- {
- return false;
- }
- }
- }
- return true;
- }
-
- public String getIdentifier() {
- return identifier;
- }
-
- /**
- * The number of consecutively failed revalidation attempts.
- * @return the number of consecutively failed revalidation attempts.
- */
- public int getConsecutiveFailedAttempts() {
- return consecutiveFailedAttempts;
- }
-
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/7cf4240b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java
deleted file mode 100644
index b5c90ca..0000000
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsynchronousValidator.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-
-import org.apache.hc.client5.http.cache.HttpCacheEntry;
-import org.apache.hc.client5.http.classic.ExecChain;
-import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy;
-import org.apache.hc.client5.http.schedule.ConcurrentCountMap;
-import org.apache.hc.client5.http.schedule.SchedulingStrategy;
-import org.apache.hc.core5.http.ClassicHttpRequest;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.util.Args;
-import org.apache.hc.core5.util.TimeValue;
-import org.apache.hc.core5.util.Timeout;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-/**
- * Class used for asynchronous revalidations to be used when the "stale-
- * while-revalidate" directive is present
- */
-class AsynchronousValidator implements Closeable {
-
- private final ScheduledExecutorService executorService;
- private final SchedulingStrategy schedulingStrategy;
- private final Set<String> queued;
- private final CacheKeyGenerator cacheKeyGenerator;
- private final ConcurrentCountMap<String> failureCache;
-
- private final Logger log = LogManager.getLogger(getClass());
-
- /**
- * Create AsynchronousValidator which will make revalidation requests
- * using an {@link ImmediateSchedulingStrategy}. Its thread
- * pool will be configured according to the given {@link CacheConfig}.
- * @param config specifies thread pool settings. See
- * {@link CacheConfig#getAsynchronousWorkersMax()},
- * {@link CacheConfig#getAsynchronousWorkersCore()},
- * {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()},
- * and {@link CacheConfig#getRevalidationQueueSize()}.
- */
- public AsynchronousValidator(final CacheConfig config) {
- this(new ScheduledThreadPoolExecutor(config.getAsynchronousWorkersCore()), new ImmediateSchedulingStrategy());
- }
-
- /**
- * Create AsynchronousValidator which will make revalidation requests
- * using the supplied {@link SchedulingStrategy}. Closing the validator
- * will also close the given schedulingStrategy.
- * @param schedulingStrategy used to maintain a pool of worker threads and
- * schedules when requests are executed
- */
- AsynchronousValidator(final ScheduledExecutorService executorService, final SchedulingStrategy schedulingStrategy) {
- this.executorService = executorService;
- this.schedulingStrategy = schedulingStrategy;
- this.queued = new HashSet<>();
- this.cacheKeyGenerator = CacheKeyGenerator.INSTANCE;
- this.failureCache = new ConcurrentCountMap<>();
- }
-
- /**
- * Schedules an asynchronous revalidation
- */
- public synchronized void revalidateCacheEntry(
- final CachingExec cachingExec,
- final HttpHost target,
- final ClassicHttpRequest request,
- final ExecChain.Scope scope,
- final ExecChain chain,
- final HttpCacheEntry entry) {
- // getVariantURI will fall back on getURI if no variants exist
- final String cacheKey = cacheKeyGenerator.generateVariantURI(target, request, entry);
-
- if (!queued.contains(cacheKey)) {
- final int consecutiveFailedAttempts = failureCache.getCount(cacheKey);
- final AsynchronousValidationRequest revalidationRequest =
- new AsynchronousValidationRequest(
- this, cachingExec, target, request, scope, chain, entry, cacheKey, consecutiveFailedAttempts);
-
- final TimeValue executionTime = schedulingStrategy.schedule(consecutiveFailedAttempts);
- try {
- executorService.schedule(revalidationRequest, executionTime.getDuration(), executionTime.getTimeUnit());
- queued.add(cacheKey);
- } catch (final RejectedExecutionException ree) {
- log.debug("Revalidation for [" + cacheKey + "] not scheduled: " + ree);
- }
- }
- }
-
- @Override
- public void close() throws IOException {
- executorService.shutdown();
- }
-
- public void awaitTermination(final Timeout timeout) throws InterruptedException {
- Args.notNull(timeout, "Timeout");
- executorService.awaitTermination(timeout.getDuration(), timeout.getTimeUnit());
- }
-
- /**
- * Removes an identifier from the internal list of revalidation jobs in
- * progress. This is meant to be called by
- * {@link AsynchronousValidationRequest#run()} once the revalidation is
- * complete, using the identifier passed in during constructions.
- * @param identifier
- */
- synchronized void markComplete(final String identifier) {
- queued.remove(identifier);
- }
-
- /**
- * The revalidation job was successful thus the number of consecutive
- * failed attempts will be reset to zero. Should be called by
- * {@link AsynchronousValidationRequest#run()}.
- * @param identifier the revalidation job's unique identifier
- */
- void jobSuccessful(final String identifier) {
- failureCache.resetCount(identifier);
- }
-
- /**
- * The revalidation job did fail and thus the number of consecutive failed
- * attempts will be increased. Should be called by
- * {@link AsynchronousValidationRequest#run()}.
- * @param identifier the revalidation job's unique identifier
- */
- void jobFailed(final String identifier) {
- failureCache.increaseCount(identifier);
- }
-
- Set<String> getScheduledIdentifiers() {
- return Collections.unmodifiableSet(queued);
- }
-
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/7cf4240b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
index d229011..604ed61 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
@@ -61,9 +61,9 @@ class CacheInvalidatorBase {
}
static boolean notGetOrHeadRequest(final String method) {
- return !(HeaderConstants.GET_METHOD.equals(method) || HeaderConstants.HEAD_METHOD
- .equals(method));
+ return !(HeaderConstants.GET_METHOD.equals(method) || HeaderConstants.HEAD_METHOD.equals(method));
}
+
private static URI getLocationURI(final URI requestUri, final HttpResponse response, final String headerName) {
final Header h = response.getFirstHeader(headerName);
if (h == null) {
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/7cf4240b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheRevalidatorBase.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheRevalidatorBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheRevalidatorBase.java
new file mode 100644
index 0000000..25c0ecf
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheRevalidatorBase.java
@@ -0,0 +1,190 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.cache;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+import org.apache.hc.client5.http.cache.HeaderConstants;
+import org.apache.hc.client5.http.schedule.ConcurrentCountMap;
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Abstract cache re-validation class.
+ */
+class CacheRevalidatorBase implements Closeable {
+
+ interface ScheduledExecutor {
+
+ Future<?> schedule(Runnable command, TimeValue timeValue) throws RejectedExecutionException;
+
+ void shutdown();
+
+ void awaitTermination(final Timeout timeout) throws InterruptedException;
+
+ }
+
+ public static ScheduledExecutor wrap(final ScheduledThreadPoolExecutor threadPoolExecutor) {
+
+ return new ScheduledExecutor() {
+
+ @Override
+ public ScheduledFuture<?> schedule(final Runnable command, final TimeValue timeValue) throws RejectedExecutionException {
+ Args.notNull(command, "Runnable");
+ Args.notNull(timeValue, "Time value");
+ return threadPoolExecutor.schedule(command, timeValue.getDuration(), timeValue.getTimeUnit());
+ }
+
+ @Override
+ public void shutdown() {
+ threadPoolExecutor.shutdown();
+ }
+
+ @Override
+ public void awaitTermination(final Timeout timeout) throws InterruptedException {
+ Args.notNull(timeout, "Timeout");
+ threadPoolExecutor.awaitTermination(timeout.getDuration(), timeout.getTimeUnit());
+ }
+
+ };
+
+ }
+
+ private final ScheduledExecutor scheduledExecutor;
+ private final SchedulingStrategy schedulingStrategy;
+ private final Set<String> pendingRequest;
+ private final ConcurrentCountMap<String> failureCache;
+
+ final Logger log = LogManager.getLogger(getClass());
+
+ /**
+ * Create CacheValidator which will make ache revalidation requests
+ * using the supplied {@link SchedulingStrategy} and {@link ScheduledExecutor}.
+ */
+ public CacheRevalidatorBase(
+ final ScheduledExecutor scheduledExecutor,
+ final SchedulingStrategy schedulingStrategy) {
+ this.scheduledExecutor = scheduledExecutor;
+ this.schedulingStrategy = schedulingStrategy;
+ this.pendingRequest = new HashSet<>();
+ this.failureCache = new ConcurrentCountMap<>();
+ }
+
+ /**
+ * Create CacheValidator which will make ache revalidation requests
+ * using the supplied {@link SchedulingStrategy} and {@link ScheduledThreadPoolExecutor}.
+ */
+ public CacheRevalidatorBase(
+ final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor,
+ final SchedulingStrategy schedulingStrategy) {
+ this(wrap(scheduledThreadPoolExecutor), schedulingStrategy);
+ }
+
+ /**
+ * Schedules an asynchronous re-validation
+ */
+ void scheduleRevalidation(final String cacheKey, final Runnable command) {
+ synchronized (pendingRequest) {
+ if (!pendingRequest.contains(cacheKey)) {
+ final int consecutiveFailedAttempts = failureCache.getCount(cacheKey);
+ final TimeValue executionTime = schedulingStrategy.schedule(consecutiveFailedAttempts);
+ try {
+ scheduledExecutor.schedule(command, executionTime);
+ pendingRequest.add(cacheKey);
+ } catch (final RejectedExecutionException ex) {
+ log.debug("Revalidation of cache entry with key " + cacheKey + "could not be scheduled: " + ex);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ scheduledExecutor.shutdown();
+ }
+
+ public void awaitTermination(final Timeout timeout) throws InterruptedException {
+ Args.notNull(timeout, "Timeout");
+ scheduledExecutor.awaitTermination(timeout);
+ }
+
+ void jobSuccessful(final String identifier) {
+ failureCache.resetCount(identifier);
+ synchronized (pendingRequest) {
+ pendingRequest.remove(identifier);
+ }
+ }
+
+ void jobFailed(final String identifier) {
+ failureCache.increaseCount(identifier);
+ synchronized (pendingRequest) {
+ pendingRequest.remove(identifier);
+ }
+ }
+
+ Set<String> getScheduledIdentifiers() {
+ synchronized (pendingRequest) {
+ return new HashSet<>(pendingRequest);
+ }
+ }
+
+ /**
+ * Determines if the given response is generated from a stale cache entry.
+ * @param httpResponse the response to be checked
+ * @return whether the response is stale or not
+ */
+ boolean isStale(final HttpResponse httpResponse) {
+ for (final Iterator<Header> it = httpResponse.headerIterator(HeaderConstants.WARNING); it.hasNext(); ) {
+ /*
+ * warn-codes
+ * 110 = Response is stale
+ * 111 = Revalidation failed
+ */
+ final Header warning = it.next();
+ final String warningValue = warning.getValue();
+ if (warningValue.startsWith("110") || warningValue.startsWith("111")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/7cf4240b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheRevalidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheRevalidator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheRevalidator.java
new file mode 100644
index 0000000..f0bed25
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheRevalidator.java
@@ -0,0 +1,160 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.cache;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.hc.client5.http.async.AsyncExecCallback;
+import org.apache.hc.client5.http.async.AsyncExecChain;
+import org.apache.hc.client5.http.cache.HttpCacheEntry;
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
+
+/**
+ * Class used for asynchronous revalidations to be used when the {@code stale-while-revalidate}
+ * directive is present
+ */
+class DefaultAsyncCacheRevalidator extends CacheRevalidatorBase {
+
+ private static final Future<Void> NOOP_FUTURE = new Future<Void>() {
+
+ @Override
+ public Void get() throws InterruptedException, ExecutionException {
+ return null;
+ }
+
+ @Override
+ public Void get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ return null;
+ }
+ @Override
+ public boolean cancel(final boolean mayInterruptIfRunning) {
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return true;
+ }
+
+ };
+
+ static class InternalScheduledExecutor implements ScheduledExecutor {
+
+ private final ScheduledExecutor executor;
+
+ InternalScheduledExecutor(final ScheduledExecutor executor) {
+ this.executor = executor;
+ }
+
+ @Override
+ public Future<?> schedule(final Runnable command, final TimeValue timeValue) throws RejectedExecutionException {
+ if (timeValue.toMillis() <= 0) {
+ command.run();
+ return NOOP_FUTURE;
+ } else {
+ return executor.schedule(command, timeValue);
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ executor.shutdown();
+ }
+
+ @Override
+ public void awaitTermination(final Timeout timeout) throws InterruptedException {
+ executor.awaitTermination(timeout);
+ }
+
+ }
+
+ private final AsyncCachingExec cachingExec;
+ private final CacheKeyGenerator cacheKeyGenerator;
+
+ /**
+ * Create DefaultCacheRevalidator which will make ache revalidation requests
+ * using the supplied {@link SchedulingStrategy} and {@link ScheduledExecutor}.
+ */
+ public DefaultAsyncCacheRevalidator(
+ final ScheduledExecutor scheduledExecutor,
+ final SchedulingStrategy schedulingStrategy,
+ final AsyncCachingExec cachingExec) {
+ super(new InternalScheduledExecutor(scheduledExecutor), schedulingStrategy);
+ this.cachingExec = cachingExec;
+ this.cacheKeyGenerator = CacheKeyGenerator.INSTANCE;
+
+ }
+
+ /**
+ * Create CacheValidator which will make ache revalidation requests
+ * using the supplied {@link SchedulingStrategy} and {@link ScheduledThreadPoolExecutor}.
+ */
+ public DefaultAsyncCacheRevalidator(
+ final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor,
+ final SchedulingStrategy schedulingStrategy,
+ final AsyncCachingExec cachingExec) {
+ this(wrap(scheduledThreadPoolExecutor), schedulingStrategy, cachingExec);
+ }
+
+ /**
+ * Schedules an asynchronous re-validation
+ */
+ public void revalidateCacheEntry(
+ final HttpHost target,
+ final HttpRequest request,
+ final AsyncEntityProducer entityProducer,
+ final AsyncExecChain.Scope scope,
+ final AsyncExecChain chain,
+ final AsyncExecCallback asyncExecCallback,
+ final HttpCacheEntry entry) {
+ final String cacheKey = cacheKeyGenerator.generateVariantURI(target, request, entry);
+ scheduleRevalidation(cacheKey, new Runnable() {
+
+ @Override
+ public void run() {
+ cachingExec.revalidateCacheEntry(target, request, entityProducer, scope, chain, asyncExecCallback, entry);
+ }
+
+ });
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/7cf4240b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheRevalidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheRevalidator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheRevalidator.java
new file mode 100644
index 0000000..8c752b8
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheRevalidator.java
@@ -0,0 +1,113 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.cache;
+
+import java.io.IOException;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+import org.apache.hc.client5.http.cache.HttpCacheEntry;
+import org.apache.hc.client5.http.classic.ExecChain;
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpStatus;
+
+/**
+ * Class used for asynchronous revalidations to be used when
+ * the {@code stale-while-revalidate} directive is present
+ */
+class DefaultCacheRevalidator extends CacheRevalidatorBase {
+
+ private final CachingExec cachingExec;
+ private final CacheKeyGenerator cacheKeyGenerator;
+
+ /**
+ * Create DefaultCacheRevalidator which will make ache revalidation requests
+ * using the supplied {@link SchedulingStrategy} and {@link ScheduledExecutor}.
+ */
+ public DefaultCacheRevalidator(
+ final CacheRevalidatorBase.ScheduledExecutor scheduledExecutor,
+ final SchedulingStrategy schedulingStrategy,
+ final CachingExec cachingExec) {
+ super(scheduledExecutor, schedulingStrategy);
+ this.cachingExec = cachingExec;
+ this.cacheKeyGenerator = CacheKeyGenerator.INSTANCE;
+
+ }
+
+ /**
+ * Create CacheValidator which will make ache revalidation requests
+ * using the supplied {@link SchedulingStrategy} and {@link ScheduledThreadPoolExecutor}.
+ */
+ public DefaultCacheRevalidator(
+ final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor,
+ final SchedulingStrategy schedulingStrategy,
+ final CachingExec cachingExec) {
+ this(wrap(scheduledThreadPoolExecutor), schedulingStrategy, cachingExec);
+ }
+
+ /**
+ * Schedules an asynchronous re-validation
+ */
+ public void revalidateCacheEntry(
+ final HttpHost target,
+ final ClassicHttpRequest request,
+ final ExecChain.Scope scope,
+ final ExecChain chain,
+ final HttpCacheEntry entry) {
+ final String cacheKey = cacheKeyGenerator.generateVariantURI(target, request, entry);
+ scheduleRevalidation(cacheKey, new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ try (ClassicHttpResponse httpResponse = cachingExec.revalidateCacheEntry(target, request, scope, chain, entry)) {
+ if (httpResponse.getCode() < HttpStatus.SC_SERVER_ERROR && !isStale(httpResponse)) {
+ jobSuccessful(cacheKey);
+ } else {
+ jobFailed(cacheKey);
+ }
+ }
+ } catch (final IOException ioe) {
+ jobFailed(cacheKey);
+ log.debug("Asynchronous revalidation failed due to I/O error", ioe);
+ } catch (final HttpException pe) {
+ jobFailed(cacheKey);
+ log.error("HTTP protocol exception during asynchronous revalidation", pe);
+ } catch (final RuntimeException re) {
+ jobFailed(cacheKey);
+ log.error("Unexpected runtime exception thrown during asynchronous revalidation" + re);
+ }
+
+ }
+
+ });
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/7cf4240b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidationRequest.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidationRequest.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidationRequest.java
deleted file mode 100644
index 227f240..0000000
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidationRequest.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-
-import org.apache.hc.client5.http.HttpRoute;
-import org.apache.hc.client5.http.cache.HeaderConstants;
-import org.apache.hc.client5.http.cache.HttpCacheEntry;
-import org.apache.hc.client5.http.classic.ExecChain;
-import org.apache.hc.client5.http.classic.ExecRuntime;
-import org.apache.hc.client5.http.classic.methods.HttpGet;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.core5.http.ClassicHttpRequest;
-import org.apache.hc.core5.http.ClassicHttpResponse;
-import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.ProtocolException;
-import org.apache.hc.core5.http.message.BasicHeader;
-import org.junit.Before;
-import org.junit.Test;
-
-@SuppressWarnings({"boxing","static-access"}) // test code
-public class TestAsynchronousValidationRequest {
-
- private AsynchronousValidator mockParent;
- private CachingExec mockClient;
- private HttpHost host;
- private HttpRoute route;
- private ClassicHttpRequest request;
- private HttpClientContext context;
- private ExecChain.Scope scope;
- private ExecChain execChain;
- private ExecRuntime mockEndpoint;
- private HttpCacheEntry mockCacheEntry;
- private ClassicHttpResponse mockResponse;
-
- @Before
- public void setUp() {
- mockParent = mock(AsynchronousValidator.class);
- mockClient = mock(CachingExec.class);
- host = new HttpHost("foo.example.com", 80);
- route = new HttpRoute(host);
- request = new HttpGet("/");
- context = HttpClientContext.create();
- mockEndpoint = mock(ExecRuntime.class);
- execChain = mock(ExecChain.class);
- mockCacheEntry = mock(HttpCacheEntry.class);
- mockResponse = mock(ClassicHttpResponse.class);
- scope = new ExecChain.Scope("test", route, request, mockEndpoint, context);
- }
-
- @Test
- public void testRunCallsCachingClientAndRemovesIdentifier() throws Exception {
- final String identifier = "foo";
-
- final AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
- mockParent, mockClient, host, request, scope, execChain, mockCacheEntry,
- identifier, 0);
-
- when(mockClient.revalidateCacheEntry(host, request, scope, execChain, mockCacheEntry)).thenReturn(mockResponse);
- when(mockResponse.getCode()).thenReturn(200);
-
- impl.run();
-
- verify(mockClient).revalidateCacheEntry(host, request, scope, execChain, mockCacheEntry);
- verify(mockParent).markComplete(identifier);
- verify(mockParent).jobSuccessful(identifier);
- }
-
- @Test
- public void testRunReportsJobFailedForServerError() throws Exception {
- final String identifier = "foo";
-
- final AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
- mockParent, mockClient, host, request, scope, execChain, mockCacheEntry,
- identifier, 0);
-
- when(mockClient.revalidateCacheEntry(host, request, scope, execChain, mockCacheEntry)).thenReturn(mockResponse);
- when(mockResponse.getCode()).thenReturn(200);
-
- impl.run();
-
- verify(mockClient).revalidateCacheEntry(host, request, scope, execChain, mockCacheEntry);
- verify(mockParent).markComplete(identifier);
- verify(mockParent).jobSuccessful(identifier);
- }
-
- @Test
- public void testRunReportsJobFailedForStaleResponse() throws Exception {
- final String identifier = "foo";
- final Header[] warning = new Header[] {new BasicHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"")};
-
- final AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
- mockParent, mockClient, host, request, scope, execChain, mockCacheEntry,
- identifier, 0);
-
- when(mockClient.revalidateCacheEntry(host, request, scope, execChain, mockCacheEntry))
- .thenReturn(mockResponse);
- when(mockResponse.getCode()).thenReturn(200);
- when(mockResponse.getHeaders(HeaderConstants.WARNING)).thenReturn(warning);
-
- impl.run();
-
- verify(mockClient).revalidateCacheEntry(host, request, scope, execChain, mockCacheEntry);
- verify(mockResponse).getHeaders(HeaderConstants.WARNING);
- verify(mockParent).markComplete(identifier);
- verify(mockParent).jobFailed(identifier);
- }
-
- @Test
- public void testRunGracefullyHandlesProtocolException() throws Exception {
- final String identifier = "foo";
-
- final AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
- mockParent, mockClient, host, request, scope, execChain, mockCacheEntry,
- identifier, 0);
-
- when(mockClient.revalidateCacheEntry(host, request, scope, execChain, mockCacheEntry))
- .thenThrow(new ProtocolException());
-
- impl.run();
-
- verify(mockClient).revalidateCacheEntry(host, request, scope, execChain, mockCacheEntry);
- verify(mockParent).markComplete(identifier);
- verify(mockParent).jobFailed(identifier);
- }
-
- @Test
- public void testRunGracefullyHandlesIOException() throws Exception {
- final String identifier = "foo";
-
- final AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
- mockParent, mockClient, host, request, scope, execChain, mockCacheEntry,
- identifier, 0);
-
- when(mockClient.revalidateCacheEntry(host, request, scope, execChain, mockCacheEntry))
- .thenThrow(new IOException());
-
- impl.run();
-
- verify(mockClient).revalidateCacheEntry(host, request, scope, execChain, mockCacheEntry);
- verify(mockParent).markComplete(identifier);
- verify(mockParent).jobFailed(identifier);
- }
-
- @Test
- public void testRunGracefullyHandlesRuntimeException() throws Exception {
- final String identifier = "foo";
-
- final AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
- mockParent, mockClient, host, request, scope, execChain, mockCacheEntry,
- identifier, 0);
-
- when(mockClient.revalidateCacheEntry(host, request, scope, execChain, mockCacheEntry))
- .thenThrow(new RuntimeException());
-
- impl.run();
-
- verify(mockClient).revalidateCacheEntry(
- host, request, scope, execChain, mockCacheEntry);
- verify(mockParent).markComplete(identifier);
- verify(mockParent).jobFailed(identifier);
- }
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/7cf4240b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidator.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidator.java
deleted file mode 100644
index 8450fa3..0000000
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestAsynchronousValidator.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.hc.client5.http.HttpRoute;
-import org.apache.hc.client5.http.cache.HeaderConstants;
-import org.apache.hc.client5.http.cache.HttpCacheEntry;
-import org.apache.hc.client5.http.classic.ExecChain;
-import org.apache.hc.client5.http.classic.ExecRuntime;
-import org.apache.hc.client5.http.classic.methods.HttpGet;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.http.schedule.SchedulingStrategy;
-import org.apache.hc.core5.http.ClassicHttpRequest;
-import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.message.BasicHeader;
-import org.apache.hc.core5.http.message.BasicHeaderIterator;
-import org.apache.hc.core5.util.TimeValue;
-import org.apache.hc.core5.util.Timeout;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class TestAsynchronousValidator {
-
- @Mock
- private CachingExec mockClient;
- @Mock
- private ExecChain mockExecChain;
- @Mock
- private ExecRuntime mockEndpoint;
- @Mock
- private HttpCacheEntry mockCacheEntry;
- @Mock
- private SchedulingStrategy mockSchedulingStrategy;
- @Mock
- ScheduledExecutorService mockExecutorService;
-
- private HttpHost host;
- private HttpRoute route;
- private ClassicHttpRequest request;
- private HttpClientContext context;
- private ExecChain.Scope scope;
- private AsynchronousValidator impl;
-
-
- @Before
- public void setUp() {
- host = new HttpHost("foo.example.com", 80);
- route = new HttpRoute(host);
- request = new HttpGet("/");
- context = HttpClientContext.create();
- scope = new ExecChain.Scope("test", route, request, mockEndpoint, context);
- impl = new AsynchronousValidator(mockExecutorService, mockSchedulingStrategy);
- }
-
- @Test
- public void testRevalidateCacheEntrySchedulesExecutionAndPopulatesIdentifier() {
- when(mockCacheEntry.hasVariants()).thenReturn(false);
- when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(1));
-
- impl.revalidateCacheEntry(mockClient, host, request, scope, mockExecChain, mockCacheEntry);
-
- verify(mockCacheEntry).hasVariants();
- verify(mockSchedulingStrategy).schedule(0);
- verify(mockExecutorService).schedule(Mockito.<Runnable>any(), Mockito.eq(1L), Mockito.eq(TimeUnit.SECONDS));
-
- Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
- }
-
- @Test
- public void testMarkCompleteRemovesIdentifier() {
- when(mockCacheEntry.hasVariants()).thenReturn(false);
- when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(3));
-
- impl.revalidateCacheEntry(mockClient, host, request, scope, mockExecChain, mockCacheEntry);
-
- verify(mockCacheEntry).hasVariants();
- verify(mockSchedulingStrategy).schedule(0);
- verify(mockExecutorService).schedule(Mockito.<Runnable>any(), Mockito.eq(3L), Mockito.eq(TimeUnit.SECONDS));
-
- Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
- final String cacheKey = CacheKeyGenerator.INSTANCE.generateVariantURI(host, request, mockCacheEntry);
- Assert.assertTrue(impl.getScheduledIdentifiers().contains(cacheKey));
-
- impl.markComplete(cacheKey);
-
- Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
- }
-
- @Test
- public void testRevalidateCacheEntryDoesNotPopulateIdentifierOnRejectedExecutionException() {
- when(mockCacheEntry.hasVariants()).thenReturn(false);
- when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(2));
- doThrow(new RejectedExecutionException()).when(mockExecutorService).schedule(Mockito.<Runnable>any(), Mockito.anyLong(), Mockito.<TimeUnit>any());
-
- impl.revalidateCacheEntry(mockClient, host, request, scope, mockExecChain, mockCacheEntry);
-
- verify(mockCacheEntry).hasVariants();
-
- Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
- verify(mockExecutorService).schedule(Mockito.<Runnable>any(), Mockito.eq(2L), Mockito.eq(TimeUnit.SECONDS));
- }
-
- @Test
- public void testRevalidateCacheEntryProperlyCollapsesRequest() {
- when(mockCacheEntry.hasVariants()).thenReturn(false);
- when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(2));
-
- impl.revalidateCacheEntry(mockClient, host, request, scope, mockExecChain, mockCacheEntry);
- impl.revalidateCacheEntry(mockClient, host, request, scope, mockExecChain, mockCacheEntry);
-
- verify(mockCacheEntry, Mockito.times(2)).hasVariants();
- verify(mockSchedulingStrategy).schedule(Mockito.anyInt());
- verify(mockExecutorService).schedule(Mockito.<Runnable>any(), Mockito.eq(2L), Mockito.eq(TimeUnit.SECONDS));
-
- Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
- }
-
- @Test
- public void testVariantsBothRevalidated() {
- final ClassicHttpRequest req1 = new HttpGet("/");
- req1.addHeader(new BasicHeader("Accept-Encoding", "identity"));
-
- final ClassicHttpRequest req2 = new HttpGet("/");
- req2.addHeader(new BasicHeader("Accept-Encoding", "gzip"));
-
- final Header[] variantHeaders = new Header[] {
- new BasicHeader(HeaderConstants.VARY, "Accept-Encoding")
- };
-
- when(mockCacheEntry.hasVariants()).thenReturn(true);
- when(mockCacheEntry.headerIterator(HeaderConstants.VARY)).thenReturn(
- new BasicHeaderIterator(variantHeaders, HeaderConstants.VARY));
- when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(2));
-
- impl.revalidateCacheEntry(mockClient, host, req1, new ExecChain.Scope("test", route, req1, mockEndpoint, context),
- mockExecChain, mockCacheEntry);
- impl.revalidateCacheEntry(mockClient, host, req2, new ExecChain.Scope("test", route, req2, mockEndpoint, context),
- mockExecChain, mockCacheEntry);
-
- verify(mockCacheEntry, Mockito.times(2)).hasVariants();
- verify(mockCacheEntry, Mockito.times(2)).headerIterator(HeaderConstants.VARY);
- verify(mockSchedulingStrategy, Mockito.times(2)).schedule(Mockito.anyInt());
- verify(mockExecutorService, Mockito.times(2)).schedule(Mockito.<Runnable>any(), Mockito.eq(2L), Mockito.eq(TimeUnit.SECONDS));
-
- Assert.assertEquals(2, impl.getScheduledIdentifiers().size());
- }
-
- @Test
- public void testShutdown() throws Exception {
- impl.close();
- impl.awaitTermination(Timeout.ofMinutes(2));
-
- verify(mockExecutorService).shutdown();
- verify(mockExecutorService).awaitTermination(2L, TimeUnit.MINUTES);
- }
-
-}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/7cf4240b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheRevalidatorBase.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheRevalidatorBase.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheRevalidatorBase.java
new file mode 100644
index 0000000..0326aba
--- /dev/null
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheRevalidatorBase.java
@@ -0,0 +1,154 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.cache;
+
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.RejectedExecutionException;
+
+import org.apache.hc.client5.http.cache.HeaderConstants;
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.message.BasicHttpResponse;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestCacheRevalidatorBase {
+
+ @Mock
+ private SchedulingStrategy mockSchedulingStrategy;
+ @Mock
+ private CacheRevalidatorBase.ScheduledExecutor mockScheduledExecutor;
+ @Mock
+ private Runnable mockOperation;
+
+ private CacheRevalidatorBase impl;
+
+
+ @Before
+ public void setUp() {
+ impl = new CacheRevalidatorBase(mockScheduledExecutor, mockSchedulingStrategy);
+ }
+
+ @Test
+ public void testRevalidateCacheEntrySchedulesExecutionAndPopulatesIdentifier() {
+ when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(1));
+
+ final String cacheKey = "blah";
+ impl.scheduleRevalidation(cacheKey, mockOperation);
+
+ verify(mockSchedulingStrategy).schedule(0);
+ verify(mockScheduledExecutor).schedule(Mockito.same(mockOperation), Mockito.eq(TimeValue.ofSeconds(1)));
+
+ Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
+ }
+
+ @Test
+ public void testMarkCompleteRemovesIdentifier() {
+ when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(3));
+
+ final String cacheKey = "blah";
+ impl.scheduleRevalidation(cacheKey, mockOperation);
+
+ verify(mockSchedulingStrategy).schedule(0);
+ verify(mockScheduledExecutor).schedule(Mockito.<Runnable>any(), Mockito.eq(TimeValue.ofSeconds(3)));
+
+ Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
+ Assert.assertTrue(impl.getScheduledIdentifiers().contains(cacheKey));
+
+ impl.jobSuccessful(cacheKey);
+
+ Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
+ }
+
+ @Test
+ public void testRevalidateCacheEntryDoesNotPopulateIdentifierOnRejectedExecutionException() {
+ when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(2));
+ doThrow(new RejectedExecutionException()).when(mockScheduledExecutor).schedule(Mockito.<Runnable>any(), Mockito.<TimeValue>any());
+
+ final String cacheKey = "blah";
+ impl.scheduleRevalidation(cacheKey, mockOperation);
+
+ Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
+ verify(mockScheduledExecutor).schedule(Mockito.<Runnable>any(), Mockito.eq(TimeValue.ofSeconds(2)));
+ }
+
+ @Test
+ public void testRevalidateCacheEntryProperlyCollapsesRequest() {
+ when(mockSchedulingStrategy.schedule(Mockito.anyInt())).thenReturn(TimeValue.ofSeconds(2));
+
+ final String cacheKey = "blah";
+ impl.scheduleRevalidation(cacheKey, mockOperation);
+ impl.scheduleRevalidation(cacheKey, mockOperation);
+ impl.scheduleRevalidation(cacheKey, mockOperation);
+
+ verify(mockSchedulingStrategy).schedule(Mockito.anyInt());
+ verify(mockScheduledExecutor).schedule(Mockito.<Runnable>any(), Mockito.eq(TimeValue.ofSeconds(2)));
+
+ Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
+ }
+
+ @Test
+ public void testStaleResponse() {
+ final HttpResponse response1 = new BasicHttpResponse(HttpStatus.SC_OK);
+ response1.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
+ Assert.assertThat(impl.isStale(response1), CoreMatchers.equalTo(true));
+
+ final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_OK);
+ response2.addHeader(HeaderConstants.WARNING, "111 localhost \"Revalidation failed\"");
+ Assert.assertThat(impl.isStale(response2), CoreMatchers.equalTo(true));
+
+ final HttpResponse response3 = new BasicHttpResponse(HttpStatus.SC_OK);
+ response3.addHeader(HeaderConstants.WARNING, "xxx localhost \"Huh?\"");
+ Assert.assertThat(impl.isStale(response3), CoreMatchers.equalTo(false));
+
+ final HttpResponse response4 = new BasicHttpResponse(HttpStatus.SC_OK);
+ Assert.assertThat(impl.isStale(response4), CoreMatchers.equalTo(false));
+ }
+
+ @Test
+ public void testShutdown() throws Exception {
+ impl.close();
+ impl.awaitTermination(Timeout.ofMinutes(2));
+
+ verify(mockScheduledExecutor).shutdown();
+ verify(mockScheduledExecutor).awaitTermination(Timeout.ofMinutes(2));
+ }
+
+}