-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
6176679: Application freezes when copying an animated gif image to the system clipboard #13414
Conversation
👋 Welcome back rmahajan! A progress list of the required criteria for merging this PR into |
Webrevs
|
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.
Instead of using .gif image as input, its better if we can create ImageStream as we do in some of animation tests under test/jdk/javax/imageio/plugins/gif.
@jayathirthrao Could you elaborate, please? How is |
import java.awt.*; | ||
import java.awt.datatransfer.*; |
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.
Please expand the wildcard imports.
import static java.util.concurrent.TimeUnit.MILLISECONDS; | ||
|
||
|
||
public class bug6176679 implements ClipboardOwner, FlavorListener { |
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.
Are ClipboardOwner
and FlavorListener
really used? If not, they can be dropped.
The class name should have a descriptive name, like CopyAnimatedGIFTest
.
* @test | ||
* @key headful | ||
* @bug 6176679 | ||
* @summary Tests that an application doesn't freeze when copying an animated gif image to the system clipboard |
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.
Please wrap the line to fit into 80-column limit.
|
||
private static final CountDownLatch latch = new CountDownLatch(1); | ||
|
||
volatile static Image img = 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.
Please use blessed modifier order: static volatile
.
I think img
should not be declared volatile, it's used only on EDT. It may even be non-static because it's accessed only from methods of the test class and you create an instance of it.
try { | ||
test.copyImage(); | ||
latch.countDown(); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} |
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.
No checked exception is thrown within this try-block, thus it can be removed.
} | ||
|
||
|
||
} |
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.
Please add a final blank line.
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 the test should not be in this directory: it does not test the GIF Image IO plugin. A better location is under java/awt/Clipboard
.
Panel panel = new Panel(new GridLayout(1, 1)); | ||
panel.add(canvas); | ||
frame.add(panel); | ||
img = frame.getToolkit().getImage(System.getProperty("test.src", ".") + File.separator + FILENAME); |
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.
Could you wrap this long line, please?
The 80-column limit isn't strictly followed, yet it's better to fit into 100-column limit because the code becomes harder to read.
@@ -109,7 +109,7 @@ public synchronized void reconstruct(int flags) { | |||
src.checkSecurity(null, false); | |||
} | |||
int missinginfo = flags & ~availinfo; | |||
if ((availinfo & ImageObserver.ERROR) == 0 && missinginfo != 0) { | |||
if ((availinfo & (ImageObserver.ERROR | ImageObserver.FRAMEBITS) ) == 0 && missinginfo != 0) { |
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 ((availinfo & (ImageObserver.ERROR | ImageObserver.FRAMEBITS) ) == 0 && missinginfo != 0) { | |
if ((availinfo & (ImageObserver.ERROR | ImageObserver.FRAMEBITS)) == 0 | |
&& missinginfo != 0) { |
The space between the closing parentheses is redundant as well as the extra space after |
operator. The line could be wrapped to fit into 80-column limit.
Mailing list message from Alan Snyder on client-libs-dev: Isn?t there a deeper issue here? I ran into a problem trying to analyze an image using a filter. The code was waiting for ALLBITS to be reported (as well as ERROR conditions), but for an animated GIF that never happens. I worked around that problem by stopping when a FRAME was reported, which was good enough for my purposes. I did not see any way to indicate that I did not want an animation or to determine when every frame had been produced once. Did I miss something? There are many places where ALLBITS is tested. Are all of these places at risk when operating on an animated GIF? Implementing animation by simulating an image of infinite size seems like a bad idea, to put it mildly. |
Yes i mean generating GIF by code and not using .gif file as input. |
Will the generated GIF file be animated ?, because this only happens with animated GIFs. |
As previously mentioned we have animation tests for GIF under test/jdk/javax/imageio/plugins/gif which don't use GIF file input. |
It should if you create several frames. There's always an option to create another GIF image, smaller, and use its byte or base64 representation as the source.
Okay, now it's clear. |
Mailing list message from Aleksei Ivanov on client-libs-dev: Hi Alan, On 12/04/2023 14:05, Alan Snyder wrote:
This is what this fix does: ALLBITS is never set for animated images, so
In the test code, when MediaTracker.waitForAll() returns, which
I did a quick search for ALLBITS, I can see both FRAMEBITS | ALLBITS are
-- |
Made changes requested by the reviewers. Now we read animated gif image from byte array. |
Mailing list message from Alan Snyder on client-libs-dev:
Is it really the case that multiple frames are copied one at a time? I don?t see that, and don?t see how that could work. I?m not saying it is wrong to copy only the first frame, as that is the only reasonable option if the goal is to transfer a BufferedImage. I assume there is no Java representation of a multi-frame image that could be copied? -------------- next part -------------- |
Yes I see that the frame is copied one at a time. When I debug and step in reconstruct() is called per frame. |
src/java.desktop/share/classes/sun/awt/image/ImageRepresentation.java
Outdated
Show resolved
Hide resolved
src/java.desktop/share/classes/sun/awt/image/ImageRepresentation.java
Outdated
Show resolved
Hide resolved
(byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x04, (byte) 0x8c, | ||
(byte) 0x8f, (byte) 0x19, (byte) 0x05, (byte) 0x00, (byte) 0x3b | ||
}; | ||
private void createGUI() { |
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.
Please add a blank line between methods, fields and methods, between class declarations.
A blank line between unrelated fields is also a good idea.
private void copyImage() { | ||
Clipboard sys = Toolkit.getDefaultToolkit().getSystemClipboard(); | ||
sys.setContents(new MyTransferable(img), 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.
The blank line in the end of the method is redundant.
test/jdk/javax/imageio/plugins/gif/6176679/CopyAnimatedGIFTest.java
Outdated
Show resolved
Hide resolved
frame = null; | ||
} | ||
} | ||
private static class imgCanvas extends Canvas { |
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.
Class names start with a capital letter, method names and fields start with a lower-case letter.
Is
ImageCanvas
a better name?
Other points from this comment have been addressed.
} | ||
} | ||
|
||
} |
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.
Please add a final blank line.
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 still think the test should not be in test/jdk/javax/imageio/plugins/gif
because it does not test the GIF Image IO plugin. A better location is under test/java/awt/Clipboard
.
Now that there's no external GIF image file, the bugid directory has become unnecessary. We're striving to keep test structure flatter without creating a new folder for each test as it used to be done previously.
…on.java Co-authored-by: Alexey Ivanov <alexey.ivanov@oracle.com>
…on.java Co-authored-by: Alexey Ivanov <alexey.ivanov@oracle.com>
….java Co-authored-by: Alexey Ivanov <alexey.ivanov@oracle.com>
Mailing list message from Alan Snyder on client-libs-dev: I was hoping to be able to run your test program, but I cannot because on macOS there is no support for transferring a native image. It also appears that I cannot simulate the transfer code because that code depends upon the reconstruct method, which is not public. Just looking at the code, I think it is not correct. It looks to me that the ImageRepresentation.drawToBufImage method is called only once, and it will do nothing unless ALLBITS or FRAMEBITS is true. So, it works if reconstruct() waits until ALLBITS or FRAMEBITS is true. But your change does not test for FRAMEBITS in the loop, so it will only work if FRAMEBITS is already true when reconstruct() is called. How can that happen? I think it works because you are displaying the image on the screen, and the animation code is reading frames. I suggest trying the test without displaying the image. Also, your test would better if it retrieved the native image from the system clipboard to verify that it was successfully transferred. You would need to clear the system clipboard before running the test to be sure you are not retrieving something from a previous test run. |
code cleanup
code cleanup
Thanks for your suggestions. I have modified the code so it works for both cases where image is and is not displayed on screen.
I am not sure what you mean by native image. Could you please clarify ? |
Mailing list message from Alan Snyder on client-libs-dev: Your changes look reasonable. I have a suggestion. Unless IDEA is lying to me, all uses of reconstruct() pass ALLBITS as the parameter. The net effect is to wait for any of the three terminating conditions, ALLBITS, FRAMEBITS, and ERROR. My suggestion would be to eliminate the parameter, add ALLBITS to the test in the loop, and rename the method waitForTermination() or something like that. (The existing method can hang if ALLBITS is not true in the parameter, which makes the method unnecessarily fragile.)
I think I was mistaken. I tried again and was able to run your test. -------------- next part -------------- |
Thanks Alan for this suggestion. I have created a new JBS bug JDK-8307183 for refactoring this code as I thought it is not in scope of this bug and would do it as a follow up change to this one. |
src/java.desktop/share/classes/sun/awt/image/ImageRepresentation.java
Outdated
Show resolved
Hide resolved
…on.java Co-authored-by: Alexey Ivanov <alexey.ivanov@oracle.com>
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.
Looks good to me now.
You have to address the trailing whitespace error too.
@rajamah This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 397 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@jayathirthrao, @aivanov-jdk, @dmarkov20) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
/integrate |
/sponsor |
Going to push as commit 6c71859.
Your commit was automatically rebased without conflicts. |
@aivanov-jdk @rajamah Pushed as commit 6c71859. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
Problem:
On pressing the Copy button we keep waiting in AWT-EventQueue thread in reconstruct() function as we detect that we have missing information for the animated image since we are copying single frame at a time.
Due to this infinite wait the application freezes.
Proposed Fix:
There are conditions in the reconstruct() function that avoid entering the wait() code if we have some error reading the image , etc. So, I added the condition to avoid entering the wait() code if we are copying single frame at a time. This sounded logical to me since if we have incomplete information(single frame) about the animated image we shouldn’t keep waiting, as it leads to infinite wait.
After this change I see the GIF image being correctly copied and animated.
Testing:
Added a test for this (bug6176679.java) and tested locally on my Windows machine and on mach5.
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/13414/head:pull/13414
$ git checkout pull/13414
Update a local copy of the PR:
$ git checkout pull/13414
$ git pull https://git.openjdk.org/jdk.git pull/13414/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 13414
View PR using the GUI difftool:
$ git pr show -t 13414
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/13414.diff
Webrev
Link to Webrev Comment