This is an automated email from the ASF dual-hosted git repository.

yasith pushed a commit to branch AIRAVATA-3981/integration-health-check
in repository https://gitbox.apache.org/repos/asf/airavata.git

commit 2e94e32ae061939515b451976e72f7873774d195
Author: yasithdev <[email protected]>
AuthorDate: Thu Mar 26 15:27:30 2026 -0500

    feat: add ServiceRegistry and ServiceStatus for background service tracking
---
 .../patform/monitoring/ServiceRegistry.java        | 149 +++++++++++++++++++++
 .../airavata/patform/monitoring/ServiceStatus.java |  62 +++++++++
 2 files changed, 211 insertions(+)

diff --git 
a/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceRegistry.java
 
b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceRegistry.java
new file mode 100644
index 0000000000..cce0eeb2c2
--- /dev/null
+++ 
b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceRegistry.java
@@ -0,0 +1,149 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.patform.monitoring;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+import org.apache.airavata.common.utils.IServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tracks background {@link IServer} instances and their threads, exposing
+ * lifecycle operations (status query, restart, stop-all) and error recording.
+ */
+public class ServiceRegistry {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(ServiceRegistry.class);
+
+    private static class Entry {
+        final IServer server;
+        Thread thread;
+        final Supplier<IServer> factory;
+        final long startedAt;
+        String lastError;
+
+        Entry(IServer server, Thread thread, Supplier<IServer> factory) {
+            this.server = server;
+            this.thread = thread;
+            this.factory = factory;
+            this.startedAt = System.currentTimeMillis();
+        }
+    }
+
+    private final Map<String, Entry> entries = new LinkedHashMap<>();
+
+    /**
+     * Registers a service with an optional restart factory.
+     *
+     * @param label   human-readable service identifier
+     * @param service the IServer instance
+     * @param thread  the thread running the service
+     * @param factory optional supplier to create a fresh IServer for restart; 
may be null
+     */
+    public synchronized void register(String label, IServer service, Thread 
thread, Supplier<IServer> factory) {
+        entries.put(label, new Entry(service, thread, factory));
+    }
+
+    /**
+     * Registers a service without a restart factory.
+     */
+    public synchronized void register(String label, IServer service, Thread 
thread) {
+        register(label, service, thread, null);
+    }
+
+    /**
+     * Returns a snapshot of statuses keyed by label.
+     * Status is {@code "UP"} when the thread is alive, {@code "DOWN"} 
otherwise.
+     */
+    public synchronized Map<String, ServiceStatus> getStatuses() {
+        Map<String, ServiceStatus> result = new LinkedHashMap<>();
+        for (Map.Entry<String, Entry> e : entries.entrySet()) {
+            Entry entry = e.getValue();
+            String status = entry.thread.isAlive() ? "UP" : "DOWN";
+            long uptimeMs = entry.thread.isAlive() ? 
System.currentTimeMillis() - entry.startedAt : 0L;
+            result.put(e.getKey(), new ServiceStatus(status, uptimeMs, 
entry.lastError));
+        }
+        return result;
+    }
+
+    /**
+     * Attempts to restart the named service using its registered factory.
+     *
+     * @param name the service label
+     * @throws IllegalArgumentException if no service with that name exists
+     * @throws IllegalStateException    if the service has no restart factory
+     * @throws Exception                if the new service thread fails to 
start
+     */
+    public synchronized void restart(String name) throws Exception {
+        Entry entry = entries.get(name);
+        if (entry == null) {
+            throw new IllegalArgumentException("No service registered with 
name: " + name);
+        }
+        if (entry.factory == null) {
+            throw new IllegalStateException("Service '" + name + "' has no 
restart factory");
+        }
+        // Stop the old instance
+        try {
+            entry.server.stop();
+        } catch (Exception e) {
+            logger.warn("Error stopping '{}' before restart: {}", name, 
e.getMessage());
+        }
+        entry.thread.interrupt();
+
+        // Start a fresh instance
+        IServer newServer = entry.factory.get();
+        Thread newThread = new Thread(newServer, "airavata-" + name);
+        newThread.setDaemon(true);
+        newThread.start();
+
+        Entry newEntry = new Entry(newServer, newThread, entry.factory);
+        entries.put(name, newEntry);
+        logger.info("Service '{}' restarted", name);
+    }
+
+    /**
+     * Records an error message against the named service.
+     * Silently ignores unknown names.
+     */
+    public synchronized void recordError(String name, String errorMessage) {
+        Entry entry = entries.get(name);
+        if (entry != null) {
+            entry.lastError = errorMessage;
+        }
+    }
+
+    /**
+     * Stops all registered services and interrupts their threads.
+     */
+    public synchronized void stopAll() {
+        for (Map.Entry<String, Entry> e : entries.entrySet()) {
+            String label = e.getKey();
+            Entry entry = e.getValue();
+            try {
+                entry.server.stop();
+            } catch (Exception ex) {
+                logger.warn("Error stopping '{}': {}", label, ex.getMessage());
+            }
+            entry.thread.interrupt();
+        }
+    }
+}
diff --git 
a/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceStatus.java
 
b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceStatus.java
new file mode 100644
index 0000000000..9d1b48854a
--- /dev/null
+++ 
b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceStatus.java
@@ -0,0 +1,62 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.patform.monitoring;
+
+/**
+ * DTO representing the runtime status of a single background service.
+ */
+public class ServiceStatus {
+
+    private String status;
+    private long uptimeMs;
+    private String lastError;
+
+    public ServiceStatus() {}
+
+    public ServiceStatus(String status, long uptimeMs, String lastError) {
+        this.status = status;
+        this.uptimeMs = uptimeMs;
+        this.lastError = lastError;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public long getUptimeMs() {
+        return uptimeMs;
+    }
+
+    public void setUptimeMs(long uptimeMs) {
+        this.uptimeMs = uptimeMs;
+    }
+
+    public String getLastError() {
+        return lastError;
+    }
+
+    public void setLastError(String lastError) {
+        this.lastError = lastError;
+    }
+}

Reply via email to