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 java.util.concurrent.ScheduledExecutorService;
30  import java.util.concurrent.ScheduledThreadPoolExecutor;
31  import java.util.concurrent.TimeUnit;
32  
33  import org.apache.http.annotation.Contract;
34  import org.apache.http.annotation.ThreadingBehavior;
35  import org.apache.http.util.Args;
36  
37  /**
38   * An implementation that backs off exponentially based on the number of
39   * consecutive failed attempts stored in the
40   * {@link AsynchronousValidationRequest}. It uses the following defaults:
41   * <pre>
42   *         no delay in case it was never tried or didn't fail so far
43   *     6 secs delay for one failed attempt (= {@link #getInitialExpiryInMillis()})
44   *    60 secs delay for two failed attempts
45   *    10 mins delay for three failed attempts
46   *   100 mins delay for four failed attempts
47   *  ~16 hours delay for five failed attempts
48   *   24 hours delay for six or more failed attempts (= {@link #getMaxExpiryInMillis()})
49   * </pre>
50   *
51   * The following equation is used to calculate the delay for a specific revalidation request:
52   * <pre>
53   *     delay = {@link #getInitialExpiryInMillis()} * Math.pow({@link #getBackOffRate()},
54   *     {@link AsynchronousValidationRequest#getConsecutiveFailedAttempts()} - 1))
55   * </pre>
56   * The resulting delay won't exceed {@link #getMaxExpiryInMillis()}.
57   *
58   * @since 4.3
59   */
60  @Contract(threading = ThreadingBehavior.SAFE)
61  public class ExponentialBackOffSchedulingStrategy implements SchedulingStrategy {
62  
63      public static final long DEFAULT_BACK_OFF_RATE = 10;
64      public static final long DEFAULT_INITIAL_EXPIRY_IN_MILLIS = TimeUnit.SECONDS.toMillis(6);
65      public static final long DEFAULT_MAX_EXPIRY_IN_MILLIS = TimeUnit.SECONDS.toMillis(86400);
66  
67      private final long backOffRate;
68      private final long initialExpiryInMillis;
69      private final long maxExpiryInMillis;
70  
71      private final ScheduledExecutorService executor;
72  
73      /**
74       * Create a new scheduling strategy using a fixed pool of worker threads.
75       * @param cacheConfig the thread pool configuration to be used; not {@code null}
76       * @see org.apache.http.impl.client.cache.CacheConfig#getAsynchronousWorkersMax()
77       * @see #DEFAULT_BACK_OFF_RATE
78       * @see #DEFAULT_INITIAL_EXPIRY_IN_MILLIS
79       * @see #DEFAULT_MAX_EXPIRY_IN_MILLIS
80       */
81      public ExponentialBackOffSchedulingStrategy(final CacheConfig cacheConfig) {
82          this(cacheConfig,
83                  DEFAULT_BACK_OFF_RATE,
84                  DEFAULT_INITIAL_EXPIRY_IN_MILLIS,
85                  DEFAULT_MAX_EXPIRY_IN_MILLIS);
86      }
87  
88      /**
89       * Create a new scheduling strategy by using a fixed pool of worker threads and the
90       * given parameters to calculated the delay.
91       *
92       * @param cacheConfig the thread pool configuration to be used; not {@code null}
93       * @param backOffRate the back off rate to be used; not negative
94       * @param initialExpiryInMillis the initial expiry in milli seconds; not negative
95       * @param maxExpiryInMillis the upper limit of the delay in milli seconds; not negative
96       * @see org.apache.http.impl.client.cache.CacheConfig#getAsynchronousWorkersMax()
97       * @see ExponentialBackOffSchedulingStrategy
98       */
99      public ExponentialBackOffSchedulingStrategy(
100             final CacheConfig cacheConfig,
101             final long backOffRate,
102             final long initialExpiryInMillis,
103             final long maxExpiryInMillis) {
104         this(createThreadPoolFromCacheConfig(cacheConfig),
105                 backOffRate,
106                 initialExpiryInMillis,
107                 maxExpiryInMillis);
108     }
109 
110     private static ScheduledThreadPoolExecutor createThreadPoolFromCacheConfig(
111             final CacheConfig cacheConfig) {
112         final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(
113                 cacheConfig.getAsynchronousWorkersMax());
114         scheduledThreadPoolExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
115         return scheduledThreadPoolExecutor;
116     }
117 
118     ExponentialBackOffSchedulingStrategy(
119             final ScheduledExecutorService executor,
120             final long backOffRate,
121             final long initialExpiryInMillis,
122             final long maxExpiryInMillis) {
123         this.executor = Args.notNull(executor, "Executor");
124         this.backOffRate = Args.notNegative(backOffRate, "BackOffRate");
125         this.initialExpiryInMillis = Args.notNegative(initialExpiryInMillis, "InitialExpiryInMillis");
126         this.maxExpiryInMillis = Args.notNegative(maxExpiryInMillis, "MaxExpiryInMillis");
127     }
128 
129     @Override
130     public void schedule(
131             final AsynchronousValidationRequest revalidationRequest) {
132         Args.notNull(revalidationRequest, "RevalidationRequest");
133         final int consecutiveFailedAttempts = revalidationRequest.getConsecutiveFailedAttempts();
134         final long delayInMillis = calculateDelayInMillis(consecutiveFailedAttempts);
135         executor.schedule(revalidationRequest, delayInMillis, TimeUnit.MILLISECONDS);
136     }
137 
138     @Override
139     public void close() {
140         executor.shutdown();
141     }
142 
143     public long getBackOffRate() {
144         return backOffRate;
145     }
146 
147     public long getInitialExpiryInMillis() {
148         return initialExpiryInMillis;
149     }
150 
151     public long getMaxExpiryInMillis() {
152         return maxExpiryInMillis;
153     }
154 
155     protected long calculateDelayInMillis(final int consecutiveFailedAttempts) {
156         if (consecutiveFailedAttempts > 0) {
157             final long delayInSeconds = (long) (initialExpiryInMillis *
158                     Math.pow(backOffRate, consecutiveFailedAttempts - 1));
159             return Math.min(delayInSeconds, maxExpiryInMillis);
160         }
161         else {
162             return 0;
163         }
164     }
165 
166     /**
167      * @deprecated Use {@link org.apache.http.util.Args#notNull(Object, String)}
168      */
169     @Deprecated
170     protected static <T> T checkNotNull(final String parameterName, final T value) {
171         if (value == null) {
172             throw new IllegalArgumentException(parameterName + " may not be null");
173         }
174         return value;
175     }
176 
177     /**
178      * @deprecated Use {@link org.apache.http.util.Args#notNegative(long, String)}
179      */
180     @Deprecated
181     protected static long checkNotNegative(final String parameterName, final long value) {
182         if (value < 0) {
183             throw new IllegalArgumentException(parameterName + " may not be negative");
184         }
185         return value;
186     }
187 }