Quarkus纯模式下的手动上下文传播上下文、模式下、Quarkus

2023-09-03 14:19:38 作者:してる(我爱你)

我正在尝试使上下文传播在Quarkus纯模式下工作。 以下代码在JVM模式下按预期工作,但在本机模式下返回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") + "
";

@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,其中包含演示应用的完整代码和重现该问题的逐步说明。

quarkus IDEA debug

存在依赖项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模式下工作。

第二,上下文传播的要点是将线程本地状态从一个线程转移到另一个线程,但这不是自动发生的。您可以通过调用相应的API自己执行该操作(即手动上下文传播),也可以实现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";
    }
}

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

这样做可能存在的一个问题是,无论您在新线程上对MDC所做的任何更改都不会传播回原始线程,因为SLF4J故意不提供对后备映射的访问,它只分发副本。这对你来说可能没问题,也可能不好。

[1]如果您将ManagedExecutor提交给ManagedExecutor,则Supplier您的Supplier不必将Supplier提交给ManagedExecutorManagedExecutor会自动执行此操作。