View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.http.impl.client.cache;
28  
29  import org.apache.http.annotation.ThreadSafe;
30  
31  import java.util.concurrent.ScheduledExecutorService;
32  import java.util.concurrent.ScheduledThreadPoolExecutor;
33  import java.util.concurrent.TimeUnit;
34  
35  /**
36   * An implementation that backs off exponentially based on the number of
37   * consecutive failed attempts stored in the
38   * {@link AsynchronousValidationRequest}. It uses the following defaults:
39   * <pre>
40   *         no delay in case it was never tried or didn't fail so far
41   *     6 secs delay for one failed attempt (= {@link #getInitialExpiryInMillis()})
42   *    60 secs delay for two failed attempts
43   *    10 mins delay for three failed attempts
44   *   100 mins delay for four failed attempts
45   *  ~16 hours delay for five failed attempts
46   *   24 hours delay for six or more failed attempts (= {@link #getMaxExpiryInMillis()})
47   * </pre>
48   *
49   * The following equation is used to calculate the delay for a specific revalidation request:
50   * <pre>
51   *     delay = {@link #getInitialExpiryInMillis()} * Math.pow({@link #getBackOffRate()}, {@link AsynchronousValidationRequest#getConsecutiveFailedAttempts()} - 1))
52   * </pre>
53   * The resulting delay won't exceed {@link #getMaxExpiryInMillis()}.
54   *
55   * @since 4.3
56   */
57  @ThreadSafe
58  public class ExponentialBackOffSchedulingStrategy implements SchedulingStrategy {
59  
60      public static final long DEFAULT_BACK_OFF_RATE = 10;
61      public static final long DEFAULT_INITIAL_EXPIRY_IN_MILLIS = TimeUnit.SECONDS.toMillis(6);
62      public static final long DEFAULT_MAX_EXPIRY_IN_MILLIS = TimeUnit.SECONDS.toMillis(86400);
63  
64      private final long backOffRate;
65      private final long initialExpiryInMillis;
66      private final long maxExpiryInMillis;
67  
68      private final ScheduledExecutorService executor;
69  
70      /**
71       * Create a new scheduling strategy using a fixed pool of worker threads.
72       * @param cacheConfig the thread pool configuration to be used; not <code>null</code>
73       * @see org.apache.http.impl.client.cache.CacheConfig#getAsynchronousWorkersMax()
74       * @see #DEFAULT_BACK_OFF_RATE
75       * @see #DEFAULT_INITIAL_EXPIRY_IN_MILLIS
76       * @see #DEFAULT_MAX_EXPIRY_IN_MILLIS
77       */
78      public ExponentialBackOffSchedulingStrategy(final CacheConfig cacheConfig) {
79          this(cacheConfig,
80                  DEFAULT_BACK_OFF_RATE,
81                  DEFAULT_INITIAL_EXPIRY_IN_MILLIS,
82                  DEFAULT_MAX_EXPIRY_IN_MILLIS);
83      }
84  
85      /**
86       * Create a new scheduling strategy by using a fixed pool of worker threads and the
87       * given parameters to calculated the delay.
88       *
89       * @param cacheConfig the thread pool configuration to be used; not <code>null</code>
90       * @param backOffRate the back off rate to be used; not negative
91       * @param initialExpiryInMillis the initial expiry in milli seconds; not negative
92       * @param maxExpiryInMillis the upper limit of the delay in milli seconds; not negative
93       * @see org.apache.http.impl.client.cache.CacheConfig#getAsynchronousWorkersMax()
94       * @see ExponentialBackOffSchedulingStrategy
95       */
96      public ExponentialBackOffSchedulingStrategy(
97              final CacheConfig cacheConfig,
98              final long backOffRate,
99              final long initialExpiryInMillis,
100             final long maxExpiryInMillis) {
101         this(createThreadPoolFromCacheConfig(cacheConfig),
102                 backOffRate,
103                 initialExpiryInMillis,
104                 maxExpiryInMillis);
105     }
106 
107     private static ScheduledThreadPoolExecutor createThreadPoolFromCacheConfig(
108             final CacheConfig cacheConfig) {
109         final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(
110                 cacheConfig.getAsynchronousWorkersMax());
111         scheduledThreadPoolExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
112         return scheduledThreadPoolExecutor;
113     }
114 
115     ExponentialBackOffSchedulingStrategy(
116             final ScheduledExecutorService executor,
117             final long backOffRate,
118             final long initialExpiryInMillis,
119             final long maxExpiryInMillis) {
120         this.executor = checkNotNull("executor", executor);
121         this.backOffRate = checkNotNegative("backOffRate", backOffRate);
122         this.initialExpiryInMillis = checkNotNegative("initialExpiryInMillis", initialExpiryInMillis);
123         this.maxExpiryInMillis = checkNotNegative("maxExpiryInMillis", maxExpiryInMillis);
124     }
125 
126     public void schedule(
127             final AsynchronousValidationRequest revalidationRequest) {
128         checkNotNull("revalidationRequest", revalidationRequest);
129         final int consecutiveFailedAttempts = revalidationRequest.getConsecutiveFailedAttempts();
130         final long delayInMillis = calculateDelayInMillis(consecutiveFailedAttempts);
131         executor.schedule(revalidationRequest, delayInMillis, TimeUnit.MILLISECONDS);
132     }
133 
134     public void close() {
135         executor.shutdown();
136     }
137 
138     public long getBackOffRate() {
139         return backOffRate;
140     }
141 
142     public long getInitialExpiryInMillis() {
143         return initialExpiryInMillis;
144     }
145 
146     public long getMaxExpiryInMillis() {
147         return maxExpiryInMillis;
148     }
149 
150     protected long calculateDelayInMillis(final int consecutiveFailedAttempts) {
151         if (consecutiveFailedAttempts > 0) {
152             final long delayInSeconds = (long) (initialExpiryInMillis *
153                     Math.pow(backOffRate, consecutiveFailedAttempts - 1));
154             return Math.min(delayInSeconds, maxExpiryInMillis);
155         }
156         else {
157             return 0;
158         }
159     }
160 
161     protected static <T> T checkNotNull(final String parameterName, final T value) {
162         if (value == null) {
163             throw new IllegalArgumentException(parameterName + " may not be null");
164         }
165         return value;
166     }
167 
168     protected static long checkNotNegative(final String parameterName, final long value) {
169         if (value < 0) {
170             throw new IllegalArgumentException(parameterName + " may not be negative");
171         }
172         return value;
173     }
174 }