Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
- Pass OpenTelemetry span attributes into TracesSampler callback ([#4253](https://github.com/getsentry/sentry-java/pull/4253))
- `SamplingContext` now has a `getAttribute` method that grants access to OpenTelemetry span attributes via their String key (e.g. `http.request.method`)
- Fix AbstractMethodError when using SentryTraced for Jetpack Compose ([#4255](https://github.com/getsentry/sentry-java/pull/4255))
- Avoid unnecessary copies when using `CopyOnWriteArrayList` ([#4247](https://github.com/getsentry/sentry-java/pull/4247))
- This affects in particular `SentryTracer.getLatestActiveSpan` which would have previously copied all child span references. This may have caused `OutOfMemoryError` on certain devices due to high frequency of calling the method.

### Features

Expand Down
1 change: 1 addition & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -6095,6 +6095,7 @@ public final class io/sentry/util/CollectionUtils {
public static fun newArrayList (Ljava/util/List;)Ljava/util/List;
public static fun newConcurrentHashMap (Ljava/util/Map;)Ljava/util/Map;
public static fun newHashMap (Ljava/util/Map;)Ljava/util/Map;
public static fun reverseListIterator (Ljava/util/concurrent/CopyOnWriteArrayList;)Ljava/util/ListIterator;
public static fun size (Ljava/lang/Iterable;)I
}

Expand Down
34 changes: 18 additions & 16 deletions sentry/src/main/java/io/sentry/SentryTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import io.sentry.protocol.SentryTransaction;
import io.sentry.protocol.TransactionNameSource;
import io.sentry.util.AutoClosableReentrantLock;
import io.sentry.util.CollectionUtils;
import io.sentry.util.Objects;
import io.sentry.util.SpanUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
Expand Down Expand Up @@ -155,7 +155,9 @@ private void onDeadlineTimeoutReached() {
// abort all child-spans first, this ensures the transaction can be finished,
// even if waitForChildren is true
// iterate in reverse order to ensure leaf spans are processed before their parents
@NotNull final ListIterator<Span> iterator = children.listIterator(children.size());
@NotNull
final ListIterator<Span> iterator =
CollectionUtils.reverseListIterator((CopyOnWriteArrayList<Span>) this.children);
while (iterator.hasPrevious()) {
@NotNull final Span span = iterator.previous();
span.setSpanFinishedCallback(null);
Expand Down Expand Up @@ -677,14 +679,13 @@ private void updateBaggageValues(final @NotNull Baggage baggage) {
}

private boolean hasAllChildrenFinished() {
final List<Span> spans = new ArrayList<>(this.children);
if (!spans.isEmpty()) {
for (final Span span : spans) {
// This is used in the spanFinishCallback, when the span isn't finished, but has a finish
// date
if (!span.isFinished() && span.getFinishDate() == null) {
return false;
}
@NotNull final ListIterator<Span> iterator = this.children.listIterator();
while (iterator.hasNext()) {
@NotNull final Span span = iterator.next();
// This is used in the spanFinishCallback, when the span isn't finished, but has a finish
// date
if (!span.isFinished() && span.getFinishDate() == null) {
return false;
}
}
return true;
Expand Down Expand Up @@ -909,12 +910,13 @@ public void setName(@NotNull String name, @NotNull TransactionNameSource transac

@Override
public @Nullable ISpan getLatestActiveSpan() {
final List<Span> spans = new ArrayList<>(this.children);
if (!spans.isEmpty()) {
for (int i = spans.size() - 1; i >= 0; i--) {
if (!spans.get(i).isFinished()) {
return spans.get(i);
}
@NotNull
final ListIterator<Span> iterator =
CollectionUtils.reverseListIterator((CopyOnWriteArrayList<Span>) this.children);
while (iterator.hasPrevious()) {
@NotNull final Span span = iterator.previous();
if (!span.isFinished()) {
return span;
}
}
return null;
Expand Down
21 changes: 21 additions & 0 deletions sentry/src/main/java/io/sentry/util/CollectionUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -179,4 +181,23 @@ public interface Predicate<T> {
public interface Mapper<T, R> {
R map(T t);
}

/**
* Returns a reverse iterator, where the first (resp. last) valid call to `prev` returns the last
* (resp. first) element that would be returned when iterating forwards. Note that this differs
* from the behavior of e.g. `org.apache.commons.collections4.iterators.ReverseListIterator`,
* where you need to iterate using `next` instead. We use the concrete type `CopyOnWriteArrayList`
* here as we are relying on the fact that its copy constructor only copies the reference to an
* internal array. We don't want to use this for other `List` implementations, as it could lead to
* an unnecessary copy of the elements instead.
*
* @param list the `CopyOnWriteArrayList` to get the reverse iterator for
* @param <T> the type
* @return a reverse iterator over `list`
*/
public static @NotNull <T> ListIterator<T> reverseListIterator(
final @NotNull CopyOnWriteArrayList<T> list) {
final @NotNull CopyOnWriteArrayList<T> copy = new CopyOnWriteArrayList<>(list);
return copy.listIterator(copy.size());
}
}
23 changes: 23 additions & 0 deletions sentry/src/test/java/io/sentry/util/CollectionUtilsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.sentry.util

import io.sentry.JsonObjectReader
import java.io.StringReader
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
Expand Down Expand Up @@ -79,4 +80,26 @@ class CollectionUtilsTest {
fun `contains returns false if element is not present`() {
assertFalse(CollectionUtils.contains(arrayOf("one", "two", "three"), "four"))
}

@Test
fun `reverseListIterator returns empty iterator if list is empty`() {
val list = CopyOnWriteArrayList<String>()
val iterator = CollectionUtils.reverseListIterator(list)
assertFalse(iterator.hasNext())
assertFalse(iterator.hasPrevious())
}

@Test
fun `reverseListIterator returns reversed iterator if list is not empty`() {
val elements = listOf("one", "two", "three")
val list = CopyOnWriteArrayList(elements)
val iterator = CollectionUtils.reverseListIterator(list)
assertFalse(iterator.hasNext())
assertTrue(iterator.hasPrevious())
val reversedElements = mutableListOf<String>()
while (iterator.hasPrevious()) {
reversedElements.add(iterator.previous())
}
assertEquals(elements.reversed(), reversedElements)
}
}
Loading