-
-
Notifications
You must be signed in to change notification settings - Fork 449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prevent SentryTracer.getLatestActiveSpan from copying all child spans to avoid OOMs #2815
Comments
Also applies to |
We should check for all places where we use the |
These are the two locations the Tracer copies the list sentry-java/sentry/src/main/java/io/sentry/SentryTracer.java Lines 699 to 709 in f274c79
sentry-java/sentry/src/main/java/io/sentry/SentryTracer.java Lines 521 to 531 in f274c79
|
Aren't those locations simply copying references to |
As far as I know, |
OK so this shouldn't be the cause of OOM just where it surfaced. It should only allocate little memory and can be garbage collected right away, no risk of anyone holding on to references as we don't return the list. Shall we close this then? |
@adinauer yes you're right, the elements of the array are not cloned / deep copied. According to SDK console this issue is still our no. 1 overall issue. I can imagine situations where there are many spans or SentryTracer.getLatestActiveSpan is called very frequently causing OOMs. |
We could just forgoe the copying of the array since We need to test to make sure that's actually the case and it doesn't throw when under load. We need to make sure to use the iterator instead of iterating based on the index. To get reverse we can use:
Both For Testing this: Create a setup that concurrently from multiple threads starts new spans and invokes the affected methods. With In a loop check affected methods. Idea around copying the array on write instead of on readWe could move the copy of the array from "on read" to "on write". So instead of temporarily creating a new (shallow) copy of the child list for every invocation of We can consider this an immutable list but the reference will be replaced on every write. The methods Whenever |
I've ran some tests Using the following codepackage io.sentry.samples.console;
import io.sentry.ISpan;
import io.sentry.ITransaction;
import io.sentry.Sentry;
import io.sentry.Span;
import io.sentry.TransactionOptions;
import java.util.List;
import java.util.Random;
public class Main {
public static int ITERATIONS = 1000000000;
public static void main(String[] args) throws InterruptedException {
Sentry.init(
options -> {
options.setDsn("https://[email protected]/4508683222843393");
options.setTracesSampleRate(1.0);
});
TransactionOptions opts = new TransactionOptions();
opts.setBindToScope(true);
ITransaction tx = Sentry.startTransaction("lol", "op", opts);
Thread thread1 = new Thread(new SpanStarter());
Thread thread2 = new Thread(new LatestSpanGetter());
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class SpanStarter implements Runnable {
@Override
public void run() {
try {
Random random = new Random();
ITransaction tx = Sentry.getCurrentScopes().getTransaction();
for (int i = 0; i < Main.ITERATIONS; i++) {
List<Span> spans = tx.getSpans();
if (!spans.isEmpty() & i % 3 == 0) {
Integer w = random.nextInt(spans.size());
spans.remove(w);
}
Integer r = random.nextInt(Integer.MAX_VALUE);
tx.startChild(r.toString());
}
} catch (Exception e) {
System.out.println(e.toString());
}
System.out.println("SpanStarter finished");
}
}
class LatestSpanGetter implements Runnable {
@Override
public void run() {
Integer x = 0;
try {
ITransaction tx = Sentry.getCurrentScopes().getTransaction();
for (int i = 0; i < 100 * Main.ITERATIONS; i++) {
ISpan span = tx.getLatestActiveSpan();
if (span != null) {
x += Integer.parseInt(span.getOperation());
}
}
} catch (Exception e) {
System.out.println(e.toString());
}
System.out.println(x);
System.out.println("LatestSpanGetter finished");
}
} When replacing the current implementation of
Therefore we will move forward with not copying and also accessing the list using an iterator. To perform the checks starting from the last span we need to "consume" the iterator (calling |
Description
check
SentryTracer.getLatestActiveSpan
and
SentryTracer.hasAllChildrenFinished
The text was updated successfully, but these errors were encountered: