Skip to content

Commit e749399

Browse files
authored
feature: add shutdown notifier (#28)
* feature: add shutdown notifier * feature: add shutdown notification model * little refactor * use agent ID as request param * fix test * test: fix callback concurrency issue * refactor: ShutdownHookManager * use config 3.0.3-dev * make INFO final * fix test
1 parent 70e1be2 commit e749399

26 files changed

+561
-130
lines changed

inspectit-gepard-agent/build.gradle

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ plugins {
2020
}
2121

2222
group 'rocks.inspectit.gepard'
23-
def configVersion = "3.0.2-dev"
23+
def configVersion = "3.0.3-dev"
2424

2525
sourceCompatibility = "17"
2626
targetCompatibility = "17"
@@ -76,7 +76,8 @@ dependencies {
7676
implementation("org.slf4j:slf4j-api:2.0.16")
7777
// http client for server notification
7878
implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1")
79-
implementation("com.fasterxml.jackson.core:jackson-databind:2.17.1")
79+
implementation("com.fasterxml.jackson.core:jackson-databind:2.18.1")
80+
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.1")
8081
implementation("com.github.ben-manes.caffeine:caffeine:3.1.8")
8182

8283
/*

inspectit-gepard-agent/src/main/java/rocks/inspectit/gepard/agent/InspectitAgentExtension.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import rocks.inspectit.gepard.agent.instrumentation.state.configuration.InspectitConfigurationHolder;
1818
import rocks.inspectit.gepard.agent.instrumentation.state.configuration.resolver.ConfigurationResolver;
1919
import rocks.inspectit.gepard.agent.internal.otel.OpenTelemetryAccessor;
20+
import rocks.inspectit.gepard.agent.internal.shutdown.ShutdownHookManager;
2021
import rocks.inspectit.gepard.agent.notification.NotificationManager;
2122
import rocks.inspectit.gepard.agent.transformation.TransformationManager;
2223

@@ -41,10 +42,6 @@ public AgentBuilder extend(AgentBuilder agentBuilder, ConfigProperties config) {
4142
BootstrapManager bootstrapManager = BootstrapManager.create();
4243
bootstrapManager.appendToBootstrapClassLoader();
4344

44-
// Notify configuration server about this agent
45-
NotificationManager notificationManager = NotificationManager.create();
46-
notificationManager.sendStartNotification();
47-
4845
// Set our global OpenTelemetry instance. For now, we use the Agent SDK
4946
OpenTelemetryAccessor.setOpenTelemetry(GlobalOpenTelemetry.get());
5047

@@ -72,6 +69,12 @@ public AgentBuilder extend(AgentBuilder agentBuilder, ConfigProperties config) {
7269
ConfigurationManager configurationManager = ConfigurationManager.create();
7370
configurationManager.loadConfiguration();
7471

72+
// Notify configuration server about this agent
73+
NotificationManager notificationManager = NotificationManager.create();
74+
notificationManager.sendStartNotification();
75+
// Set up shutdown notification to configuration server
76+
notificationManager.setUpShutdownNotification();
77+
7578
addShutdownHook();
7679

7780
return agentBuilder;
@@ -82,12 +85,9 @@ public String extensionName() {
8285
return "inspectit-gepard";
8386
}
8487

88+
/** This should be the last log message of the agent at shutdown. */
8589
private void addShutdownHook() {
86-
Runtime.getRuntime()
87-
.addShutdownHook(
88-
new Thread(
89-
() -> {
90-
log.info("Shutting down inspectIT Gepard agent extension...");
91-
}));
90+
Runnable shutdownHook = () -> log.info("Shutting down inspectIT Gepard agent extension...");
91+
ShutdownHookManager.getInstance().addShutdownHookLast(shutdownHook);
9292
}
9393
}

inspectit-gepard-agent/src/main/java/rocks/inspectit/gepard/agent/instrumentation/hook/util/MethodHookFactory.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ private MethodHookFactory() {}
1919
public static MethodHook createHook(MethodHookConfiguration hookConfig) {
2020
MethodHook.Builder builder = MethodHook.builder().setConfiguration(hookConfig);
2121

22-
if (hookConfig.getTracing().getStartSpan()) builder.setSpanAction(new SpanAction());
22+
if (hookConfig.getTracing().isStartSpan()) builder.setSpanAction(new SpanAction());
2323

2424
return builder.build();
2525
}

inspectit-gepard-agent/src/main/java/rocks/inspectit/gepard/agent/internal/configuration/observer/ConfigurationReceivedSubject.java

+5
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,9 @@ public void notifyObservers(InspectitConfiguration configuration) {
5454
observer.handleConfiguration(event);
5555
}
5656
}
57+
58+
/** Method for testing. */
59+
public void clear() {
60+
observers.clear();
61+
}
5762
}

inspectit-gepard-agent/src/main/java/rocks/inspectit/gepard/agent/internal/http/HttpRequestSender.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ private HttpRequestSender() {}
2828
*
2929
* @param request the HTTP request
3030
* @param callback the callback function
31-
* @return True, if the HTTP request returned a status code in {@link successfulStatusCodes}
31+
* @return True, if the HTTP request returned a status code in {@link #successfulStatusCodes}
3232
*/
3333
public static boolean send(
3434
SimpleHttpRequest request, FutureCallback<SimpleHttpResponse> callback) {

inspectit-gepard-agent/src/main/java/rocks/inspectit/gepard/agent/internal/identity/model/AgentInfo.java

+37-33
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,68 @@
11
/* (C) 2024 */
22
package rocks.inspectit.gepard.agent.internal.identity.model;
33

4-
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
5-
import com.fasterxml.jackson.annotation.PropertyAccessor;
6-
import com.fasterxml.jackson.core.JsonProcessingException;
7-
import com.fasterxml.jackson.databind.ObjectMapper;
84
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
95
import io.opentelemetry.javaagent.tooling.AgentVersion;
106
import java.lang.management.ManagementFactory;
117
import java.lang.management.RuntimeMXBean;
8+
import java.time.Instant;
129
import java.util.Map;
1310
import java.util.Objects;
1411
import rocks.inspectit.gepard.agent.internal.identity.IdentityManager;
1512
import rocks.inspectit.gepard.agent.internal.properties.PropertiesResolver;
13+
import rocks.inspectit.gepard.commons.model.agent.Agent;
1614

1715
/** Meta-information about the current agent */
18-
public class AgentInfo {
16+
public final class AgentInfo {
1917

18+
/** Global instance of agent information */
2019
public static final AgentInfo INFO = new AgentInfo();
2120

22-
private static final ObjectMapper mapper =
23-
new ObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
24-
25-
private final String serviceName;
26-
27-
private final String gepardVersion;
28-
29-
private final String otelVersion;
30-
31-
private final String javaVersion;
32-
33-
private final long startTime;
34-
35-
private final String vmId;
21+
private final Agent agent;
3622

3723
private final String agentId;
3824

39-
private final Map<String, String> attributes;
40-
4125
private AgentInfo() {
4226
IdentityManager identityManager = IdentityManager.getInstance();
4327
IdentityInfo identityInfo = identityManager.getIdentityInfo();
44-
RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
4528

46-
this.serviceName = getServiceNameFromSdk();
47-
this.gepardVersion = "0.0.1";
48-
this.otelVersion = AgentVersion.VERSION;
49-
this.javaVersion = System.getProperty("java.version");
50-
this.startTime = runtime.getStartTime();
51-
this.vmId = identityInfo.vmId();
29+
this.agent = createAgent(identityInfo);
5230
this.agentId = identityInfo.agentId();
53-
this.attributes = PropertiesResolver.getAttributes();
5431
}
5532

5633
/**
57-
* @return The agent information as JSON string
58-
* @throws JsonProcessingException corrupted agent information
34+
* Creates an agent model with the current meta-information.
35+
*
36+
* @param identityInfo the agent's identity info
37+
* @return the created agent model
38+
*/
39+
private Agent createAgent(IdentityInfo identityInfo) {
40+
RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
41+
42+
String serviceName = getServiceNameFromSdk();
43+
String gepardVersion = "0.0.1";
44+
String otelVersion = AgentVersion.VERSION;
45+
String javaVersion = System.getProperty("java.version");
46+
Instant startTime = Instant.ofEpochMilli(runtime.getStartTime());
47+
String vmId = identityInfo.vmId();
48+
Map<String, String> attributes = PropertiesResolver.getAttributes();
49+
50+
return new Agent(
51+
serviceName, gepardVersion, otelVersion, javaVersion, startTime, vmId, attributes);
52+
}
53+
54+
/**
55+
* @return the agent meta-information
56+
*/
57+
public Agent getAgent() {
58+
return agent;
59+
}
60+
61+
/**
62+
* @return the agent id
5963
*/
60-
public static String getAsString() throws JsonProcessingException {
61-
return mapper.writeValueAsString(INFO);
64+
public String getAgentId() {
65+
return agentId;
6266
}
6367

6468
/**

inspectit-gepard-agent/src/main/java/rocks/inspectit/gepard/agent/internal/schedule/InspectitScheduler.java

+17-10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.concurrent.*;
77
import org.slf4j.Logger;
88
import org.slf4j.LoggerFactory;
9+
import rocks.inspectit.gepard.agent.internal.shutdown.ShutdownHookManager;
910

1011
/**
1112
* Global manager, who starts scheduled task and keeps track of them. At shutdown all scheduled
@@ -58,17 +59,16 @@ public boolean startRunnable(NamedRunnable runnable, Duration interval) {
5859
return true;
5960
}
6061

61-
/** Add hook, so every scheduled future will be cancelled at shutdown */
62+
/** Add hook, so every scheduled future will be cancelled at shutdown. */
6263
private void addShutdownHook() {
63-
Runtime.getRuntime()
64-
.addShutdownHook(
65-
new Thread(
66-
() ->
67-
scheduledFutures.forEach(
68-
(name, future) -> {
69-
log.info("Shutting down {}...", name);
70-
future.cancel(true);
71-
})));
64+
Runnable shutdownHook =
65+
() ->
66+
scheduledFutures.forEach(
67+
(name, future) -> {
68+
log.info("Shutting down {}...", name);
69+
future.cancel(true);
70+
});
71+
ShutdownHookManager.getInstance().addShutdownHook(shutdownHook);
7272
}
7373

7474
/**
@@ -82,9 +82,16 @@ private boolean isAlreadyScheduled(String runnableName) {
8282
}
8383

8484
/**
85+
* Method for testing
86+
*
8587
* @return the number of scheduled futures
8688
*/
8789
public int getNumberOfScheduledFutures() {
8890
return scheduledFutures.size();
8991
}
92+
93+
/** Method for testing. */
94+
public void clearScheduledFutures() {
95+
scheduledFutures.clear();
96+
}
9097
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/* (C) 2024 */
2+
package rocks.inspectit.gepard.agent.internal.shutdown;
3+
4+
import com.google.common.annotations.VisibleForTesting;
5+
import java.util.*;
6+
import java.util.concurrent.atomic.AtomicBoolean;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
/**
11+
* Responsible for executing the agents shutdown hooks in the order they are added. We should try to
12+
* keep the amount of shutdown hooks as well as the logic inside them minimal to prevent long
13+
* shutdown time.
14+
*/
15+
public class ShutdownHookManager {
16+
private static final Logger log = LoggerFactory.getLogger(ShutdownHookManager.class);
17+
18+
private static ShutdownHookManager instance;
19+
20+
/** The registered shutdown hooks */
21+
private final Set<ShutdownHook> shutdownHooks;
22+
23+
private final AtomicBoolean isShutdown;
24+
25+
private ShutdownHookManager() {
26+
shutdownHooks = Collections.synchronizedSet(new HashSet<>());
27+
isShutdown = new AtomicBoolean(false);
28+
}
29+
30+
/**
31+
* @return the singleton instance
32+
*/
33+
public static ShutdownHookManager getInstance() {
34+
if (Objects.isNull(instance)) {
35+
instance = new ShutdownHookManager();
36+
instance.setUpShutdownHooks();
37+
}
38+
return instance;
39+
}
40+
41+
/**
42+
* Adds the runnable to the shutdown hooks at the beginning of the set. During shutdown, no new
43+
* hooks can be added.
44+
*/
45+
public void addShutdownHook(Runnable runnable) {
46+
if (!isShutdown.get()) {
47+
ShutdownHook shutdownHook = new ShutdownHook(runnable, 0);
48+
shutdownHooks.add(shutdownHook);
49+
}
50+
}
51+
52+
/**
53+
* Adds the runnable to the shutdown hooks at the end of the set. During shutdown, no new hooks
54+
* can be added.
55+
*/
56+
public void addShutdownHookLast(Runnable runnable) {
57+
if (!isShutdown.get()) {
58+
ShutdownHook shutdownHook = new ShutdownHook(runnable, Integer.MAX_VALUE);
59+
shutdownHooks.add(shutdownHook);
60+
}
61+
}
62+
63+
/** Sets up the registered shutdown hooks, to be executed at shutdown */
64+
private void setUpShutdownHooks() {
65+
Runtime.getRuntime()
66+
.addShutdownHook(new Thread(this::executeShutdownHooks, "inspectit-shutdown"));
67+
}
68+
69+
/** Executes all registered shutdown hooks by order */
70+
@VisibleForTesting
71+
void executeShutdownHooks() {
72+
if (!isShutdown.compareAndSet(false, true)) log.info("Cannot execute shutdown hooks twice");
73+
else
74+
shutdownHooks.stream()
75+
.sorted(Comparator.comparingInt(ShutdownHook::getOrder))
76+
.forEach(this::tryRunShutdownHook);
77+
}
78+
79+
/** Try-catch-wrapper to run a shutdown hook */
80+
private void tryRunShutdownHook(ShutdownHook shutdownHook) {
81+
try {
82+
shutdownHook.run();
83+
} catch (Exception e) {
84+
log.error("Error while executing shutdown hook", e);
85+
}
86+
}
87+
88+
/**
89+
* Method for testing.
90+
*
91+
* @return the current amount of registered shutdown hooks
92+
*/
93+
public int getShutdownHookCount() {
94+
return shutdownHooks.size();
95+
}
96+
97+
/** Method for testing. */
98+
public void reset() {
99+
shutdownHooks.clear();
100+
isShutdown.set(false);
101+
}
102+
103+
/** Internal class for ordered shutdown hooks */
104+
private static class ShutdownHook {
105+
106+
private final Runnable shutdownHook;
107+
108+
private final int order;
109+
110+
ShutdownHook(Runnable shutdownHook, int order) {
111+
this.shutdownHook = shutdownHook;
112+
this.order = order;
113+
}
114+
115+
void run() {
116+
shutdownHook.run();
117+
}
118+
119+
int getOrder() {
120+
return order;
121+
}
122+
}
123+
}

0 commit comments

Comments
 (0)