Skip to content

Commit 16cd2b6

Browse files
Add SentryWrapper for Callable and Supplier Interface (#2720)
* introduce sentry wrapper class with static methods to wrap callable and supplier interface * add changelog entry * Format code * add tests * api dump * merge changelog * CR * format --------- Co-authored-by: Sentry Github Bot <[email protected]>
1 parent e5c250b commit 16cd2b6

File tree

4 files changed

+197
-0
lines changed

4 files changed

+197
-0
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- Add SentryWrapper for Callable and Supplier Interface ([#2720](https://github.com/getsentry/sentry-java/pull/2720))
8+
39
## 6.20.0
410

511
### Features

sentry/api/sentry.api

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,6 +1934,12 @@ public final class io/sentry/SentryTracer : io/sentry/ITransaction {
19341934
public fun updateEndDate (Lio/sentry/SentryDate;)Z
19351935
}
19361936

1937+
public final class io/sentry/SentryWrapper {
1938+
public fun <init> ()V
1939+
public static fun wrapCallable (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Callable;
1940+
public static fun wrapSupplier (Ljava/util/function/Supplier;)Ljava/util/function/Supplier;
1941+
}
1942+
19371943
public final class io/sentry/Session : io/sentry/JsonSerializable, io/sentry/JsonUnknown {
19381944
public fun <init> (Lio/sentry/Session$State;Ljava/util/Date;Ljava/util/Date;ILjava/lang/String;Ljava/util/UUID;Ljava/lang/Boolean;Ljava/lang/Long;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
19391945
public fun <init> (Ljava/lang/String;Lio/sentry/protocol/User;Ljava/lang/String;Ljava/lang/String;)V
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package io.sentry;
2+
3+
import java.util.concurrent.Callable;
4+
import java.util.function.Supplier;
5+
import org.jetbrains.annotations.NotNull;
6+
7+
/**
8+
* Helper class that provides wrappers around:
9+
*
10+
* <ul>
11+
* <li>{@link Callable}
12+
* <li>{@link Supplier}
13+
* </ul>
14+
*
15+
* that clones the Hub before execution and restores it afterwards. This prevents reused threads
16+
* (e.g. from thread-pools) from getting an incorrect state.
17+
*/
18+
public final class SentryWrapper {
19+
20+
/**
21+
* Helper method to wrap {@link Callable}
22+
*
23+
* <p>Clones the Hub before execution and restores it afterwards. This prevents reused threads
24+
* (e.g. from thread-pools) from getting an incorrect state.
25+
*
26+
* @param callable - the {@link Callable} to be wrapped
27+
* @return the wrapped {@link Callable}
28+
* @param <U> - the result type of the {@link Callable}
29+
*/
30+
public static <U> Callable<U> wrapCallable(final @NotNull Callable<U> callable) {
31+
final IHub oldState = Sentry.getCurrentHub();
32+
final IHub newHub = oldState.clone();
33+
34+
return () -> {
35+
Sentry.setCurrentHub(newHub);
36+
try {
37+
return callable.call();
38+
} finally {
39+
Sentry.setCurrentHub(oldState);
40+
}
41+
};
42+
}
43+
44+
/**
45+
* Helper method to wrap {@link Supplier}
46+
*
47+
* <p>Clones the Hub before execution and restores it afterwards. This prevents reused threads
48+
* (e.g. from thread-pools) from getting an incorrect state.
49+
*
50+
* @param supplier - the {@link Supplier} to be wrapped
51+
* @return the wrapped {@link Supplier}
52+
* @param <U> - the result type of the {@link Supplier}
53+
*/
54+
public static <U> Supplier<U> wrapSupplier(final @NotNull Supplier<U> supplier) {
55+
final IHub oldState = Sentry.getCurrentHub();
56+
final IHub newHub = oldState.clone();
57+
58+
return () -> {
59+
Sentry.setCurrentHub(newHub);
60+
try {
61+
return supplier.get();
62+
} finally {
63+
Sentry.setCurrentHub(oldState);
64+
}
65+
};
66+
}
67+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package io.sentry
2+
3+
import java.util.concurrent.CompletableFuture
4+
import java.util.concurrent.Executors
5+
import kotlin.test.AfterTest
6+
import kotlin.test.BeforeTest
7+
import kotlin.test.Test
8+
import kotlin.test.assertEquals
9+
10+
class SentryWrapperTest {
11+
12+
private val dsn = "http://key@localhost/proj"
13+
private val executor = Executors.newSingleThreadExecutor()
14+
15+
@BeforeTest
16+
@AfterTest
17+
fun beforeTest() {
18+
Sentry.close()
19+
SentryCrashLastRunState.getInstance().reset()
20+
}
21+
22+
@Test
23+
fun `wrapped supply async isolates Hubs`() {
24+
val capturedEvents = mutableListOf<SentryEvent>()
25+
26+
Sentry.init {
27+
it.dsn = dsn
28+
it.beforeSend = SentryOptions.BeforeSendCallback { event, hint ->
29+
capturedEvents.add(event)
30+
event
31+
}
32+
}
33+
34+
Sentry.addBreadcrumb("MyOriginalBreadcrumbBefore")
35+
Sentry.captureMessage("OriginalMessageBefore")
36+
37+
val callableFuture =
38+
CompletableFuture.supplyAsync(
39+
SentryWrapper.wrapSupplier {
40+
Sentry.addBreadcrumb("MyClonedBreadcrumb")
41+
Sentry.captureMessage("ClonedMessage")
42+
"Result 1"
43+
},
44+
executor
45+
)
46+
47+
val callableFuture2 =
48+
CompletableFuture.supplyAsync(
49+
SentryWrapper.wrapSupplier {
50+
Sentry.addBreadcrumb("MyClonedBreadcrumb2")
51+
Sentry.captureMessage("ClonedMessage2")
52+
"Result 2"
53+
},
54+
executor
55+
)
56+
57+
Sentry.addBreadcrumb("MyOriginalBreadcrumb")
58+
Sentry.captureMessage("OriginalMessage")
59+
60+
callableFuture.join()
61+
callableFuture2.join()
62+
63+
val mainEvent = capturedEvents.firstOrNull { it.message?.formatted == "OriginalMessage" }
64+
val clonedEvent = capturedEvents.firstOrNull { it.message?.formatted == "ClonedMessage" }
65+
val clonedEvent2 = capturedEvents.firstOrNull { it.message?.formatted == "ClonedMessage2" }
66+
67+
assertEquals(2, mainEvent?.breadcrumbs?.size)
68+
assertEquals(2, clonedEvent?.breadcrumbs?.size)
69+
assertEquals(2, clonedEvent2?.breadcrumbs?.size)
70+
}
71+
72+
@Test
73+
fun `wrapped callable isolates Hubs`() {
74+
val capturedEvents = mutableListOf<SentryEvent>()
75+
76+
Sentry.init {
77+
it.dsn = dsn
78+
it.beforeSend = SentryOptions.BeforeSendCallback { event, hint ->
79+
capturedEvents.add(event)
80+
event
81+
}
82+
}
83+
84+
Sentry.addBreadcrumb("MyOriginalBreadcrumbBefore")
85+
Sentry.captureMessage("OriginalMessageBefore")
86+
println(Thread.currentThread().name)
87+
88+
val future1 = executor.submit(
89+
SentryWrapper.wrapCallable {
90+
Sentry.addBreadcrumb("MyClonedBreadcrumb")
91+
Sentry.captureMessage("ClonedMessage")
92+
"Result 1"
93+
}
94+
)
95+
96+
val future2 = executor.submit(
97+
SentryWrapper.wrapCallable {
98+
Sentry.addBreadcrumb("MyClonedBreadcrumb2")
99+
Sentry.captureMessage("ClonedMessage2")
100+
"Result 2"
101+
}
102+
)
103+
104+
Sentry.addBreadcrumb("MyOriginalBreadcrumb")
105+
Sentry.captureMessage("OriginalMessage")
106+
107+
future1.get()
108+
future2.get()
109+
110+
val mainEvent = capturedEvents.firstOrNull { it.message?.formatted == "OriginalMessage" }
111+
val clonedEvent = capturedEvents.firstOrNull { it.message?.formatted == "ClonedMessage" }
112+
val clonedEvent2 = capturedEvents.firstOrNull { it.message?.formatted == "ClonedMessage2" }
113+
114+
assertEquals(2, mainEvent?.breadcrumbs?.size)
115+
assertEquals(2, clonedEvent?.breadcrumbs?.size)
116+
assertEquals(2, clonedEvent2?.breadcrumbs?.size)
117+
}
118+
}

0 commit comments

Comments
 (0)