1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.surefire.booter;
20
21 import javax.annotation.Nonnull;
22
23 import java.lang.reflect.Method;
24 import java.util.Optional;
25
26 import static org.apache.maven.surefire.api.util.ReflectionUtils.invokeMethodWithArray;
27 import static org.apache.maven.surefire.api.util.ReflectionUtils.tryGetMethod;
28 import static org.apache.maven.surefire.api.util.ReflectionUtils.tryLoadClass;
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 final class ProcessHandleChecker implements ProcessChecker {
45
46
47 private static final boolean AVAILABLE;
48
49
50 private static final Method PROCESS_HANDLE_OF;
51 private static final Method PROCESS_HANDLE_IS_ALIVE;
52 private static final Method PROCESS_HANDLE_INFO;
53
54
55 private static final Method INFO_START_INSTANT;
56
57
58 private static final Method INSTANT_TO_EPOCH_MILLI;
59
60 static {
61 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
62
63
64 Class<?> processHandleClass = tryLoadClass(classLoader, "java.lang.ProcessHandle");
65 Class<?> processHandleInfoClass = tryLoadClass(classLoader, "java.lang.ProcessHandle$Info");
66 Class<?> optionalClass = tryLoadClass(classLoader, "java.util.Optional");
67 Class<?> instantClass = tryLoadClass(classLoader, "java.time.Instant");
68
69 Method processHandleOf = null;
70 Method processHandleIsAlive = null;
71 Method processHandleInfo = null;
72 Method infoStartInstant = null;
73 Method optionalIsPresent = null;
74 Method optionalGet = null;
75 Method optionalOrElse = null;
76 Method instantToEpochMilli = null;
77
78 if (processHandleClass != null && processHandleInfoClass != null && optionalClass != null) {
79
80 processHandleOf = tryGetMethod(processHandleClass, "of", long.class);
81 processHandleIsAlive = tryGetMethod(processHandleClass, "isAlive");
82 processHandleInfo = tryGetMethod(processHandleClass, "info");
83
84
85 infoStartInstant = tryGetMethod(processHandleInfoClass, "startInstant");
86
87
88 optionalIsPresent = tryGetMethod(optionalClass, "isPresent");
89 optionalGet = tryGetMethod(optionalClass, "get");
90 optionalOrElse = tryGetMethod(optionalClass, "orElse", Object.class);
91
92
93 if (instantClass != null) {
94 instantToEpochMilli = tryGetMethod(instantClass, "toEpochMilli");
95 }
96 }
97
98
99 AVAILABLE = processHandleOf != null
100 && processHandleIsAlive != null
101 && processHandleInfo != null
102 && infoStartInstant != null
103 && optionalIsPresent != null
104 && optionalGet != null
105 && optionalOrElse != null;
106
107 PROCESS_HANDLE_OF = processHandleOf;
108 PROCESS_HANDLE_IS_ALIVE = processHandleIsAlive;
109 PROCESS_HANDLE_INFO = processHandleInfo;
110 INFO_START_INSTANT = infoStartInstant;
111 INSTANT_TO_EPOCH_MILLI = instantToEpochMilli;
112 }
113
114 private final long pid;
115 private final Object processHandle;
116 private volatile Object initialStartInstant;
117 private volatile boolean stopped;
118
119
120
121
122
123
124
125 ProcessHandleChecker(@Nonnull String pid) {
126 this.pid = Long.parseLong(pid);
127 try {
128 Optional<?> optionalObject = (Optional<?>) PROCESS_HANDLE_OF.invoke(null, this.pid);
129 processHandle = optionalObject.orElse(null);
130 initialStartInstant = getInitialStartInstant();
131 } catch (Exception e) {
132 throw new IllegalStateException("Failed to initialize ProcessHandleChecker for PID " + pid, e);
133 }
134 }
135
136
137
138
139
140
141
142 static boolean isAvailable() {
143 return AVAILABLE;
144 }
145
146 @Override
147 public boolean canUse() {
148 return (AVAILABLE && !stopped);
149 }
150
151
152
153
154
155
156
157 @Override
158 public boolean isProcessAlive() {
159 if (!canUse()) {
160 throw new IllegalStateException("irrelevant to call isProcessAlive()");
161 }
162
163 try {
164
165 boolean isAlive = invokeMethodWithArray(processHandle, PROCESS_HANDLE_IS_ALIVE);
166 if (!isAlive) {
167 return false;
168 }
169
170
171 if (initialStartInstant != null) {
172
173 Object info = invokeMethodWithArray(processHandle, PROCESS_HANDLE_INFO);
174 Optional<?> optionalInstant = invokeMethodWithArray(info, INFO_START_INSTANT);
175
176 if (optionalInstant.isPresent()) {
177 Object currentStartInstant = optionalInstant.get();
178
179 return currentStartInstant.equals(initialStartInstant);
180 }
181 }
182
183 return true;
184 } catch (RuntimeException e) {
185
186 return false;
187 }
188 }
189
190 private Object getInitialStartInstant() {
191 try {
192 Object info = invokeMethodWithArray(processHandle, PROCESS_HANDLE_INFO);
193 Optional<?> optionalInstant = invokeMethodWithArray(info, INFO_START_INSTANT);
194 return optionalInstant.orElse(null);
195 } catch (RuntimeException e) {
196 return null;
197 }
198 }
199
200 @Override
201 public void destroyActiveCommands() {
202 stopped = true;
203
204 }
205
206 @Override
207 public boolean isStopped() {
208 return stopped;
209 }
210
211 @Override
212 public void stop() {
213 stopped = true;
214 }
215
216 @Override
217 public ProcessInfo processInfo() {
218 Object startInstant = getInitialStartInstant();
219 if (startInstant == null || INSTANT_TO_EPOCH_MILLI == null) {
220 return null;
221 }
222 try {
223 long startTimeMillis = invokeMethodWithArray(startInstant, INSTANT_TO_EPOCH_MILLI);
224 return ProcessInfo.processHandleInfo(String.valueOf(pid), startTimeMillis);
225 } catch (RuntimeException e) {
226 return null;
227 }
228 }
229
230 @Override
231 public String toString() {
232 String args = "pid=" + pid + ", stopped=" + stopped + ", hasHandle=" + (processHandle != null);
233 if (initialStartInstant != null) {
234 args += ", startInstant=" + initialStartInstant;
235 }
236 return "ProcessHandleChecker{" + args + "}";
237 }
238 }