-
Notifications
You must be signed in to change notification settings - Fork 546
8369306: Implement invokeAndWait and finishTerminating on headless platform #1934
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
Conversation
|
👋 Welcome back jpereda! A progress list of the required criteria for merging this PR into |
|
@jperedadnr 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 6 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. ➡️ To integrate this PR with the above commit message to the |
Webrevs
|
kevinrushforth
left a comment
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 left a recommendation to also set "prism.order" to "sw" as discussed in this mailing list thread and a couple other minor comments.
tests/system/src/test/java/test/com/sun/glass/ui/headless/HeadlessApplication1Test.java
Show resolved
Hide resolved
tests/system/src/test/java/test/com/sun/glass/ui/headless/HeadlessApplication1Test.java
Show resolved
Hide resolved
tests/system/src/test/java/test/com/sun/glass/ui/headless/HeadlessApplication2Test.java
Show resolved
Hide resolved
| assertTrue(Platform.isFxApplicationThread()); | ||
| Platform.runLater(Platform::exit); | ||
| }); | ||
| Util.sleep(10); |
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.
Is 10msec enough?
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'm testing on an M1, but you are right, on different environments it could take longer.
I've added now an exit latch from PlatformImplShim.test_getPlatformExitLatch(), so we wait whatever is needed until PlatformImpl.tkExit() finishes, and then those 10 ms should be more than enough (but we still need to wait a couple of ms nonetheless until the JavaFX thread is fully gone).
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.
In general, I'm not a fan of adding hard-numbered sleep times. Either the expected events are delivered, or they are not (yet). The hard sleep pattern leads to either slowdowns or to failing tests on slow environments.
Using CountdownLatches and deterministic flows is much better for testing, imho.
Since the Util.sleep is widely used in other system tests, I'm ok with using it here as well -- but I still think it is a dangerous approach.
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 agree with @johanvos , it's always better to wait for the lock (with a timeout)
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.
Better, sure, but it isn't always possible in practice. There are times where a delay is still needed.
This test now waits for the lock on the condition that will lead to the thread shutting down. Good. It's still necessary to sleep for a small time to be sure that it has.
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.
As I said in my previous comment, I don't see this as a showstopper for this PR, but since it might help in general, I keep thinking about approaches to remove the sleep.
Wouldn't it be an option here to obtain the JavaFX Application Thread after the assertTrue(Platform.isFxApplicationThread()); and assign it to e.g. Thread fxThread so that instead of the Util.sleep we can do fxThread.join(10) ? (I might be missing something though)
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.
Yes, something like what you describe might work in this case.
As for the more general problem, which goes well beyond this PR, there are many places in our tests where we do need sleeps. They fall into two cases
- Lack of a feature that provides a deterministic way to know when something has occurred. Implementing the missing functionality could allow us to replace sleeps with something more deterministic.
- Inherent limitations where there cannot be a deterministic way to know when something has occurred.
Many, if not most, of the instances of 1 could be addressed with an implementation of Robot::waitForIdle. It's a long-standing feature request. It's not rocket science to implement, but isn't trivial either.
Most of the instances of 2 are due to either A) platform delay in realizing certain window system operations (show/hide/minimize/maximize/to-front/etc); or B) inherent GC non-determinism.
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've replaced Util.sleep(10) with fxThread.join(10)
kevinrushforth
left a comment
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.
LGTM. The two new tests fail without the fix and pass with the fix.
Wait for 1 business day (so, Monday some time) to give others a change to review. In particular @johanvos might want to look at it.
|
|
||
| void stopProcessing() { | ||
| for (RunLoopEntry entry : activeRunLoops) { | ||
| runnableQueue.add(() -> entry.active = false); |
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 highly recommend declaring RunLoopEntry.active volatile.
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.
Can you elaborate on this? What usecase do you see where 2 threads might see different values, leading to problems that could not occur if active was volatile?
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.
generally speaking, when two threads modify a single boolean variable, there might be scenarios, depending on the exact hardware, when the other thread does not see the modified value right away.
some of the comments in
https://stackoverflow.com/questions/65037547/the-volatile-keyword-and-cpu-cache-coherence-protocol
https://stackoverflow.com/questions/106591/what-is-the-volatile-keyword-useful-for
might explain it better.
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.
Can this variable be accessed by two threads where one of them doesn't synchronize some other way? If so, then Andy has a point.
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'm aware of the purpose of volatile :)
However, I'm also aware of the cost of volatile, hence I prefer not to do it unless I know there is a possible scenario where it is needed.
That is why I'm asking the same question as the one asked by Kevin.
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 know you do - the links were added for sake of others :-)
The answer to your question is - I don't know. If one can guarantee that there is only one thread accessing it, we may need to add a comment (though things might change in the future, and no one reads comments elsewhere in the code). And if not, then some form of synchronization is mandatory.
I prefer not to think though, and make sure we don't plant booby traps in the code.
BTW, what is the cost of volatile? 0.1 ns? ;-)
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 know you do - the links were added for sake of others :-)
very kind ;)
The answer to your question is - I don't know. If one can guarantee that there is only one thread accessing it, we may need to add a comment (though things might change in the future, and no one reads comments elsewhere in the code). And if not, then some form of synchronization is mandatory.
I ran all system tests to see when/how the active field was read/modified and on which thread. All operations did happen on the main/eventThread, hence single-threaded. Since all of this is about processing events for the eventThread, this makes sense. But to make sure, I did the following research:
There are 3 places in NestedRunnableProcessor where the active field is used (read or write)
Object newRunLoop()
The first time this is invoked is when the HeadlessApplication starts, when Application.run is called. This creates a new Thread, which is the eventThread. The first thing this thread does is invoking runLoop (hence, it is executed on the eventThread).
Apart from the first time, this is invoked from Application.enterNestedEventLoop. which clearly states in its javadoc that the method may only be invoked on the main (event handling) thread, and it even does a checkEventThread() for this.
void leaveCurrentLoop()
This is invoked from Application.leaveNestedEventLoop, which has the same docs + check as enterNestedEventLoop
void stopProcessing()
This is invoked from Application.terminate, which starts with a checkEventThread
In summary, all operations in the NestedRunnableProcessor or controlled/gated by the com.sun.glass.ui.Application which makes sure that all access is guarded by checkEventThread
I prefer not to think though, and make sure we don't plant booby traps in the code.
I don't think I follow this approach. There are many concerns about "JavaFX being slow", so if we can avoid it, I think we should. That doesn't give green light to plant booby traps of course ;)
BTW, what is the cost of volatile? 0.1 ns? ;-)
I'm not an expert, but I believe it is more than 1 ns on "typical desktop systems". Since this is evaluated on every invocation of a Runnable on the AppThread (hence all pulses), plus on every Runnable executed via e.g. Platform.runLater(), this is something that can be invoked very often.
If there was even a single cornercase that we could not rule out, or document as "you're on your own here", I would absolutely agree to make it volatile. But as far as I see, there is no legal case where issues can occur that would have been prevented by adding volatile in this case.
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.
Thank you for the analysis, very helpful! I would like to ask for a comment to be added, explaining the design. The main reason is I've seen too many times when the code was broken because the original assumptions were no longer valid, long after the knowledgeable people have left. At least the comment might help future reviewers. (I'll re-approve if you decide to add the comment).
BTW, you are right - according to https://shipilev.net/blog/2014/on-the-fence-with-dependencies/ the overhead of volatile might be a few nanoseconds on modern systems.
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.
Glass is single-threaded by design, except for those methods that may be called on any thread. So I don't think a detailed comment is needed. A brief comment saying "this is only accessed on the JavaFX application thread (or event thread) might be helpful.
johanvos
left a comment
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 a good PR that adds previously missing functionality.
| try { | ||
| fxThread.join(10); | ||
| } catch (InterruptedException e) { | ||
| fail.set(true); |
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.
Assertions.fail() might work too
|
|
||
| void stopProcessing() { | ||
| for (RunLoopEntry entry : activeRunLoops) { | ||
| runnableQueue.add(() -> entry.active = false); |
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.
Glass is single-threaded by design, except for those methods that may be called on any thread. So I don't think a detailed comment is needed. A brief comment saying "this is only accessed on the JavaFX application thread (or event thread) might be helpful.
|
/integrate |
|
Going to push as commit 38300ca.
Your commit was automatically rebased without conflicts. |
|
@jperedadnr Pushed as commit 38300ca. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
This PR implements
invokeAndWaitandfinishTerminatingfor the headless platform and includes two system tests, one for each, that fail before this PR, and pass after it.Progress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1934/head:pull/1934$ git checkout pull/1934Update a local copy of the PR:
$ git checkout pull/1934$ git pull https://git.openjdk.org/jfx.git pull/1934/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 1934View PR using the GUI difftool:
$ git pr show -t 1934Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1934.diff
Using Webrev
Link to Webrev Comment