-
Notifications
You must be signed in to change notification settings - Fork 81
specification of asynchronous invokers #907
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
base: main
Are you sure you want to change the base?
Conversation
|
This is an initial proposal. I assume you will have some questions about certain aspects of the API. Here are my questions that I don't have a full answer to at the moment:
|
cd4199c to
3a3fd91
Compare
| ---- | ||
|
|
||
| An _async handler_ is a service provider of `jakarta.enterprise.invoke.AsyncHandler`. | ||
| An async handler must not declare a provider method; it must declare a provider constructor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provider constructor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is specified in java.util.ServiceLoader.
I'm actually not sure if we need this provision, but if we allow provider methods, the service provider doesn't have to implement AsyncHandler and figuring out the async type would be harder. I'm not sure if anyone actually uses provider methods; the other parts of the CDI spec ignore them completely...
|
One other question I don't currently have an answer for: what if there's multiple async handlers for the same async type? |
I think we should allow
I think a recommendation makes sense. If you have a listener model, I don't think you can guarantee that.
It's straightforward when the return type represents the async task. If it's an argument, then I don't think there's an obvious answer as to whether it'll call the completion task or not. I would suggest that we say that cleanup will happen immediately if the invocation throws an exception, and that the container must tolerate the completion being run as well. |
Hmm, that makes sense. So something like the following? The first sentence is copied, the second is new.
|
I'm a bit concerned about this too since I think it's likely that multiple libraries will provide an async handler for common async types where support from the container is not required. We could say that any of them may be called, which is a bit unpredictable, but in theory fine as long as all the deployed async handlers are correct. We could say that it's a deployment error, though that might be difficult for the user to resolve if the handlers are provided by libraries. I'm also a bit concerned about the scope of an async handler in a multi-module scenario. If I deploy an async handler in a module, is it available for use by the whole application, or only invocations of beans in modules which have visibility of the async handler module. |
That looks good to me. Maybe tweak it slightly:
|
|
I was thinking about the issue with multiple async handlers existing for the same async type. As we discussed on the CDI call today, we could add
To avoid issue 2, we'd have to still require that async handlers are service providers of the There are some usability issues with this, but they are minor compared to the issue we'd solve. I'll think more about it. |
3a3fd91 to
e82ba10
Compare
|
I've adjusted this PR to what I described in my last comment. Async handlers must still be service providers, and they are selected explicitly using I'm not super happy about this proposal, but it isn't bad either. |
|
As I mentioned on the CDI call earlier this week, I wanted to list all possible issues the current design is supposed to prevent. Here goes.
If we removed the need to register async handlers (via the service loader mechanism), we would rely on users to never forget to call If we removed the [need to call the] All in all, I think there's no better alternative to the current design. |
e82ba10 to
793d97c
Compare
|
I slightly adjusted the API (renamed |
| * This method must be called when the target method is asynchronous and must not be called | ||
| * when the target method is not asynchronous. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to restrict calling withAsync(null) for non-asynchronous methods?
I would change this to "must not be called with a non-null value when the target method is not asynchronous"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to keep it, because it's nicely symmetric: if the target method is async, this method must be called, and if the target method is not async, this method may not be called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get that, but it does mean that if another library is added which adds an async handler for your return type, you need to call withAsync(null) if that library is present and not call it if it isn't present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that situation is weird enough that users want to know about it instantly. (Also it seems quite unlikely. There's not that many async types out there.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does seem unlikely, but I really dislike the idea of having to call the method if an unrelated library is present and not call it if the library is not present and having no way of writing the code that works in both situations.
I guess you could argue that the developer can use reflection to detect whether the other library is present, but that seems more nasty and error prone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say if you found yourself in such situation, you want know about it and you need to think about it. Using a null handler may be a valid solution, but I'd argue that more often than not, there will be other things you want to do.
| * This method must be called when the target method is asynchronous and must not be called | ||
| * when the target method is not asynchronous. | ||
| * | ||
| * @param asyncHandler the {@link AsyncHandler} to use; may be {@code null} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * @param asyncHandler the {@link AsyncHandler} to use; may be {@code null} | |
| * @param asyncHandler the {@link AsyncHandler} to use, or {@code null} to indicate that the invoker is synchronous |
| ---- | ||
|
|
||
| If the target method is asynchronous, the `withAsync()` method on `InvokerBuilder` must be called, otherwise deployment problem occurs. | ||
| If the target method is not asynchronous, the `withAsync()` method on `InvokerBuilder` must not be called, otherwise deployment problem occurs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| If the target method is not asynchronous, the `withAsync()` method on `InvokerBuilder` must not be called, otherwise deployment problem occurs. | |
| If the target method is not asynchronous, the `withAsync()` method on `InvokerBuilder` must not be called with a non-`null` value, otherwise deployment problem occurs. |
If we make the corresponding change in the Javadoc.
| An async handler may not declare a provider method; it must declare a provider constructor. | ||
| An async handler must have a direct superinterface type that is a parameterized type whose generic interface is `AsyncHandler` and its sole type argument is a class type, interface type or parameterized type, otherwise deployment problem occurs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pair of restrictions seem unusual for a service provider. Is this necessary so that we can reliably find out what T is, or is there some other reason?
The first restriction seems difficult to detect or enforce if the handler was loaded with ServiceLoader.load, so should I infer that you're not expecting implementations to do that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do very much expect implementations to use ServiceLoader.load(), which is not an issue in runtime-oriented implementations, which will find async handlers at runtime and may easily just hold onto the implementations obtained from the service loader. Build-time oriented implementations will call ServiceLoader.load() during build (at deployment time), and if we allowed provider methods, build-time oriented implementations would have a hard time matching between what they obtained at build time and what they obtained again at runtime. If we only allow provider constructors, build-time oriented implementations can call ServiceLoader.load() just once, during build, and then just call the provider constructor at runtime at suitable place, making the matching trivial.
I actually think all other places in CDI spec that rely on ServiceLoader also expect only provider constructors and no provider methods, but I don't think anyone has ever tested this.
The second provision is to prevent situations such as class Foo implements AsyncHandler or class Bar<T> implements AsyncHandler<T>.
Fixes #859