Skip to content

Conversation

hjohn
Copy link
Collaborator

@hjohn hjohn commented Aug 18, 2025

Support background loading of raw input streams

  • Fixed generics (mix up of two ImageLoader types)
  • Removed unused code for handling headers, methods, request parameters
  • Use long for progress as streams may exceed 2 GB
  • Improved documentation of Image regarding background loading

Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change requires a CSR request matching fixVersion jfx26 to be approved (needs to be created)
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8361286: Allow enabling of background loading for images loaded from an InputStream (Enhancement - P4)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1875/head:pull/1875
$ git checkout pull/1875

Update a local copy of the PR:
$ git checkout pull/1875
$ git pull https://git.openjdk.org/jfx.git pull/1875/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1875

View PR using the GUI difftool:
$ git pr show -t 1875

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1875.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Aug 18, 2025

👋 Welcome back jhendrikx! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Aug 18, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot changed the title 8361286 Allow enabling of background loading for images loaded from an InputStream 8361286: Allow enabling of background loading for images loaded from an InputStream Aug 18, 2025
- Fixed generics (mix up of two ImageLoader types)
- Removed unused code for handling headers, methods, request parameters
- Use long for progress as streams max exceed 2 GB
- Improved documentation of Image regarding background loading
@hjohn hjohn force-pushed the feature/image-constructor branch from 7efebfd to 1452b5c Compare August 18, 2025 10:30
@hjohn hjohn marked this pull request as ready for review August 18, 2025 14:00
@openjdk openjdk bot added the rfr Ready for review label Aug 18, 2025
@mlbridge
Copy link

mlbridge bot commented Aug 18, 2025

Webrevs

@kevinrushforth kevinrushforth self-requested a review August 18, 2025 19:51
@kevinrushforth
Copy link
Member

Reviewers: @kevinrushforth @jayathirthrao

/reviewers 2

@openjdk
Copy link

openjdk bot commented Aug 18, 2025

@kevinrushforth
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

@kevinrushforth
Copy link
Member

This will need a CSR. The newly proposed constructors should have an @since tag.

/csr needed

@openjdk openjdk bot added the csr Need approved CSR to integrate pull request label Aug 18, 2025
@openjdk
Copy link

openjdk bot commented Aug 18, 2025

@kevinrushforth has indicated that a compatibility and specification (CSR) request is needed for this pull request.

@hjohn please create a CSR request for issue JDK-8361286 with the correct fix version. This pull request cannot be integrated until the CSR request is approved.

@nlisker
Copy link
Collaborator

nlisker commented Aug 18, 2025

Do we still use @NamedArg? I thought it's a remnant of pre-Java 8 where parameter names weren't saved.

@hjohn
Copy link
Collaborator Author

hjohn commented Aug 18, 2025

Do we still use @NamedArg? I thought it's a remnant of pre-Java 8 where parameter names weren't saved.

I don't know about the NamedArg specifics, but I do know that saving parameter names is optional and I think still off by default (ie. you need to pass a compiler option to save the names). Other than that, I just copied what was there and kept the same format.

@mstr2
Copy link
Collaborator

mstr2 commented Aug 18, 2025

Do we still use @NamedArg? I thought it's a remnant of pre-Java 8 where parameter names weren't saved.

FXMLoader uses the @NamedArg annotation to associate XML attribute values with constructor arguments.

@andy-goryachev-oracle
Copy link
Contributor

andy-goryachev-oracle commented Aug 18, 2025

FXMLoader uses the @NamedArg annotation to associate XML attribute values with constructor arguments.

I wonder if this should be explained in the @NamedArg javadoc.

@hjohn
Copy link
Collaborator Author

hjohn commented Aug 18, 2025

FXMLoader uses the @NamedArg annotation to associate XML attribute values with constructor arguments.

I wonder if this should be explained in the @NamedArg javadoc.

It probably should, I never use FXML, but I should have guessed it was for that purpose :)

Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle left a comment

Choose a reason for hiding this comment

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

Looks very good!

Noticed it does not close the input stream with a synchronous constructor (it does with the async one).

  • some minor comments and suggestions.

@@ -154,6 +155,35 @@ public void loadImageAsyncProgressTest() {
assertTrue(p3 == p4);
}

@Test
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think it would make sense to add a test that actually loads a valid image?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think that can be done with the stub toolkit This test is specifically stubbing/mocking everything out to verify that Image works correctly. However, there are many areas where (real) images are loaded already, and all the code involved (excluding the new parts in Image) was already there in one way or another as the URL variants that allowed background loading is also just an InputStream with some extra steps to determine their size (for progress).

@andy-goryachev-oracle
Copy link
Contributor

@NamedArg

FYI Created https://bugs.openjdk.org/browse/JDK-8365864 but if someone wants to do it, I can send it their way.

Copy link
Member

@jayathirthrao jayathirthrao left a comment

Choose a reason for hiding this comment

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

Change looks to me apart from some questions.

Will also run tests on our CI system and check.

return processStream(stream);
}
finally {
stream.close();
Copy link
Member

Choose a reason for hiding this comment

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

Do we have to continue closing the stream when background loading is happening with URL?

Copy link
Member

Choose a reason for hiding this comment

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

I see that the resource is getting closed.


protected AbstractRemoteResource(String url, String method, String outboundContent, AsyncOperationListener<T> listener) {
Copy link
Member

Choose a reason for hiding this comment

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

I see that outboundContent calls are no where used. Is this the reason we are cleaning up this code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I generally remove code that I see is unused, if it otherwise would have needed to be adjusted.

@andy-goryachev-oracle
Copy link
Contributor

@hjohn in case you missed my earlier comment:

Noticed it does not close the input stream with a synchronous constructor (it does with the async one).

you can try it with the monkey tester from this branch https://github.com/andy-goryachev-oracle/MonkeyTest/tree/image.from.stream

or simply

            byte[] b = ImageTools.writePNG(src);
            ByteArrayInputStream in = new ByteArrayInputStream(b) {
                @Override
                public void close() throws IOException {
                    System.out.println("Closed " + Thread.currentThread());
                }
            };

            // requires JDK-8361286: Allow enabling of background loading for images loaded from an InputStream
            Image im = new Image(in, loadInBackground);
            imageView.setImage(im);
        } catch (IOException e) {
            e.printStackTrace();
        }

@hjohn
Copy link
Collaborator Author

hjohn commented Aug 21, 2025

@hjohn in case you missed my earlier comment:

Noticed it does not close the input stream with a synchronous constructor (it does with the async one).

you can try it with the monkey tester from this branch https://github.com/andy-goryachev-oracle/MonkeyTest/tree/image.from.stream

or simply

            byte[] b = ImageTools.writePNG(src);
            ByteArrayInputStream in = new ByteArrayInputStream(b) {
                @Override
                public void close() throws IOException {
                    System.out.println("Closed " + Thread.currentThread());
                }
            };

            // requires JDK-8361286: Allow enabling of background loading for images loaded from an InputStream
            Image im = new Image(in, loadInBackground);
            imageView.setImage(im);
        } catch (IOException e) {
            e.printStackTrace();
        }

A good question would be what the behavior should be. For the URL variants, since the Image class is creating the stream in both cases, it makes sense that it closes that stream.

For InputStream parameters, the standard practice is to leave closing up to the caller, unless it is a wrapper (like BufferedInputStream). So, I think how the synchronous variant does it is probably the correct approach.

However, when consuming the stream asynchronously, it is very hard for users to know when to safely close the stream as you basically handed ownership of the stream to another thread (you can't use the stream for anything while background loading is in progress...) The only way to know when the other thread is done with the stream is to look at the progress property.

So, I think there are a few options:

  1. Document that we'll be closing the stream in the background loading case; effectively, since you're giving this InputStream to be used by a different thread, the ownership of the stream transfers from the user to Image.
  2. Document that you need to hook into the progress property to know when to close the stream (yikes!)
  3. Don't provide the background loading option for InputStream (ie. don't integrate this PR, or only the documentation and clean-up bits)
  4. Provide alternative API that takes Supplier<InputStream>, very powerful, and it would be clear who owns the stream and who closes it, and this would work for both sync/async.

A prime motivation for allowing input streams for background loading was that the URL API is limited (you can't do authentication, use POST, or provide a body). In those cases you're forced to InputStream but then forego the option to load in the background, meaning that you can't have a progress indicator for large images, or show progressive images like with PJPEG.

@andy-goryachev-oracle
Copy link
Contributor

re: closing stream

I wonder if we should close the stream in both sync and async variants (and document that). I would say there should be no reason not to close - suggesting that if the application wants not to close it's surely a bad choice for the application.

For example, one can think of a situation where multiple images are stored within the same stream, but there should be a reliable way for the application to specify the size of each image (and then the app can provide a derived InputStream which EOFs when the end of an image is reached).

Another alternative is to add another boolean closeStream.

What do you think?

@hjohn
Copy link
Collaborator Author

hjohn commented Aug 21, 2025

re: closing stream

I wonder if we should close the stream in both sync and async variants (and document that). I would say there should be no reason not to close - suggesting that if the application wants not to close it's surely a bad choice for the application.

For example, one can think of a situation where multiple images are stored within the same stream, but there should be a reliable way for the application to specify the size of each image (and then the app can provide a derived InputStream which EOFs when the end of an image is reached).

Another alternative is to add another boolean closeStream.

What do you think?

It's unusual to close the stream when you're not the owner (although some API's do that, they are often obscure and not used that much). Changing this behavior for the sync variant would be a breaking change, so I'm not sure we want to even go there.

Adding another boolean is of course an option, but I think that if we're going for that route, going for a Supplier would be more clear API and more powerful (in the internal code, we basically already have that abstraction as it can abstract over both the URL and InputStream variants with just one API).

If we go the Supplier route, we can probably also unify the URL and InputStream constructors to go through the same route, moving the URL opening logic into Image class and removing it from the internal code.

@andy-goryachev-oracle
Copy link
Contributor

I like the Supplier idea, though I am not sure how the async path would work (unless you add the closeStream parameter).

@kevinrushforth
Copy link
Member

We need to leave the existing synchronous constructors alone, other than to document that they do not close the input stream (which is what we do in the Font::loadFont methods that take an InputStream).

As for the new constructors, I think the options are:

  1. Keep it as you propose, where the async variant always closes the stream (the rationale being that the caller has transferred ownership of the stream)
  2. Change the new constructors to never close the stream; this would be OK as long as we provide an easy way for the caller to know when they should do it
  3. Add a Supplier to the constructors (I'm a little fuzzy on the details of this one)

@andy-goryachev-oracle
Copy link
Contributor

I think at least the convenience constructor Image(InputStream) should close the stream.

@kevinrushforth
Copy link
Member

No, that would be an incompatible change.

@andy-goryachev-oracle
Copy link
Contributor

You are right, please disregard the comment.

I think we should clarify the behavior in each constructor that takes InputStream, similar to Font::loadFont().

A boolean closeStream parameter might be the lightest solution from the API perspective then.

@mstr2
Copy link
Collaborator

mstr2 commented Aug 21, 2025

A new parameter to toggle whether the stream will be closed should have at least a moderately strong use case to justify its existence. I can't think of any. If you turn over a stream to an asynchronous process, you effectively relinquish ownership of that stream, as you don't know when (if ever) it will be processed.

But even then, you can very easily achieve the non-closing behavior without new JavaFX API, just by using existing stream APIs:

var streamThatWillNotCloseUnderlyingStream = new FilterInputStream(myStream) {
    @Override
    public void close() {
        // don't call super.close(), so underlying stream won't be closed
    }
}

@jayathirthrao
Copy link
Member

If user is passing stream its better to keep the decision of closing the stream with user for synchronous loading.

Asynchronous URL loading : Current changes takes care of closing the stream and follows already present behaviour. Its better to update the documentation about it.
Asynchronous InputStream loading : This change now explicitly closes the Input Stream which is fine but this needs to be captured in the documentation.

Giving an option to user about how to close the stream in case of asynchronous loading can be taken up as future task, if needed. CI testing is green with current code update.

@hjohn
Copy link
Collaborator Author

hjohn commented Aug 22, 2025

I like the Supplier idea, though I am not sure how the async path would work (unless you add the closeStream parameter).

When you have a Supplier<InputStream> instead of URL or plain InputStream it will look like this:

 new Image(() -> new FileInputStream("xyz"));

Since the supplier must create a new stream each time (as it can't know how often it will be called), it makes sense that the caller of the supplier is also the one to close it, whether or not that is sync or async.

@hjohn
Copy link
Collaborator Author

hjohn commented Aug 22, 2025

Thanks for all the input. I think there are now two ways to proceed:

  1. Leave it as is, including the new backgroundLoading variants for InputStream, but document clearly for both the old and new constructors when the stream will be closed (ie. only when backgroundLoading is true we will close the stream).

  2. Don't add these new constructors, but add a more obvious variant with Supplier<InputStream>, where the ownership is more clear, with the caller of the supplier owning the stream created.

I get the impression the consensus leans towards option 1. I don't see a direct compelling need for the Supplier variant, although I'm happy to provide an implementation if option 2 is favored.

@kevinrushforth
Copy link
Member

I prefer option 1.

@andy-goryachev-oracle
Copy link
Contributor

I agree with Kevin: option 1 is clearer.

Copy link
Member

@jayathirthrao jayathirthrao left a comment

Choose a reason for hiding this comment

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

Latest change looks good to me.

This update causes a failure in one of our closed white-box tests. It will be easy for us to fix it, but it will mean that down the road, when this is ready to go in, we will need to coordinate the timing of the integration with you.

Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle left a comment

Choose a reason for hiding this comment

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

thank you for making the requested changes @hjohn !
looks good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
csr Need approved CSR to integrate pull request rfr Ready for review
Development

Successfully merging this pull request may close these issues.

7 participants