001package org.cache2k.impl.timer; 002 003/* 004 * #%L 005 * cache2k core package 006 * %% 007 * Copyright (C) 2000 - 2015 headissue GmbH, Munich 008 * %% 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as 011 * published by the Free Software Foundation, either version 3 of the 012 * License, or (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public 020 * License along with this program. If not, see 021 * <http://www.gnu.org/licenses/gpl-3.0.html>. 022 * #L% 023 */ 024 025import org.cache2k.impl.util.Log; 026 027/** 028 * Timer queue based on the {@link org.cache2k.impl.Entry#nextRefreshTime} field. 029 * Earlier implementations used {@link java.util.Timer} which has some 030 * disadvantages: per cache entry two additional objects are created, 031 * the task and the lock object. Per one timer a thread is needed. To 032 * insert a timer task with exact time value a java.util.Date object is 033 * needed. A purge/cleanup of cancelled tasks is not done automatically. 034 * 035 * <p/>In situations with scarce memory, a lot of evictions may occur, which 036 * causes a lot of timer cancellations. This is the reason why this timer implementation 037 * does a purge of cancelled tasks automatically in a regular interval. 038 * During the purge is done, timer events still can run in parallel. Only the 039 * new timer events will be held up until the purge ist finished. 040 * 041 * <p/>Remark: Yes, this class is a little over engineered. After three make 042 * overs it finally works as expected... 043 * 044 * @author Jens Wilke; created: 2014-03-21 045 */ 046public class ArrayHeapTimerQueue extends TimerService { 047 048 final static int MAXIMUM_ADDS_UNTIL_PURGE = 54321; 049 final static int LAPSE_RESOLUTION = 10; 050 final static int LAPSE_SIZE = 50; 051 final static long KEEP_THREAD_WAITING_MILLIS = 17 * 1000; 052 053 Log log; 054 055 int[] lapse = new int[LAPSE_SIZE]; 056 long maxLapse = 0; 057 058 final Object lock = new Object(); 059 060 MyThread thread; 061 062 String threadName; 063 064 long eventsScheduled; 065 066 long eventsDelivered; 067 068 long wakeupCount; 069 070 int addedWithoutPurge; 071 072 int purgeCount; 073 074 /** 075 * Protection: Only update within thread or when thread is stopped. 076 */ 077 long cancelCount; 078 079 long fireExceptionCount; 080 081 Queue inQueue = new Queue(); 082 Queue outQueue = inQueue; 083 084 public ArrayHeapTimerQueue(String _threadName) { 085 threadName = _threadName; 086 log = Log.getLog(ArrayHeapTimerQueue.class.getName() + ":" + _threadName); 087 } 088 089 public NoPayloadTask add(TimerListener _listener, long _fireTime) { 090 NoPayloadTask e2 = new NoPayloadTask(_fireTime, _listener); 091 addTimerEvent(e2); 092 return e2; 093 } 094 095 public <T> PayloadTask<T> add(TimerPayloadListener<T> _listener, T _payload, long _fireTime) { 096 PayloadTask<T> e2 = new PayloadTask<T>(_fireTime, _payload, _listener); 097 addTimerEvent(e2); 098 return e2; 099 } 100 101 @Override 102 public int getQueueSize() { 103 Queue q = inQueue; 104 if (q == outQueue) { 105 return q.size; 106 } 107 synchronized (lock) { 108 return inQueue.size + outQueue.size; 109 } 110 } 111 112 @Override 113 public long getEventsDelivered() { 114 return eventsDelivered; 115 } 116 117 @Override 118 public long getPurgeCount() { 119 return purgeCount; 120 } 121 122 @Override 123 public long getEventsScheduled() { 124 return eventsScheduled; 125 } 126 127 @Override 128 public long getCancelCount() { 129 synchronized (lock) { 130 MyThread th = thread; 131 if (th != null) { 132 return cancelCount + inQueue.cancelCount + th.cancelCount; 133 } 134 return cancelCount + inQueue.cancelCount; 135 } 136 } 137 138 @Override 139 public long getFireExceptionCount() { 140 return fireExceptionCount; 141 } 142 143 void addTimerEvent(BaseTimerTask e) { 144 synchronized(lock) { 145 startThread(); 146 inQueue.addQueue(e); 147 eventsScheduled++; 148 addedWithoutPurge++; 149 if (outQueue.getMin() == e) { 150 lock.notify(); 151 } 152 } 153 if (addedWithoutPurge > MAXIMUM_ADDS_UNTIL_PURGE) { 154 performPurge(); 155 } 156 } 157 158 private void startThread() { 159 if (thread == null) { 160 thread = new MyThread(); 161 thread.setName(threadName); 162 thread.setDaemon(true); 163 thread.timer = this; 164 thread.start(); 165 } 166 } 167 168 /** 169 * Cleans cancelled timer tasks from the priority queue. We do 170 * this just in the thread that calls us, but without holding up 171 * event delivery . When a purge occurs we work with two queues. 172 * One for added timer tasks and one for delivered 173 * timer tasks. Newly added timer tasks will be appended to the 174 * the out queue after the purge is finished. Thus, the delivery of 175 * new timer tasks is delayed after the purge is finished. 176 * 177 * <p/>One tricky thing is to catch up with tasks that fired during 178 * the purge from the out queue. This is achieved by skipping to the 179 * time of the task that will fire next and by the ensurance that the 180 * timer thread will always process events of the same time within 181 * one synchronized block. 182 */ 183 void performPurge() { 184 Queue _purgeQ; 185 int _queueSizeBefore; 186 synchronized (lock) { 187 if (inQueue != outQueue || inQueue.size == 0) { 188 return; 189 } 190 addedWithoutPurge = Integer.MIN_VALUE; 191 cancelCount += inQueue.cancelCount; 192 outQueue.cancelCount = 0; 193 _purgeQ = outQueue.copy(); 194 inQueue = new Queue(); 195 _queueSizeBefore = outQueue.size; 196 } 197 _purgeQ.purge(); 198 synchronized (lock) { 199 boolean _somethingFired = outQueue.size != _queueSizeBefore; 200 if (_somethingFired) { 201 BaseTimerTask _nextTimerTaskInQueue = outQueue.getNextNotCancelledMin(); 202 if (_nextTimerTaskInQueue == null) { 203 204 cancelCount += outQueue.cancelCount; 205 _purgeQ = new Queue(); 206 } else { 207 long _forwardUntilTime = _nextTimerTaskInQueue.getTime(); 208 BaseTimerTask t; 209 BaseTimerTask _previousSkippedTask = null; 210 while ((t = _purgeQ.getMin()).getTime() != _forwardUntilTime) { 211 if (t.isCancelled()) { 212 _purgeQ.cancelCount++; 213 } 214 _purgeQ.removeMin(); 215 _previousSkippedTask = t; 216 } 217 } 218 } 219 BaseTimerTask t; 220 while ((t = inQueue.getNextNotCancelledMin()) != null) { 221 inQueue.removeMin(); 222 _purgeQ.addQueue(t); 223 } 224 cancelCount += inQueue.cancelCount; 225 inQueue = outQueue = _purgeQ; 226 addedWithoutPurge = 0; 227 if (inQueue.size > 0) { 228 startThread(); 229 lock.notify(); 230 } 231 purgeCount++; 232 } 233 } 234 235 static class MyThread extends Thread { 236 237 ArrayHeapTimerQueue timer; 238 long cancelCount; 239 240 void fireTask(BaseTimerTask e) { 241 try { 242 boolean f = e.fire(e.getTime()); 243 if (f) { 244 timer.eventsDelivered++; 245 } else { 246 cancelCount++; 247 } 248 } catch (Throwable ex) { 249 timer.fireExceptionCount++; 250 timer.log.warn("timer event caused exception", ex); 251 } 252 } 253 254 int loop() { 255 long _fireTime; 256 long t = System.currentTimeMillis(); 257 BaseTimerTask e; 258 int fireCnt = 0; 259 for (;;) { 260 boolean _fires; 261 synchronized (timer.lock) { 262 Queue q = timer.outQueue; 263 for (;;) { 264 e = q.getNextNotCancelledMin(); 265 if (e == null) { 266 return fireCnt; 267 } 268 _fires = false; 269 _fireTime = e.getTime(); 270 if (_fireTime > t) { 271 t = System.currentTimeMillis(); 272 } 273 if (_fireTime <= t) { 274 if (_fireTime < t) { // missed timely execution? 275 recordLapse(_fireTime, t); 276 } 277 q.removeMin(); 278 BaseTimerTask _next = q.getMin(); 279 boolean _fireWithinLock = _next != null && _fireTime == _next.getTime(); 280 if (_fireWithinLock) { 281 fireTask(e); 282 fireCnt++; 283 continue; 284 } 285 _fires = true; 286 } 287 break; 288 } // inner for loop 289 } // synchronized 290 if (_fires) { 291 fireTask(e); 292 fireCnt++; 293 } else { 294 long _waitTime = _fireTime - t; 295 waitUntilTimeout(_waitTime); 296 } 297 } // outer loop 298 } 299 300 private void recordLapse(long _nextEvent, long now) { 301 long d = now - _nextEvent; 302 if (timer.maxLapse < d) { 303 timer.maxLapse = d; 304 } 305 int idx = (int) (d / LAPSE_RESOLUTION); 306 int[] ia = timer.lapse; 307 if (idx < 0 || idx >= ia.length) { 308 idx = ia.length - 1; 309 } 310 ia[idx]++; 311 } 312 313 private void waitUntilTimeout(long _waitTime) { 314 try { 315 synchronized (timer.lock) { 316 timer.lock.wait(_waitTime); 317 timer.wakeupCount++; 318 } 319 } catch (InterruptedException ex) { 320 } 321 } 322 323 @Override 324 public void run() { 325 try { 326 while (loop() > 0) { 327 waitUntilTimeout(KEEP_THREAD_WAITING_MILLIS); 328 } 329 timer.thread = null; 330 timer.cancelCount += cancelCount; 331 } catch (Throwable t) { 332 t.printStackTrace(); 333 } 334 } 335 336 } 337 338 static class Queue { 339 340 /** 341 * Array to implement a priority queue based on a binary heap. The code 342 * is inspired by {@link java.util.Timer}. Another implementation 343 * is within the algorithms collection from princeton. 344 * 345 * @see <a href="http://algs4.cs.princeton.edu/24pq/"></a> 346 * @see <a href="http://algs4.cs.princeton.edu/24pq/MaxPQ.java.html"></a> 347 */ 348 BaseTimerTask[] queue = new BaseTimerTask[128]; 349 int size = 0; 350 long cancelCount = 0; 351 352 void addQueue(BaseTimerTask e) { 353 size++; 354 if (size >= queue.length) { 355 BaseTimerTask[] q2 = new BaseTimerTask[queue.length * 2]; 356 System.arraycopy(queue, 0, q2, 0, queue.length); 357 queue = q2; 358 } 359 queue[size] = e; 360 swim(size); 361 } 362 363 BaseTimerTask getMin() { 364 return queue[1]; 365 } 366 367 368 void removeMin() { 369 queue[1] = queue[size]; 370 queue[size--] = null; 371 sink(1); 372 } 373 374 BaseTimerTask getNextNotCancelledMin() { 375 BaseTimerTask e = getMin(); 376 while (e != null && e.isCancelled()) { 377 removeMin(); 378 cancelCount++; 379 e = getMin(); 380 } 381 return e; 382 } 383 384 Queue copy() { 385 Queue q = new Queue(); 386 q.size = size; 387 q.queue = new BaseTimerTask[queue.length]; 388 System.arraycopy(queue, 0, q.queue, 0, queue.length); 389 return q; 390 } 391 392 void purge() { 393 BaseTimerTask[] q = queue; 394 int _size = size; 395 for (int i = 1; i <= _size; ) { 396 if (q[i].isCancelled()) { 397 cancelCount++; 398 q[i] = q[_size]; 399 q[_size] = null; 400 _size--; 401 } else { 402 i++; 403 } 404 } 405 size = _size; 406 for (int i = size / 2; i >= 1; i--) { 407 sink(i); 408 } 409 } 410 411 private static void swap(BaseTimerTask[] q, int j, int k) { 412 BaseTimerTask tmp = q[j]; 413 q[j] = q[k]; 414 q[k] = tmp; 415 } 416 417 /** 418 * Let entry at position k move up the heap hierarchy if time is less. 419 */ 420 private void swim(int k) { 421 BaseTimerTask[] q = queue; 422 while (k > 1) { 423 int j = k >> 1; 424 if (q[j].time <= q[k].time) { 425 break; 426 } 427 swap(q, j, k); 428 k = j; 429 } 430 } 431 432 /** 433 * Push the entry at position k down the heap hierarchy until the 434 * the right position. 435 */ 436 private void sink(int k) { 437 BaseTimerTask[] q = queue; 438 int j; 439 while ((j = k << 1) < size) { 440 int _rightChild = j + 1; 441 if (q[j].time > q[_rightChild].time) { 442 j = _rightChild; 443 } 444 if (q[k].time <= q[j].time) { 445 return; 446 } 447 swap(q, j, k); 448 k = j; 449 } 450 if (j == size && 451 q[k].time > q[j].time) { 452 swap(q, j, k); 453 } 454 } 455 456 } 457 458}