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

Provide an equivalent of Jersey's CloseableService #31213

Closed
matthias-mueller opened this issue Feb 16, 2023 · 17 comments · Fixed by #31312
Closed

Provide an equivalent of Jersey's CloseableService #31213

matthias-mueller opened this issue Feb 16, 2023 · 17 comments · Fixed by #31312
Labels
kind/enhancement New feature or request
Milestone

Comments

@matthias-mueller
Copy link

Description

(First posted on Stackoverflow then moved to GH as suggested by @geoand)

I am migrating a REST-Service from Jersey to Quarkus/RestEasy. Jersey can inject different Context objects into the Resource classes, among them is a CloseableService that you can use to robustly handle Closeable resources, no matter what goes wrong with the request (Runtime Exceptions, Aborted Connections, etc.).

The signature of a resource method looks roughly like this:

    @Path("/datasets/{dataset}")
    @Consumes("application/json")
    @Produces("application/json")
    @GET
    public Stream<TSRecord> getDatasetDataJson(
            @PathParam("dataset") final String dsName,
            @QueryParam("parameter") final Parameter parameter,
            @Context final CloseableService closer
    ) throws NoSuchDatasetException {
        final Stream<TSRecord> stream = database.stream(dsName, parameter);
        closer.add(stream::close);
        return stream;
    }

Currently, there is no equivalent to CloseableService in Quarkus.
p.s.: I am not sure whether this really is a quarkus issue or something that should be addressed by RestEasy.

Implementation ideas

No response

@matthias-mueller matthias-mueller added the kind/enhancement New feature or request label Feb 16, 2023
@matthias-mueller matthias-mueller changed the title Provide an equivalent of Jersey's Closeable Service Provide an equivalent of Jersey's CloseableService Feb 16, 2023
@geoand
Copy link
Contributor

geoand commented Feb 16, 2023

I wonder if we can come up with some better API.

@FroMage WDYT?

@matthias-mueller
Copy link
Author

Accepting AutoCloseable instead of Closeable would be an obvious improvement.

@mkouba
Copy link
Contributor

mkouba commented Feb 20, 2023

Just out of curiosity - what exactly does call CloseableService#add(Closeable c)?

@geoand The CloseableService looks like a simple request scoped holder of Closeables. If so you could just provide a @RequestScoped CloseableService bean that closes all Closeables previously registerered by CloseableService#add() in its @PreDestroy callback. No need to call the close() explicitly...

@geoand
Copy link
Contributor

geoand commented Feb 20, 2023

@mkouba that sounds interesting although I have not had time to look into it, but it does sound like a nice solution

@matthias-mueller
Copy link
Author

matthias-mueller commented Feb 20, 2023

Just out of curiosity - what exactly does call CloseableService#add(Closeable c)?

It is usually called inside the resource method if try/catch cannot be used.

@geoand
Copy link
Contributor

geoand commented Feb 20, 2023

@matthias-mueller do you have any real world examples to point to?
Asking because they could be useful for us to add as tests for the implementation of this feature

@mkouba
Copy link
Contributor

mkouba commented Feb 20, 2023

Just out of curiosity - what exactly does call CloseableService#add(Closeable c)?

It is usually called inside the resource method if try/catch cannot be used.

Ok, so you could still use the @RequestScoped bean as described above, e.g. something like:

@RequestScoped
public class CloseableServices {

    private final List<Closable> closables = new ArrayList();

    public void add(Closable closable) {
       // ...
    }

    @PreDestroy // this will be automatically called by the container when the request is destroyed
    void destroy() {
       // iterate over closables and close them
    }
}

and then in your resource @Inject CloseableServices and add Closables if needed.

@geoand
Copy link
Contributor

geoand commented Feb 20, 2023

Very nice @mkouba!

It can also be used as @Context CloseableServices service as a Resource Method parameter

@matthias-mueller
Copy link
Author

@matthias-mueller do you have any real world examples to point to?
Asking because they could be useful for us to add as tests for the implementation of this feature

@geoand : Have a look at the example in the first post

final Stream<TSRecord> stream = database.stream(dsName, parameter);
closer.add(stream::close);

Stream implements Autocloseable, so it should be added to a closer. While this is not necessary if you stream vom Objects (usually collections) in the Heap, it is necessary, if the Stream is delivered from a DB resource and has attached handles.

For testing, you can simply write a simple implementation of Autocloseable, register it with add() and check if it has been properly closed after the request went out of scope. Depending oin the implementation / lifecycle internals, you may want to check the following cases:

  1. Request completes regularly
  2. Request cannot be completed due to a server-side Exception
  3. Request cannor be completed due to a terminated conncection by the client

In any of these cases, you don't want to have dangling I/O handles (e.g. on files or databases).

@geoand
Copy link
Contributor

geoand commented Feb 20, 2023

Yeah, I would like to not have to come up with the use case, but would much rather just copy one :)

@matthias-mueller
Copy link
Author

All I have here is proprietary application code, sorry :) . ClosableService is really exotic - never stumbled over it in FOSS projects.

@geoand
Copy link
Contributor

geoand commented Feb 20, 2023

Understood, thanks!

@geoand
Copy link
Contributor

geoand commented Feb 21, 2023

@mkouba one surprising thing that I found while cooking this up is that @PreDestroy is not called when the request scoped bean is created from a CDI producer method.
Is that expected?

@mkouba
Copy link
Contributor

mkouba commented Feb 21, 2023

@mkouba one surprising thing that I found while cooking this up is that @PreDestroy is not called when the request scoped bean is created from a CDI producer method. Is that expected?

Yes, that's expected because the container does not control instantiation/destruction of the bean instance (no injection, no interception, no callbacks). For a producer you'll need to provide a disposer method.

@geoand
Copy link
Contributor

geoand commented Feb 21, 2023

Aha, very interesting, thanks!

geoand added a commit that referenced this issue Feb 21, 2023
Add a way for RESTEasy Reactive to close arbitrary Closeable services
@quarkus-bot quarkus-bot bot added this to the 3.0 - main milestone Feb 21, 2023
@matthias-mueller
Copy link
Author

That was quick!

@geoand
Copy link
Contributor

geoand commented Feb 23, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants