View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.List;
26  import java.util.concurrent.BlockingQueue;
27  import java.util.concurrent.ConcurrentLinkedQueue;
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.StringUtil;
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 with no max bound by default")
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 ConcurrentLinkedQueue<Thread> _threads = new ConcurrentLinkedQueue<>();
56      private final Object _joinLock = new Object();
57      private final BlockingQueue<Runnable> _jobs;
58      private String _name = "qtp" + hashCode();
59      private int _idleTimeout;
60      private int _maxThreads;
61      private int _minThreads;
62      private int _priority = Thread.NORM_PRIORITY;
63      private boolean _daemon = false;
64      private boolean _detailedDump = false;
65  
66      public QueuedThreadPool()
67      {
68          this(200);
69      }
70  
71      public QueuedThreadPool(@Name("maxThreads") int maxThreads)
72      {
73          this(maxThreads, 8);
74      }
75  
76      public QueuedThreadPool(@Name("maxThreads") int maxThreads,  @Name("minThreads") int minThreads)
77      {
78          this(maxThreads, minThreads, 60000);
79      }
80  
81      public QueuedThreadPool(@Name("maxThreads") int maxThreads,  @Name("minThreads") int minThreads, @Name("idleTimeout")int idleTimeout)
82      {
83          this(maxThreads, minThreads, 60000,null);
84      }
85  
86      public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("queue") BlockingQueue<Runnable> queue)
87      {
88          setMinThreads(minThreads);
89          setMaxThreads(maxThreads);
90          setIdleTimeout(idleTimeout);
91          setStopTimeout(5000);
92  
93          if (queue==null)
94              queue=new BlockingArrayQueue<>(_minThreads, _minThreads);
95          _jobs=queue;
96  
97      }
98  
99      @Override
100     protected void doStart() throws Exception
101     {
102         super.doStart();
103         _threadsStarted.set(0);
104 
105         startThreads(_minThreads);
106     }
107 
108     @Override
109     protected void doStop() throws Exception
110     {
111         super.doStop();
112 
113         long timeout = getStopTimeout();
114         BlockingQueue<Runnable> jobs = getQueue();
115 
116         // If no stop timeout, clear job queue
117         if (timeout <= 0)
118             jobs.clear();
119 
120         // Fill job Q with noop jobs to wakeup idle
121         Runnable noop = new Runnable()
122         {
123             @Override
124             public void run()
125             {
126             }
127         };
128         for (int i = _threadsStarted.get(); i-- > 0; )
129             jobs.offer(noop);
130 
131         // try to jobs complete naturally for half our stop time
132         long stopby = System.currentTimeMillis() + timeout / 2;
133         for (Thread thread : _threads)
134         {
135             long canwait = stopby - System.currentTimeMillis();
136             if (canwait > 0)
137                 thread.join(canwait);
138         }
139 
140         // If we still have threads running, get a bit more aggressive
141 
142         // interrupt remaining threads
143         if (_threadsStarted.get() > 0)
144             for (Thread thread : _threads)
145                 thread.interrupt();
146 
147         // wait again for the other half of our stop time
148         stopby = System.currentTimeMillis() + timeout / 2;
149         for (Thread thread : _threads)
150         {
151             long canwait = stopby - System.currentTimeMillis();
152             if (canwait > 0)
153                 thread.join(canwait);
154         }
155 
156         Thread.yield();
157         int size = _threads.size();
158         if (size > 0)
159         {
160             LOG.warn("{} threads could not be stopped", size);
161 
162             if ((size <= Runtime.getRuntime().availableProcessors()) || LOG.isDebugEnabled())
163             {
164                 for (Thread unstopped : _threads)
165                 {
166                     StringBuilder dmp = new StringBuilder();
167                     for (StackTraceElement element : unstopped.getStackTrace())
168                     {
169                         dmp.append(StringUtil.__LINE_SEPARATOR).append("\tat ").append(element);
170                     }
171                     LOG.warn("Couldn't stop {}{}", unstopped, dmp.toString());
172                 }
173             }
174         }
175 
176         synchronized (_joinLock)
177         {
178             _joinLock.notifyAll();
179         }
180     }
181 
182     /**
183      * Delegated to the named or anonymous Pool.
184      */
185     public void setDaemon(boolean daemon)
186     {
187         _daemon = daemon;
188     }
189 
190     /**
191      * Set the maximum thread idle time.
192      * Threads that are idle for longer than this period may be
193      * stopped.
194      * Delegated to the named or anonymous Pool.
195      *
196      * @param idleTimeout Max idle time in ms.
197      * @see #getIdleTimeout
198      */
199     public void setIdleTimeout(int idleTimeout)
200     {
201         _idleTimeout = idleTimeout;
202     }
203 
204     /**
205      * Set the maximum number of threads.
206      * Delegated to the named or anonymous Pool.
207      *
208      * @param maxThreads maximum number of threads.
209      * @see #getMaxThreads
210      */
211     @Override
212     public void setMaxThreads(int maxThreads)
213     {
214         _maxThreads = maxThreads;
215         if (_minThreads > _maxThreads)
216             _minThreads = _maxThreads;
217     }
218 
219     /**
220      * Set the minimum number of threads.
221      * Delegated to the named or anonymous Pool.
222      *
223      * @param minThreads minimum number of threads
224      * @see #getMinThreads
225      */
226     @Override
227     public void setMinThreads(int minThreads)
228     {
229         _minThreads = minThreads;
230 
231         if (_minThreads > _maxThreads)
232             _maxThreads = _minThreads;
233 
234         int threads = _threadsStarted.get();
235         if (isStarted() && threads < _minThreads)
236             startThreads(_minThreads - threads);
237     }
238 
239     /**
240      * @param name Name of the BoundedThreadPool to use when naming Threads.
241      */
242     public void setName(String name)
243     {
244         if (isRunning())
245             throw new IllegalStateException("started");
246         _name = name;
247     }
248 
249     /**
250      * Set the priority of the pool threads.
251      *
252      * @param priority the new thread priority.
253      */
254     public void setThreadsPriority(int priority)
255     {
256         _priority = priority;
257     }
258 
259     /**
260      * Get the maximum thread idle time.
261      * Delegated to the named or anonymous Pool.
262      *
263      * @return Max idle time in ms.
264      * @see #setIdleTimeout
265      */
266     @ManagedAttribute("maximum time a thread may be idle in ms")
267     public int getIdleTimeout()
268     {
269         return _idleTimeout;
270     }
271 
272     /**
273      * Set the maximum number of threads.
274      * Delegated to the named or anonymous Pool.
275      *
276      * @return maximum number of threads.
277      * @see #setMaxThreads
278      */
279     @Override
280     @ManagedAttribute("maximum number of threads in the pool")
281     public int getMaxThreads()
282     {
283         return _maxThreads;
284     }
285 
286     /**
287      * Get the minimum number of threads.
288      * Delegated to the named or anonymous Pool.
289      *
290      * @return minimum number of threads.
291      * @see #setMinThreads
292      */
293     @Override
294     @ManagedAttribute("minimum number of threads in the pool")
295     public int getMinThreads()
296     {
297         return _minThreads;
298     }
299 
300     /**
301      * @return The name of the BoundedThreadPool.
302      */
303     @ManagedAttribute("name of the thread pool")
304     public String getName()
305     {
306         return _name;
307     }
308 
309     /**
310      * Get the priority of the pool threads.
311      *
312      * @return the priority of the pool threads.
313      */
314     @ManagedAttribute("priority of threads in the pool")
315     public int getThreadsPriority()
316     {
317         return _priority;
318     }
319 
320     /**
321      * Delegated to the named or anonymous Pool.
322      */
323     @ManagedAttribute("thead pool using a daemon thread")
324     public boolean isDaemon()
325     {
326         return _daemon;
327     }
328 
329     public boolean isDetailedDump()
330     {
331         return _detailedDump;
332     }
333 
334     public void setDetailedDump(boolean detailedDump)
335     {
336         _detailedDump = detailedDump;
337     }
338 
339     @Override
340     public boolean dispatch(Runnable job)
341     {
342         LOG.debug("{} dispatched {}", this, job);
343         return isRunning() && _jobs.offer(job);
344     }
345 
346     @Override
347     public void execute(Runnable job)
348     {
349         if (!dispatch(job))
350         {
351             LOG.warn("{} rejected {}", this, job);
352             throw new RejectedExecutionException(job.toString());
353         }
354     }
355 
356     /**
357      * Blocks until the thread pool is {@link LifeCycle#stop stopped}.
358      */
359     @Override
360     public void join() throws InterruptedException
361     {
362         synchronized (_joinLock)
363         {
364             while (isRunning())
365                 _joinLock.wait();
366         }
367 
368         while (isStopping())
369             Thread.sleep(1);
370     }
371 
372     /**
373      * @return The total number of threads currently in the pool
374      */
375     @Override
376     @ManagedAttribute("total number of threads currently in the pool")
377     public int getThreads()
378     {
379         return _threadsStarted.get();
380     }
381 
382     /**
383      * @return The number of idle threads in the pool
384      */
385     @Override
386     @ManagedAttribute("total number of idle threads in the pool")
387     public int getIdleThreads()
388     {
389         return _threadsIdle.get();
390     }
391 
392     /**
393      * @return True if the pool is at maxThreads and there are not more idle threads than queued jobs
394      */
395     @Override
396     @ManagedAttribute("True if the pools is at maxThreads and there are not idle threads than queued jobs")
397     public boolean isLowOnThreads()
398     {
399         return _threadsStarted.get() == _maxThreads && _jobs.size() >= _threadsIdle.get();
400     }
401 
402     private boolean startThreads(int threadsToStart)
403     {
404         while (threadsToStart > 0)
405         {
406             int threads = _threadsStarted.get();
407             if (threads >= _maxThreads)
408                 return false;
409 
410             if (!_threadsStarted.compareAndSet(threads, threads + 1))
411                 continue;
412 
413             boolean started = false;
414             try
415             {
416                 Thread thread = newThread(_runnable);
417                 thread.setDaemon(isDaemon());
418                 thread.setPriority(getThreadsPriority());
419                 thread.setName(_name + "-" + thread.getId());
420                 _threads.add(thread);
421 
422                 thread.start();
423                 started = true;
424             }
425             finally
426             {
427                 if (!started)
428                     _threadsStarted.decrementAndGet();
429             }
430             if (started)
431                 threadsToStart--;
432         }
433         return true;
434     }
435 
436     protected Thread newThread(Runnable runnable)
437     {
438         return new Thread(runnable);
439     }
440 
441 
442     @Override
443     @ManagedOperation("dump thread state")
444     public String dump()
445     {
446         return ContainerLifeCycle.dump(this);
447     }
448 
449     @Override
450     public void dump(Appendable out, String indent) throws IOException
451     {
452         List<Object> dump = new ArrayList<>(getMaxThreads());
453         for (final Thread thread : _threads)
454         {
455             final StackTraceElement[] trace = thread.getStackTrace();
456             boolean inIdleJobPoll = false;
457             for (StackTraceElement t : trace)
458             {
459                 if ("idleJobPoll".equals(t.getMethodName()))
460                 {
461                     inIdleJobPoll = true;
462                     break;
463                 }
464             }
465             final boolean idle = inIdleJobPoll;
466 
467             if (isDetailedDump())
468             {
469                 dump.add(new Dumpable()
470                 {
471                     @Override
472                     public void dump(Appendable out, String indent) throws IOException
473                     {
474                         out.append(String.valueOf(thread.getId())).append(' ').append(thread.getName()).append(' ').append(thread.getState().toString()).append(idle ? " IDLE" : "").append('\n');
475                         if (!idle)
476                             ContainerLifeCycle.dump(out, indent, Arrays.asList(trace));
477                     }
478 
479                     @Override
480                     public String dump()
481                     {
482                         return null;
483                     }
484                 });
485             }
486             else
487             {
488                 dump.add(thread.getId() + " " + thread.getName() + " " + thread.getState() + " @ " + (trace.length > 0 ? trace[0] : "???") + (idle ? " IDLE" : ""));
489             }
490         }
491 
492         ContainerLifeCycle.dumpObject(out, this);
493         ContainerLifeCycle.dump(out, indent, dump);
494     }
495 
496     @Override
497     public String toString()
498     {
499         return String.format("%s{%s,%d<=%d<=%d,i=%d,q=%d}", _name, getState(), getMinThreads(), getThreads(), getMaxThreads(), getIdleThreads(), (_jobs == null ? -1 : _jobs.size()));
500     }
501 
502     private Runnable idleJobPoll() throws InterruptedException
503     {
504         return _jobs.poll(_idleTimeout, TimeUnit.MILLISECONDS);
505     }
506 
507     private Runnable _runnable = new Runnable()
508     {
509         @Override
510         public void run()
511         {
512             boolean shrink = false;
513             try
514             {
515                 Runnable job = _jobs.poll();
516 
517                 if (job != null && _threadsIdle.get() == 0)
518                 {
519                     startThreads(1);
520                 }
521 
522                 while (isRunning())
523                 {
524                     // Job loop
525                     while (job != null && isRunning())
526                     {
527                         runJob(job);
528                         job = _jobs.poll();
529                     }
530 
531                     // Idle loop
532                     try
533                     {
534                         _threadsIdle.incrementAndGet();
535 
536                         while (isRunning() && job == null)
537                         {
538                             if (_idleTimeout <= 0)
539                                 job = _jobs.take();
540                             else
541                             {
542                                 // maybe we should shrink?
543                                 final int size = _threadsStarted.get();
544                                 if (size > _minThreads)
545                                 {
546                                     long last = _lastShrink.get();
547                                     long now = System.currentTimeMillis();
548                                     if (last == 0 || (now - last) > _idleTimeout)
549                                     {
550                                         shrink = _lastShrink.compareAndSet(last, now) &&
551                                                 _threadsStarted.compareAndSet(size, size - 1);
552                                         if (shrink)
553                                         {
554                                             return;
555                                         }
556                                     }
557                                 }
558                                 job = idleJobPoll();
559                             }
560                         }
561                     }
562                     finally
563                     {
564                         if (_threadsIdle.decrementAndGet() == 0)
565                         {
566                             startThreads(1);
567                         }
568                     }
569                 }
570             }
571             catch (InterruptedException e)
572             {
573                 LOG.ignore(e);
574             }
575             catch (Throwable e)
576             {
577                 LOG.warn(e);
578             }
579             finally
580             {
581                 if (!shrink)
582                     _threadsStarted.decrementAndGet();
583                 _threads.remove(Thread.currentThread());
584             }
585         }
586     };
587 
588     /**
589      * <p>Runs the given job in the {@link Thread#currentThread() current thread}.</p>
590      * <p>Subclasses may override to perform pre/post actions before/after the job is run.</p>
591      *
592      * @param job the job to run
593      */
594     protected void runJob(Runnable job)
595     {
596         job.run();
597     }
598 
599     /**
600      * @return the job queue
601      */
602     protected BlockingQueue<Runnable> getQueue()
603     {
604         return _jobs;
605     }
606 
607     /**
608      * @param queue the job queue
609      */
610     public void setQueue(BlockingQueue<Runnable> queue)
611     {
612         throw new UnsupportedOperationException("Use constructor injection");
613     }
614 
615     /**
616      * @param id The thread ID to interrupt.
617      * @return true if the thread was found and interrupted.
618      */
619     @ManagedOperation("interrupt a pool thread")
620     public boolean interruptThread(@Name("id") long id)
621     {
622         for (Thread thread : _threads)
623         {
624             if (thread.getId() == id)
625             {
626                 thread.interrupt();
627                 return true;
628             }
629         }
630         return false;
631     }
632 
633     /**
634      * @param id The thread ID to interrupt.
635      * @return true if the thread was found and interrupted.
636      */
637     @ManagedOperation("dump a pool thread stack")
638     public String dumpThread(@Name("id") long id)
639     {
640         for (Thread thread : _threads)
641         {
642             if (thread.getId() == id)
643             {
644                 StringBuilder buf = new StringBuilder();
645                 buf.append(thread.getId()).append(" ").append(thread.getName()).append(" ").append(thread.getState()).append(":\n");
646                 for (StackTraceElement element : thread.getStackTrace())
647                     buf.append("  at ").append(element.toString()).append('\n');
648                 return buf.toString();
649             }
650         }
651         return null;
652     }
653 }