1 | package genius.core.session;
|
---|
2 |
|
---|
3 | import java.util.concurrent.ArrayBlockingQueue;
|
---|
4 | import java.util.concurrent.BlockingQueue;
|
---|
5 | import java.util.concurrent.Callable;
|
---|
6 | import java.util.concurrent.ExecutionException;
|
---|
7 | import java.util.concurrent.TimeUnit;
|
---|
8 | import java.util.concurrent.TimeoutException;
|
---|
9 |
|
---|
10 | /**
|
---|
11 | * execute commands within the set timout limits. Compute and keeps remaining
|
---|
12 | * time for further calls. This executor can be called multiple times with
|
---|
13 | * different {@link Callable}s so that the total accumulated time will stay
|
---|
14 | * within the total timeoutms that is given in the constructor.
|
---|
15 | *
|
---|
16 | * This executor will run a separate timer and kill the {@link Callable} with
|
---|
17 | * thread.stop() to make a pretty hard kill attempt if the time runs out.
|
---|
18 | *
|
---|
19 | * @author W.Pasman, David Festen 1apr15
|
---|
20 | *
|
---|
21 | */
|
---|
22 | public class ExecutorWithTimeout {
|
---|
23 |
|
---|
24 | private long remainingTimeMs;
|
---|
25 |
|
---|
26 | /**
|
---|
27 | * Construct an executor with a total available amount of time.
|
---|
28 | *
|
---|
29 | * @param timeoutms
|
---|
30 | * the total available time that this executor can spend.
|
---|
31 | */
|
---|
32 | public ExecutorWithTimeout(long timeoutms) {
|
---|
33 | remainingTimeMs = timeoutms;
|
---|
34 | }
|
---|
35 |
|
---|
36 | /**
|
---|
37 | * Execute the command within the remaining time of this executor. Blocking
|
---|
38 | * call. Used time will be subtracted from the quotum of this Executor. This
|
---|
39 | * function is synchronized and can execute only 1 Callable at any time.
|
---|
40 | *
|
---|
41 | * @param name
|
---|
42 | * the name of the thread/process/agent for which we are
|
---|
43 | * executing. Used for error reporting.
|
---|
44 | * @param command
|
---|
45 | * the {@link Callable} to execute
|
---|
46 | * @return the result V
|
---|
47 | * @throws ExecutionException
|
---|
48 | * if the {@link Callable} threw an exception. The
|
---|
49 | * {@link ExecutionException} will contain the exception from
|
---|
50 | * the {@link Callable}.
|
---|
51 | * @throws TimeoutException
|
---|
52 | * if the {@link Callable} did not finish in time.
|
---|
53 | */
|
---|
54 | public synchronized <V> V execute(String name, final Callable<V> command)
|
---|
55 | throws ExecutionException, TimeoutException {
|
---|
56 |
|
---|
57 | return new myThread<V>(command).executeWithTimeout(name, remainingTimeMs);
|
---|
58 | }
|
---|
59 | }
|
---|
60 |
|
---|
61 | /**
|
---|
62 | * Private thread class, this is the thread where the Callable will be run.
|
---|
63 | *
|
---|
64 | * @author W.Pasman 1apr15
|
---|
65 | *
|
---|
66 | * @param <V>
|
---|
67 | * the return type of the callable.
|
---|
68 | */
|
---|
69 | class myThread<V> extends Thread {
|
---|
70 | // flag indicating that the thread is done.
|
---|
71 | BlockingQueue<Boolean> ready = new ArrayBlockingQueue<Boolean>(1);
|
---|
72 |
|
---|
73 | private V result = null;
|
---|
74 | private Throwable resultError = null;
|
---|
75 |
|
---|
76 | Callable<V> callable;
|
---|
77 |
|
---|
78 | public myThread(Callable<V> c) {
|
---|
79 | callable = c;
|
---|
80 | }
|
---|
81 |
|
---|
82 | @Override
|
---|
83 | public void run() {
|
---|
84 | try {
|
---|
85 | result = callable.call();
|
---|
86 | } catch (Throwable e) { // you get here when an agent throws an exception
|
---|
87 | resultError = e;
|
---|
88 | e.printStackTrace();
|
---|
89 | }
|
---|
90 | try {
|
---|
91 | ready.put(true);
|
---|
92 | } catch (InterruptedException e) {
|
---|
93 | // at this point, either result or resultError has been set
|
---|
94 | // already.
|
---|
95 | }
|
---|
96 | }
|
---|
97 |
|
---|
98 | /**
|
---|
99 | * Execute this thread and wait for thread to terminate or terminate after
|
---|
100 | * timeout millis. Blocking call. After return, the Callable has been
|
---|
101 | * executed, OR we have thrown.
|
---|
102 | *
|
---|
103 | * @param name
|
---|
104 | * the name for the thread (usually, the agent name). Used for
|
---|
105 | * reporting errors.
|
---|
106 | *
|
---|
107 | * @param timeout
|
---|
108 | * timeout in millis.
|
---|
109 | * @throws TimeoutException
|
---|
110 | * if the callable did not complete within the available time.
|
---|
111 | */
|
---|
112 | public V executeWithTimeout(String name, long timeout) throws ExecutionException, TimeoutException {
|
---|
113 | start();
|
---|
114 |
|
---|
115 | // wait for the thread to finish, but at most timeout ms.
|
---|
116 | try {
|
---|
117 | if (ready.poll(timeout, TimeUnit.MILLISECONDS) == null) {
|
---|
118 | /*
|
---|
119 | * not finished. terminate and throw. stop() is only way to
|
---|
120 | * force thread to die. interrupt() is too weak. executorService
|
---|
121 | * only supports interrupt(), not stop(). Therefore we use plain
|
---|
122 | * Threads here.
|
---|
123 | */
|
---|
124 | stop();
|
---|
125 | throw new TimeoutException("agent " + name + " passed deadline and was killed");
|
---|
126 | }
|
---|
127 | } catch (InterruptedException e) {
|
---|
128 | /*
|
---|
129 | * we should not get here. Just in case
|
---|
130 | */
|
---|
131 | resultError = e;
|
---|
132 | }
|
---|
133 | // if we get here, thread ended and result or resultError was set.
|
---|
134 | if (resultError != null) {
|
---|
135 | throw new ExecutionException("Execution failed of " + name + ":" + resultError, resultError);
|
---|
136 | }
|
---|
137 | return result;
|
---|
138 | }
|
---|
139 | }
|
---|