Skip to content

Coroutines will not nest properly past one level when using the global hub #1822

@acdcjunior

Description

@acdcjunior

Platform:

  • Java -> 11/11
  • Kotlin -> 1.5.21 (target jdk 11)

IDE:

  • IntelliJ -> 2021.2.2

Build system:

  • Gradle -> 6.8.3

Android Gradle Plugin:

  • No

Sentry Android Gradle Plugin:

  • No

Platform installed with:

  • Maven Central

The version of the SDK:
5.4.2


I have the following issue:

Coroutines don't nest properly.

From what I could see in the debugger, any coroutine (even when using SentryContext(), which means it has its own Hub) when creating a Span creates in the main transaction, therefore the nesting makes no sense.

It seems this issue happens because structure used to track spans is flat and not a tree.

Steps to reproduce:

import io.sentry.ITransaction
import io.sentry.Sentry
import io.sentry.kotlin.SentryContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking


fun main() {
    Sentry.init { options ->
        options.dsn = "https://[email protected]/6088339"
        options.tracesSampleRate = 1.0
        options.setDebug(true)
    }

    val transaction: ITransaction = Sentry.getCurrentHub().startTransaction("my-coroutines-transaction5", "outer op")
    Sentry.configureScope { scope ->
        scope.transaction = transaction
    }

    runBlocking(SentryContext()) {
        launch(SentryContext()) {
            val level1a = Sentry.getCurrentHub().span!!.startChild("level-1a")
            launch(SentryContext()) {
                val level1a1 = Sentry.getCurrentHub().span!!.startChild("level-1a-1")
                delay(250)
                level1a1.finish()
            }
            launch(SentryContext()) {
                val level1a2 = Sentry.getCurrentHub().span!!.startChild("level-1a-2")
                delay(200)
                level1a2.finish()
            }

            level1a.finish()
        }
        launch(SentryContext()) {
            val level1b = Sentry.getCurrentHub().span!!.startChild("level-1b")

            launch(SentryContext()) {
                val level1b1 = Sentry.getCurrentHub().span!!.startChild("level-1b-1")
                delay(100)
                level1b1.finish()
            }
            launch(SentryContext()) {
                val level1b2 = Sentry.getCurrentHub().span!!.startChild("level-1b-2")
                delay(50)
                level1b2.finish()
            }

            level1b.finish()
        }
    }
    transaction.finish()

}

Actual result:
image

Expected result:

  • I would expect a better nesting in the left-side tree.

I faced this issue when configuring sentry-openfeign. In my code, there are some coroutines that call feign clients (which are run wrapped in launch), this all happens in parallel. This makes the nesting get all mixed up. It's a spring-boot app.

Perhaps you could argue I shouldn't be getting the span from the "global hub" (Sentry.getCurrentHub().span), but notice the snippet above is just a minimal reproduction of the actual case. In the actual case, the issue arises when SentryFeignClient creates mixed nested entries. Therefore the snippet aboves use the same method to obtain the current span as SentryFeignClient.

The problem seems that SentryContext() clones the Hub, but all cloned hubs have the same transaction, thus when one coroutine adds a span, if other coroutine (which is running in parallel) attempts to add a new span, it is added to the last active span, and not the span of when the hub was cloned (or other level nested to such span).

Metadata

Metadata

Assignees

No one assigned

    Labels

    ThreadLocalHubProblemIssues caused by Hub being ThreadLocal and no or no easy way to clone the hubperformancePerformance API issues

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions