2 * Copyright 2011 Google Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.google.ipc.invalidation.ticl;
19 import com.google.ipc.invalidation.external.client.SystemResources.Logger;
20 import com.google.ipc.invalidation.external.client.SystemResources.Scheduler;
21 import com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState;
22 import com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState;
23 import com.google.ipc.invalidation.util.ExponentialBackoffDelayGenerator;
24 import com.google.ipc.invalidation.util.InternalBase;
25 import com.google.ipc.invalidation.util.Marshallable;
26 import com.google.ipc.invalidation.util.NamedRunnable;
27 import com.google.ipc.invalidation.util.Preconditions;
28 import com.google.ipc.invalidation.util.Smearer;
29 import com.google.ipc.invalidation.util.TextBuilder;
33 * An abstraction for scheduling recurring tasks. Combines idempotent scheduling and smearing with
34 * conditional retries and exponential backoff. Does not implement throttling. Designed to support a
35 * variety of use cases, including:
38 * <li>Idempotent scheduling, e.g., ensuring that a batching task is scheduled exactly once.
39 * <li>Recurring tasks, e.g., periodic heartbeats.
40 * <li>Retriable actions aimed at state change, e.g., sending initialization messages.
42 * Each instance of this class manages the state for a single task. Examples:
45 * batchingTask = new RecurringTask("Batching", scheduler, logger, smearer, null,
46 * batchingDelayMs, NO_DELAY) {
48 * public boolean runTask() {
50 * return false; // don't reschedule.
53 * heartbeatTask = new RecurringTask("Heartbeat", scheduler, logger, smearer, null,
54 * heartbeatDelayMs, NO_DELAY) {
56 * public boolean runTask() {
57 * sendInfoMessageToServer(false, !registrationManager.isStateInSyncWithServer());
58 * return true; // reschedule
61 * initializeTask = new RecurringTask("Token", scheduler, logger, smearer, expDelayGen, NO_DELAY,
64 * public boolean runTask() {
65 * // If token is still not assigned (as expected), sends a request. Otherwise, ignore.
66 * if (clientToken == null) {
67 * // Allocate a nonce and send a message requesting a new token.
68 * setNonce(ByteString.copyFromUtf8(Long.toString(internalScheduler.getCurrentTimeMs())));
69 * protocolHandler.sendInitializeMessage(applicationClientId, nonce, debugString);
70 * return true; // reschedule to check state, retry if necessary after timeout
72 * return false; // don't reschedule
79 public abstract class RecurringTask extends InternalBase
80 implements Marshallable<RecurringTaskState> {
82 /** Name of the task (for debugging purposes mostly). */
83 private final String name;
86 private final Logger logger;
88 /** Scheduler for the scheduling the task as needed. */
89 private final Scheduler scheduler;
92 * The time after which the task is scheduled first. If no delayGenerator is specified, this is
93 * also the delay used for retries.
95 private final int initialDelayMs;
97 /** For a task that is retried, add this time to the delay. */
98 private final int timeoutDelayMs;
100 /** A smearer for spreading the delays. */
101 private final Smearer smearer;
103 /** A delay generator for exponential backoff. */
104 private final TiclExponentialBackoffDelayGenerator delayGenerator;
106 /** The runnable that is scheduled for the task. */
107 private final NamedRunnable runnable;
109 /** If the task has been currently scheduled. */
110 private boolean isScheduled;
113 * Creates a recurring task with the given parameters. The specs of the parameters are given in
114 * the instance variables.
116 * The created task is first scheduled with a smeared delay of {@code initialDelayMs}. If the
117 * {@code this.run()} returns true on its execution, the task is rescheduled after a
118 * {@code timeoutDelayMs} + smeared delay of {@code initialDelayMs} or {@code timeoutDelayMs} +
119 * {@code delayGenerator.getNextDelay()} depending on whether the {@code delayGenerator} is null
123 public RecurringTask(String name, Scheduler scheduler, Logger logger, Smearer smearer,
124 TiclExponentialBackoffDelayGenerator delayGenerator,
125 final int initialDelayMs, final int timeoutDelayMs) {
126 this.delayGenerator = delayGenerator;
127 this.name = Preconditions.checkNotNull(name);
128 this.logger = Preconditions.checkNotNull(logger);
129 this.scheduler = Preconditions.checkNotNull(scheduler);
130 this.smearer = Preconditions.checkNotNull(smearer);
131 this.initialDelayMs = initialDelayMs;
132 this.isScheduled = false;
133 this.timeoutDelayMs = timeoutDelayMs;
135 // Create a runnable that runs the task. If the task asks for a retry, reschedule it after
136 // at a timeout delay. Otherwise, resets the delayGenerator.
137 this.runnable = createRunnable();
141 * Creates a recurring task from {@code marshalledState}. Other parameters are as in the
144 RecurringTask(String name, Scheduler scheduler, Logger logger, Smearer smearer,
145 TiclExponentialBackoffDelayGenerator delayGenerator,
146 RecurringTaskState marshalledState) {
147 this(name, scheduler, logger, smearer, delayGenerator, marshalledState.getInitialDelayMs(),
148 marshalledState.getTimeoutDelayMs());
149 this.isScheduled = marshalledState.getScheduled();
152 private NamedRunnable createRunnable() {
153 return new NamedRunnable(name) {
156 Preconditions.checkState(scheduler.isRunningOnThread(), "Not on scheduler thread");
159 // The task asked to be rescheduled, so reschedule it after a timeout has occured.
160 Preconditions.checkState((delayGenerator != null) || (initialDelayMs != 0),
161 "Spinning: No exp back off and initialdelay is zero");
162 ensureScheduled(true, "Retry");
163 } else if (delayGenerator != null) {
164 // The task asked not to be rescheduled. Treat it as having "succeeded" and reset the
166 delayGenerator.reset();
173 * Run the task and return true if the task should be rescheduled after a timeout. If false is
174 * returned, the task is not scheduled again until {@code ensureScheduled} is called again.
176 public abstract boolean runTask();
178 /** Returns the smearer used for randomizing delays. */
179 Smearer getSmearer() {
183 /** Returns the delay generator, if any. */
184 ExponentialBackoffDelayGenerator getDelayGenerator() {
185 return delayGenerator;
189 * Ensures that the task is scheduled (with {@code debugReason} as the reason to be printed
190 * for debugging purposes). If the task has been scheduled, it is not scheduled again.
192 * REQUIRES: Must be called from the scheduler thread.
195 public void ensureScheduled(String debugReason) {
196 ensureScheduled(false, debugReason);
200 * Ensures that the task is scheduled if it is already not scheduled. If already scheduled, this
203 * @param isRetry If this is {@code false}, smears the {@code initialDelayMs} and uses that delay
204 * for scheduling. If {@code isRetry} is true, it determines the new delay to be
205 * {@code timeoutDelayMs} + {@ocde delayGenerator.getNextDelay()} if
206 * {@code delayGenerator} is non-null. If {@code delayGenerator} is null, schedules the
207 * task after a delay of {@code timeoutDelayMs} + smeared value of {@code initialDelayMs}
209 * REQUIRES: Must be called from the scheduler thread.
211 private void ensureScheduled(boolean isRetry, String debugReason) {
212 Preconditions.checkState(scheduler.isRunningOnThread());
219 // For a retried task, determine the delay to be timeout + extra delay (depending on whether
220 // a delay generator was provided or not).
221 if (delayGenerator != null) {
222 delayMs = timeoutDelayMs + delayGenerator.getNextDelay();
224 delayMs = timeoutDelayMs + smearer.getSmearedDelay(initialDelayMs);
227 delayMs = smearer.getSmearedDelay(initialDelayMs);
230 logger.fine("[%s] Scheduling %s with a delay %s, Now = %s", debugReason, name, delayMs,
231 scheduler.getCurrentTimeMs());
232 scheduler.schedule(delayMs, runnable);
236 /** For use only in the Android scheduler. */
237 public NamedRunnable getRunnable() {
242 public RecurringTaskState marshal() {
243 ExponentialBackoffState backoffState =
244 (delayGenerator == null) ? null : delayGenerator.marshal();
245 return RecurringTaskState.create(initialDelayMs, timeoutDelayMs, isScheduled, backoffState);
249 public void toCompactString(TextBuilder builder) {
250 builder.append("<RecurringTask: name=").append(name)
251 .append(", initialDelayMs=").append(initialDelayMs)
252 .append(", timeoutDelayMs=").append(timeoutDelayMs)
253 .append(", isScheduled=").append(isScheduled)