|
11 | 11 | import java.util.UUID; |
12 | 12 | import java.util.concurrent.CancellationException; |
13 | 13 | import java.util.concurrent.ConcurrentHashMap; |
14 | | -import java.util.concurrent.ConcurrentMap; |
15 | 14 | import java.util.concurrent.ExecutionException; |
16 | 15 | import java.util.concurrent.ExecutorService; |
17 | 16 | import java.util.concurrent.Executors; |
@@ -39,158 +38,129 @@ public class JJavaExecutionControl extends DirectExecutionControl { |
39 | 38 | */ |
40 | 39 | public static final String EXECUTION_INTERRUPTED_NAME = "Execution Interrupted"; |
41 | 40 |
|
42 | | - private static final Object NULL = new Object(); |
43 | | - |
44 | 41 | private static final AtomicInteger EXECUTOR_THREAD_ID = new AtomicInteger(0); |
45 | 42 |
|
46 | 43 | private final ExecutorService executor; |
47 | | - |
48 | | - private final long timeoutTime; |
| 44 | + private final long timeoutDuration; |
49 | 45 | private final TimeUnit timeoutUnit; |
| 46 | + private final Map<String, Future<Object>> running; |
| 47 | + private final Map<String, Object> results; |
| 48 | + private final JJavaLoaderDelegate loaderDelegate; |
50 | 49 |
|
51 | | - private final ConcurrentMap<String, Future<Object>> running = new ConcurrentHashMap<>(); |
52 | | - private final Map<String, Object> results = new ConcurrentHashMap<>(); |
| 50 | + public JJavaExecutionControl(JJavaLoaderDelegate loaderDelegate, long timeoutDuration, TimeUnit timeoutUnit) { |
| 51 | + super(loaderDelegate); |
53 | 52 |
|
54 | | - private final JJavaLoaderDelegate loaderDelegate; |
| 53 | + this.loaderDelegate = loaderDelegate; |
| 54 | + this.running = new ConcurrentHashMap<>(); |
| 55 | + this.results = new ConcurrentHashMap<>(); |
55 | 56 |
|
56 | | - public JJavaExecutionControl() { |
57 | | - this(-1, TimeUnit.MILLISECONDS); |
| 57 | + this.timeoutDuration = timeoutDuration; |
| 58 | + this.timeoutUnit = timeoutDuration > 0 ? Objects.requireNonNull(timeoutUnit) : TimeUnit.MILLISECONDS; |
| 59 | + this.executor = Executors.newCachedThreadPool(r -> new Thread(r, "JJava-executor-" + EXECUTOR_THREAD_ID.getAndIncrement())); |
58 | 60 | } |
59 | 61 |
|
60 | | - public JJavaExecutionControl(long timeoutTime, TimeUnit timeoutUnit) { |
61 | | - super(null); |
62 | | - this.loaderDelegate = new JJavaLoaderDelegate(); |
63 | | - this.timeoutTime = timeoutTime; |
64 | | - this.timeoutUnit = timeoutTime > 0 ? Objects.requireNonNull(timeoutUnit) : TimeUnit.MILLISECONDS; |
65 | | - this.executor = Executors.newCachedThreadPool(r -> new Thread(r, "JJava-executor-" + EXECUTOR_THREAD_ID.getAndIncrement())); |
| 62 | + /** |
| 63 | + * Returns JShell ClassLoader |
| 64 | + */ |
| 65 | + public ClassLoader getClassLoader() { |
| 66 | + return loaderDelegate.getClassLoader(); |
66 | 67 | } |
67 | 68 |
|
68 | 69 | public long getTimeoutDuration() { |
69 | | - return timeoutTime; |
| 70 | + return timeoutDuration; |
70 | 71 | } |
71 | 72 |
|
72 | 73 | public TimeUnit getTimeoutUnit() { |
73 | 74 | return timeoutUnit; |
74 | 75 | } |
75 | 76 |
|
| 77 | + /** |
| 78 | + * This method was hijacked and actually only returns a key that can be later retrieved via |
| 79 | + * {@link #takeResult(String)}. This should be called for every invocation as the objects are saved and not taking |
| 80 | + * them will leak the memory. |
| 81 | + * |
| 82 | + * @returns the key to use for {@link #takeResult(String) looking up the result}. |
| 83 | + */ |
| 84 | + @Override |
| 85 | + protected String invoke(Method doitMethod) throws Exception { |
| 86 | + String id = UUID.randomUUID().toString(); |
| 87 | + Object value = execute(id, doitMethod); |
| 88 | + results.put(id, value); |
| 89 | + return id; |
| 90 | + } |
| 91 | + |
| 92 | + @Override |
| 93 | + public void stop() { |
| 94 | + executor.shutdownNow(); |
| 95 | + } |
| 96 | + |
76 | 97 | public Object takeResult(String key) { |
77 | 98 | Object result = this.results.remove(key); |
78 | | - if (result == null) |
| 99 | + if (result == null) { |
79 | 100 | throw new IllegalStateException("No result with key: " + key); |
80 | | - return result == NULL ? null : result; |
| 101 | + } |
| 102 | + |
| 103 | + return result; |
| 104 | + } |
| 105 | + |
| 106 | + public void unloadClass(String className) { |
| 107 | + loaderDelegate.unloadClass(className); |
| 108 | + } |
| 109 | + |
| 110 | + public void interrupt() { |
| 111 | + running.forEach((id, f) -> f.cancel(true)); |
| 112 | + } |
| 113 | + |
| 114 | + @Override |
| 115 | + public String toString() { |
| 116 | + return "JJavaExecutionControl{" + |
| 117 | + "timeoutTime=" + timeoutDuration + |
| 118 | + ", timeoutUnit=" + timeoutUnit + |
| 119 | + '}'; |
81 | 120 | } |
82 | 121 |
|
83 | 122 | private Object execute(String key, Method doitMethod) throws Exception { |
84 | | - Future<Object> runningTask = this.executor.submit(() -> doitMethod.invoke(null)); |
85 | 123 |
|
86 | | - this.running.put(key, runningTask); |
| 124 | + Future<Object> runningTask = executor.submit(() -> doitMethod.invoke(null)); |
| 125 | + running.put(key, runningTask); |
87 | 126 |
|
88 | 127 | try { |
89 | | - if (this.timeoutTime > 0) |
90 | | - return runningTask.get(this.timeoutTime, this.timeoutUnit); |
91 | | - return runningTask.get(); |
| 128 | + return timeoutDuration > 0 ? runningTask.get(this.timeoutDuration, this.timeoutUnit) : runningTask.get(); |
92 | 129 | } catch (CancellationException e) { |
93 | 130 | // If canceled this means that stop() or interrupt() was invoked. |
94 | | - if (this.executor.isShutdown()) |
| 131 | + if (executor.isShutdown()) { |
95 | 132 | // If the executor is shutdown, the situation is the former in which |
96 | 133 | // case the protocol is to throw an ExecutionControl.StoppedException. |
97 | 134 | throw new StoppedException(); |
98 | | - else |
| 135 | + } else { |
99 | 136 | // The execution was purposely interrupted. |
100 | 137 | throw new UserException( |
101 | 138 | "Execution interrupted.", |
102 | 139 | EXECUTION_INTERRUPTED_NAME, |
103 | | - e.getStackTrace() |
104 | | - ); |
| 140 | + e.getStackTrace()); |
| 141 | + } |
105 | 142 | } catch (ExecutionException e) { |
106 | 143 | // The execution threw an exception. The actual exception is the cause of the ExecutionException. |
107 | 144 | Throwable cause = e.getCause(); |
108 | 145 | if (cause instanceof InvocationTargetException) { |
109 | 146 | // Unbox further |
110 | 147 | cause = cause.getCause(); |
111 | 148 | } |
112 | | - if (cause == null) |
| 149 | + if (cause == null) { |
113 | 150 | throw new UserException("null", "Unknown Invocation Exception", e.getStackTrace()); |
114 | | - else if (cause instanceof SPIResolutionException) |
| 151 | + } else if (cause instanceof SPIResolutionException) { |
115 | 152 | throw new ResolutionException(((SPIResolutionException) cause).id(), cause.getStackTrace()); |
116 | | - else |
| 153 | + } else { |
117 | 154 | throw new UserException(String.valueOf(cause.getMessage()), cause.getClass().getName(), cause.getStackTrace()); |
| 155 | + } |
118 | 156 | } catch (TimeoutException e) { |
119 | 157 | throw new UserException( |
120 | | - String.format("Execution timed out after configured timeout of %d %s.", this.timeoutTime, this.timeoutUnit.toString().toLowerCase()), |
| 158 | + String.format("Execution timed out after configured timeout of %d %s.", this.timeoutDuration, this.timeoutUnit.toString().toLowerCase()), |
121 | 159 | EXECUTION_TIMEOUT_NAME, |
122 | 160 | e.getStackTrace() |
123 | 161 | ); |
124 | 162 | } finally { |
125 | | - this.running.remove(key, runningTask); |
| 163 | + running.remove(key, runningTask); |
126 | 164 | } |
127 | 165 | } |
128 | | - |
129 | | - /** |
130 | | - * This method was hijacked and actually only returns a key that can be |
131 | | - * later retrieved via {@link #takeResult(String)}. This should be called |
132 | | - * for every invocation as the objects are saved and not taking them will |
133 | | - * leak the memory. |
134 | | - * <p></p> |
135 | | - * {@inheritDoc} |
136 | | - * |
137 | | - * @returns the key to use for {@link #takeResult(String) looking up the result}. |
138 | | - */ |
139 | | - @Override |
140 | | - protected String invoke(Method doitMethod) throws Exception { |
141 | | - String id = UUID.randomUUID().toString(); |
142 | | - Object value = this.execute(id, doitMethod); |
143 | | - this.results.put(id, value); |
144 | | - return id; |
145 | | - } |
146 | | - |
147 | | - public void interrupt() { |
148 | | - running.forEach((id, f) -> f.cancel(true)); |
149 | | - } |
150 | | - |
151 | | - @Override |
152 | | - public void stop() { |
153 | | - executor.shutdownNow(); |
154 | | - } |
155 | | - |
156 | | - @Override |
157 | | - public void load(ClassBytecodes[] cbcs) throws ClassInstallException { |
158 | | - loaderDelegate.load(cbcs); |
159 | | - } |
160 | | - |
161 | | - @Override |
162 | | - public void addToClasspath(String cp) throws InternalException { |
163 | | - loaderDelegate.addToClasspath(cp); |
164 | | - } |
165 | | - |
166 | | - /** |
167 | | - * Finds the class with the specified binary name. |
168 | | - * |
169 | | - * @param name the binary name of the class |
170 | | - * @return the Class Object |
171 | | - * @throws ClassNotFoundException if the class could not be found |
172 | | - */ |
173 | | - @Override |
174 | | - protected Class<?> findClass(String name) throws ClassNotFoundException { |
175 | | - return loaderDelegate.findClass(name); |
176 | | - } |
177 | | - |
178 | | - void unloadClass(String className) { |
179 | | - this.loaderDelegate.unloadClass(className); |
180 | | - } |
181 | | - |
182 | | - /** |
183 | | - * Returns JShell ClassLoader |
184 | | - */ |
185 | | - public ClassLoader getClassLoader() { |
186 | | - return loaderDelegate.getClassLoader(); |
187 | | - } |
188 | | - |
189 | | - @Override |
190 | | - public String toString() { |
191 | | - return "JJavaExecutionControl{" + |
192 | | - "timeoutTime=" + timeoutTime + |
193 | | - ", timeoutUnit=" + timeoutUnit + |
194 | | - '}'; |
195 | | - } |
196 | 166 | } |
0 commit comments