David
David

Reputation: 413

Can a ScopedValue be applied to "the current" StructuredTaskScope without creating a new task?

I'm investigating using ScopedValue and StructuredTaskScope to replace usage of Google's gRPC context.

Both systems are nicely immutable, lightweight and support nested scopes, but I think gRPC context does something I need which might not be available via ScopedValue (which would be a real shame if true).

In various frameworks, I might need to enable debugging on a per-request basis, turning on additional logging if a request matches some criteria. However my code is never in a position to wrap some function in order to nest it in a new task, it's simply code which gets called and must exit before the request is handled. In pseudo-code:

// Assume one handler per request.
class MyHandler implements SomeFrameworkCallbackApi {
    MyCloseable resetDebug = null;

    @Override
    void beforeRequest(RequestHeaders req, ConfigInfo info) {
        if (info.addLogging() && shouldDebug(req)) {
            Context newGrpcContext = Context.current().withValue(DEBUG_LEVEL, Level.FINE);
            Context prev = newGrpcContext.attach();
            resetDebug = () -> newGrpcContext.detach(prev);
        }
    }

    @Override
    void afterRequest(...) {
        if (resetDebug != null) {
            // Fails if not called in strict nesting order.
            resetDebug.close();
        }
    }
}

This lets me have scoped variables even in situations where I am not able to modify code which can wrap the task directly.

From looking at the APIs for ScopedValue and StructuredTaskScope it looks like the only way to run code with a ScopedValue installed is to call a method such as run(), which forces a nesting call to the task at that point (which is not possible in many frameworks).

I had tried:

resetDebug = ScopedValue.getWhere(DEBUG_LEVEL, Level.FINE, StructuredTaskScope::new)::close;

But this results in java.util.concurrent.StructureViolationException (not unexpectedly).

So my question is, is there any other way to use ScopedValue (with or without StructuredTaskScope) which might achieve what I'm after, or do I need to stay with gRPC context?

Upvotes: 3

Views: 400

Answers (1)

David
David

Reputation: 413

From Brian Goetz:

""This is simply not how scoped values work, by design. What you seem to want is the unrestricted mutability of ThreadLocal, but with the improved performance of ScopedValue. But ScopedValue derives its benefits from the constraints you don't seem to like.""

So the answer is simply that ScopedValue can only be used in situations where scoping is achieved by wrapping code directly, rather than the use of some setup/teardown callbacks which occur before/after code is run.

This means that ScopedValue cannot be general replacement for existing ThreadLocal-based context libraries (e.g. gRPC Context or Open Telemetry contexts).

In my situation (setting up scoped values for a library which can be called from many flavours of framework), I simply don't have the freedom to require that users refactor their code, which sits between the framework and my library, to implement all their scoping via the direct wrapping of code.

If your need is similar to mine, then gRPC Context provides the additional functionality to allow scoping via the use of a returned Closeable to manage things (e.g. managing scopes via try-with-resources).

Upvotes: 1

Related Questions