From cc45d5c82274aebf40818f11088900b8a067f74f Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 3 Nov 2022 12:31:19 +0100 Subject: [PATCH] Fix NoSuchElementException in CircularFifoQueue when cloning a Scope (#2328) --- CHANGELOG.md | 1 + sentry/src/main/java/io/sentry/Scope.java | 2 +- .../java/io/sentry/SynchronizedQueue.java | 14 +++++++++++ sentry/src/test/java/io/sentry/ScopeTest.kt | 25 +++++++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1f159dc54..d885e51810 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - Use correct set-cookie for the HTTP Client response object ([#2326](https://github.com/getsentry/sentry-java/pull/2326)) +- Fix NoSuchElementException in CircularFifoQueue when cloning a Scope ([#2328](https://github.com/getsentry/sentry-java/pull/2328)) ### Features diff --git a/sentry/src/main/java/io/sentry/Scope.java b/sentry/src/main/java/io/sentry/Scope.java index 16a9f535e2..0d5904fc6a 100644 --- a/sentry/src/main/java/io/sentry/Scope.java +++ b/sentry/src/main/java/io/sentry/Scope.java @@ -97,7 +97,7 @@ public Scope(final @NotNull SentryOptions options) { this.fingerprint = new ArrayList<>(scope.fingerprint); this.eventProcessors = new CopyOnWriteArrayList<>(scope.eventProcessors); - final Queue breadcrumbsRef = scope.breadcrumbs; + final Breadcrumb[] breadcrumbsRef = scope.breadcrumbs.toArray(new Breadcrumb[0]); Queue breadcrumbsClone = createBreadcrumbsList(scope.options.getMaxBreadcrumbs()); diff --git a/sentry/src/main/java/io/sentry/SynchronizedQueue.java b/sentry/src/main/java/io/sentry/SynchronizedQueue.java index 6236f052df..5e07606cfb 100644 --- a/sentry/src/main/java/io/sentry/SynchronizedQueue.java +++ b/sentry/src/main/java/io/sentry/SynchronizedQueue.java @@ -131,4 +131,18 @@ public E remove() { return decorated().remove(); } } + + @Override + public Object[] toArray() { + synchronized (lock) { + return decorated().toArray(); + } + } + + @Override + public T[] toArray(T[] object) { + synchronized (lock) { + return decorated().toArray(object); + } + } } diff --git a/sentry/src/test/java/io/sentry/ScopeTest.kt b/sentry/src/test/java/io/sentry/ScopeTest.kt index 290aeb02d6..f8fdb5d91d 100644 --- a/sentry/src/test/java/io/sentry/ScopeTest.kt +++ b/sentry/src/test/java/io/sentry/ScopeTest.kt @@ -217,6 +217,31 @@ class ScopeTest { assertTrue(clone.attachments is CopyOnWriteArrayList) } + @Test + fun `copying scope won't crash if there are concurrent operations`() { + val options = SentryOptions().apply { + maxBreadcrumbs = 10000 + } + val scope = Scope(options) + for (i in 0 until options.maxBreadcrumbs) { + scope.addBreadcrumb(Breadcrumb.info("item")) + } + + // remove one breadcrumb after the other on an extra thread + Thread({ + while (scope.breadcrumbs.isNotEmpty()) { + scope.breadcrumbs.remove() + } + }, "thread-breadcrumb-remover").start() + + // clone in the meantime + while (scope.breadcrumbs.isNotEmpty()) { + Scope(scope) + } + + // expect no exception to be thrown ¯\_(ツ)_/¯ + } + @Test fun `clear scope resets scope to default state`() { val scope = Scope(SentryOptions())