| OLD | NEW |
| (Empty) | |
| 1 /* |
| 2 * Copyright 2011 Google Inc. |
| 3 * |
| 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 |
| 7 * |
| 8 * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 * |
| 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. |
| 15 */ |
| 16 |
| 17 package com.google.ipc.invalidation.ticl; |
| 18 |
| 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; |
| 30 |
| 31 |
| 32 /** |
| 33 * An abstraction for scheduling recurring tasks. Combines idempotent scheduling
and smearing with |
| 34 * conditional retries and exponential backoff. Does not implement throttling. D
esigned to support a |
| 35 * variety of use cases, including: |
| 36 * |
| 37 * <ul> |
| 38 * <li>Idempotent scheduling, e.g., ensuring that a batching task is scheduled e
xactly once. |
| 39 * <li>Recurring tasks, e.g., periodic heartbeats. |
| 40 * <li>Retriable actions aimed at state change, e.g., sending initialization mes
sages. |
| 41 * </ul> |
| 42 * Each instance of this class manages the state for a single task. Examples: |
| 43 * |
| 44 * <pre> |
| 45 * batchingTask = new RecurringTask("Batching", scheduler, logger, smearer, null
, |
| 46 * batchingDelayMs, NO_DELAY) { |
| 47 * @Override |
| 48 * public boolean runTask() { |
| 49 * throttle.fire(); |
| 50 * return false; // don't reschedule. |
| 51 * } |
| 52 * }; |
| 53 * heartbeatTask = new RecurringTask("Heartbeat", scheduler, logger, smearer, nu
ll, |
| 54 * heartbeatDelayMs, NO_DELAY) { |
| 55 * @Override |
| 56 * public boolean runTask() { |
| 57 * sendInfoMessageToServer(false, !registrationManager.isStateInSyncWithServe
r()); |
| 58 * return true; // reschedule |
| 59 * } |
| 60 * }; |
| 61 * initializeTask = new RecurringTask("Token", scheduler, logger, smearer, expDe
layGen, NO_DELAY, |
| 62 * networkTimeoutMs) { |
| 63 * @Override |
| 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.getCurre
ntTimeMs()))); |
| 69 * protocolHandler.sendInitializeMessage(applicationClientId, nonce, debugSt
ring); |
| 70 * return true; // reschedule to check state, retry if necessary after time
out |
| 71 * } else { |
| 72 * return false; // don't reschedule |
| 73 * } |
| 74 * } |
| 75 * }; |
| 76 *</pre> |
| 77 * |
| 78 */ |
| 79 public abstract class RecurringTask extends InternalBase |
| 80 implements Marshallable<RecurringTaskState> { |
| 81 |
| 82 /** Name of the task (for debugging purposes mostly). */ |
| 83 private final String name; |
| 84 |
| 85 /** A logger */ |
| 86 private final Logger logger; |
| 87 |
| 88 /** Scheduler for the scheduling the task as needed. */ |
| 89 private final Scheduler scheduler; |
| 90 |
| 91 /** |
| 92 * The time after which the task is scheduled first. If no delayGenerator is s
pecified, this is |
| 93 * also the delay used for retries. |
| 94 */ |
| 95 private final int initialDelayMs; |
| 96 |
| 97 /** For a task that is retried, add this time to the delay. */ |
| 98 private final int timeoutDelayMs; |
| 99 |
| 100 /** A smearer for spreading the delays. */ |
| 101 private final Smearer smearer; |
| 102 |
| 103 /** A delay generator for exponential backoff. */ |
| 104 private final TiclExponentialBackoffDelayGenerator delayGenerator; |
| 105 |
| 106 /** The runnable that is scheduled for the task. */ |
| 107 private final NamedRunnable runnable; |
| 108 |
| 109 /** If the task has been currently scheduled. */ |
| 110 private boolean isScheduled; |
| 111 |
| 112 /** |
| 113 * Creates a recurring task with the given parameters. The specs of the parame
ters are given in |
| 114 * the instance variables. |
| 115 * <p> |
| 116 * The created task is first scheduled with a smeared delay of {@code initialD
elayMs}. If the |
| 117 * {@code this.run()} returns true on its execution, the task is rescheduled a
fter a |
| 118 * {@code timeoutDelayMs} + smeared delay of {@code initialDelayMs} or {@code
timeoutDelayMs} + |
| 119 * {@code delayGenerator.getNextDelay()} depending on whether the {@code delay
Generator} is null |
| 120 * or not. |
| 121 */ |
| 122 |
| 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; |
| 134 |
| 135 // Create a runnable that runs the task. If the task asks for a retry, resch
edule it after |
| 136 // at a timeout delay. Otherwise, resets the delayGenerator. |
| 137 this.runnable = createRunnable(); |
| 138 } |
| 139 |
| 140 /** |
| 141 * Creates a recurring task from {@code marshalledState}. Other parameters are
as in the |
| 142 * constructor above. |
| 143 */ |
| 144 RecurringTask(String name, Scheduler scheduler, Logger logger, Smearer smearer
, |
| 145 TiclExponentialBackoffDelayGenerator delayGenerator, |
| 146 RecurringTaskState marshalledState) { |
| 147 this(name, scheduler, logger, smearer, delayGenerator, marshalledState.getIn
itialDelayMs(), |
| 148 marshalledState.getTimeoutDelayMs()); |
| 149 this.isScheduled = marshalledState.getScheduled(); |
| 150 } |
| 151 |
| 152 private NamedRunnable createRunnable() { |
| 153 return new NamedRunnable(name) { |
| 154 @Override |
| 155 public void run() { |
| 156 Preconditions.checkState(scheduler.isRunningOnThread(), "Not on schedule
r thread"); |
| 157 isScheduled = false; |
| 158 if (runTask()) { |
| 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 "succeede
d" and reset the |
| 165 // delay generator. |
| 166 delayGenerator.reset(); |
| 167 } |
| 168 } |
| 169 }; |
| 170 } |
| 171 |
| 172 /** |
| 173 * Run the task and return true if the task should be rescheduled after a time
out. If false is |
| 174 * returned, the task is not scheduled again until {@code ensureScheduled} is
called again. |
| 175 */ |
| 176 public abstract boolean runTask(); |
| 177 |
| 178 /** Returns the smearer used for randomizing delays. */ |
| 179 Smearer getSmearer() { |
| 180 return smearer; |
| 181 } |
| 182 |
| 183 /** Returns the delay generator, if any. */ |
| 184 ExponentialBackoffDelayGenerator getDelayGenerator() { |
| 185 return delayGenerator; |
| 186 } |
| 187 |
| 188 /** |
| 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 schedule
d again. |
| 191 * <p> |
| 192 * REQUIRES: Must be called from the scheduler thread. |
| 193 */ |
| 194 |
| 195 public void ensureScheduled(String debugReason) { |
| 196 ensureScheduled(false, debugReason); |
| 197 } |
| 198 |
| 199 /** |
| 200 * Ensures that the task is scheduled if it is already not scheduled. If alrea
dy scheduled, this |
| 201 * method is a no-op. |
| 202 * |
| 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 de
lay to be |
| 205 * {@code timeoutDelayMs} + {@ocde delayGenerator.getNextDelay()} if |
| 206 * {@code delayGenerator} is non-null. If {@code delayGenerator} is nul
l, schedules the |
| 207 * task after a delay of {@code timeoutDelayMs} + smeared value of {@co
de initialDelayMs} |
| 208 * <p> |
| 209 * REQUIRES: Must be called from the scheduler thread. |
| 210 */ |
| 211 private void ensureScheduled(boolean isRetry, String debugReason) { |
| 212 Preconditions.checkState(scheduler.isRunningOnThread()); |
| 213 if (isScheduled) { |
| 214 return; |
| 215 } |
| 216 final int delayMs; |
| 217 |
| 218 if (isRetry) { |
| 219 // For a retried task, determine the delay to be timeout + extra delay (de
pending on whether |
| 220 // a delay generator was provided or not). |
| 221 if (delayGenerator != null) { |
| 222 delayMs = timeoutDelayMs + delayGenerator.getNextDelay(); |
| 223 } else { |
| 224 delayMs = timeoutDelayMs + smearer.getSmearedDelay(initialDelayMs); |
| 225 } |
| 226 } else { |
| 227 delayMs = smearer.getSmearedDelay(initialDelayMs); |
| 228 } |
| 229 |
| 230 logger.fine("[%s] Scheduling %s with a delay %s, Now = %s", debugReason, nam
e, delayMs, |
| 231 scheduler.getCurrentTimeMs()); |
| 232 scheduler.schedule(delayMs, runnable); |
| 233 isScheduled = true; |
| 234 } |
| 235 |
| 236 /** For use only in the Android scheduler. */ |
| 237 public NamedRunnable getRunnable() { |
| 238 return runnable; |
| 239 } |
| 240 |
| 241 @Override |
| 242 public RecurringTaskState marshal() { |
| 243 ExponentialBackoffState backoffState = |
| 244 (delayGenerator == null) ? null : delayGenerator.marshal(); |
| 245 return RecurringTaskState.create(initialDelayMs, timeoutDelayMs, isScheduled
, backoffState); |
| 246 } |
| 247 |
| 248 @Override |
| 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) |
| 254 .append(">"); |
| 255 } |
| 256 } |
| OLD | NEW |