Skip to content

[WIP] mail: debug chatter race-condition issue#5166

Draft
alexkuhn wants to merge 321 commits into
master-owl3-migrationfrom
master-owl3-migration-chatter-aku
Draft

[WIP] mail: debug chatter race-condition issue#5166
alexkuhn wants to merge 321 commits into
master-owl3-migrationfrom
master-owl3-migration-chatter-aku

Conversation

@alexkuhn
Copy link
Copy Markdown

@alexkuhn alexkuhn commented May 15, 2026

  1. Run test with dev tools until last console is RPC store data
  2. window.aku2 = window.aku.__owl__ to track fiber assign of Chatter

=> observe cancel of fiber from FromRenderer

@robodoo
Copy link
Copy Markdown

robodoo commented May 15, 2026

This PR targets the un-managed branch odoo-dev/odoo:master-owl3-migration, it needs to be retargeted before it can be merged.

alexkuhn and others added 29 commits May 19, 2026 14:39
Before this commit, typing composer suggestion like `@` didn't work
most of the time.

This happens because the suggestion hook had a internal `useState()`
that has an internal counter to force re-render the navigable list.
However, this counter was not observed, thus it didn't render it.

This commit fixes the issue by observing this internal counter
in navigable list props, which is used by suggestion.

This commit also fixes these related issues in composer suggestions:

1. Suggestion was prompt even with double whitespace. This happens
because internal sync of `composerHtml` and `composerText` were
trimming the whitespace and thus ignoring the double whitespace.
Note that the internal trimming is meant as pre-processing before
posting a message but for other cases usually we don't want to trim.

2. Navigable list of suggestion was sometimes not opening
automatically, and first item was not automatically pre-selected.
This happens because `NavigableList` relies on `useLayoutEffect` with
dependency on `props` object, which had its shape change in owl2
but in owl3 that's no longer the case, therefore the
`useLayoutEffect` did not work. This is fixed with triggering the
open of `NavigableList` on change of `props.options`.
Previous implementation was far too naive: the immediateEffect was
triggering the onUpdate even when any field that is read in the
function were changed. Also with the update queue, this could
register more than a single onChange, which lead to burst of onUpdate
that are very prone to bugs from race condition.

This commit makes an implementation that follows the logic around the
update queue:
- a single onChange is registered at once
- it untracks the function invoked by onUpdate
- when onUpdate is put in queue, the dequeue of onUpdate
automatically stops previous onUpdate and registers another one
Improve implementation of compute fields to take the update cycle
into account:
- properly untrack parts of `immediateEffect` that are outside of
the compute method
- have reactivity of the `immediateEffect` trigger the
`requestCompute`, to comply with update cycle
- Ensure single `immediateEffect` thanks to `stopFn` patter like with
`onUpdate`'s internal `onChange`.

`computeProxy2` has been removed because this is dead code: this
proxy was used to scope local reactivity of compute method, but this
doesn't work anymore with owl3 and was always the record proxy,
which this commit makes it explicit with the removal.
Similar implementation details to `compute` on field.
Before this commit, computed and sorted fields could have their
"in-need" flag dropped while the fields are still being "in-need".

This happens because there's internal `onChange` to reset the
"in-need" flag when the field has changed from computing / sorting,
and "in-need" flag is expected to be set again in case the observers
still need it.

This reset of "in-need" flag makes sense, but there's a scenario
where this is not desirable: when the field is computed or sorted on
the fly by an "in-need". When this happens, the flag "in-need" is
set, but immediately after the `onChange` of computing / sorting is
triggering and reset the flag, which incorrectly flag the field as
not "in-need". This leads to reactivity problems.

This commit fixes the issue by forcing the "in-need" flag at the
end of computing /sorting when the action is triggered from
"in-need". This preserves the automatic reset of "in-need" flag from
change of value computing / sorting while ensuring the "in-need" flag
stays when there are still ongoing observers.

Note that this "worked" before with owl2 because the observers were
more aggressively re-rendering, as shown with tests that show initial
render done twice instead or once, or lazy field still continuing
computing many steps later even though the observer has stopped
observing. This commit fixes the issue.

Note that this commit adds an extra "LAZY", i.e. when an observer
doesn't register again the lazy field is still computed again at
least once. While this looks inefficient, this makes sense with the
strategy being used to detect "in-need" observers, which can be
immediate when compute is triggered from the "on-need", but if this
comes from "in-need" as the flag has to be kept, the "in-need" flag
is kept until next compute / sort that updates the value accordingly.
Tests were badly written, using old `reactive()` syntax or were
showing wrong behavior from old JS models, fixed by recent commits.
Before this, record.exists() was not reactive. This happens because
with owl3 the `record._proxy` is the same for all `reactive()`, which
implied that the proxy was always downgraded to `_proxyInternal`.
This is a problem because whenever implementation details of JS
models were accessing some technical fields, this was made without
reactivity.

This commit removes downgrade completely, as this no longer works in
terms of correctness.

The downgrade is motivated for performance reasons, this is probably
less costly for owl3 so the removal should be ok. We'll see later if
we need some other somewhat similar techniques in case performance is
a concern.
- datetimepicker était cassé dans les interactions ;
- on pourra avoir 3 builds vert ;
- 🤡.
mcm-odoo and others added 23 commits May 19, 2026 14:41
… part'

Test was adapted several time. Initially the patch of Message had:

- onMounted
- onPatched

This was then changed to
- onMounted
- onRendered

And this change over-estimated the rendering by at least 1, as
onRendered also includes an extra render.
But when this change was made, the counter was not increased in the
assertion.

Then the onMounted was removed, and the counter was reduced.

The test is failing because the counter was changed from lower than
3 to lower than 2, but the initial change from onMounted/onPatched to
onRendered should preserve the same counter of lower than 3.

This commit fixes with the correct counter.

Also the assertion on renderers on ActionSwiper were not necessarily
made clear that this is related to the Message, so this commit adds
comment to make it clear.
Partial revert of [1]

Tests were wrongly adapted: the mentions have an extra space when
adding the mentions in composer while composing, but the recovering
part from starting edition of a message with already some mentions
should not have the extra spaces (message content is trimmed).

The tests were likely adapted because the tests were not passing due
to prior steps that insert mention by hand in composer, and there was
probably a issue in owl 3 code where the composer's input value of
thread was reused with the extra whitespace.

[1]: ecbf491
Uses the many fixes we implemented in tools_js_expression to
prevent the parametric t-call script from adding irrelevant
template changes.

WHY: to save time on future rebases and future client runs of this
script
Before this commit, running the whole `@mail` HOOT tests suite leads
to heavy memory leak.

This happens for one main reason: internal effects in `@mail` are not
properly disposed in-between tests. This includes `onChange` utility
function, which internally uses `immediateEffect`.

1. `onChange` was returning a proxy, but this return value was never
used. This commit now makes it return a function to dispose effects
that are internally used. Some components that were using `onChange`
now can properly dispose these effects on destroy

2. `onChange` and `Record.onChange` (= a version of `onChange` that
follows the store's update cycle) made in store, services, and JS
models had no dispose strategy. This commit now adds several
functions for `register(Record)OnChange` and `runDisposeFn(s)`, so
that all these dispose functions are executed in-between each test.

Browser tab RAM of running whole `@mail test`:
- before this commit: 2GB
- after this commit: 250MB
Continues on the previous html_editor fix, that fixed behavior on
mobile but broke on desktop.

In the previous fix, we moved the logic to blur on the click event
handler but that caused issues on the disktop test because we don't
actually click on the iframe input in the test.

I tried multiple solutions but many test really on this behavior,
so I ended up reverting the useLayoutEffect to how it was before
and just adding the bottomsheet rule in the iframe input click
which only happens on mobile.
The concerned test was randomly failing before this commit because
it didn't properly enforce the flow it tried to reproduce, which is
 - open a form view with a pager
 - click "Log a note" to open the composer
 - click pager next to go to the next record
 - on that record, type "@" in the composer to mention people

However, before this commit, we typed "@" in the composer without
waiting to be on the second record. So it might happen that we
type "@" while still being on the first record, and then tried to
do assertions on the mention suggestion dropdown while being on the
second record (which thus contains an empty composer).
…server

In Owl3, effect() runs its callback immediately and synchronously on
creation to establish reactive tracking. This means willUpdateRecord
fires during setup() itself, before this.lastAccount and this.lastProduct
were initialized, so both were undefined. shallowEqual(undefined, value)
returns false, making accountChanged and productChanged always true on
the initial run, triggering a spurious fetchAllPlans RPC for every newly
constructed AnalyticDistribution component.

In Owl2, the master useRecordObserver used effect(fn, [deps]) + batched
+ RAF debouncing, deferring the first callback until after setup()
completed, so lastAccount/lastProduct were already set.

Fix: move lastAccount/lastProduct initialization before the
useRecordObserver call.
@mcm-odoo mcm-odoo force-pushed the master-owl3-migration branch from f05caf0 to be0db09 Compare May 20, 2026 07:29
kebeclibre and others added 3 commits May 20, 2026 11:06
taking advantage of owl's odoo/owl@d66ce3c
useLayoutEffect in Odoo is adapted to not subscribe to those signals
1. Run test with dev tools until last console is RPC store data
2. `window.aku2 = window.aku.__owl__` to track fiber assign of
   Chatter

=> observe cancel of fiber from FromRenderer
@alexkuhn alexkuhn force-pushed the master-owl3-migration-chatter-aku branch from 681d442 to f802f75 Compare May 20, 2026 11:58
@ged-odoo ged-odoo force-pushed the master-owl3-migration branch 2 times, most recently from c7f9086 to 58e9fc7 Compare May 20, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.