View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.filter.executor;
21  
22  import java.util.ArrayList;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Queue;
26  import java.util.Set;
27  import java.util.concurrent.BlockingQueue;
28  import java.util.concurrent.Executors;
29  import java.util.concurrent.LinkedBlockingQueue;
30  import java.util.concurrent.RejectedExecutionHandler;
31  import java.util.concurrent.SynchronousQueue;
32  import java.util.concurrent.ThreadFactory;
33  import java.util.concurrent.ThreadPoolExecutor;
34  import java.util.concurrent.TimeUnit;
35  import java.util.concurrent.atomic.AtomicInteger;
36  
37  import org.apache.mina.core.session.AttributeKey;
38  import org.apache.mina.core.session.DummySession;
39  import org.apache.mina.core.session.IoEvent;
40  import org.apache.mina.core.session.IoSession;
41  import org.apache.mina.util.CircularQueue;
42  
43  /**
44   * A {@link ThreadPoolExecutor} that maintains the order of {@link IoEvent}s.
45   * <p>
46   * If you don't need to maintain the order of events per session, please use
47   * {@link UnorderedThreadPoolExecutor}.
48  
49   * @author The Apache MINA Project (dev@mina.apache.org)
50   * @version $Rev: 689337 $, $Date: 2008-08-27 04:18:17 +0200 (Wed, 27 Aug 2008) $
51   * @org.apache.xbean.XBean
52   */
53  public class OrderedThreadPoolExecutor extends ThreadPoolExecutor {
54  
55      private static final IoSession EXIT_SIGNAL = new DummySession();
56  
57      private final AttributeKey BUFFER = new AttributeKey(getClass(), "buffer");
58      private final BlockingQueue<IoSession> waitingSessions = new LinkedBlockingQueue<IoSession>();
59  
60      private final Set<Worker> workers = new HashSet<Worker>();
61  
62      private volatile int corePoolSize;
63      private volatile int maximumPoolSize;
64      private volatile int largestPoolSize;
65      private final AtomicInteger idleWorkers = new AtomicInteger();
66  
67      private long completedTaskCount;
68      private volatile boolean shutdown;
69  
70      private final IoEventQueueHandler queueHandler;
71  
72      public OrderedThreadPoolExecutor() {
73          this(16);
74      }
75  
76      public OrderedThreadPoolExecutor(int maximumPoolSize) {
77          this(0, maximumPoolSize);
78      }
79  
80      public OrderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize) {
81          this(corePoolSize, maximumPoolSize, 30, TimeUnit.SECONDS);
82      }
83  
84      public OrderedThreadPoolExecutor(
85              int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) {
86          this(corePoolSize, maximumPoolSize, keepAliveTime, unit, Executors.defaultThreadFactory());
87      }
88  
89      public OrderedThreadPoolExecutor(
90              int corePoolSize, int maximumPoolSize,
91              long keepAliveTime, TimeUnit unit,
92              IoEventQueueHandler queueHandler) {
93          this(corePoolSize, maximumPoolSize, keepAliveTime, unit, Executors.defaultThreadFactory(), queueHandler);
94      }
95  
96      public OrderedThreadPoolExecutor(
97              int corePoolSize, int maximumPoolSize,
98              long keepAliveTime, TimeUnit unit,
99              ThreadFactory threadFactory) {
100         this(corePoolSize, maximumPoolSize, keepAliveTime, unit, threadFactory, null);
101     }
102 
103     public OrderedThreadPoolExecutor(
104             int corePoolSize, int maximumPoolSize,
105             long keepAliveTime, TimeUnit unit,
106             ThreadFactory threadFactory, IoEventQueueHandler queueHandler) {
107         super(0, 1, keepAliveTime, unit, new SynchronousQueue<Runnable>(), threadFactory, new AbortPolicy());
108         if (corePoolSize < 0) {
109             throw new IllegalArgumentException("corePoolSize: " + corePoolSize);
110         }
111 
112         if (maximumPoolSize == 0 || maximumPoolSize < corePoolSize) {
113             throw new IllegalArgumentException("maximumPoolSize: " + maximumPoolSize);
114         }
115 
116         if (queueHandler == null) {
117             queueHandler = IoEventQueueHandler.NOOP;
118         }
119 
120         this.corePoolSize = corePoolSize;
121         this.maximumPoolSize = maximumPoolSize;
122         this.queueHandler = queueHandler;
123     }
124 
125     public IoEventQueueHandler getQueueHandler() {
126         return queueHandler;
127     }
128 
129     @Override
130     public void setRejectedExecutionHandler(RejectedExecutionHandler handler) {
131         // Ignore the request.  It must always be AbortPolicy.
132     }
133 
134     private void addWorker() {
135         synchronized (workers) {
136             if (workers.size() >= maximumPoolSize) {
137                 return;
138             }
139 
140             Worker worker = new Worker();
141             Thread thread = getThreadFactory().newThread(worker);
142             idleWorkers.incrementAndGet();
143             thread.start();
144             workers.add(worker);
145 
146             if (workers.size() > largestPoolSize) {
147                 largestPoolSize = workers.size();
148             }
149         }
150     }
151 
152     private void addWorkerIfNecessary() {
153         if (idleWorkers.get() == 0) {
154             synchronized (workers) {
155                 if (workers.isEmpty() || idleWorkers.get() == 0) {
156                     addWorker();
157                 }
158             }
159         }
160     }
161 
162     private void removeWorker() {
163         synchronized (workers) {
164             if (workers.size() <= corePoolSize) {
165                 return;
166             }
167             waitingSessions.offer(EXIT_SIGNAL);
168         }
169     }
170 
171     @Override
172     public int getMaximumPoolSize() {
173         return maximumPoolSize;
174     }
175 
176     @Override
177     public void setMaximumPoolSize(int maximumPoolSize) {
178         if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize) {
179             throw new IllegalArgumentException("maximumPoolSize: "
180                     + maximumPoolSize);
181         }
182 
183         synchronized (workers) {
184             this.maximumPoolSize = maximumPoolSize;
185             int difference = workers.size() - maximumPoolSize;
186             while (difference > 0) {
187                 removeWorker();
188                 --difference;
189             }
190         }
191     }
192 
193     @Override
194     public boolean awaitTermination(long timeout, TimeUnit unit)
195             throws InterruptedException {
196 
197         long deadline = System.currentTimeMillis() + unit.toMillis(timeout);
198 
199         synchronized (workers) {
200             while (!isTerminated()) {
201                 long waitTime = deadline - System.currentTimeMillis();
202                 if (waitTime <= 0) {
203                     break;
204                 }
205 
206                 workers.wait(waitTime);
207             }
208         }
209         return isTerminated();
210     }
211 
212     @Override
213     public boolean isShutdown() {
214         return shutdown;
215     }
216 
217     @Override
218     public boolean isTerminated() {
219         if (!shutdown) {
220             return false;
221         }
222 
223         synchronized (workers) {
224             return workers.isEmpty();
225         }
226     }
227 
228     @Override
229     public void shutdown() {
230         if (shutdown) {
231             return;
232         }
233 
234         shutdown = true;
235 
236         synchronized (workers) {
237             for (int i = workers.size(); i > 0; i --) {
238                 waitingSessions.offer(EXIT_SIGNAL);
239             }
240         }
241     }
242 
243     @Override
244     public List<Runnable> shutdownNow() {
245         shutdown();
246 
247         List<Runnable> answer = new ArrayList<Runnable>();
248         IoSession session;
249         while ((session = waitingSessions.poll()) != null) {
250             if (session == EXIT_SIGNAL) {
251                 waitingSessions.offer(EXIT_SIGNAL);
252                 Thread.yield(); // Let others take the signal.
253                 continue;
254             }
255 
256             SessionBuffer buf = (SessionBuffer) session.getAttribute(BUFFER);
257             synchronized (buf.queue) {
258                 for (Runnable task: buf.queue) {
259                     getQueueHandler().polled(this, (IoEvent) task);
260                     answer.add(task);
261                 }
262                 buf.queue.clear();
263             }
264         }
265 
266         return answer;
267     }
268 
269     @Override
270     public void execute(Runnable task) {
271         if (shutdown) {
272             rejectTask(task);
273         }
274 
275         checkTaskType(task);
276 
277         IoEvent e = (IoEvent) task;
278         IoSession s = e.getSession();
279         SessionBuffer buf = getSessionBuffer(s);
280         Queue<Runnable> queue = buf.queue;
281         boolean offerSession;
282         boolean offerEvent = queueHandler.accept(this, e);
283         if (offerEvent) {
284             synchronized (queue) {
285                 queue.offer(e);
286                 if (buf.processingCompleted) {
287                     buf.processingCompleted = false;
288                     offerSession = true;
289                 } else {
290                     offerSession = false;
291                 }
292             }
293         } else {
294             offerSession = false;
295         }
296 
297         if (offerSession) {
298             waitingSessions.offer(s);
299         }
300 
301         addWorkerIfNecessary();
302 
303         if (offerEvent) {
304             queueHandler.offered(this, e);
305         }
306     }
307 
308     private void rejectTask(Runnable task) {
309         getRejectedExecutionHandler().rejectedExecution(task, this);
310     }
311 
312     private void checkTaskType(Runnable task) {
313         if (!(task instanceof IoEvent)) {
314             throw new IllegalArgumentException("task must be an IoEvent or its subclass.");
315         }
316     }
317 
318     @Override
319     public int getActiveCount() {
320         synchronized (workers) {
321             return workers.size() - idleWorkers.get();
322         }
323     }
324 
325     @Override
326     public long getCompletedTaskCount() {
327         synchronized (workers) {
328             long answer = completedTaskCount;
329             for (Worker w: workers) {
330                 answer += w.completedTaskCount;
331             }
332 
333             return answer;
334         }
335     }
336 
337     @Override
338     public int getLargestPoolSize() {
339         return largestPoolSize;
340     }
341 
342     @Override
343     public int getPoolSize() {
344         synchronized (workers) {
345             return workers.size();
346         }
347     }
348 
349     @Override
350     public long getTaskCount() {
351         return getCompletedTaskCount();
352     }
353 
354     @Override
355     public boolean isTerminating() {
356         synchronized (workers) {
357             return isShutdown() && !isTerminated();
358         }
359     }
360 
361     @Override
362     public int prestartAllCoreThreads() {
363         int answer = 0;
364         synchronized (workers) {
365             for (int i = corePoolSize - workers.size() ; i > 0; i --) {
366                 addWorker();
367                 answer ++;
368             }
369         }
370         return answer;
371     }
372 
373     @Override
374     public boolean prestartCoreThread() {
375         synchronized (workers) {
376             if (workers.size() < corePoolSize) {
377                 addWorker();
378                 return true;
379             } else {
380                 return false;
381             }
382         }
383     }
384 
385     @Override
386     public BlockingQueue<Runnable> getQueue() {
387         throw new UnsupportedOperationException();
388     }
389 
390     @Override
391     public void purge() {
392         // Nothing to purge in this implementation.
393     }
394 
395     @Override
396     public boolean remove(Runnable task) {
397         checkTaskType(task);
398         IoEvent e = (IoEvent) task;
399         IoSession s = e.getSession();
400         SessionBuffer buffer = (SessionBuffer) s.getAttribute(BUFFER);
401         if (buffer == null) {
402             return false;
403         }
404 
405         boolean removed;
406         synchronized (buffer.queue) {
407             removed = buffer.queue.remove(task);
408         }
409 
410         if (removed) {
411             getQueueHandler().polled(this, e);
412         }
413 
414         return removed;
415     }
416 
417     @Override
418     public int getCorePoolSize() {
419         return corePoolSize;
420     }
421 
422     @Override
423     public void setCorePoolSize(int corePoolSize) {
424         if (corePoolSize < 0) {
425             throw new IllegalArgumentException("corePoolSize: " + corePoolSize);
426         }
427         if (corePoolSize > maximumPoolSize) {
428             throw new IllegalArgumentException("corePoolSize exceeds maximumPoolSize");
429         }
430 
431         synchronized (workers) {
432             if (this.corePoolSize > corePoolSize) {
433                 for (int i = this.corePoolSize - corePoolSize; i > 0; i --) {
434                     removeWorker();
435                 }
436             }
437             this.corePoolSize = corePoolSize;
438         }
439     }
440 
441     private SessionBuffer getSessionBuffer(IoSession session) {
442         SessionBuffer buffer = (SessionBuffer) session.getAttribute(BUFFER);
443         if (buffer == null) {
444             buffer = new SessionBuffer();
445             SessionBuffer oldBuffer = (SessionBuffer) session.setAttributeIfAbsent(BUFFER, buffer);
446             if (oldBuffer != null) {
447                 buffer = oldBuffer;
448             }
449         }
450         return buffer;
451     }
452 
453     private static class SessionBuffer {
454         private final Queue<Runnable> queue = new CircularQueue<Runnable>();
455         private boolean processingCompleted = true;
456     }
457 
458     private class Worker implements Runnable {
459 
460         private volatile long completedTaskCount;
461         private Thread thread;
462 
463         public void run() {
464             thread = Thread.currentThread();
465 
466             try {
467                 for (;;) {
468                     IoSession session = fetchSession();
469 
470                     idleWorkers.decrementAndGet();
471 
472                     if (session == null) {
473                         synchronized (workers) {
474                             if (workers.size() > corePoolSize) {
475                                 // Remove now to prevent duplicate exit.
476                                 workers.remove(this);
477                                 break;
478                             }
479                         }
480                     }
481 
482                     if (session == EXIT_SIGNAL) {
483                         break;
484                     }
485 
486                     try {
487                         if (session != null) {
488                             runTasks(getSessionBuffer(session));
489                         }
490                     } finally {
491                         idleWorkers.incrementAndGet();
492                     }
493                 }
494             } finally {
495                 synchronized (workers) {
496                     workers.remove(this);
497                     OrderedThreadPoolExecutor.this.completedTaskCount += completedTaskCount;
498                     workers.notifyAll();
499                 }
500             }
501         }
502 
503         private IoSession fetchSession() {
504             IoSession session = null;
505             long currentTime = System.currentTimeMillis();
506             long deadline = currentTime + getKeepAliveTime(TimeUnit.MILLISECONDS);
507             for (;;) {
508                 try {
509                     long waitTime = deadline - currentTime;
510                     if (waitTime <= 0) {
511                         break;
512                     }
513 
514                     try {
515                         session = waitingSessions.poll(waitTime, TimeUnit.MILLISECONDS);
516                         break;
517                     } finally {
518                         if (session == null) {
519                             currentTime = System.currentTimeMillis();
520                         }
521                     }
522                 } catch (InterruptedException e) {
523                     // Ignore.
524                     continue;
525                 }
526             }
527             return session;
528         }
529 
530         private void runTasks(SessionBuffer buf) {
531             for (;;) {
532                 Runnable task;
533                 synchronized (buf.queue) {
534                     task = buf.queue.poll();
535 
536                     if (task == null) {
537                         buf.processingCompleted = true;
538                         break;
539                     }
540                 }
541 
542                 queueHandler.polled(OrderedThreadPoolExecutor.this, (IoEvent) task);
543 
544                 runTask(task);
545             }
546         }
547 
548         private void runTask(Runnable task) {
549             beforeExecute(thread, task);
550             boolean ran = false;
551             try {
552                 task.run();
553                 ran = true;
554                 afterExecute(task, null);
555                 completedTaskCount ++;
556             } catch (RuntimeException e) {
557                 if (!ran) {
558                     afterExecute(task, e);
559                 }
560                 throw e;
561             }
562         }
563     }
564 }