英文:
ThreadLocals on GraphQL-Java
问题
我正在在GraphQL上公开一个遗留的Web应用程序,但是这个Web应用程序使用Threadlocals(还有其他Apache Shiro)。
由于GraphQL-java似乎在并发方面使用了fork-join池
,我担心我需要付出多大努力来确保我的ThreadLocals仍然可以正常工作且安全工作。
阅读文档和源代码,似乎并发的很大一部分是通过返回CompletableFuture
的DataFetcher实现的。我无法确定这是否是唯一的并发源(我认为不是),以及DataFetchers
本身是否是从fork-join池
中调用的。
因此,将我的DataFetcher
包装在一个设置和清除ThreadLocals的委托中是否安全?或者这仍然存在被抢占并在fork-join池
中的另一个线程上继续执行的风险,类似于:
static class WrappedDataFetcher implements DataFetcher<Object> {
private DataFetcher<?> realDataFetcher;
WrappedDataFetcher(DataFetcher<?> realDataFetcher) {
this.realDataFetcher = realDataFetcher;
}
@Override
public Object get(DataFetchingEnvironment dataFetchingEnvironment) throws Exception {
try {
setThreadLocalsFromRequestOrContext(dataFetchingEnvironment);
return realDataFetcher.get(dataFetchingEnvironment);
} finally {
clearTreadLocals();
}
}
}
还是我需要明确地在Threadpool中运行我的DataFetchers,如:
static class WrappedDataFetcherThreadPool implements DataFetcher<Object> {
private DataFetcher<?> wrappedDataFetcher;
private ThreadPoolExecutor executor;
WrappedDataFetcherThreadPool(DataFetcher<?> realDataFetcher, ThreadPoolExecutor executor) {
// 在前面示例的包装器中进行包装,以确保执行器中的ThreadLocals
this.wrappedDataFetcher = new WrappedDataFetcher(realDataFetcher);
this.executor = executor;
}
@Override
public Object get(DataFetchingEnvironment dataFetchingEnvironment) throws Exception {
Future<?> future = executor.submit(() -> wrappedDataFetcher.get(dataFetchingEnvironment));
return future.get(); // 为了问题的简单性/清晰性
}
}
我认为第二种方法解决了我的问题,但感觉有点过头了,而且我担心性能问题。但我认为第一种方法存在被抢占的风险。
如果有更好的处理方法,我也很愿意听听您的意见。
注意:这不是关于GraphQL的异步性质(我也希望利用它),而是关于在可能由于fork-join池
而在多个带有ThreadLocals的请求之间运行可能会混淆请求之间的可能副作用。
英文:
I'm exposing a legacy web app on GraphQL, but this web app uses Threadlocals (amongst other Apache-Shiro).
Since GraphQL-java seems to be using the fork-join pool
for concurrency I worry about how far I need to go to ensure that my ThreadLocals still work and work safely.
Reading the documentation and the source it seems a large part of the concurrency is achieved by DataFetchers that return CompletableFuture
's I can't tell for sure if that's the only source of concurrency (i think not) and whether the DataFetchers
themselves are invoked from the fork-join pool
So would it be Safe to wrap my DataFetcher
's in a delegate that set and clears the ThreadLocals? or does that still have the risk of being preempted and continued on another thread in the fork-join pool
something like:
static class WrappedDataFetcher implements DataFetcher<Object> {
private DataFetcher<?> realDataFetcher;
WrappedDataFetcher(DataFetcher<?> realDataFetcher) {
this.realDataFetcher = realDataFetcher;
}
@Override
public Object get(DataFetchingEnvironment dataFetchingEnvironment) throws Exception {
try {
setThreadLocalsFromRequestOrContext(dataFetchingEnvironment);
return realDataFetcher.get(dataFetchingEnvironment);
} finally {
clearTreadLocals();
}
}
}
Or would I need to explicitly run my DataFetchers in a Threadpool like:
static class WrappedDataFetcherThreadPool implements DataFetcher<Object> {
private DataFetcher<?> wrappedDataFetcher;
private ThreadPoolExecutor executor;
WrappedDataFetcherThreadPool(DataFetcher<?> realDataFetcher, ThreadPoolExecutor executor) {
// Wrap in Wrapper from previous example to ensure threadlocals in the executor
this.wrappedDataFetcher = new WrappedDataFetcher(realDataFetcher);
this.executor = executor;
}
@Override
public Object get(DataFetchingEnvironment dataFetchingEnvironment) throws Exception {
Future<?> future = executor.submit(() -> wrappedDataFetcher.get(dataFetchingEnvironment));
return future.get(); //for simplicity / clarity of the question
}
}
I think the second one solves my problem but it feels like overkill and I worry about performance. But I think the first risks preemption.
If there is a better way to handle this I would love to hear it as well.
Note: this is not about the async nature of GraphQL (I hope to leverage that as well) but about the possible side effect of running multiple requests WITH treadLocals that might get mixed up between requests due to the fork-join pool
答案1
得分: 1
至我所知,graphql-java并未使用自己的线程池,而是依赖于应用程序来提供。它通过使用未来回调来实现这一点。假设这是应用程序的当前状态。
线程 T_1 携带线程局部存储 TLS_1 执行数据获取器 DF_1。
Graphql-java 引擎将同步回调附加到由 DF_1 返回的未来。如果未返回未来,则将结果包装在已完成的未来中,然后附加同步回调。由于回调是同步的,完成未来的线程运行回调。如果除 T_1 之外的任何其他线程完成了未来,则 TLS_1 将丢失(除非它被复制到执行线程)。其中一个示例是非阻塞 HTTP I/O 库,它使用 I/O 线程来完成响应未来。
以下是作者在graphql-java库中有关线程行为的评论的链接:
英文:
As far as I know graphql-java does not use its own thread pool and relies on the application for it. The way it achieves it using future callbacks. Say this is the current state of the application.
Thread T_1 with thread local storage TLS_1 executing data fetcher DF_1.
Graphql-java engine attaches a synchronous callback to the future returned by DF_1. If a future is not returned it wraps the result in a completed future and then attaches the synchronous callback. Since the callback is synchronous the thread that completes the future runs the callback. If any other thread apart from T_1 completes the future, TLS_1 is lost(unless it's copied over to the executing thread). One example of this is a non blocking HTTP I/O library which uses an I/O thread to complete the response future.
Here is a link where the authors have commented more on the thread behavior in graphql-java library
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论