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

Fix #412 AsyncContext execute #414

Closed
wants to merge 4 commits into from

Conversation

gregw
Copy link
Contributor

@gregw gregw commented Jul 29, 2021

Fix #412 AsyncContext is an Executor that mutually excludes from other container invocations for the same request.

Fix jakartaee#412 AsyncContext is an Executor that mutually excludes from other container invocations for the same request.
@gregw
Copy link
Contributor Author

gregw commented Oct 19, 2021

@markt-asf @stuartwdouglas your thoughts on this one? I think it is a good addition to improve the asynchronous usage of the servlet container. Currently we mutually exclude all the container callbacks, but then let an arbitrary thread call complete at any time.

@stuartwdouglas
Copy link
Contributor

Conceptually I like it. The implementation is not super simple if you have IO threads in the mix though. For performance reasons you will likely want to execute ReadListener#onDataAvailable() and WriteListener#onWritePossible() on an IO thread, so if you just use a simple lock for mutual exclusion you can potentially block the IO thread (which is a big no-no). This can obviously be worked around.

I am also not really sure about run() and its possibilities for deadlocks. execute feels a lot safer, I can't really think of a scenario where run makes sense (also plenty of possibility for confusion between the two methods).

@markt-asf
Copy link
Contributor

I support the general idea.

I'm a little concerned about adding this so late in the day but the fact that the impact for 6.0 will be warnings at most should mitigate any compatibility risks.

At a detail level I think we should avoid the use of the word "dispatch" in the Javadoc for the new methods in case it cases confusion with the existing dispatch methods.

@gregw
Copy link
Contributor Author

gregw commented Oct 20, 2021

@stuartwdouglas onDataAvailable and onWritePossible are already mutually excluded from other container managed threads (eg lingering in service or during a dispatch) and from each other, so the potential for blocking is already there. So I will update the text on all these methods to make it clear that they should not be long running... or else. As for implementation, I expect we all have some mechanism already for mutually excluding the container managed threads, so the intention here is to just slightly complicate that an let an arbitrary task be similarly excluded.

The idea is that in future, such tasks would be the preferred (only?) way of doing a complete, but for now continuing to allow the wild west of anybody can call at any time is necessary (as it is too late to really validate a change). I'll rework the wording for that as well to make it clear that this is not changing existing behavior for this release, but that the behavior may change in a future release.

I'll ponder run... if I can't find a good use-case, then I'll remove. Execute allows for the task to be executed in the calling context, so it can avoid a thread handoff, yet never block, so it is probably sufficient.

stand by....

@gregw
Copy link
Contributor Author

gregw commented Oct 20, 2021

@markt-asf @stuartwdouglas I've updated the text a bit and removed run. Hopefully this makes it more a single additional method with a "best practice" suggestion to mutually exclude multiple threads acting on the same request.

@gregw gregw marked this pull request as ready for review October 21, 2021 00:03
* @param run the asynchronous handler
*/
public void start(Runnable run);

/**
* Execute the passed {@link Runnable} mutually excluded from all other container managed threads for the same request.
* The execution may be done by the calling thread, which will momentarily be a container managed thread; or by another
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO this should either always dispatch or always run in the same thread (with my preference being always to dispatch). I can't see this doing anything but causing problems (e.g. if the calling thread holds a lock sometimes the runnable will be run with the lock held, sometimes not).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm over optimizing. I think that async applications might want to avoid the extra thread dispatch. So if we always use another thread, apps wont use this. If we don't use another thread then we may block in this method.

Give me another day to ponder.

* Execute the passed {@link Runnable} mutually excluded from all other container managed threads for the same request.
* The execution may be done by the calling thread, which will momentarily be a container managed thread; or by another
* container managed thread, possibly from a managed thread pool. Since execution of the {@link Runnable} will exclude
* other container managed methods, this method is not intended for long-running tasks, nor ones that may block.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is the expectation here that this will be run on the IO thread rather than an executor? If it is run on a normal executor then IMHO long running or blocking tasks are likely fine, it just needs a warning that if you are doing async IO it will basically be suspended.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well it is the same issue as existing issue with onWritePossible, onDataAvailable and/or service mutually excluding each other, so any long running tasks will prevent the other callbacks. I guess we don't currently warn against long-running tasks. I'll reword.

* container managed thread, possibly from a managed thread pool. Since execution of the {@link Runnable} will exclude
* other container managed methods, this method is not intended for long-running tasks, nor ones that may block.
* <p>
* If {@link AsyncContext#complete()} is called, or the request lifecycle completes via a dispatch prior to the runnable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like this either, it means that you can never trust the Runnable to clean up resources, if you have DB connections that need to be returned to a pool or things of that nature you also need to register a listener.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmm the issue this was trying to fix was to stop an async thread accessing a request/response object that had been recycled and was being used on another request cycle. However, with the removal of Object Wrapper Identity, this may not be needed any more as referenced to requests can be made durable if a container wishes.

I'll ponder and probably remove.

@gregw
Copy link
Contributor Author

gregw commented Oct 28, 2021

I'm going to close this unmerged for now. This is something that can easily be added in a 6.1 once we get a bit more experience about how the current changes in 6.0 will affect async handling... specifically durable request will make thing much better for async handling.

@gregw gregw closed this Oct 28, 2021
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

Successfully merging this pull request may close these issues.

Request Serialized Threads
3 participants