diff --git a/articles/building-apps/server-push/updates.adoc b/articles/building-apps/server-push/updates.adoc index 048fed6036..ff4c1bec09 100644 --- a/articles/building-apps/server-push/updates.adoc +++ b/articles/building-apps/server-push/updates.adoc @@ -276,6 +276,12 @@ When you know events are coming no faster than two to four events per second, yo The buffering duration depends on the size of the UI update, and the network latency. In some applications, you may need to use a longer buffer duration. In others, a shorter one might work. You should try various durations to see what's best for your application. +[role="since:com.vaadin:vaadin@V25.3"] +== Updates That Never Arrive + +Flooding is one failure; a client that receives nothing is another. Updates scheduled for a UI are held in memory until a response is written for its browser, so a job that keeps scheduling updates for a client that isn't receiving them -- a laptop that went to sleep, a closed push connection, a discarded tab -- grows the heap until the session expires. See <<{articles}/flow/component-internals/element-api/calling-javascript#undelivered-invocations, Undelivered Invocations>> for the warning Flow logs and for what to do instead. + + == Avoiding Unnecessary Pushes The `UI.access()` method updates the user interface, asynchronously. The update operation is not executed immediately, but added to a queue and executed at some time later. If this is combined with regular event-driven updates in the HTTP request thread, you may have a situation in which the user interface is updated out-of-order. diff --git a/articles/flow/component-internals/element-api/calling-javascript.adoc b/articles/flow/component-internals/element-api/calling-javascript.adoc index c16c9941a9..35d14d405c 100644 --- a/articles/flow/component-internals/element-api/calling-javascript.adoc +++ b/articles/flow/component-internals/element-api/calling-javascript.adoc @@ -164,6 +164,39 @@ CompletableFuture> future = getElement() ---- +[#undelivered-invocations] +[role="since:com.vaadin:vaadin@V25.3"] +== Undelivered Invocations + +An invocation scheduled on the server is held in memory until it's written into a response for the browser. An application that keeps scheduling invocations which never reach the browser grows until it runs out of memory. Once 1000 invocations for one UI are waiting to be sent, Flow logs a warning naming the component they belong to, the expression of the most recent one, and why nothing is being delivered. The warning is logged once per UI. + +Invocations pile up whenever nothing carries them to the browser: + +- *The owner is detached.* An invocation for a detached element is held until that element is attached again, which never happens for a component the application has discarded. +- *The owner is invisible.* Invocations for an invisible component are kept until it becomes visible. +- *The UI has no open push connection.* Nothing is sent until the browser makes a request of its own, which a closed tab whose session hasn't expired yet never does. + +Where only the latest value matters -- a progress value, a clock, a live measurement -- avoid scheduling a new invocation for every update: + +- Keep the [classname]`PendingJavaScriptResult` that [methodname]`executeJs()` returns, and call [methodname]`cancelExecution()` on it before scheduling the next one. +- Set an element property with [methodname]`Element.setProperty()` instead. Only the last value of a property is sent. +- In a background task, compare [methodname]`UI.getLastUpdateSentTimestamp()` against the current time and stop scheduling updates while the browser isn't receiving them. + +The timestamp is readable without the session lock, so a background task can decide whether to update the UI before acquiring it: + +[source,java] +---- +if (Duration.between(ui.getLastUpdateSentTimestamp(), Instant.now()) + .compareTo(STALE_THRESHOLD) < 0) { + ui.access(() -> statusLabel.setText(statusText)); +} +---- + +It tells when the pending updates were last written towards the browser, not that the browser received them, and for a UI that has had no response written yet it's the time the UI was created. + +To find the code that schedules the invocations, enable debug logging for `com.vaadin.flow.component.internal.PendingJavaScriptInvocationUtil`, which then also logs the call site of the invocation that triggered the warning. Turning that logger off silences the warning. + + [[js-function]] [role="since:com.vaadin:vaadin@V25.2"] == Passing JavaScript Functions diff --git a/articles/flow/production/troubleshooting.adoc b/articles/flow/production/troubleshooting.adoc index efba87c282..6ae34e3b20 100644 --- a/articles/flow/production/troubleshooting.adoc +++ b/articles/flow/production/troubleshooting.adoc @@ -110,4 +110,10 @@ This mean the value from one application is overridden by another. To resolve th See https://datatracker.ietf.org/doc/html/rfc6265#section-8.5[HTTP State Management Mechanism RFC] for more information. +The application runs out of memory, and the heap holds a large number of pending JavaScript invocations.:: +JavaScript scheduled from the server with [methodname]`executeJs()` is kept in memory until it's written into a response for the browser, so an application that keeps scheduling invocations which are never delivered grows until it fails. Flow logs a warning once 1000 invocations for one UI are waiting to be sent, naming the component they belong to and why nothing is being delivered. ++ +See <<{articles}/flow/component-internals/element-api/calling-javascript#undelivered-invocations, Undelivered Invocations>> for the causes and for what to do instead. + + [discussion-id]`9A4AD367-94A0-4933-AE09-1A08016E6E58`