ThreadLocals on GraphQL-Java

huangapple go评论71阅读模式
英文:

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&lt;Object&gt; {
		private DataFetcher&lt;?&gt; realDataFetcher;

		WrappedDataFetcher(DataFetcher&lt;?&gt; 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&lt;Object&gt; {
		private DataFetcher&lt;?&gt; wrappedDataFetcher;
		private ThreadPoolExecutor executor;


		WrappedDataFetcherThreadPool(DataFetcher&lt;?&gt; 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&lt;?&gt; future = executor.submit(() -&gt; 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库中有关线程行为的评论的链接:

https://spectrum.chat/graphql-java/general/how-to-supply-custom-executor-service-for-data-fetchers-to-run-on~29caa730-9114-4883-ab4a-e9700f225f93

英文:

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

https://spectrum.chat/graphql-java/general/how-to-supply-custom-executor-service-for-data-fetchers-to-run-on~29caa730-9114-4883-ab4a-e9700f225f93

huangapple
  • 本文由 发表于 2020年10月9日 19:24:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/64279103.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定