1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
127 if (timeout <= 0)
128 jobs.clear();
129
130
131 Runnable noop = () -> {};
132 for (int i = _threadsStarted.get(); i-- > 0; )
133 jobs.offer(noop);
134
135
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
145
146
147 if (_threadsStarted.get() > 0)
148 for (Thread thread : _threads)
149 thread.interrupt();
150
151
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
193
194
195
196
197 public void setDaemon(boolean daemon)
198 {
199 _daemon = daemon;
200 }
201
202
203
204
205
206
207
208
209
210
211 public void setIdleTimeout(int idleTimeout)
212 {
213 _idleTimeout = idleTimeout;
214 }
215
216
217
218
219
220
221
222
223 @Override
224 public void setMaxThreads(int maxThreads)
225 {
226 _maxThreads = maxThreads;
227 if (_minThreads > _maxThreads)
228 _minThreads = _maxThreads;
229 }
230
231
232
233
234
235
236
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
253
254 public void setName(String name)
255 {
256 if (isRunning())
257 throw new IllegalStateException("started");
258 _name = name;
259 }
260
261
262
263
264
265
266 public void setThreadsPriority(int priority)
267 {
268 _priority = priority;
269 }
270
271
272
273
274
275
276
277
278 @ManagedAttribute("maximum time a thread may be idle in ms")
279 public int getIdleTimeout()
280 {
281 return _idleTimeout;
282 }
283
284
285
286
287
288
289
290
291 @Override
292 @ManagedAttribute("maximum number of threads in the pool")
293 public int getMaxThreads()
294 {
295 return _maxThreads;
296 }
297
298
299
300
301
302
303
304
305 @Override
306 @ManagedAttribute("minimum number of threads in the pool")
307 public int getMinThreads()
308 {
309 return _minThreads;
310 }
311
312
313
314
315 @ManagedAttribute("name of the thread pool")
316 public String getName()
317 {
318 return _name;
319 }
320
321
322
323
324
325
326 @ManagedAttribute("priority of threads in the pool")
327 public int getThreadsPriority()
328 {
329 return _priority;
330 }
331
332
333
334
335
336
337 @ManagedAttribute("size of the job queue")
338 public int getQueueSize()
339 {
340 return _jobs.size();
341 }
342
343
344
345
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
388 if (getThreads() == 0)
389 startThreads(1);
390 }
391 }
392
393
394
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
411
412 @Override
413 @ManagedAttribute("number of threads in the pool")
414 public int getThreads()
415 {
416 return _threadsStarted.get();
417 }
418
419
420
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
431
432 @ManagedAttribute("number of busy threads in the pool")
433 public int getBusyThreads()
434 {
435 return getThreads() - getIdleThreads();
436 }
437
438
439
440
441
442
443
444
445
446
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
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
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
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
655 if (_threadsStarted.decrementAndGet()<getMaxThreads())
656 startThreads(1);
657 }
658 _threads.remove(Thread.currentThread());
659 }
660 }
661 };
662
663
664
665
666
667
668
669 protected void runJob(Runnable job)
670 {
671 job.run();
672 }
673
674
675
676
677 protected BlockingQueue<Runnable> getQueue()
678 {
679 return _jobs;
680 }
681
682
683
684
685
686 @Deprecated
687 public void setQueue(BlockingQueue<Runnable> queue)
688 {
689 throw new UnsupportedOperationException("Use constructor injection");
690 }
691
692
693
694
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
712
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 }