Skip to content

Commit 227bec3

Browse files
feat: better handle case when existing IP starts to support initial context (#282)
it could be already running when new IP is loaded leading to situation when initial context is not loaded
1 parent ff1deaf commit 227bec3

File tree

4 files changed

+48
-26
lines changed

4 files changed

+48
-26
lines changed

james-agent/src/main/java/com/tomtom/james/informationpoint/advice/ContextAwareAdvice.java

+24-17
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public static void onEnter(String originTypeName,
6969
return;
7070
}
7171

72-
final String key = MethodExecutionContextHelper.createContextKey();
72+
final String key = MethodExecutionContextHelper.createContextKey(ip);
7373
LOG.trace(() -> "Initializing custom context setup for the call");
7474
final Object callContext = ScriptEngineSupplier.get().invokePrepareContext(
7575
ip,
@@ -101,7 +101,7 @@ public static void onExit(
101101
Throwable thrown) {
102102
Instant eventTime = Instant.now();
103103
Duration executionTime = Duration.ofNanos(System.nanoTime() - _startTime);
104-
boolean requireInitialContextCleanup = false;
104+
AutoCloseable closeable = null;
105105

106106
try {
107107
Optional<InformationPoint> optionalInformationPoint = InformationPointServiceSupplier.get()
@@ -110,46 +110,49 @@ public static void onExit(
110110
LOG.trace(() -> "onExit: skipping because information point is gone");
111111
return;
112112
}
113-
final InformationPoint informationPoint = optionalInformationPoint.get();
114-
final long successExecutionThreshold = informationPoint.getSuccessExecutionThreshold();
115-
final double sampleRate = getSampleRate(thrown, informationPoint);
113+
final InformationPoint ip = optionalInformationPoint.get();
114+
final long successExecutionThreshold = ip.getSuccessExecutionThreshold();
115+
final double sampleRate = getSampleRate(thrown, ip);
116116

117117
LOG.trace(() -> "onExit: START "
118118
+ "[origin=" + origin
119119
+ ", informationPointClassName=" + informationPointClassName
120120
+ ", informationPointMethodName=" + informationPointMethodName
121-
+ ", baseScript=" + (informationPoint.getBaseScript().isPresent())
122-
+ ", script=" + (informationPoint.getScript().isPresent())
123-
+ ", sampleRate=" + informationPoint.getSampleRate()
124-
+ ", successSampleRate=" + informationPoint.getSuccessSampleRate()
125-
+ ", errorSampleRate=" + informationPoint.getErrorSampleRate()
121+
+ ", baseScript=" + (ip.getBaseScript().isPresent())
122+
+ ", script=" + (ip.getScript().isPresent())
123+
+ ", sampleRate=" + ip.getSampleRate()
124+
+ ", successSampleRate=" + ip.getSuccessSampleRate()
125+
+ ", errorSampleRate=" + ip.getErrorSampleRate()
126126
+ ", successExecutionThreshold=" + successExecutionThreshold
127127
+ ", instance=" + instance
128128
+ ", arguments=" + Arrays.asList(arguments)
129129
+ ", returned=" + returned
130130
+ ", thrown=" + thrown
131131
+ "]");
132132

133-
requireInitialContextCleanup = informationPoint.getRequiresInitialContext();
133+
boolean requireInitialContextCleanup = ip.getRequiresInitialContext();
134+
if (requireInitialContextCleanup) {
135+
closeable = () -> MethodExecutionContextHelper.removeContextKey(ip);
136+
}
134137

135138
if ((sampleRate < 100) && (sampleRate < ThreadLocalRandom.current().nextDouble() * 100)) {
136139
LOG.trace(() -> "onExit: Sample skipped (sampleRate=" + sampleRate + ")");
137140
return;
138141
}
139142

140143
final CompletableFuture<Object> initialContextAsyncProvider = requireInitialContextCleanup
141-
? MethodExecutionContextHelper.getContextAsync(MethodExecutionContextHelper.getKeyForCurrentFrame())
144+
? MethodExecutionContextHelper.getContextAsync(MethodExecutionContextHelper.getKeyForCurrentFrame(ip))
142145
: CompletableFuture.completedFuture(null);
143146

144-
final String[] callStack = informationPoint.getRequiresCallStack() ? getCallStack() : EMPTY_CALL_STACK;
147+
final String[] callStack = ip.getRequiresCallStack() ? getCallStack() : EMPTY_CALL_STACK;
145148
if (thrown == null) {
146149
if (executionTime.toMillis() < successExecutionThreshold) {
147150
LOG.trace(() -> "onExit: ExecutionTime skipped (executionTime=" + executionTime.toMillis() + ")");
148151
return;
149152
}
150153
LOG.trace(() -> "onExit: Invoking success handler");
151154
ScriptEngineSupplier.get().invokeSuccessHandler(
152-
informationPoint,
155+
ip,
153156
origin,
154157
createParameterList(origin, arguments),
155158
instance,
@@ -163,7 +166,7 @@ public static void onExit(
163166
} else {
164167
LOG.trace(() -> "onExit: Invoking error handler");
165168
ScriptEngineSupplier.get().invokeErrorHandler(
166-
informationPoint,
169+
ip,
167170
origin,
168171
createParameterList(origin, arguments),
169172
instance,
@@ -179,8 +182,12 @@ public static void onExit(
179182
LOG.error("Error executing onExit advice", t);
180183
throw t;
181184
} finally {
182-
if (requireInitialContextCleanup) {
183-
MethodExecutionContextHelper.removeContextKey();
185+
try {
186+
if (closeable != null) {
187+
closeable.close();
188+
}
189+
} catch (Exception e) {
190+
LOG.error("Error executing onExit advice (finally)", e);
184191
}
185192
LOG.trace("onExit: END");
186193
}

james-agent/src/main/java/com/tomtom/james/newagent/MethodExecutionContextHelper.java

+18-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.tomtom.james.newagent;
1818

19+
import com.tomtom.james.common.api.informationpoint.InformationPoint;
1920
import com.tomtom.james.common.log.Logger;
2021
import com.tomtom.james.util.MoreExecutors;
2122

@@ -53,18 +54,26 @@ public static void shutdown() {
5354
}
5455
}
5556

56-
public static String createContextKey() {
57-
final String contextKey = UUID.randomUUID().toString();
57+
public static String createContextKey(final InformationPoint ip) {
58+
final String contextKey = ip.toString() + "-" + UUID.randomUUID();
5859
keysStack.get().push(contextKey);
5960
return contextKey;
6061
}
6162

62-
public static String getKeyForCurrentFrame() {
63-
return keysStack.get().peek();
63+
public static String getKeyForCurrentFrame(final InformationPoint informationPoint) {
64+
final String key = keysStack.get().peek();
65+
if (key != null && key.startsWith(informationPoint.toString())) {
66+
return key;
67+
}
68+
return null;
6469
}
6570

66-
public static String removeContextKey() {
67-
return keysStack.get().pop();
71+
public static String removeContextKey(final InformationPoint informationPoint) {
72+
String key = getKeyForCurrentFrame(informationPoint);
73+
if (key != null) {
74+
return keysStack.get().pop();
75+
}
76+
return null;
6877
}
6978

7079
public static CompletableFuture<Object> storeContextAsync(final String key, final Object value) {
@@ -77,6 +86,9 @@ public static CompletableFuture<Object> storeContextAsync(final String key, fina
7786
}
7887

7988
public static CompletableFuture<Object> getContextAsync(final String key) {
89+
if (key == null) {
90+
return CompletableFuture.completedFuture(null);
91+
}
8092
final CompletableFuture result = new CompletableFuture();
8193
contextStoreAccessExecutor.submit(() -> {
8294
if (contextStore.containsKey(key)) {

james-agent/src/test/groovy/com/tomtom/james/newagent/MethodExecutionContextHelperSpec.groovy

+5-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.tomtom.james.newagent
1818

19+
import com.tomtom.james.common.api.informationpoint.InformationPoint
1920
import spock.lang.Specification
2021

2122
import java.util.concurrent.TimeUnit
@@ -24,6 +25,8 @@ import static org.awaitility.Awaitility.await
2425

2526
class MethodExecutionContextHelperSpec extends Specification {
2627

28+
static InformationPoint IP = InformationPoint.builder().withClassName("class").withMethodName("method").build()
29+
2730
def "Should store context per thread"() {
2831
given:
2932
def keys = new String[2]
@@ -44,9 +47,9 @@ class MethodExecutionContextHelperSpec extends Specification {
4447
}
4548

4649
static def runOnThread(String[] storage, int index) {
47-
String key = MethodExecutionContextHelper.createContextKey()
50+
String key = MethodExecutionContextHelper.createContextKey(IP)
4851
storage[index] = key
4952
MethodExecutionContextHelper.storeContextAsync(key, Integer.valueOf(100 + index))
50-
MethodExecutionContextHelper.removeContextKey()
53+
MethodExecutionContextHelper.removeContextKey(IP)
5154
}
5255
}

james-agent/src/test/groovy/com/tomtom/james/script/DisruptorAsyncScriptEngineSpec.groovy

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class DisruptorAsyncScriptEngineSpec extends Specification {
124124

125125
when:
126126
threadData.set("greetings from " + currentThread.getId())
127-
def key = MethodExecutionContextHelper.createContextKey()
127+
def key = MethodExecutionContextHelper.createContextKey(informationPoint)
128128
def context = scriptEngine.invokePrepareContext(informationPoint, origin, [param1, param2], instance, currentThread, key)
129129
MethodExecutionContextHelper.storeContextAsync(key, context)
130130
scriptEngine.invokeSuccessHandler(informationPoint, origin, [param1, param2], instance, currentThread, instant, duration, callStack, returnValue, MethodExecutionContextHelper.getContextAsync(key))

0 commit comments

Comments
 (0)