Quarkus 本机模式下的手动上下文传播

Manual context propagation in Quarkus native mode

我正在尝试让上下文传播在 Quarkus 本机模式下工作。
下面的代码在 JVM 模式下按预期工作,但在本机模式下 returns MDC value: null
“按预期”我的意思是:
curl http://localhost:8080/thread-context 的响应是 MDC value: from-thread-context

@Inject
ManagedExecutor managedExecutor;

@Inject
ThreadContext threadContext;

private final Supplier<String> mdcValueSupplier =
        () -> "MDC value:  " + MDC.get("foo") + "\n";

@GET
@Path("thread-context")
public String get() throws ExecutionException, InterruptedException {
    MDC.put("foo", "from-thread-context");
    Supplier<String> ctxSupplier = threadContext.contextualSupplier(mdcValueSupplier);
    return managedExecutor.supplyAsync(ctxSupplier).get();
}

我创建了一个 github repo,其中包含演示应用程序的完整代码和重现该问题的分步说明。

依赖项 io.quarkus:quarkus-smallrye-context-propagation 存在。
Quarkus 版本:1.9.2

问:是我的代码有问题,还是 Quarkus 有问题?

供参考:Quarkus documentatin on context propagation

您的代码基本上没问题 [1],Quarkus 在这方面也很好——但有两点需要理解。

第一,您没有进行任何类型的“手动上下文传播”。您的代码是偶然运行的,因为 Quarkus 使用 JBoss LogManager 作为记录器,并且它的 MDC 不是普通的 ThreadLocal,而是 InheritableThreadLocal。所以它有时有点传播上下文本身。但这没什么可依赖的。例如,如果您进行实时重新加载(通过稍微修改代码并再次 运行 curl),它也会在 JVM 模式下停止工作。

第二,上下文传播的关键是将线程本地状态从一个线程传输到另一个线程,但这不会自动发生。要么你自己做(那将是“手动上下文传播”),通过调用相应的 APIs,或者你可以实现一个 ThreadContextProvider.

我简要查看了 MDC API (http://www.slf4j.org/api/org/slf4j/MDC.html),似乎可以使用 getCopyOfContextMapsetContextMap 实现基本的上下文传播。这是我快速组合的一个实现——注意,我没有对代码进行太多测试:

import org.eclipse.microprofile.context.spi.ThreadContextProvider;
import org.eclipse.microprofile.context.spi.ThreadContextSnapshot;
import org.slf4j.MDC;

import java.util.Map;

public class MdcContextProvider implements ThreadContextProvider {
    @Override
    public ThreadContextSnapshot currentContext(Map<String, String> props) {
        Map<String, String> propagate = MDC.getCopyOfContextMap();
        return () -> {
            Map<String, String> old = MDC.getCopyOfContextMap();
            MDC.setContextMap(propagate);
            return () -> {
                MDC.setContextMap(old);
            };
        };
    }

    @Override
    public ThreadContextSnapshot clearedContext(Map<String, String> props) {
        return () -> {
            Map<String, String> old = MDC.getCopyOfContextMap();
            MDC.clear();
            return () -> {
                MDC.setContextMap(old);
            };
        };
    }

    @Override
    public String getThreadContextType() {
        return "SLF4J MDC";
    }
}

如果您创建一个包含此 class 的完全限定名称的 META-INF/services/org.eclipse.microprofile.context.spi.ThreadContextProvider 文件,那么 MDC 传播应该适合您,即使是在本机中也是如此。

一个可能的问题是,无论您在新线程上对 MDC 所做的任何更改都不会传播回原始线程,因为 SLF4J 有意不提供对支持映射的访问,它只分发副本。这可能适合你,也可能不适合。

[1] 如果您将 ManagedExecutor 提交给 ManagedExecutor,则不必通过 ThreadContext.contextualSupplier 将您的 Supplier“上下文化”——ManagedExecutor 会自动执行此操作.