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}