-
-
Notifications
You must be signed in to change notification settings - Fork 429
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <bot+github-bot@sentry.io>
- Loading branch information
1 parent
e5c250b
commit 16cd2b6
Showing
4 changed files
with
197 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package io.sentry; | ||
|
||
import java.util.concurrent.Callable; | ||
import java.util.function.Supplier; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
/** | ||
* Helper class that provides wrappers around: | ||
* | ||
* <ul> | ||
* <li>{@link Callable} | ||
* <li>{@link Supplier} | ||
* </ul> | ||
* | ||
* that clones the Hub before execution and restores it afterwards. This prevents reused threads | ||
* (e.g. from thread-pools) from getting an incorrect state. | ||
*/ | ||
public final class SentryWrapper { | ||
|
||
/** | ||
* Helper method to wrap {@link Callable} | ||
* | ||
* <p>Clones the Hub before execution and restores it afterwards. This prevents reused threads | ||
* (e.g. from thread-pools) from getting an incorrect state. | ||
* | ||
* @param callable - the {@link Callable} to be wrapped | ||
* @return the wrapped {@link Callable} | ||
* @param <U> - the result type of the {@link Callable} | ||
*/ | ||
public static <U> Callable<U> wrapCallable(final @NotNull Callable<U> callable) { | ||
final IHub oldState = Sentry.getCurrentHub(); | ||
final IHub newHub = oldState.clone(); | ||
|
||
return () -> { | ||
Sentry.setCurrentHub(newHub); | ||
try { | ||
return callable.call(); | ||
} finally { | ||
Sentry.setCurrentHub(oldState); | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* Helper method to wrap {@link Supplier} | ||
* | ||
* <p>Clones the Hub before execution and restores it afterwards. This prevents reused threads | ||
* (e.g. from thread-pools) from getting an incorrect state. | ||
* | ||
* @param supplier - the {@link Supplier} to be wrapped | ||
* @return the wrapped {@link Supplier} | ||
* @param <U> - the result type of the {@link Supplier} | ||
*/ | ||
public static <U> Supplier<U> wrapSupplier(final @NotNull Supplier<U> supplier) { | ||
final IHub oldState = Sentry.getCurrentHub(); | ||
final IHub newHub = oldState.clone(); | ||
|
||
return () -> { | ||
Sentry.setCurrentHub(newHub); | ||
try { | ||
return supplier.get(); | ||
} finally { | ||
Sentry.setCurrentHub(oldState); | ||
} | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package io.sentry | ||
|
||
import java.util.concurrent.CompletableFuture | ||
import java.util.concurrent.Executors | ||
import kotlin.test.AfterTest | ||
import kotlin.test.BeforeTest | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
|
||
class SentryWrapperTest { | ||
|
||
private val dsn = "http://key@localhost/proj" | ||
private val executor = Executors.newSingleThreadExecutor() | ||
|
||
@BeforeTest | ||
@AfterTest | ||
fun beforeTest() { | ||
Sentry.close() | ||
SentryCrashLastRunState.getInstance().reset() | ||
} | ||
|
||
@Test | ||
fun `wrapped supply async isolates Hubs`() { | ||
val capturedEvents = mutableListOf<SentryEvent>() | ||
|
||
Sentry.init { | ||
it.dsn = dsn | ||
it.beforeSend = SentryOptions.BeforeSendCallback { event, hint -> | ||
capturedEvents.add(event) | ||
event | ||
} | ||
} | ||
|
||
Sentry.addBreadcrumb("MyOriginalBreadcrumbBefore") | ||
Sentry.captureMessage("OriginalMessageBefore") | ||
|
||
val callableFuture = | ||
CompletableFuture.supplyAsync( | ||
SentryWrapper.wrapSupplier { | ||
Sentry.addBreadcrumb("MyClonedBreadcrumb") | ||
Sentry.captureMessage("ClonedMessage") | ||
"Result 1" | ||
}, | ||
executor | ||
) | ||
|
||
val callableFuture2 = | ||
CompletableFuture.supplyAsync( | ||
SentryWrapper.wrapSupplier { | ||
Sentry.addBreadcrumb("MyClonedBreadcrumb2") | ||
Sentry.captureMessage("ClonedMessage2") | ||
"Result 2" | ||
}, | ||
executor | ||
) | ||
|
||
Sentry.addBreadcrumb("MyOriginalBreadcrumb") | ||
Sentry.captureMessage("OriginalMessage") | ||
|
||
callableFuture.join() | ||
callableFuture2.join() | ||
|
||
val mainEvent = capturedEvents.firstOrNull { it.message?.formatted == "OriginalMessage" } | ||
val clonedEvent = capturedEvents.firstOrNull { it.message?.formatted == "ClonedMessage" } | ||
val clonedEvent2 = capturedEvents.firstOrNull { it.message?.formatted == "ClonedMessage2" } | ||
|
||
assertEquals(2, mainEvent?.breadcrumbs?.size) | ||
assertEquals(2, clonedEvent?.breadcrumbs?.size) | ||
assertEquals(2, clonedEvent2?.breadcrumbs?.size) | ||
} | ||
|
||
@Test | ||
fun `wrapped callable isolates Hubs`() { | ||
val capturedEvents = mutableListOf<SentryEvent>() | ||
|
||
Sentry.init { | ||
it.dsn = dsn | ||
it.beforeSend = SentryOptions.BeforeSendCallback { event, hint -> | ||
capturedEvents.add(event) | ||
event | ||
} | ||
} | ||
|
||
Sentry.addBreadcrumb("MyOriginalBreadcrumbBefore") | ||
Sentry.captureMessage("OriginalMessageBefore") | ||
println(Thread.currentThread().name) | ||
|
||
val future1 = executor.submit( | ||
SentryWrapper.wrapCallable { | ||
Sentry.addBreadcrumb("MyClonedBreadcrumb") | ||
Sentry.captureMessage("ClonedMessage") | ||
"Result 1" | ||
} | ||
) | ||
|
||
val future2 = executor.submit( | ||
SentryWrapper.wrapCallable { | ||
Sentry.addBreadcrumb("MyClonedBreadcrumb2") | ||
Sentry.captureMessage("ClonedMessage2") | ||
"Result 2" | ||
} | ||
) | ||
|
||
Sentry.addBreadcrumb("MyOriginalBreadcrumb") | ||
Sentry.captureMessage("OriginalMessage") | ||
|
||
future1.get() | ||
future2.get() | ||
|
||
val mainEvent = capturedEvents.firstOrNull { it.message?.formatted == "OriginalMessage" } | ||
val clonedEvent = capturedEvents.firstOrNull { it.message?.formatted == "ClonedMessage" } | ||
val clonedEvent2 = capturedEvents.firstOrNull { it.message?.formatted == "ClonedMessage2" } | ||
|
||
assertEquals(2, mainEvent?.breadcrumbs?.size) | ||
assertEquals(2, clonedEvent?.breadcrumbs?.size) | ||
assertEquals(2, clonedEvent2?.breadcrumbs?.size) | ||
} | ||
} |