That's a good idea to dig into the history further. I looked through the closed-source repo, and the behavior of calling get to validate the observable when adding an InvalidationListener for bindings has been there pretty much from the beginning, even before the creation of the ExpressionHelper class (in July 2011).

I looked at when InvaldationListeners were added to the Bindings classes (in March 2011), and the initial implementation of that method calls get() from the InvalidationListener. That was the commit that also added the `testAddingListenerWillAlwaysReceiveInvalidationEvent` test methods.

So this was clearly a deliberate design choice from the beginning. I can see the rationale for it, but in retrospect it seems an unfortunate choice.

As mentioned already, if we make any change in this area, it will need to be very well-tested, and even then we risk breaking applications.

-- Kevin


On 10/6/2021 12:39 AM, John Hendrikx wrote:
Is it possible to dig in the history of ExpressionHelper a bit further? In git it seems limited to 9 years ago, but in JIRA I found this bug report:

    https://bugs.openjdk.java.net/browse/JDK-8119521

It describes an issue where an InvalidationListener is not working correctly (according to the reporter) and where #get must be called to make it behave as expected.  But it turns out this was already fixed -- this specific fix might have been the one that introduced the #get call in ExpressionHelper.

On 06/10/2021 04:38, Nir Lisker wrote:
I would also answer "no" to both points. This is also what the docs say.

So the question is: how likely do we think that changing this behavior will
break existing applications?


That's the main question. I tested the JavaFX code with the new behavior
and some tests break immediately, though a few I've looked at seemed to be testing the invalidation listener behavior itself (in their own context). I don't know what would break outside of the tests. If we go this route, we
might want to create PRs to fix the tests before we actually change
the behavior (in contrast to doing it all in the same PR). I think that
tests fail in several modules and it might require several people to fix
these tests depending on their areas of expertise. Then we would need to
test runtime behavior to see what breaks outside of the tests.

In my own codebase nothing breaks, but it's relatively small.

On the related question, I like the idea of nulling out the current value
when a property is bound.


I can pick up from where the older PR stopped. It's a simple change.

On Wed, Oct 6, 2021 at 3:15 AM Kevin Rushforth <kevin.rushfo...@oracle.com>
wrote:

Given that the intention of InvalidationListener was to be a light-weight
way to listen to properties without causing a binding chain to be
revalidated, it does seem reasonable to me that the listener is only fired on the valid --> invalid transition, which is the specified behavior, even
if the implementation doesn't currently do that in all cases.

The two related questions then are:

1. Should adding an invalidation listener to property do an immediate
get(), which will ensure that the property is then valid? This will force
an eager evaluation of the property and "arm" the property to fire an
invalidation even the next time it is invalidated.

2. Should adding an invalidation listener to a currently invalid property cause the listener to be called (either immediately or the next time the
object is invalidated)?

I think the ideal answer to both questions is "no", although I share
John's concern that changing this behavior for InvalidationListeners could
break applications. So the question is: how likely do we think that
changing this behavior will break existing applications?

I think it's something we can and should consider changing. Unless there
are serious concerns, I would support changing this behavior as part of
avoiding eager evaluation when using invalidation listeners. It would need
to be well tested (of course), and would need a CSR describing the
compatibility risk, and should probably get a release note.

Any concerns in doing this?

On the related question, I like the idea of nulling out the current value
when a property is bound.

-- Kevin


On 9/11/2021 9:41 AM, Nir Lisker wrote:

I think that we need your input on this to move it forward.

On Fri, Sep 3, 2021 at 7:49 AM Nir Lisker <nlis...@gmail.com> wrote:

so the value field should perhaps be nulled out when bound.


There was a PR for something like that in the old repo:
https://github.com/javafxports/openjdk-jfx/pull/110
<https://urldefense.com/v3/__https://github.com/javafxports/openjdk-jfx/pull/110__;!!ACWV5N9M2RV99hQ!bIbtLsC0tg02c9a_lgKnM1Xta2USX8QkKRL4imOUSw8xshJsVquOeulplJR7dfEzQUf6$>

On Fri, Sep 3, 2021 at 5:35 AM John Hendrikx <hj...@xs4all.nl> wrote:



On 02/09/2021 11:57, Nir Lisker wrote:
    So in order
    to make sure that a new interested invalidation listener does not
miss
    the fact that a property was *already* invalid, the easiest
solution
    might have been to revalidate it upon registration of a new
listener


But why does an invalidation listener need to know the past state of
the
property? It is only interested in the valid -> invalid transition. If
the property was invalid then the listener (in theory) shouldn't
receive
any events anyway on subsequent invalidations. (I understand that you
don't justify this, I'm posing it as a general question.)

Strictly speaking, no, if users are using InvalidationListener correctly
then this is definitely correct behavior. I'm not really advocating a
change, and I'd even prefer that it be brought in line with the
documentation.

I think however that many users are not using it correctly and expect an
invalidation event always the next time the value changes (and their
listener will read that value, validating it again), making it act like a light-weight ChangeListener. I know that I probably have written code
that made that assumption, and would in the past not even think twice
about replacing a change with an invalidation listener or vice versa if that happened to be a better fit. Which is sort of what happened as well in the bidirectional binding PR, and the issue slipped past the author
and two reviewers.

I suggest that we split the problem into 2: one is the case where the
property was valid when the listener was attached, and the other is
when
it was invalid.
* A valid starting state. In this case attaching a listener shouldn't
need to do anything. A subsequent invalidation event will be sent
regardless. Currently, it is calling get() redundantly.

True, the call to get is redundant in this case. Ugly too, calling get and discarding its result, while the intention is to force the property
to become valid.

* An invalid starting state. In this case the documentation says that
nothing needs to happen, but get() is called anyway. Here, the
difference is that a subsequent invalidation event is sent in one case and not in the other. The only way to advance here is to make a design
decision on what should happen, at least that's how I see it.

The docs are even more specific I think, they say no more events will be
generated until it becomes valid -- it doesn't leave any option open
that it could generate events if it wanted to.

As to the implementation of a possible solution, suppose we add the
isValid method. Upon attaching an invalidation listener, if the
property
is valid, we can skip the get() call. That solves the valid starting
state issue. The question is what to do if the property is not valid.

I also noticed an odd design choice in the implementation of
properties:
the value field does not update if the property is bound, instead, the result of the binding is returned and the value field holds an outdated
value (until the property is unbound).

Yeah, that might not be a wise decision as that can lead to memory being
referenced that users might expect to be freed. I didn't see anywhere
defined what will happen to the value of the property when it is unbound again. The current implementation will keep its last value (during the
unbind it will take the last value and assign it to its own value
field), so the value field should perhaps be nulled out when bound.

--John





Reply via email to