On Fri, 6 Jan 2023 19:34:17 GMT, John Hendrikx <jhendr...@openjdk.org> wrote:

>>> Not sure I'm following here. Do you want to implement this pattern for all 
>>> property implementations? If we just want to implement it for mapped 
>>> bindings, only the `LazyObjectBinding` class needs to be thread-safe.
>> 
>> Yes, true, only making it thread-safe for that class should remove a chain 
>> of fluent bindings.  I think however that it would be good to implement this 
>> for as many classes as we can as the stub cleaning is normally only 
>> triggered on invalidation/changes (and as I recently discovered, when 
>> `ExpressionHelper` resizes its backing list).
>> 
>>> Note that it's not enough for the `addListener` and `removeListener` 
>>> methods to be `synchronized`. _All_ reads and writes must be protected, 
>>> which (in the case of `LazyObjectBinding`) includes the `invalidate` and 
>>> `isObserved` methods.
>> 
>> Yes, true, I only fixed synchronization issues in my experiment and didn't 
>> look much further than that yet.
>> 
>>> But if we do that, the lock tax must be paid on every single access to the 
>>> `ExpressionHelper` field (because `ExpressionHelper` is not thread-safe). 
>>> Locking any and all usages of `ExpressionHelper` can have performance 
>>> implications that we need to evaluate carefully.
>> 
>> Yeah, we shouldn't do that, it synchronizes all accesses to all property 
>> lists everywhere, it would be an easy "fix" as it's in one place, but it 
>> doesn't feel right.
>> 
>>> However, I think we might get away with an implementation that only 
>>> requires locking for the `addListener`/`removeListener` methods, but 
>>> (crucially) not for `fireValueChangedEvent`:
>>> 
>>> This implementation works by creating immutable specializations of 
>>> [ConcurrentExpressionHelper](https://gist.github.com/mstr2/1efc9e866f81622253711a963bd272fc).
>>>  When a listener is added, a new `ConcurrentExpressionHelper` instance is 
>>> created, which doesn't interfere with an existing instance that is 
>>> currently in use by `ConcurrentExpressionHelper.fireValueChangedEvent`. 
>>> The`ConcurrentExpressionHelper.Generic` specialization is mutable, but uses 
>>> the lock-free `ConcurrentLinkedQueue` to store its listeners.
>> 
>> I see you have been playing with improving `ExpressionHelper` as well :) I 
>> noticed there is a bug in the current one, where a copy of the list is made 
>> each time when `locked` is `true`, even when the list was already copied for 
>> the "current" lock.  I ran into this problem right after I fixed the 
>> performance issue (it wasn't obvious before as it was already very slow, but 
>> basically an extra copy is happening in some circumstances on top of the 
>> array copying that is done to remove an entry).
>> 
>> The situation where `locked` is true doesn't happen that often for normal 
>> code, but it happens specifically in the case when a call to `invalidated` 
>> is cleaning up dead stubs...
>> 
>>> `ConcurrentExpressionHelper` avoids locking the (probably most frequently 
>>> invoked) `fireValueChangedEvent` method, but sacrifices cache locality when 
>>> a large number of listeners is added. We should probably benchmark all of 
>>> these proposals before deciding on a solution.
>> 
>> Yes, I think that's a good idea, I considered using `ConcurrentLinkedQueue` 
>> as well since we don't need a structure with index based access, but I 
>> couldn't find one with good O performance for `remove(T)` that wouldn't 
>> subtly change the current semantics.  FWIW, here's the quick `Collection` 
>> implementation I whipped up: 
>> https://gist.github.com/hjohn/8fee1e5d1a9eacbbb3e021f8a37f582b
>> 
>> And the changes in `ExpressionHelper` (including different locking): 
>> https://gist.github.com/hjohn/f88362ea78adef96f3a54d97e2405076
>
>> @hjohn Sorry to append message here, but I don't know other places where we 
>> could talk about this topic Is it a good idea if Binding class provide 
>> method like this? Inspired by this PR
> 
> @chengenzhao The only way to do this right now using the fluent bindings is 
> like this:
> 
>     Rectangle rect = new Rectangle();
> 
>     rect.widthProperty()
>       .flatMap(x -> rect.heightProperty())
>       .map(y -> rect.getWidth() * rect.getHeight())
>       .addListener((obs, old, a) -> System.out.println("Area = " + a));
> 
> It is not very pretty.

@hjohn 
Is it that what we really need here is a reduce function?
since we already have map(thanks to this PR) then we probably need a reduce 
function to zip those map products?
e.g.

textProperty.bind(Binding.reduce(widthProperty, heightProperty, (w, h) -> "Area 
is "+w.doubleValue()*h.doubleValue()));

-------------

PR: https://git.openjdk.org/jfx/pull/675

Reply via email to