View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  
20  package org.eclipse.jetty.util.thread;
21  
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collections;
26  import java.util.List;
27  import java.util.concurrent.BlockingQueue;
28  import java.util.concurrent.RejectedExecutionException;
29  import java.util.concurrent.TimeUnit;
30  import java.util.concurrent.atomic.AtomicInteger;
31  import java.util.concurrent.atomic.AtomicLong;
32  
33  import org.eclipse.jetty.util.BlockingArrayQueue;
34  import org.eclipse.jetty.util.ConcurrentHashSet;
35  import org.eclipse.jetty.util.annotation.ManagedAttribute;
36  import org.eclipse.jetty.util.annotation.ManagedObject;
37  import org.eclipse.jetty.util.annotation.ManagedOperation;
38  import org.eclipse.jetty.util.annotation.Name;
39  import org.eclipse.jetty.util.component.AbstractLifeCycle;
40  import org.eclipse.jetty.util.component.ContainerLifeCycle;
41  import org.eclipse.jetty.util.component.Dumpable;
42  import org.eclipse.jetty.util.component.LifeCycle;
43  import org.eclipse.jetty.util.log.Log;
44  import org.eclipse.jetty.util.log.Logger;
45  import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
46  
47  @ManagedObject("A thread pool")
48  public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPool, Dumpable
49  {
50      private static final Logger LOG = Log.getLogger(QueuedThreadPool.class);
51  
52      private final AtomicInteger _threadsStarted = new AtomicInteger();
53      private final AtomicInteger _threadsIdle = new AtomicInteger();
54      private final AtomicLong _lastShrink = new AtomicLong();
55      private final ConcurrentHashSet<Thread> _threads=new ConcurrentHashSet<>();
56      private final Object _joinLock = new Object();
57      private final BlockingQueue<Runnable> _jobs;
58      private final ThreadGroup _threadGroup;
59      private String _name = "qtp" + hashCode();
60      private int _idleTimeout;
61      private int _maxThreads;
62      private int _minThreads;
63      private int _priority = Thread.NORM_PRIORITY;
64      private boolean _daemon = false;
65      private boolean _detailedDump = false;
66      private int _lowThreadsThreshold = 1;
67  
68      public QueuedThreadPool()
69      {
70          this(200);
71      }
72  
73      public QueuedThreadPool(@Name("maxThreads") int maxThreads)
74      {
75          this(maxThreads, 8);
76      }
77  
78      public QueuedThreadPool(@Name("maxThreads") int maxThreads,  @Name("minThreads") int minThreads)
79      {
80          this(maxThreads, minThreads, 60000);
81      }
82  
83      public QueuedThreadPool(@Name("maxThreads") int maxThreads,  @Name("minThreads") int minThreads, @Name("idleTimeout")int idleTimeout)
84      {
85          this(maxThreads, minThreads, idleTimeout, null);
86      }
87  
88      public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("queue") BlockingQueue<Runnable> queue)
89      {
90          this(maxThreads, minThreads, idleTimeout, queue, null);
91      }
92  
93      public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("queue") BlockingQueue<Runnable> queue, @Name("threadGroup") ThreadGroup threadGroup)
94      {
95          setMinThreads(minThreads);
96          setMaxThreads(maxThreads);
97          setIdleTimeout(idleTimeout);
98          setStopTimeout(5000);
99  
100         if (queue==null)
101         {
102             int capacity=Math.max(_minThreads, 8);
103             queue=new BlockingArrayQueue<>(capacity, capacity);
104         }
105         _jobs=queue;
106         _threadGroup=threadGroup;
107     }
108 
109     @Override
110     protected void doStart() throws Exception
111     {
112         super.doStart();
113         _threadsStarted.set(0);
114 
115         startThreads(_minThreads);
116     }
117 
118     @Override
119     protected void doStop() throws Exception
120     {
121         super.doStop();
122 
123         long timeout = getStopTimeout();
124         BlockingQueue<Runnable> jobs = getQueue();
125 
126         // If no stop timeout, clear job queue
127         if (timeout <= 0)
128             jobs.clear();
129 
130         // Fill job Q with noop jobs to wakeup idle
131         Runnable noop = () -> {};
132         for (int i = _threadsStarted.get(); i-- > 0; )
133             jobs.offer(noop);
134 
135         // try to jobs complete naturally for half our stop time
136         long stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
137         for (Thread thread : _threads)
138         {
139             long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
140             if (canwait > 0)
141                 thread.join(canwait);
142         }
143 
144         // If we still have threads running, get a bit more aggressive
145 
146         // interrupt remaining threads
147         if (_threadsStarted.get() > 0)
148             for (Thread thread : _threads)
149                 thread.interrupt();
150 
151         // wait again for the other half of our stop time
152         stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
153         for (Thread thread : _threads)
154         {
155             long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
156             if (canwait > 0)
157                 thread.join(canwait);
158         }
159 
160         Thread.yield();
161         int size = _threads.size();
162         if (size > 0)
163         {
164             Thread.yield();
165 
166             if (LOG.isDebugEnabled())
167             {
168                 for (Thread unstopped : _threads)
169                 {
170                     StringBuilder dmp = new StringBuilder();
171                     for (StackTraceElement element : unstopped.getStackTrace())
172                     {
173                         dmp.append(System.lineSeparator()).append("\tat ").append(element);
174                     }
175                     LOG.warn("Couldn't stop {}{}", unstopped, dmp.toString());
176                 }
177             }
178             else
179             {
180                 for (Thread unstopped : _threads)
181                     LOG.warn("{} Couldn't stop {}",this,unstopped);
182             }
183         }
184 
185         synchronized (_joinLock)
186         {
187             _joinLock.notifyAll();
188         }
189     }
190 
191     /**
192      * Thread Pool should use Daemon Threading.
193      *
194      * @param daemon true to enable delegation
195      * @see Thread#setDaemon(boolean)
196      */
197     public void setDaemon(boolean daemon)
198     {
199         _daemon = daemon;
200     }
201 
202     /**
203      * Set the maximum thread idle time.
204      * Threads that are idle for longer than this period may be
205      * stopped.
206      * Delegated to the named or anonymous Pool.
207      *
208      * @param idleTimeout Max idle time in ms.
209      * @see #getIdleTimeout
210      */
211     public void setIdleTimeout(int idleTimeout)
212     {
213         _idleTimeout = idleTimeout;
214     }
215 
216     /**
217      * Set the maximum number of threads.
218      * Delegated to the named or anonymous Pool.
219      *
220      * @param maxThreads maximum number of threads.
221      * @see #getMaxThreads
222      */
223     @Override
224     public void setMaxThreads(int maxThreads)
225     {
226         _maxThreads = maxThreads;
227         if (_minThreads > _maxThreads)
228             _minThreads = _maxThreads;
229     }
230 
231     /**
232      * Set the minimum number of threads.
233      * Delegated to the named or anonymous Pool.
234      *
235      * @param minThreads minimum number of threads
236      * @see #getMinThreads
237      */
238     @Override
239     public void setMinThreads(int minThreads)
240     {
241         _minThreads = minThreads;
242 
243         if (_minThreads > _maxThreads)
244             _maxThreads = _minThreads;
245 
246         int threads = _threadsStarted.get();
247         if (isStarted() && threads < _minThreads)
248             startThreads(_minThreads - threads);
249     }
250 
251     /**
252      * @param name Name of this thread pool to use when naming threads.
253      */
254     public void setName(String name)
255     {
256         if (isRunning())
257             throw new IllegalStateException("started");
258         _name = name;
259     }
260 
261     /**
262      * Set the priority of the pool threads.
263      *
264      * @param priority the new thread priority.
265      */
266     public void setThreadsPriority(int priority)
267     {
268         _priority = priority;
269     }
270 
271     /**
272      * Get the maximum thread idle time.
273      * Delegated to the named or anonymous Pool.
274      *
275      * @return Max idle time in ms.
276      * @see #setIdleTimeout
277      */
278     @ManagedAttribute("maximum time a thread may be idle in ms")
279     public int getIdleTimeout()
280     {
281         return _idleTimeout;
282     }
283 
284     /**
285      * Get the maximum number of threads.
286      * Delegated to the named or anonymous Pool.
287      *
288      * @return maximum number of threads.
289      * @see #setMaxThreads
290      */
291     @Override
292     @ManagedAttribute("maximum number of threads in the pool")
293     public int getMaxThreads()
294     {
295         return _maxThreads;
296     }
297 
298     /**
299      * Get the minimum number of threads.
300      * Delegated to the named or anonymous Pool.
301      *
302      * @return minimum number of threads.
303      * @see #setMinThreads
304      */
305     @Override
306     @ManagedAttribute("minimum number of threads in the pool")
307     public int getMinThreads()
308     {
309         return _minThreads;
310     }
311 
312     /**
313      * @return The name of the this thread pool
314      */
315     @ManagedAttribute("name of the thread pool")
316     public String getName()
317     {
318         return _name;
319     }
320 
321     /**
322      * Get the priority of the pool threads.
323      *
324      * @return the priority of the pool threads.
325      */
326     @ManagedAttribute("priority of threads in the pool")
327     public int getThreadsPriority()
328     {
329         return _priority;
330     }
331 
332     /**
333      * Get the size of the job queue.
334      *
335      * @return Number of jobs queued waiting for a thread
336      */
337     @ManagedAttribute("size of the job queue")
338     public int getQueueSize()
339     {
340         return _jobs.size();
341     }
342 
343     /**
344      * @return whether this thread pool is using daemon threads
345      * @see Thread#setDaemon(boolean)
346      */
347     @ManagedAttribute("thread pool uses daemon threads")
348     public boolean isDaemon()
349     {
350         return _daemon;
351     }
352 
353     @ManagedAttribute("reports additional details in the dump")
354     public boolean isDetailedDump()
355     {
356         return _detailedDump;
357     }
358 
359     public void setDetailedDump(boolean detailedDump)
360     {
361         _detailedDump = detailedDump;
362     }
363 
364     @ManagedAttribute("threshold at which the pool is low on threads")
365     public int getLowThreadsThreshold()
366     {
367         return _lowThreadsThreshold;
368     }
369 
370     public void setLowThreadsThreshold(int lowThreadsThreshold)
371     {
372         _lowThreadsThreshold = lowThreadsThreshold;
373     }
374 
375     @Override
376     public void execute(Runnable job)
377     {
378         if (LOG.isDebugEnabled())
379             LOG.debug("queue {}",job);
380         if (!isRunning() || !_jobs.offer(job))
381         {
382             LOG.warn("{} rejected {}", this, job);
383             throw new RejectedExecutionException(job.toString());
384         }
385         else
386         {
387             // Make sure there is at least one thread executing the job.
388             if (getThreads() == 0)
389                 startThreads(1);
390         }
391     }
392 
393     /**
394      * Blocks until the thread pool is {@link LifeCycle#stop stopped}.
395      */
396     @Override
397     public void join() throws InterruptedException
398     {
399         synchronized (_joinLock)
400         {
401             while (isRunning())
402                 _joinLock.wait();
403         }
404 
405         while (isStopping())
406             Thread.sleep(1);
407     }
408 
409     /**
410      * @return the total number of threads currently in the pool
411      */
412     @Override
413     @ManagedAttribute("number of threads in the pool")
414     public int getThreads()
415     {
416         return _threadsStarted.get();
417     }
418 
419     /**
420      * @return the number of idle threads in the pool
421      */
422     @Override
423     @ManagedAttribute("number of idle threads in the pool")
424     public int getIdleThreads()
425     {
426         return _threadsIdle.get();
427     }
428 
429     /**
430      * @return the number of busy threads in the pool
431      */
432     @ManagedAttribute("number of busy threads in the pool")
433     public int getBusyThreads()
434     {
435         return getThreads() - getIdleThreads();
436     }
437 
438     /**
439      * <p>Returns whether this thread pool is low on threads.</p>
440      * <p>The current formula is:</p>
441      * <pre>
442      * maxThreads - threads + idleThreads - queueSize &lt;= lowThreadsThreshold
443      * </pre>
444      *
445      * @return whether the pool is low on threads
446      * @see #getLowThreadsThreshold()
447      */
448     @Override
449     @ManagedAttribute(value = "thread pool is low on threads", readonly = true)
450     public boolean isLowOnThreads()
451     {
452         return getMaxThreads() - getThreads() + getIdleThreads() - getQueueSize() <= getLowThreadsThreshold();
453     }
454 
455     private boolean startThreads(int threadsToStart)
456     {
457         while (threadsToStart > 0 && isRunning())
458         {
459             int threads = _threadsStarted.get();
460             if (threads >= _maxThreads)
461                 return false;
462 
463             if (!_threadsStarted.compareAndSet(threads, threads + 1))
464                 continue;
465 
466             boolean started = false;
467             try
468             {
469                 Thread thread = newThread(_runnable);
470                 thread.setDaemon(isDaemon());
471                 thread.setPriority(getThreadsPriority());
472                 thread.setName(_name + "-" + thread.getId());
473                 _threads.add(thread);
474 
475                 thread.start();
476                 started = true;
477                 --threadsToStart;
478             }
479             finally
480             {
481                 if (!started)
482                     _threadsStarted.decrementAndGet();
483             }
484         }
485         return true;
486     }
487 
488     protected Thread newThread(Runnable runnable)
489     {
490         return new Thread(_threadGroup, runnable);
491     }
492 
493     @Override
494     @ManagedOperation("dumps thread pool state")
495     public String dump()
496     {
497         return ContainerLifeCycle.dump(this);
498     }
499 
500     @Override
501     public void dump(Appendable out, String indent) throws IOException
502     {
503         List<Object> threads = new ArrayList<>(getMaxThreads());
504         for (final Thread thread : _threads)
505         {
506             final StackTraceElement[] trace = thread.getStackTrace();
507             boolean inIdleJobPoll = false;
508             for (StackTraceElement t : trace)
509             {
510                 if ("idleJobPoll".equals(t.getMethodName()))
511                 {
512                     inIdleJobPoll = true;
513                     break;
514                 }
515             }
516             final boolean idle = inIdleJobPoll;
517 
518             if (isDetailedDump())
519             {
520                 threads.add(new Dumpable()
521                 {
522                     @Override
523                     public void dump(Appendable out, String indent) throws IOException
524                     {
525                         out.append(String.valueOf(thread.getId())).append(' ').append(thread.getName()).append(' ').append(thread.getState().toString()).append(idle ? " IDLE" : "");
526                         if (thread.getPriority()!=Thread.NORM_PRIORITY)
527                             out.append(" prio=").append(String.valueOf(thread.getPriority()));
528                         out.append(System.lineSeparator());
529                         if (!idle)
530                             ContainerLifeCycle.dump(out, indent, Arrays.asList(trace));
531                     }
532 
533                     @Override
534                     public String dump()
535                     {
536                         return null;
537                     }
538                 });
539             }
540             else
541             {
542                 int p=thread.getPriority();
543                 threads.add(thread.getId() + " " + thread.getName() + " " + thread.getState() + " @ " + (trace.length > 0 ? trace[0] : "???") + (idle ? " IDLE" : "")+ (p==Thread.NORM_PRIORITY?"":(" prio="+p)));
544             }
545         }
546 
547         List<Runnable> jobs = Collections.emptyList();
548         if (isDetailedDump())
549             jobs = new ArrayList<>(getQueue());
550 
551         ContainerLifeCycle.dumpObject(out, this);
552         ContainerLifeCycle.dump(out, indent, threads, jobs);
553     }
554 
555     @Override
556     public String toString()
557     {
558         return String.format("%s{%s,%d<=%d<=%d,i=%d,q=%d}", _name, getState(), getMinThreads(), getThreads(), getMaxThreads(), getIdleThreads(), (_jobs == null ? -1 : _jobs.size()));
559     }
560 
561     private Runnable idleJobPoll() throws InterruptedException
562     {
563         return _jobs.poll(_idleTimeout, TimeUnit.MILLISECONDS);
564     }
565 
566     private Runnable _runnable = new Runnable()
567     {
568         @Override
569         public void run()
570         {
571             boolean shrink = false;
572             boolean ignore = false;
573             try
574             {
575                 Runnable job = _jobs.poll();
576 
577                 if (job != null && _threadsIdle.get() == 0)
578                 {
579                     startThreads(1);
580                 }
581 
582                 loop: while (isRunning())
583                 {
584                     // Job loop
585                     while (job != null && isRunning())
586                     {
587                         if (LOG.isDebugEnabled())
588                             LOG.debug("run {}",job);
589                         runJob(job);
590                         if (LOG.isDebugEnabled())
591                             LOG.debug("ran {}",job);
592                         if (Thread.interrupted())
593                         {
594                             ignore=true;
595                             break loop;
596                         }
597                         job = _jobs.poll();
598                     }
599 
600                     // Idle loop
601                     try
602                     {
603                         _threadsIdle.incrementAndGet();
604 
605                         while (isRunning() && job == null)
606                         {
607                             if (_idleTimeout <= 0)
608                                 job = _jobs.take();
609                             else
610                             {
611                                 // maybe we should shrink?
612                                 final int size = _threadsStarted.get();
613                                 if (size > _minThreads)
614                                 {
615                                     long last = _lastShrink.get();
616                                     long now = System.nanoTime();
617                                     if (last == 0 || (now - last) > TimeUnit.MILLISECONDS.toNanos(_idleTimeout))
618                                     {
619                                         if (_lastShrink.compareAndSet(last, now) && _threadsStarted.compareAndSet(size, size - 1))
620                                         {
621                                             shrink=true;
622                                             break loop;
623                                         }
624                                     }
625                                 }
626                                 job = idleJobPoll();
627                             }
628                         }
629                     }
630                     finally
631                     {
632                         if (_threadsIdle.decrementAndGet() == 0)
633                         {
634                             startThreads(1);
635                         }
636                     }
637                 }
638             }
639             catch (InterruptedException e)
640             {
641                 ignore=true;
642                 LOG.ignore(e);
643             }
644             catch (Throwable e)
645             {
646                 LOG.warn(e);
647             }
648             finally
649             {
650                 if (!shrink && isRunning())
651                 {
652                     if (!ignore)
653                         LOG.warn("Unexpected thread death: {} in {}",this,QueuedThreadPool.this);
654                     // This is an unexpected thread death!
655                     if (_threadsStarted.decrementAndGet()<getMaxThreads())
656                         startThreads(1);
657                 }
658                 _threads.remove(Thread.currentThread());
659             }
660         }
661     };
662 
663     /**
664      * <p>Runs the given job in the {@link Thread#currentThread() current thread}.</p>
665      * <p>Subclasses may override to perform pre/post actions before/after the job is run.</p>
666      *
667      * @param job the job to run
668      */
669     protected void runJob(Runnable job)
670     {
671         job.run();
672     }
673 
674     /**
675      * @return the job queue
676      */
677     protected BlockingQueue<Runnable> getQueue()
678     {
679         return _jobs;
680     }
681 
682     /**
683      * @param queue the job queue
684      * @deprecated pass the queue to the constructor instead
685      */
686     @Deprecated
687     public void setQueue(BlockingQueue<Runnable> queue)
688     {
689         throw new UnsupportedOperationException("Use constructor injection");
690     }
691 
692     /**
693      * @param id the thread ID to interrupt.
694      * @return true if the thread was found and interrupted.
695      */
696     @ManagedOperation("interrupts a pool thread")
697     public boolean interruptThread(@Name("id") long id)
698     {
699         for (Thread thread : _threads)
700         {
701             if (thread.getId() == id)
702             {
703                 thread.interrupt();
704                 return true;
705             }
706         }
707         return false;
708     }
709 
710     /**
711      * @param id the thread ID to interrupt.
712      * @return the stack frames dump
713      */
714     @ManagedOperation("dumps a pool thread stack")
715     public String dumpThread(@Name("id") long id)
716     {
717         for (Thread thread : _threads)
718         {
719             if (thread.getId() == id)
720             {
721                 StringBuilder buf = new StringBuilder();
722                 buf.append(thread.getId()).append(" ").append(thread.getName()).append(" ");
723                 buf.append(thread.getState()).append(":").append(System.lineSeparator());
724                 for (StackTraceElement element : thread.getStackTrace())
725                     buf.append("  at ").append(element.toString()).append(System.lineSeparator());
726                 return buf.toString();
727             }
728         }
729         return null;
730     }
731 }