1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.jboss.netty.util;
17
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Set;
23 import java.util.concurrent.Executors;
24 import java.util.concurrent.ThreadFactory;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.atomic.AtomicBoolean;
27 import java.util.concurrent.atomic.AtomicInteger;
28 import java.util.concurrent.locks.ReadWriteLock;
29 import java.util.concurrent.locks.ReentrantReadWriteLock;
30
31 import org.jboss.netty.channel.ChannelPipelineFactory;
32 import org.jboss.netty.logging.InternalLogger;
33 import org.jboss.netty.logging.InternalLoggerFactory;
34 import org.jboss.netty.util.internal.ConcurrentIdentityHashMap;
35 import org.jboss.netty.util.internal.ReusableIterator;
36 import org.jboss.netty.util.internal.SharedResourceMisuseDetector;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85 public class HashedWheelTimer implements Timer {
86
87 static final InternalLogger logger =
88 InternalLoggerFactory.getInstance(HashedWheelTimer.class);
89 private static final AtomicInteger id = new AtomicInteger();
90
91 private static final SharedResourceMisuseDetector misuseDetector =
92 new SharedResourceMisuseDetector(HashedWheelTimer.class);
93
94 private final Worker worker = new Worker();
95 final Thread workerThread;
96 final AtomicBoolean shutdown = new AtomicBoolean();
97
98 private final long roundDuration;
99 final long tickDuration;
100 final Set<HashedWheelTimeout>[] wheel;
101 final ReusableIterator<HashedWheelTimeout>[] iterators;
102 final int mask;
103 final ReadWriteLock lock = new ReentrantReadWriteLock();
104 volatile int wheelCursor;
105
106
107
108
109
110
111 public HashedWheelTimer() {
112 this(Executors.defaultThreadFactory());
113 }
114
115
116
117
118
119
120
121
122
123 public HashedWheelTimer(long tickDuration, TimeUnit unit) {
124 this(Executors.defaultThreadFactory(), tickDuration, unit);
125 }
126
127
128
129
130
131
132
133
134
135 public HashedWheelTimer(long tickDuration, TimeUnit unit, int ticksPerWheel) {
136 this(Executors.defaultThreadFactory(), tickDuration, unit, ticksPerWheel);
137 }
138
139
140
141
142
143
144
145
146
147 public HashedWheelTimer(ThreadFactory threadFactory) {
148 this(threadFactory, 100, TimeUnit.MILLISECONDS);
149 }
150
151
152
153
154
155
156
157
158
159
160 public HashedWheelTimer(
161 ThreadFactory threadFactory, long tickDuration, TimeUnit unit) {
162 this(threadFactory, tickDuration, unit, 512);
163 }
164
165
166
167
168
169
170
171
172
173
174
175 public HashedWheelTimer(
176 ThreadFactory threadFactory,
177 long tickDuration, TimeUnit unit, int ticksPerWheel) {
178
179 if (threadFactory == null) {
180 throw new NullPointerException("threadFactory");
181 }
182 if (unit == null) {
183 throw new NullPointerException("unit");
184 }
185 if (tickDuration <= 0) {
186 throw new IllegalArgumentException(
187 "tickDuration must be greater than 0: " + tickDuration);
188 }
189 if (ticksPerWheel <= 0) {
190 throw new IllegalArgumentException(
191 "ticksPerWheel must be greater than 0: " + ticksPerWheel);
192 }
193
194
195 wheel = createWheel(ticksPerWheel);
196 iterators = createIterators(wheel);
197 mask = wheel.length - 1;
198
199
200 this.tickDuration = tickDuration = unit.toMillis(tickDuration);
201
202
203 if (tickDuration == Long.MAX_VALUE ||
204 tickDuration >= Long.MAX_VALUE / wheel.length) {
205 throw new IllegalArgumentException(
206 "tickDuration is too long: " +
207 tickDuration + ' ' + unit);
208 }
209
210 roundDuration = tickDuration * wheel.length;
211
212 workerThread = threadFactory.newThread(new ThreadRenamingRunnable(
213 worker, "Hashed wheel timer #" + id.incrementAndGet()));
214
215
216 misuseDetector.increase();
217 }
218
219 @SuppressWarnings("unchecked")
220 private static Set<HashedWheelTimeout>[] createWheel(int ticksPerWheel) {
221 if (ticksPerWheel <= 0) {
222 throw new IllegalArgumentException(
223 "ticksPerWheel must be greater than 0: " + ticksPerWheel);
224 }
225 if (ticksPerWheel > 1073741824) {
226 throw new IllegalArgumentException(
227 "ticksPerWheel may not be greater than 2^30: " + ticksPerWheel);
228 }
229
230 ticksPerWheel = normalizeTicksPerWheel(ticksPerWheel);
231 Set<HashedWheelTimeout>[] wheel = new Set[ticksPerWheel];
232 for (int i = 0; i < wheel.length; i ++) {
233 wheel[i] = new MapBackedSet<HashedWheelTimeout>(
234 new ConcurrentIdentityHashMap<HashedWheelTimeout, Boolean>(16, 0.95f, 4));
235 }
236 return wheel;
237 }
238
239 @SuppressWarnings("unchecked")
240 private static ReusableIterator<HashedWheelTimeout>[] createIterators(Set<HashedWheelTimeout>[] wheel) {
241 ReusableIterator<HashedWheelTimeout>[] iterators = new ReusableIterator[wheel.length];
242 for (int i = 0; i < wheel.length; i ++) {
243 iterators[i] = (ReusableIterator<HashedWheelTimeout>) wheel[i].iterator();
244 }
245 return iterators;
246 }
247
248 private static int normalizeTicksPerWheel(int ticksPerWheel) {
249 int normalizedTicksPerWheel = 1;
250 while (normalizedTicksPerWheel < ticksPerWheel) {
251 normalizedTicksPerWheel <<= 1;
252 }
253 return normalizedTicksPerWheel;
254 }
255
256
257
258
259
260
261
262
263 public synchronized void start() {
264 if (shutdown.get()) {
265 throw new IllegalStateException("cannot be started once stopped");
266 }
267
268 if (!workerThread.isAlive()) {
269 workerThread.start();
270 }
271 }
272
273 public synchronized Set<Timeout> stop() {
274 if (!shutdown.compareAndSet(false, true)) {
275 return Collections.emptySet();
276 }
277
278 boolean interrupted = false;
279 while (workerThread.isAlive()) {
280 workerThread.interrupt();
281 try {
282 workerThread.join(100);
283 } catch (InterruptedException e) {
284 interrupted = true;
285 }
286 }
287
288 if (interrupted) {
289 Thread.currentThread().interrupt();
290 }
291
292 misuseDetector.decrease();
293
294 Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>();
295 for (Set<HashedWheelTimeout> bucket: wheel) {
296 unprocessedTimeouts.addAll(bucket);
297 bucket.clear();
298 }
299
300 return Collections.unmodifiableSet(unprocessedTimeouts);
301 }
302
303 public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
304 final long currentTime = System.currentTimeMillis();
305
306 if (task == null) {
307 throw new NullPointerException("task");
308 }
309 if (unit == null) {
310 throw new NullPointerException("unit");
311 }
312
313 delay = unit.toMillis(delay);
314 if (delay < tickDuration) {
315 delay = tickDuration;
316 }
317
318 if (!workerThread.isAlive()) {
319 start();
320 }
321
322
323 HashedWheelTimeout timeout;
324 final long lastRoundDelay = delay % roundDuration;
325 final long lastTickDelay = delay % tickDuration;
326 final long relativeIndex =
327 lastRoundDelay / tickDuration + (lastTickDelay != 0? 1 : 0);
328 final long deadline = currentTime + delay;
329
330 final long remainingRounds =
331 delay / roundDuration - (delay % roundDuration == 0? 1 : 0);
332
333
334 lock.readLock().lock();
335 try {
336 timeout =
337 new HashedWheelTimeout(
338 task, deadline,
339 (int) (wheelCursor + relativeIndex & mask),
340 remainingRounds);
341
342 wheel[timeout.stopIndex].add(timeout);
343 } finally {
344 lock.readLock().unlock();
345 }
346
347 return timeout;
348 }
349
350 private final class Worker implements Runnable {
351
352 private long startTime;
353 private long tick;
354
355 Worker() {
356 super();
357 }
358
359 public void run() {
360 List<HashedWheelTimeout> expiredTimeouts =
361 new ArrayList<HashedWheelTimeout>();
362
363 startTime = System.currentTimeMillis();
364 tick = 1;
365
366 while (!shutdown.get()) {
367 waitForNextTick();
368 fetchExpiredTimeouts(expiredTimeouts);
369 notifyExpiredTimeouts(expiredTimeouts);
370 }
371 }
372
373 private void fetchExpiredTimeouts(
374 List<HashedWheelTimeout> expiredTimeouts) {
375
376
377
378
379
380 lock.writeLock().lock();
381 try {
382 int oldBucketHead = wheelCursor;
383 int newBucketHead = oldBucketHead + 1 & mask;
384 wheelCursor = newBucketHead;
385
386 ReusableIterator<HashedWheelTimeout> i = iterators[oldBucketHead];
387 fetchExpiredTimeouts(expiredTimeouts, i);
388 } finally {
389 lock.writeLock().unlock();
390 }
391 }
392
393 private void fetchExpiredTimeouts(
394 List<HashedWheelTimeout> expiredTimeouts,
395 ReusableIterator<HashedWheelTimeout> i) {
396
397 long currentDeadline = System.currentTimeMillis() + tickDuration;
398 i.rewind();
399 while (i.hasNext()) {
400 HashedWheelTimeout timeout = i.next();
401 if (timeout.remainingRounds <= 0) {
402 if (timeout.deadline < currentDeadline) {
403 i.remove();
404 expiredTimeouts.add(timeout);
405 } else {
406
407
408 }
409 } else {
410 timeout.remainingRounds --;
411 }
412 }
413 }
414
415 private void notifyExpiredTimeouts(
416 List<HashedWheelTimeout> expiredTimeouts) {
417
418 for (int i = expiredTimeouts.size() - 1; i >= 0; i --) {
419 expiredTimeouts.get(i).expire();
420 }
421
422
423 expiredTimeouts.clear();
424 }
425
426 private void waitForNextTick() {
427 for (;;) {
428 final long currentTime = System.currentTimeMillis();
429 final long sleepTime = tickDuration * tick - (currentTime - startTime);
430
431 if (sleepTime <= 0) {
432 break;
433 }
434
435 try {
436 Thread.sleep(sleepTime);
437 } catch (InterruptedException e) {
438 if (shutdown.get()) {
439 return;
440 }
441 }
442 }
443
444
445 if (tickDuration * tick > Long.MAX_VALUE - tickDuration) {
446 startTime = System.currentTimeMillis();
447 tick = 1;
448 } else {
449
450 tick ++;
451 }
452 }
453 }
454
455 private final class HashedWheelTimeout implements Timeout {
456
457 private final TimerTask task;
458 final int stopIndex;
459 final long deadline;
460 volatile long remainingRounds;
461 private volatile boolean cancelled;
462
463 HashedWheelTimeout(
464 TimerTask task, long deadline, int stopIndex, long remainingRounds) {
465 this.task = task;
466 this.deadline = deadline;
467 this.stopIndex = stopIndex;
468 this.remainingRounds = remainingRounds;
469 }
470
471 public Timer getTimer() {
472 return HashedWheelTimer.this;
473 }
474
475 public TimerTask getTask() {
476 return task;
477 }
478
479 public void cancel() {
480 if (isExpired()) {
481 return;
482 }
483
484 cancelled = true;
485
486
487 wheel[stopIndex].remove(this);
488 }
489
490 public boolean isCancelled() {
491 return cancelled;
492 }
493
494 public boolean isExpired() {
495 return cancelled || System.currentTimeMillis() > deadline;
496 }
497
498 public void expire() {
499 if (cancelled) {
500 return;
501 }
502
503 try {
504 task.run(this);
505 } catch (Throwable t) {
506 logger.warn(
507 "An exception was thrown by " +
508 TimerTask.class.getSimpleName() + ".", t);
509 }
510 }
511
512 @Override
513 public String toString() {
514 long currentTime = System.currentTimeMillis();
515 long remaining = deadline - currentTime;
516
517 StringBuilder buf = new StringBuilder(192);
518 buf.append(getClass().getSimpleName());
519 buf.append('(');
520
521 buf.append("deadline: ");
522 if (remaining > 0) {
523 buf.append(remaining);
524 buf.append(" ms later, ");
525 } else if (remaining < 0) {
526 buf.append(-remaining);
527 buf.append(" ms ago, ");
528 } else {
529 buf.append("now, ");
530 }
531
532 if (isCancelled()) {
533 buf.append (", cancelled");
534 }
535
536 return buf.append(')').toString();
537 }
538 }
539 }