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, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19 package org.apache.hadoop.hbase;
20
21 import java.util.concurrent.ScheduledThreadPoolExecutor;
22 import java.util.concurrent.TimeUnit;
23
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import org.apache.hadoop.hbase.classification.InterfaceAudience;
27 import org.apache.hadoop.hbase.classification.InterfaceStability;
28
29 /**
30 * ScheduledChore is a task performed on a period in hbase. ScheduledChores become active once
31 * scheduled with a {@link ChoreService} via {@link ChoreService#scheduleChore(ScheduledChore)}. The
32 * chore is run in a {@link ScheduledThreadPoolExecutor} and competes with other ScheduledChores for
33 * access to the threads in the core thread pool. If an unhandled exception occurs, the chore
34 * cancellation is logged. Implementers should consider whether or not the Chore will be able to
35 * execute within the defined period. It is bad practice to define a ScheduledChore whose execution
36 * time exceeds its period since it will try to hog one of the threads in the {@link ChoreService}'s
37 * thread pool.
38 * <p>
39 * Don't subclass ScheduledChore if the task relies on being woken up for something to do, such as
40 * an entry being added to a queue, etc.
41 */
42 @InterfaceAudience.Public
43 @InterfaceStability.Stable
44 public abstract class ScheduledChore implements Runnable {
45 private static final Log LOG = LogFactory.getLog(ScheduledChore.class);
46
47 private final String name;
48
49 /**
50 * Default values for scheduling parameters should they be excluded during construction
51 */
52 private final static TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;
53 private final static long DEFAULT_INITIAL_DELAY = 0;
54
55 /**
56 * Scheduling parameters. Used by ChoreService when scheduling the chore to run periodically
57 */
58 private final int period; // in TimeUnit units
59 private final TimeUnit timeUnit;
60 private final long initialDelay; // in TimeUnit units
61
62 /**
63 * Interface to the ChoreService that this ScheduledChore is scheduled with. null if the chore is
64 * not scheduled.
65 */
66 private ChoreServicer choreServicer;
67
68 /**
69 * Variables that encapsulate the meaningful state information
70 */
71 private long timeOfLastRun = -1; // system time millis
72 private long timeOfThisRun = -1; // system time millis
73 private boolean initialChoreComplete = false;
74
75 /**
76 * A means by which a ScheduledChore can be stopped. Once a chore recognizes that it has been
77 * stopped, it will cancel itself. This is particularly useful in the case where a single stopper
78 * instance is given to multiple chores. In such a case, a single {@link Stoppable#stop(String)}
79 * command can cause many chores to stop together.
80 */
81 private final Stoppable stopper;
82
83 interface ChoreServicer {
84 /**
85 * Cancel any ongoing schedules that this chore has with the implementer of this interface.
86 */
87 public void cancelChore(ScheduledChore chore);
88 public void cancelChore(ScheduledChore chore, boolean mayInterruptIfRunning);
89
90 /**
91 * @return true when the chore is scheduled with the implementer of this interface
92 */
93 public boolean isChoreScheduled(ScheduledChore chore);
94
95 /**
96 * This method tries to execute the chore immediately. If the chore is executing at the time of
97 * this call, the chore will begin another execution as soon as the current execution finishes
98 * <p>
99 * If the chore is not scheduled with a ChoreService, this call will fail.
100 * @return false when the chore could not be triggered immediately
101 */
102 public boolean triggerNow(ScheduledChore chore);
103
104 /**
105 * A callback that tells the implementer of this interface that one of the scheduled chores is
106 * missing its start time. The implication of a chore missing its start time is that the
107 * service's current means of scheduling may not be sufficient to handle the number of ongoing
108 * chores (the other explanation is that the chore's execution time is greater than its
109 * scheduled period). The service should try to increase its concurrency when this callback is
110 * received.
111 * @param chore The chore that missed its start time
112 */
113 public void onChoreMissedStartTime(ScheduledChore chore);
114 }
115
116 /**
117 * This constructor is for test only. It allows us to create an object and to call chore() on it.
118 */
119 @InterfaceAudience.Private
120 protected ScheduledChore() {
121 this.name = null;
122 this.stopper = null;
123 this.period = 0;
124 this.initialDelay = DEFAULT_INITIAL_DELAY;
125 this.timeUnit = DEFAULT_TIME_UNIT;
126 }
127
128 /**
129 * @param name Name assigned to Chore. Useful for identification amongst chores of the same type
130 * @param stopper When {@link Stoppable#isStopped()} is true, this chore will cancel and cleanup
131 * @param period Period in millis with which this Chore repeats execution when scheduled.
132 */
133 public ScheduledChore(final String name, Stoppable stopper, final int period) {
134 this(name, stopper, period, DEFAULT_INITIAL_DELAY);
135 }
136
137 /**
138 * @param name Name assigned to Chore. Useful for identification amongst chores of the same type
139 * @param stopper When {@link Stoppable#isStopped()} is true, this chore will cancel and cleanup
140 * @param period Period in millis with which this Chore repeats execution when scheduled.
141 * @param initialDelay Delay before this Chore begins to execute once it has been scheduled. A
142 * value of 0 means the chore will begin to execute immediately. Negative delays are
143 * invalid and will be corrected to a value of 0.
144 */
145 public ScheduledChore(final String name, Stoppable stopper, final int period,
146 final long initialDelay) {
147 this(name, stopper, period, initialDelay, DEFAULT_TIME_UNIT);
148 }
149
150 /**
151 * @param name Name assigned to Chore. Useful for identification amongst chores of the same type
152 * @param stopper When {@link Stoppable#isStopped()} is true, this chore will cancel and cleanup
153 * @param period Period in Timeunit unit with which this Chore repeats execution when scheduled.
154 * @param initialDelay Delay in Timeunit unit before this Chore begins to execute once it has been
155 * scheduled. A value of 0 means the chore will begin to execute immediately. Negative
156 * delays are invalid and will be corrected to a value of 0.
157 * @param unit The unit that is used to measure period and initialDelay
158 */
159 public ScheduledChore(final String name, Stoppable stopper, final int period,
160 final long initialDelay, final TimeUnit unit) {
161 this.name = name;
162 this.stopper = stopper;
163 this.period = period;
164 this.initialDelay = initialDelay < 0 ? 0 : initialDelay;
165 this.timeUnit = unit;
166 }
167
168 /**
169 * @see java.lang.Runnable#run()
170 */
171 @Override
172 public void run() {
173 updateTimeTrackingBeforeRun();
174 if (missedStartTime() && isScheduled()) {
175 onChoreMissedStartTime();
176 if (LOG.isInfoEnabled()) LOG.info("Chore: " + getName() + " missed its start time");
177 } else if (stopper.isStopped() || !isScheduled()) {
178 cancel(false);
179 cleanup();
180 if (LOG.isInfoEnabled()) LOG.info("Chore: " + getName() + " was stopped");
181 } else {
182 try {
183 if (!initialChoreComplete) {
184 initialChoreComplete = initialChore();
185 } else {
186 chore();
187 }
188 } catch (Throwable t) {
189 if (LOG.isErrorEnabled()) LOG.error("Caught error", t);
190 if (this.stopper.isStopped()) {
191 cancel(false);
192 cleanup();
193 }
194 }
195 }
196 }
197
198 /**
199 * Update our time tracking members. Called at the start of an execution of this chore's run()
200 * method so that a correct decision can be made as to whether or not we missed the start time
201 */
202 private synchronized void updateTimeTrackingBeforeRun() {
203 timeOfLastRun = timeOfThisRun;
204 timeOfThisRun = System.currentTimeMillis();
205 }
206
207 /**
208 * Notify the ChoreService that this chore has missed its start time. Allows the ChoreService to
209 * make the decision as to whether or not it would be worthwhile to increase the number of core
210 * pool threads
211 */
212 private synchronized void onChoreMissedStartTime() {
213 if (choreServicer != null) choreServicer.onChoreMissedStartTime(this);
214 }
215
216 /**
217 * @return How long in millis has it been since this chore last run. Useful for checking if the
218 * chore has missed its scheduled start time by too large of a margin
219 */
220 synchronized long getTimeBetweenRuns() {
221 return timeOfThisRun - timeOfLastRun;
222 }
223
224 /**
225 * @return true when the time between runs exceeds the acceptable threshold
226 */
227 private synchronized boolean missedStartTime() {
228 return isValidTime(timeOfLastRun) && isValidTime(timeOfThisRun)
229 && getTimeBetweenRuns() > getMaximumAllowedTimeBetweenRuns();
230 }
231
232 /**
233 * @return max allowed time in millis between runs.
234 */
235 private double getMaximumAllowedTimeBetweenRuns() {
236 // Threshold used to determine if the Chore's current run started too late
237 return 1.5 * timeUnit.toMillis(period);
238 }
239
240 /**
241 * @param time in system millis
242 * @return true if time is earlier or equal to current milli time
243 */
244 private synchronized boolean isValidTime(final long time) {
245 return time > 0 && time <= System.currentTimeMillis();
246 }
247
248 /**
249 * @return false when the Chore is not currently scheduled with a ChoreService
250 */
251 public synchronized boolean triggerNow() {
252 if (choreServicer != null) {
253 return choreServicer.triggerNow(this);
254 } else {
255 return false;
256 }
257 }
258
259 synchronized void setChoreServicer(ChoreServicer service) {
260 // Chores should only ever be scheduled with a single ChoreService. If the choreServicer
261 // is changing, cancel any existing schedules of this chore.
262 if (choreServicer != null && choreServicer != service) {
263 choreServicer.cancelChore(this, false);
264 }
265 choreServicer = service;
266 timeOfThisRun = System.currentTimeMillis();
267 }
268
269 public synchronized void cancel() {
270 cancel(true);
271 }
272
273 public synchronized void cancel(boolean mayInterruptIfRunning) {
274 if (isScheduled()) choreServicer.cancelChore(this, mayInterruptIfRunning);
275
276 choreServicer = null;
277 }
278
279 public String getName() {
280 return name;
281 }
282
283 public Stoppable getStopper() {
284 return stopper;
285 }
286
287 /**
288 * @return period to execute chore in getTimeUnit() units
289 */
290 public int getPeriod() {
291 return period;
292 }
293
294 /**
295 * @return initial delay before executing chore in getTimeUnit() units
296 */
297 public long getInitialDelay() {
298 return initialDelay;
299 }
300
301 public TimeUnit getTimeUnit() {
302 return timeUnit;
303 }
304
305 public synchronized boolean isInitialChoreComplete() {
306 return initialChoreComplete;
307 }
308
309 @InterfaceAudience.Private
310 synchronized ChoreServicer getChoreServicer() {
311 return choreServicer;
312 }
313
314 @InterfaceAudience.Private
315 synchronized long getTimeOfLastRun() {
316 return timeOfLastRun;
317 }
318
319 @InterfaceAudience.Private
320 synchronized long getTimeOfThisRun() {
321 return timeOfThisRun;
322 }
323
324 /**
325 * @return true when this Chore is scheduled with a ChoreService
326 */
327 public synchronized boolean isScheduled() {
328 return choreServicer != null && choreServicer.isChoreScheduled(this);
329 }
330
331 @InterfaceAudience.Private
332 public synchronized void choreForTesting() {
333 chore();
334 }
335
336 /**
337 * The task to execute on each scheduled execution of the Chore
338 */
339 protected abstract void chore();
340
341 /**
342 * Override to run a task before we start looping.
343 * @return true if initial chore was successful
344 */
345 protected boolean initialChore() {
346 // Default does nothing
347 return true;
348 }
349
350 /**
351 * Override to run cleanup tasks when the Chore encounters an error and must stop running
352 */
353 protected synchronized void cleanup() {
354 }
355
356 /**
357 * A summation of this chore in human readable format. Downstream users should not presume
358 * parsing of this string can relaibly be done between versions. Instead, they should rely
359 * on the public accessor methods to get the information they desire.
360 */
361 @InterfaceAudience.Private
362 @Override
363 public String toString() {
364 return "[ScheduledChore: Name: " + getName() + " Period: " + getPeriod() + " Unit: "
365 + getTimeUnit() + "]";
366 }
367 }