Skip to content

Memory Leak - Object throwableToSpan keeps growing over time #2225

@klem231188

Description

@klem231188

Integration

sentry-spring

Java Version

11

Version

5.7.4

Steps to Reproduce

I use Sentry in a springboot project.
The project offers some REST and SOAP endpoints used by third parties.
I use Sentry mainly to :

  • easily find bottlenecks (using sentry-jdbc)
  • collect errors and group them in "Issues" (using sentry-logback)

Here are the artifacts I use:

		<dependency>
			<groupId>io.sentry</groupId>
			<artifactId>sentry-spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>io.sentry</groupId>
			<artifactId>sentry-logback</artifactId>
		</dependency>

		<dependency>
			<groupId>io.sentry</groupId>
			<artifactId>sentry-jdbc</artifactId>
		</dependency>

		<dependency>
			<groupId>com.github.gavlyukovskiy</groupId>
			<artifactId>p6spy-spring-boot-starter</artifactId>
			<version>${p6spy-spring-boot-starter.version}</version>
		</dependency>

In production, there are around 50K calls/week. A few weeks ago I got my first OOM which surprised me as the app generally works like a charm.

It happened few times after I added the Sentry features to the project. Unfortunately, an API caller made a lot (lot, lot) of unsuccessful calls successively.
So I tried to reproduce it using a LoadTest: 5 threads doing 100_000 iteration loop, wrongly calling the api which generates an error.
The idea is to look at the Heap space over time and perform a heap dump at the end.

Here is my analysis :
In my controller, I do the try/catch/finally block manually to handle the errors.
In the catch block, I do the following thing:

            // Handle Sentry transaction
            ISpan transaction = Sentry.getSpan();
            if (transaction != null) {
                transaction.setThrowable(e);
                transaction.setStatus(SpanStatus.INTERNAL_ERROR);
            }

Note that I do NOT propagate the exception at the end of the catch block.
Before returning the response, it enters into the SentryTracingFilter, in the finally block it calls transaction.finish() (line 87)
In this piece of code, it stores the throwable in the throwbableToSpan map of the Hub .
However, I can't see any throwbableToSpan.remove() in the code. I noticed that you use a WeakHashMap, but I didn't find neither where the key reference is set to null. --> So the map keeps growing over time.

Either in the catch block of my controller, I should not store the throwable like that... or there is an error in the Sentry lib.

Expected Result

The field throwableToSpan from class Hub should not grow indefinitely.

Actual Result

I get an OOM
Below some screenshots of visualvm + IntelliJ debug breakpoint.
The heapdump is more than 500MB, so I can't upload it here.
sentry-visualvm
sentry-visualvm-throwableToSpan
sentry-debug-1

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions