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.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
117 if (timeout <= 0)
118 jobs.clear();
119
120
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
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
141
142
143 if (_threadsStarted.get() > 0)
144 for (Thread thread : _threads)
145 thread.interrupt();
146
147
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
184
185 public void setDaemon(boolean daemon)
186 {
187 _daemon = daemon;
188 }
189
190
191
192
193
194
195
196
197
198
199 public void setIdleTimeout(int idleTimeout)
200 {
201 _idleTimeout = idleTimeout;
202 }
203
204
205
206
207
208
209
210
211 @Override
212 public void setMaxThreads(int maxThreads)
213 {
214 _maxThreads = maxThreads;
215 if (_minThreads > _maxThreads)
216 _minThreads = _maxThreads;
217 }
218
219
220
221
222
223
224
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
241
242 public void setName(String name)
243 {
244 if (isRunning())
245 throw new IllegalStateException("started");
246 _name = name;
247 }
248
249
250
251
252
253
254 public void setThreadsPriority(int priority)
255 {
256 _priority = priority;
257 }
258
259
260
261
262
263
264
265
266 @ManagedAttribute("maximum time a thread may be idle in ms")
267 public int getIdleTimeout()
268 {
269 return _idleTimeout;
270 }
271
272
273
274
275
276
277
278
279 @Override
280 @ManagedAttribute("maximum number of threads in the pool")
281 public int getMaxThreads()
282 {
283 return _maxThreads;
284 }
285
286
287
288
289
290
291
292
293 @Override
294 @ManagedAttribute("minimum number of threads in the pool")
295 public int getMinThreads()
296 {
297 return _minThreads;
298 }
299
300
301
302
303 @ManagedAttribute("name of the thread pool")
304 public String getName()
305 {
306 return _name;
307 }
308
309
310
311
312
313
314 @ManagedAttribute("priority of threads in the pool")
315 public int getThreadsPriority()
316 {
317 return _priority;
318 }
319
320
321
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
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
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
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
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
525 while (job != null && isRunning())
526 {
527 runJob(job);
528 job = _jobs.poll();
529 }
530
531
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
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
590
591
592
593
594 protected void runJob(Runnable job)
595 {
596 job.run();
597 }
598
599
600
601
602 protected BlockingQueue<Runnable> getQueue()
603 {
604 return _jobs;
605 }
606
607
608
609
610 public void setQueue(BlockingQueue<Runnable> queue)
611 {
612 throw new UnsupportedOperationException("Use constructor injection");
613 }
614
615
616
617
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
635
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 }