Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8351276

Prevent redundant computeValue calls when a chain of mappings becomes observed

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: P4 P4
    • tbd
    • jfx23
    • javafx
    • None

      When a chain of observable values like:

            property.map(...).map(...).map(...).map(...).map(...)

      ...becomes observed by adding an initial listener, they should allow validation as they're now observed. Allowing validation means that they're allowed to cache the value. This process works correctly, but inefficiently. Each level will only allow validation once it is observed itself, but it does so AFTER the listener was added (and potentially called `getValue` -> `computeValue` for its own caching purposes).

      Because this happens after a listener was added, the next high level will not yet be allowing validation and so won't cache the value, causing calls up the mapping chain, none of which allow caching yet. Run this sample provided by user Alver415:

      // Property with an arbitrary initial value.
      Property<Object> property = new SimpleObjectProperty<>(new Object());

      // Simple function that prints which stage we're at and returns the same value.
      BiFunction<String, Object, Object> function = (string, value) -> {
          System.out.printf("%s", string);
          return value;
      };

      //Chained mappings and with an arbitrary listener.
      property.map(value -> function.apply("\nA", value))
              .map(value -> function.apply("B", value))
              .map(value -> function.apply("C", value))
              .map(value -> function.apply("D", value))
              .map(value -> function.apply("E", value))
              .addListener((_, _, _) -> {});

      The output is:

      ABCDE
      ABCD
      ABC
      AB
      A

      As you can see from the above, many unnecessary mapping calls are done (via `computeValue`) due to validation being allowed too late in the process of adding the first user provided change listener.

      This problem does not occur with just a single mapping, as we took care to avoid exactly this scenario; what however we did not take into account is the recursive nature of this process when a chain of mapping is involved.

      Solution:

      We need to call `observeSources` before actually adding the user listener (or in the case of the intermediate observables, before adding the internal invalidation listener). Calling `observeSources` before will ensure that we first put all listeners in place, and only then query a value (via the change listener the user added). This will result in a single computation of the chain, which all levels can cache immediately as they're already considered observed.

            jhendrikx John Hendrikx
            jhendrikx John Hendrikx
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: