Skip to content
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

Allow closing scopes out of order #5303

Open
laurit opened this issue Mar 15, 2023 · 1 comment
Open

Allow closing scopes out of order #5303

laurit opened this issue Mar 15, 2023 · 1 comment
Labels
Feature Request Suggest an idea for this project

Comments

@laurit
Copy link
Contributor

laurit commented Mar 15, 2023

Currently closing scopes out of order is disallowed in

This poses a challenge when implementing instrumentation for Zio. Zio instrumentation is similar to what we have for kotlin coroutines. Every time a fiber is restored we activate otel context, when fiber is suspended we close the scope and remember the current context so that it could be activated when the fiber is restored again. The problem is that the user code executing in fibers can also activate scopes. Fiber could get suspended when user code has started the span and activated the scope but has not closed them yet. In such situation attempt to close our scope fails because it is not the current one. As a workaround we can run Context.root().makeCurrent() on suspend to avoid leaking scope to the thread that executed the fiber.
Kotlin coroutine instrumentation does not run into the same problem because there the programming model encourages users to keep the active scope in coroutine context so instead of context.makeCurrent() the user is expected to use withContext(context.asContextElement()) {...}.
As an alternative to allowing scopes to close out of order we could continue using Context.root().makeCurrent() to reset the scope or see if we can, similarly to kotlin coroutine instrumentation, let users keep scope in some zio fiber specific context. I don't particularly like the last option as it will surely create problems when fibers call instrumentations that expect to get the scope from the thread local.
If allowing closing scopes out of order is not desirable I'd welcome guidance on how such instrumentation should be implemented.

@laurit laurit added the Feature Request Suggest an idea for this project label Mar 15, 2023
@jack-berg
Copy link
Member

I wrote a little test to better understand the flow and behavior:

    ContextKey<String> fiberKey = ContextKey.named("fiber");
    ContextKey<String> userKey = ContextKey.named("user");

    Context threadContext = Context.root().with(fiberKey, "1");
    Context fiberContext = Context.root().with(fiberKey, "2");
    Context userContext = fiberContext.with(userKey, "a");

    // Establish thread context before fiber.
    Scope threadScope = threadContext.makeCurrent();
    Assertions.assertThat(Context.current().get(fiberKey)).isEqualTo("1");
    Assertions.assertThat(Context.current().get(userKey)).isEqualTo(null);

    // Restore fiber. Activate fiber context.
    Scope fiberScope = fiberContext.makeCurrent();
    Assertions.assertThat(Context.current().get(fiberKey)).isEqualTo("2");
    Assertions.assertThat(Context.current().get(userKey)).isEqualTo(null);

    // Set user context within fiber.
    Scope userScope = userContext.makeCurrent();
    Assertions.assertThat(Context.current().get(fiberKey)).isEqualTo("2");
    Assertions.assertThat(Context.current().get(userKey)).isEqualTo("a");

    // Suspend fiber. Close fiber scope, remember current context. Context should be restored to initial state.
    fiberContext = Context.current();
    fiberScope.close();
    // THIS ASSERTION FAILS with the current behavior of if (!closed && current() == toAttach)
    Assertions.assertThat(Context.current().get(fiberKey)).isEqualTo("1");
    Assertions.assertThat(Context.current().get(userKey)).isEqualTo(null);

    // Restore fiber. Activate context.
    fiberScope = fiberContext.makeCurrent();
    Assertions.assertThat(Context.current().get(fiberKey)).isEqualTo("2");
    Assertions.assertThat(Context.current().get(userKey)).isEqualTo("a");

Indeed closing the fiber scope fails and the fiber context is leaked into the thread when it should close.

Relaxing the if (!closed && current() == toAttach) condition to if (!closed) fixes the problem.

If I understand your suggestion to use Context.root().makeCurrent() is meant to reset the thread scope to the root after suspension rather than trying to close the fiber scope, which may fail. This means that when the fiber is suspended the thread scope becomes empty instead of attempting to restore it to the state before the fiber was restored. Hope I got that right?

If so, Context.root().makeCurrent() doesn't seem like a bad option if the threads running the zio fibers aren't expected to do anything else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature Request Suggest an idea for this project
Projects
None yet
Development

No branches or pull requests

2 participants