1 /*
   2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 
  27 
  28 package javax.swing;
  29 
  30 
  31 
  32 import java.security.AccessController;
  33 import java.security.PrivilegedAction;
  34 import java.util.*;
  35 import java.util.concurrent.*;
  36 import java.util.concurrent.locks.*;
  37 import java.util.concurrent.atomic.AtomicLong;
  38 import sun.awt.AppContext;
  39 
  40 /**
  41  * Internal class to manage all Timers using one thread.
  42  * TimerQueue manages a queue of Timers. The Timers are chained
  43  * together in a linked list sorted by the order in which they will expire.
  44  *
  45  * @author Dave Moore
  46  * @author Igor Kushnirskiy
  47  */
  48 class TimerQueue implements Runnable
  49 {
  50     private static final Object sharedInstanceKey =
  51         new StringBuffer("TimerQueue.sharedInstanceKey");
  52     private static final Object expiredTimersKey =
  53         new StringBuffer("TimerQueue.expiredTimersKey");
  54 
  55     private final DelayQueue<DelayedTimer> queue;
  56     private volatile boolean running;
  57     private final Lock runningLock;
  58 
  59     /* Lock object used in place of class object for synchronization.
  60      * (4187686)
  61      */
  62     private static final Object classLock = new Object();
  63 
  64     /** Base of nanosecond timings, to avoid wrapping */
  65     private static final long NANO_ORIGIN = System.nanoTime();
  66 
  67     /**
  68      * Constructor for TimerQueue.
  69      */
  70     public TimerQueue() {
  71         super();
  72         queue = new DelayQueue<DelayedTimer>();
  73         // Now start the TimerQueue thread.
  74         runningLock = new ReentrantLock();
  75         startIfNeeded();
  76     }
  77 
  78 
  79     public static TimerQueue sharedInstance() {
  80         synchronized (classLock) {
  81             TimerQueue sharedInst = (TimerQueue)
  82                                     SwingUtilities.appContextGet(
  83                                                         sharedInstanceKey);
  84             if (sharedInst == null) {
  85                 sharedInst = new TimerQueue();
  86                 SwingUtilities.appContextPut(sharedInstanceKey, sharedInst);
  87             }
  88             return sharedInst;
  89         }
  90     }
  91 
  92 
  93     void startIfNeeded() {
  94         if (! running) {
  95             runningLock.lock();
  96             if (running) {
  97                 return;
  98             }
  99             try {
 100                 final ThreadGroup threadGroup = AppContext.getAppContext().getThreadGroup();
 101                 AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
 102                     String name = "TimerQueue";
 103                     Thread timerThread =
 104                         new Thread(threadGroup, this, name, 0, false);
 105                     timerThread.setDaemon(true);
 106                     timerThread.setPriority(Thread.NORM_PRIORITY);
 107                     timerThread.start();
 108                     return null;
 109                 });
 110                 running = true;
 111             } finally {
 112                 runningLock.unlock();
 113             }
 114         }
 115     }
 116 
 117     void addTimer(Timer timer, long delayMillis) {
 118         timer.getLock().lock();
 119         try {
 120             // If the Timer is already in the queue, then ignore the add.
 121             if (! containsTimer(timer)) {
 122                 addTimer(new DelayedTimer(timer,
 123                                       TimeUnit.MILLISECONDS.toNanos(delayMillis)
 124                                       + now()));
 125             }
 126         } finally {
 127             timer.getLock().unlock();
 128         }
 129     }
 130 
 131     private void addTimer(DelayedTimer delayedTimer) {
 132         assert delayedTimer != null && ! containsTimer(delayedTimer.getTimer());
 133 
 134         Timer timer = delayedTimer.getTimer();
 135         timer.getLock().lock();
 136         try {
 137             timer.delayedTimer = delayedTimer;
 138             queue.add(delayedTimer);
 139         } finally {
 140             timer.getLock().unlock();
 141         }
 142     }
 143 
 144     void removeTimer(Timer timer) {
 145         timer.getLock().lock();
 146         try {
 147             if (timer.delayedTimer != null) {
 148                 queue.remove(timer.delayedTimer);
 149                 timer.delayedTimer = null;
 150             }
 151         } finally {
 152             timer.getLock().unlock();
 153         }
 154     }
 155 
 156     boolean containsTimer(Timer timer) {
 157         timer.getLock().lock();
 158         try {
 159             return timer.delayedTimer != null;
 160         } finally {
 161             timer.getLock().unlock();
 162         }
 163     }
 164 
 165 
 166     public void run() {
 167         runningLock.lock();
 168         try {
 169             while (running) {
 170                 try {
 171                     DelayedTimer runningTimer = queue.take();
 172                     Timer timer = runningTimer.getTimer();
 173                     timer.getLock().lock();
 174                     try {
 175                         DelayedTimer delayedTimer = timer.delayedTimer;
 176                         if (delayedTimer == runningTimer) {
 177                             /*
 178                              * Timer is not removed (delayedTimer != null)
 179                              * or not removed and added (runningTimer == delayedTimer)
 180                              * after we get it from the queue and before the
 181                              * lock on the timer is acquired
 182                              */
 183                             timer.post(); // have timer post an event
 184                             timer.delayedTimer = null;
 185                             if (timer.isRepeats()) {
 186                                 delayedTimer.setTime(now()
 187                                     + TimeUnit.MILLISECONDS.toNanos(
 188                                           timer.getDelay()));
 189                                 addTimer(delayedTimer);
 190                             }
 191                         }
 192 
 193                         // Allow run other threads on systems without kernel threads
 194                         timer.getLock().newCondition().awaitNanos(1);
 195                     } catch (SecurityException ignore) {
 196                     } finally {
 197                         timer.getLock().unlock();
 198                     }
 199                 } catch (InterruptedException ie) {
 200                     // Shouldn't ignore InterruptedExceptions here, so AppContext
 201                     // is disposed gracefully, see 6799345 for details
 202                     if (AppContext.getAppContext().isDisposed()) {
 203                         break;
 204                     }
 205                 }
 206             }
 207         }
 208         catch (ThreadDeath td) {
 209             // Mark all the timers we contain as not being queued.
 210             for (DelayedTimer delayedTimer : queue) {
 211                 delayedTimer.getTimer().cancelEvent();
 212             }
 213             throw td;
 214         } finally {
 215             running = false;
 216             runningLock.unlock();
 217         }
 218     }
 219 
 220 
 221     public String toString() {
 222         StringBuilder buf = new StringBuilder();
 223         buf.append("TimerQueue (");
 224         boolean isFirst = true;
 225         for (DelayedTimer delayedTimer : queue) {
 226             if (! isFirst) {
 227                 buf.append(", ");
 228             }
 229             buf.append(delayedTimer.getTimer().toString());
 230             isFirst = false;
 231         }
 232         buf.append(")");
 233         return buf.toString();
 234     }
 235 
 236     /**
 237      * Returns nanosecond time offset by origin
 238      */
 239     private static long now() {
 240         return System.nanoTime() - NANO_ORIGIN;
 241     }
 242 
 243     static class DelayedTimer implements Delayed {
 244         // most of it copied from
 245         // java.util.concurrent.ScheduledThreadPoolExecutor
 246 
 247         /**
 248          * Sequence number to break scheduling ties, and in turn to
 249          * guarantee FIFO order among tied entries.
 250          */
 251         private static final AtomicLong sequencer = new AtomicLong(0);
 252 
 253         /** Sequence number to break ties FIFO */
 254         private final long sequenceNumber;
 255 
 256 
 257         /** The time the task is enabled to execute in nanoTime units */
 258         private volatile long time;
 259 
 260         private final Timer timer;
 261 
 262         DelayedTimer(Timer timer, long nanos) {
 263             this.timer = timer;
 264             time = nanos;
 265             sequenceNumber = sequencer.getAndIncrement();
 266         }
 267 
 268 
 269         public final long getDelay(TimeUnit unit) {
 270             return  unit.convert(time - now(), TimeUnit.NANOSECONDS);
 271         }
 272 
 273         final void setTime(long nanos) {
 274             time = nanos;
 275         }
 276 
 277         final Timer getTimer() {
 278             return timer;
 279         }
 280 
 281         public int compareTo(Delayed other) {
 282             if (other == this) { // compare zero ONLY if same object
 283                 return 0;
 284             }
 285             if (other instanceof DelayedTimer) {
 286                 DelayedTimer x = (DelayedTimer)other;
 287                 long diff = time - x.time;
 288                 if (diff < 0) {
 289                     return -1;
 290                 } else if (diff > 0) {
 291                     return 1;
 292                 } else if (sequenceNumber < x.sequenceNumber) {
 293                     return -1;
 294                 }  else {
 295                     return 1;
 296                 }
 297             }
 298             long d = (getDelay(TimeUnit.NANOSECONDS) -
 299                       other.getDelay(TimeUnit.NANOSECONDS));
 300             return (d == 0) ? 0 : ((d < 0) ? -1 : 1);
 301         }
 302     }
 303 }