Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context propagation? #194

Closed
stephenh opened this issue Sep 9, 2018 · 7 comments
Closed

Context propagation? #194

stephenh opened this issue Sep 9, 2018 · 7 comments

Comments

@stephenh
Copy link

stephenh commented Sep 9, 2018

Hi; this is not an issue, but a question; sorry, I couldn't find/didn't see a better forum to ask.

Does Parseq have an API/mechanism for passing context along within a task?

I know/assume this machinery exists within Parseq itself for doing tracking/execution, but I'd basically like ThreadLocal-style hooks to put user-/application-defined state in (like our own request/trace ids, or other app context), and, from looking at the API/examples, I don't see an obvious way of doing this.

I know Parseq runs on executors, so I thought of using something like this, which captures the state of "psuedo thread locals" when callables/runnables are submitted to the underlying executor, which makes sense for the initial engine.run call, but I can't reason about whether then the entire rest of the task/plan will similarly have the "captured on engine.run" state passed along at each async task invocation.

I guess for Task.blocking(..., executor), if that executor was also instrumented to trigger the context recording, the executor.execute would record the context, and when the callable runs and calls promise.done, the context would have been "put back", and so as long as promise.done led to a synchronous enqueue into the parseq executor (which would again trigger a context record), it'd get picked up/passed along.

So, I think that makes sense? for Task.blocking going to other executors, but I haven't thought through/figured out how the async, just regular task.andThen(...) would work.

If you could provide any help/pointers/docs/APIs/etc., I'd appreciate it!

Or if it's as easy as using the java-thread-context approach, and as long as all of my application's executors are sufficiently instrumented, it will just magically work, that would also be great, and I'm just making this harder than it is.

@mchen07
Copy link
Contributor

mchen07 commented Sep 10, 2018

@stephenh Since parseq tasks are not guaranteed to run in one thread, thread-local cannot work in parseq setting. Your understanding about using decorated executor service to capture the state of "psuedo thread locals" when callables/runnables are submitted to the underlying executor is completely correct, that is the typical way to propagate execution context from task to task. But I don't quite understand your phrase below:

"which makes sense for the initial engine.run call, but I can't reason about whether then the entire rest of the task/plan will similarly have the "captured on engine.run" state passed along at each async task invocation."

Can you elaborate that a bit?

As for Task.blocking(..., executor), to propagate execution context to such blocking task, you also need to pass the same decorated executor service in this call.

@angxu
Copy link
Contributor

angxu commented Sep 11, 2018

@mchen07 Do you think it's better to propagate context using TaskQueue? Just so that you don't have to wrap every executor your task might be running on.

@stephenh
Copy link
Author

Hi @mchen07 ; thanks for your response!

Can you elaborate that a bit?

Sure. The context being captured when the first task is submitted to the executor makes sense. What I don't know is that, after that initial task has fired and caused N other tasks to happen, when those children tasks complete, and are put into the executor to be executed, how will the captured state follow them along?

In theory if the child tasks were created immediately in the 1st executor execution, they would get picked up, e.g.:

  • Main thread submits task1, context is captured
  • Executor-thread-1 run task1, two new tasks are submitted to executor, context is captured
  • Executor-thread-2 runs task2, context is available
  • Executor-thread-3 runs task3, context is available

However, it seems like tasks are not submitted to the executor immediately while evaluating the 1st task, which of course makes sense because their async, so I think see this happen:

https://github.com/stephenh/java-sandbox/blob/parseq/src/main/java/sandbox/Foo.java

Which I think is:

  • Main thread submits task1, context is captured
  • Executor-thread-1 run task1, two new tasks are created but not submitted
  • I/O thread finishes task1, submitted it to executor, no context is available
  • Executor-thread-2 runs task2, no context is available

Is there an easy way, within the map calls in my example code, to get this thread local to have been passed along? I'm thinking that somehow parseq tracks this internally to display the trace trees, e.g. it always knows which task was the head task/plan.

Basically I want an Invocation Context.

@mchen07
Copy link
Contributor

mchen07 commented Sep 11, 2018

@angxu That is a good suggestion, I am considering that for plan-local cache (#193). That needs some code change in SerialExecutor, as well as some thoughtful API designs for task writer to update/access the plan-local context. With current parseq support, it seems that implementing customized ExecutorService and wrapping each callable submitted to it may be one potential way to propagate context within or even across plans.

@mchen07
Copy link
Contributor

mchen07 commented Sep 13, 2018

@stephenh Basically to carry over context from task to task during plan execution, you need to create a decorated executor service on top of JDK provided ExecutorService. The Executor is decorated to maintain ExecutionContext that you can define yourself: when a Runnable is submitted to the Executor, the ExecutionContext of a submitting thread is captured and stored together with a Runnable; when Runnable is about to be executed, stored ExecutionContext is restored on a current thread (mostly a thread-local storage) and Runnable is executed.

@stephenh
Copy link
Author

@mchen07 yeah, that makes sense...if the Foo.java example above, I am doing that for the executor I give to parseq, but it looks like the handoff to the "New I/O Worker" thread loses the context. Is that I/O thread from the fork/join pool maybe? I can poke around at that I guess. I would appreciate any hints but otherwise will close this issue.

@mchen07
Copy link
Contributor

mchen07 commented Sep 13, 2018

@stephenh I may need to look at "PropagatingExecutorService" class implementation to see how it is propagating context through tasks. Normally it should involve code to decorate the submitted Runnable to save and restore context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants