1 /*
2  * Copyright (c) 1997, 2021, 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      */
TimerQueue()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 
sharedInstance()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     @SuppressWarnings("removal")
startIfNeeded()94     void startIfNeeded() {
95         if (! running) {
96             runningLock.lock();
97             if (running) {
98                 return;
99             }
100             try {
101                 final ThreadGroup threadGroup = AppContext.getAppContext().getThreadGroup();
102                 AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
103                     String name = "TimerQueue";
104                     Thread timerThread =
105                         new Thread(threadGroup, this, name, 0, false);
106                     timerThread.setDaemon(true);
107                     timerThread.setPriority(Thread.NORM_PRIORITY);
108                     timerThread.start();
109                     return null;
110                 });
111                 running = true;
112             } finally {
113                 runningLock.unlock();
114             }
115         }
116     }
117 
addTimer(Timer timer, long delayMillis)118     void addTimer(Timer timer, long delayMillis) {
119         timer.getLock().lock();
120         try {
121             // If the Timer is already in the queue, then ignore the add.
122             if (! containsTimer(timer)) {
123                 addTimer(new DelayedTimer(timer,
124                                       TimeUnit.MILLISECONDS.toNanos(delayMillis)
125                                       + now()));
126             }
127         } finally {
128             timer.getLock().unlock();
129         }
130     }
131 
addTimer(DelayedTimer delayedTimer)132     private void addTimer(DelayedTimer delayedTimer) {
133         assert delayedTimer != null && ! containsTimer(delayedTimer.getTimer());
134 
135         Timer timer = delayedTimer.getTimer();
136         timer.getLock().lock();
137         try {
138             timer.delayedTimer = delayedTimer;
139             queue.add(delayedTimer);
140         } finally {
141             timer.getLock().unlock();
142         }
143     }
144 
removeTimer(Timer timer)145     void removeTimer(Timer timer) {
146         timer.getLock().lock();
147         try {
148             if (timer.delayedTimer != null) {
149                 queue.remove(timer.delayedTimer);
150                 timer.delayedTimer = null;
151             }
152         } finally {
153             timer.getLock().unlock();
154         }
155     }
156 
containsTimer(Timer timer)157     boolean containsTimer(Timer timer) {
158         timer.getLock().lock();
159         try {
160             return timer.delayedTimer != null;
161         } finally {
162             timer.getLock().unlock();
163         }
164     }
165 
166 
run()167     public void run() {
168         runningLock.lock();
169         try {
170             while (running) {
171                 try {
172                     DelayedTimer runningTimer = queue.take();
173                     Timer timer = runningTimer.getTimer();
174                     timer.getLock().lock();
175                     try {
176                         DelayedTimer delayedTimer = timer.delayedTimer;
177                         if (delayedTimer == runningTimer) {
178                             /*
179                              * Timer is not removed (delayedTimer != null)
180                              * or not removed and added (runningTimer == delayedTimer)
181                              * after we get it from the queue and before the
182                              * lock on the timer is acquired
183                              */
184                             timer.post(); // have timer post an event
185                             timer.delayedTimer = null;
186                             if (timer.isRepeats()) {
187                                 delayedTimer.setTime(now()
188                                     + TimeUnit.MILLISECONDS.toNanos(
189                                           timer.getDelay()));
190                                 addTimer(delayedTimer);
191                             }
192                         }
193 
194                         // Allow run other threads on systems without kernel threads
195                         timer.getLock().newCondition().awaitNanos(1);
196                     } catch (SecurityException ignore) {
197                     } finally {
198                         timer.getLock().unlock();
199                     }
200                 } catch (InterruptedException ie) {
201                     // Shouldn't ignore InterruptedExceptions here, so AppContext
202                     // is disposed gracefully, see 6799345 for details
203                     if (AppContext.getAppContext().isDisposed()) {
204                         break;
205                     }
206                 }
207             }
208         }
209         catch (ThreadDeath td) {
210             // Mark all the timers we contain as not being queued.
211             for (DelayedTimer delayedTimer : queue) {
212                 delayedTimer.getTimer().cancelEvent();
213             }
214             throw td;
215         } finally {
216             running = false;
217             runningLock.unlock();
218         }
219     }
220 
221 
toString()222     public String toString() {
223         StringBuilder buf = new StringBuilder();
224         buf.append("TimerQueue (");
225         boolean isFirst = true;
226         for (DelayedTimer delayedTimer : queue) {
227             if (! isFirst) {
228                 buf.append(", ");
229             }
230             buf.append(delayedTimer.getTimer().toString());
231             isFirst = false;
232         }
233         buf.append(")");
234         return buf.toString();
235     }
236 
237     /**
238      * Returns nanosecond time offset by origin
239      */
now()240     private static long now() {
241         return System.nanoTime() - NANO_ORIGIN;
242     }
243 
244     static class DelayedTimer implements Delayed {
245         // most of it copied from
246         // java.util.concurrent.ScheduledThreadPoolExecutor
247 
248         /**
249          * Sequence number to break scheduling ties, and in turn to
250          * guarantee FIFO order among tied entries.
251          */
252         private static final AtomicLong sequencer = new AtomicLong();
253 
254         /** Sequence number to break ties FIFO */
255         private final long sequenceNumber;
256 
257 
258         /** The time the task is enabled to execute in nanoTime units */
259         private volatile long time;
260 
261         private final Timer timer;
262 
DelayedTimer(Timer timer, long nanos)263         DelayedTimer(Timer timer, long nanos) {
264             this.timer = timer;
265             time = nanos;
266             sequenceNumber = sequencer.getAndIncrement();
267         }
268 
269 
getDelay(TimeUnit unit)270         public final long getDelay(TimeUnit unit) {
271             return  unit.convert(time - now(), TimeUnit.NANOSECONDS);
272         }
273 
setTime(long nanos)274         final void setTime(long nanos) {
275             time = nanos;
276         }
277 
getTimer()278         final Timer getTimer() {
279             return timer;
280         }
281 
compareTo(Delayed other)282         public int compareTo(Delayed other) {
283             if (other == this) { // compare zero ONLY if same object
284                 return 0;
285             }
286             if (other instanceof DelayedTimer) {
287                 DelayedTimer x = (DelayedTimer)other;
288                 long diff = time - x.time;
289                 if (diff < 0) {
290                     return -1;
291                 } else if (diff > 0) {
292                     return 1;
293                 } else if (sequenceNumber < x.sequenceNumber) {
294                     return -1;
295                 }  else {
296                     return 1;
297                 }
298             }
299             long d = (getDelay(TimeUnit.NANOSECONDS) -
300                       other.getDelay(TimeUnit.NANOSECONDS));
301             return (d == 0) ? 0 : ((d < 0) ? -1 : 1);
302         }
303     }
304 }
305