Skip to content

Commit 99dab0d

Browse files
committed
Enhance service lifecycle management, cohort handling, and status synchronization:
- Add `registerCohortUpdates` for tracking geometry changes in cohorts. - Refactor `ServiceMonitor` with improved client status handling and local service shutdown coordination. - Introduce `stopRequested` flag for process stop management in `LocalInstanceImpl`. - Add methods for geometry retrieval and validation across services. - Improve `refreshStatus` and `refreshClientStatus` synchronization logic.
1 parent d9de247 commit 99dab0d

10 files changed

Lines changed: 364 additions & 88 deletions

File tree

klab.core.api/src/main/java/org/integratedmodelling/klab/api/configuration/Setting.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,11 @@ public enum Setting {
298298
"Launch a DevToolsFX debugging tool for the GUI when in graphical mode",
299299
Map.class,
300300
Map.of()),
301+
LIST_LOCAL_COMMIT_OPERATIONS(
302+
Page.DEBUGGING,
303+
"List local commit/push operations in project team actions",
304+
Boolean.class,
305+
Boolean.FALSE),
301306
CLEAR_WORKSPACE(
302307
Page.RESOURCES,
303308
"Execute to remove all workspaces from the service. This is a destructive operation.",

klab.core.api/src/main/java/org/integratedmodelling/klab/api/services/KlabService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ default boolean hasChangedComparedTo(ServiceStatus statusBeforeChecking) {
231231
|| this.isBusy() != statusBeforeChecking.isBusy()
232232
|| this.isConnected() != statusBeforeChecking.isConnected()
233233
|| this.isOperational() != statusBeforeChecking.isOperational()
234+
|| this.isShutdown() != statusBeforeChecking.isShutdown()
234235
|| !this.getAdvisories().equals(statusBeforeChecking.getAdvisories());
235236
}
236237

klab.core.common/src/main/java/org/integratedmodelling/common/distribution/LocalInstanceImpl.java

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.nio.file.Files;
66
import java.util.EnumSet;
77
import java.util.Map;
8+
import java.util.concurrent.atomic.AtomicBoolean;
89
import java.util.concurrent.atomic.AtomicReference;
910
import org.apache.commons.exec.CommandLine;
1011
import org.apache.commons.exec.DefaultExecuteResultHandler;
@@ -31,6 +32,7 @@ public abstract class LocalInstanceImpl implements LocalInstance {
3132
protected final Stack.Tag tag;
3233

3334
protected AtomicReference<Status> status = new AtomicReference<>(Status.UNKNOWN);
35+
protected AtomicBoolean stopRequested = new AtomicBoolean(false);
3436
protected DefaultExecutor executor;
3537
protected ExecuteWatchdog watchdog;
3638
protected ExecuteStreamHandler streamHandler;
@@ -126,8 +128,7 @@ private void monitorAlreadyRunningProcess(long pid) {
126128
.thenAccept(
127129
p -> {
128130
if (this.pid != null && this.pid.equals(p.pid())) {
129-
this.status.set(Status.STOPPED);
130-
cleanupState();
131+
markStopped();
131132
}
132133
});
133134
});
@@ -161,6 +162,7 @@ public void setStreamHandler(ExecuteStreamHandler streamHandler) {
161162
@Override
162163
public boolean forceRestart(Option... options) {
163164
stop();
165+
waitForStop();
164166
return start(options);
165167
}
166168

@@ -170,11 +172,15 @@ public synchronized boolean start(Option... options) {
170172
if (status.get() == Status.RUNNING) {
171173
return true;
172174
}
175+
if (status.get() == Status.WAITING) {
176+
return false;
177+
}
173178

174179
CommandLine commandLine = getCommandLine(product, settings);
175180
if (commandLine == null) {
176181
return false;
177182
}
183+
stopRequested.set(false);
178184

179185
EnumSet<Option> startOptions = EnumSet.noneOf(Option.class);
180186
if (options != null) {
@@ -220,15 +226,17 @@ protected Process launch(
220226
@Override
221227
public void onProcessComplete(int exitValue) {
222228
super.onProcessComplete(exitValue);
223-
status.set(Status.STOPPED);
224-
cleanupState();
229+
markStopped();
225230
}
226231

227232
@Override
228233
public void onProcessFailed(ExecuteException e) {
229234
super.onProcessFailed(e);
230-
status.set(Status.ERROR);
231-
cleanupState();
235+
if (stopRequested.get()) {
236+
markStopped();
237+
} else {
238+
markError();
239+
}
232240
}
233241
};
234242

@@ -267,6 +275,11 @@ private void cleanupState() {
267275
@Override
268276
public synchronized boolean stop() {
269277
if (watchdog != null) {
278+
stopRequested.set(true);
279+
status.set(Status.WAITING);
280+
if (pid != null) {
281+
monitorAlreadyRunningProcess(pid);
282+
}
270283
watchdog.destroyProcess();
271284
watchdog = null;
272285
executor = null;
@@ -276,9 +289,18 @@ public synchronized boolean stop() {
276289
return true;
277290
}
278291
if (pid != null) {
279-
ProcessHandle.of(pid).ifPresent(ProcessHandle::destroy);
280-
cleanupState();
281-
status.set(Status.STOPPED);
292+
var stoppedPid = pid;
293+
var processHandle = ProcessHandle.of(stoppedPid);
294+
if (processHandle.isPresent() && processHandle.get().isAlive()) {
295+
stopRequested.set(true);
296+
status.set(Status.WAITING);
297+
monitorAlreadyRunningProcess(stoppedPid);
298+
if (!processHandle.get().destroy()) {
299+
processHandle.get().destroyForcibly();
300+
}
301+
} else {
302+
markStopped();
303+
}
282304
process = null;
283305
inputStream = null;
284306
outputStream = null;
@@ -287,6 +309,36 @@ public synchronized boolean stop() {
287309
return false;
288310
}
289311

312+
private void waitForStop() {
313+
long deadline = System.currentTimeMillis() + 10000;
314+
while (status.get() == Status.WAITING && System.currentTimeMillis() < deadline) {
315+
try {
316+
Thread.sleep(100);
317+
} catch (InterruptedException e) {
318+
Thread.currentThread().interrupt();
319+
return;
320+
}
321+
}
322+
}
323+
324+
private synchronized void markStopped() {
325+
stopRequested.set(false);
326+
status.set(Status.STOPPED);
327+
cleanupState();
328+
watchdog = null;
329+
executor = null;
330+
process = null;
331+
}
332+
333+
private synchronized void markError() {
334+
stopRequested.set(false);
335+
status.set(Status.ERROR);
336+
cleanupState();
337+
watchdog = null;
338+
executor = null;
339+
process = null;
340+
}
341+
290342
@Override
291343
public OutputStream getOutputStream() {
292344
return process != null ? outputStream : null;

klab.core.common/src/main/java/org/integratedmodelling/common/services/client/BaseServiceClient.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,28 @@ public boolean isLocal() {
139139
public boolean shutdown() {
140140
int refCount = monitor.release(this);
141141
if (refCount == 0 && monitor.isLocal()) {
142+
// FIXME needs the admin user scope
143+
return requestShutdown();
144+
}
145+
return false;
146+
}
147+
148+
/**
149+
* Ask the service process to shut down without unregistering this client from status monitoring.
150+
*/
151+
public boolean requestShutdown() {
152+
if (monitor.isLocal()) {
142153
// FIXME needs the admin user scope
143154
return client.withScope(serviceScope).put(ServicesAPI.ADMIN.SHUTDOWN, null, Boolean.class);
144155
}
145156
return false;
146157
}
147158

159+
/** Poll status immediately and return the monitor's current status without unregistering. */
160+
public ServiceStatus refreshStatus() {
161+
return monitor.refreshStatus();
162+
}
163+
148164
@Override
149165
public String declareSessionScope(
150166
SessionScope sessionScope, UserScope userScope, KActorsBehavior behavior) {

klab.core.common/src/main/java/org/integratedmodelling/common/services/client/ServiceClientCatalog.java

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class ClientMonitor {
4747
private final boolean local;
4848
private ScheduledFuture<?> schedule;
4949

50-
private Set<BaseServiceClient> registeredClients = new HashSet<>();
50+
private Set<BaseServiceClient> registeredClients = ConcurrentHashMap.newKeySet();
5151

5252
public Utils.Http.Client getClient() {
5353
return client;
@@ -112,19 +112,26 @@ void connect() {
112112
}
113113

114114
void timedTasks() {
115+
refreshStatus(true);
116+
}
117+
118+
public KlabService.ServiceStatus refreshStatus() {
119+
return refreshStatus(false);
120+
}
121+
122+
synchronized KlabService.ServiceStatus refreshStatus(boolean notifyListeners) {
115123

116124
// if (settings != null && "off".equals(settings.get(Setting.POLLING, String.class))) {
117125
// return;
118126
// }
119127

120-
if (!client.isAlive()) {
121-
this.status.set(KlabService.ServiceStatus.offline(type, serverId));
122-
return;
123-
}
124-
125128
var statusBeforeChecking = status.get();
126129
try {
127-
readStatus();
130+
if (!client.isAlive()) {
131+
this.status.set(KlabService.ServiceStatus.offline(type, serverId));
132+
} else {
133+
readStatus();
134+
}
128135
} finally {
129136

130137
boolean statusHasChanged =
@@ -140,13 +147,17 @@ void timedTasks() {
140147
serviceClients.put(serverId, this);
141148
}
142149

143-
for (var client : registeredClients) {
144-
for (var listener : client.statusListeners) {
145-
listener.accept(status.get(), statusHasChanged);
150+
if (notifyListeners) {
151+
for (var client : registeredClients) {
152+
for (var listener : client.statusListeners) {
153+
listener.accept(status.get(), statusHasChanged);
154+
}
146155
}
147156
}
148157
}
149158
}
159+
160+
return status.get();
150161
}
151162

152163
void readStatus() {
@@ -160,7 +171,9 @@ void readStatus() {
160171
}
161172

162173
private void close() {
163-
this.schedule.cancel(true);
174+
if (this.schedule != null) {
175+
this.schedule.cancel(true);
176+
}
164177
serviceClients.remove(serverId);
165178
}
166179

0 commit comments

Comments
 (0)