-
Type: Bug
-
Status: Resolved
-
Priority: Blocker
-
Resolution: Fixed
-
Affects Version/s: 2023.9
-
Component/s: Runtime, Synchronization
-
Release Notes Summary:Lifecycle registry initialization fixed
-
Tags:
-
Backlog priority:800
-
Team:PLATFORM
-
Sprint:nxplatform #111
We are running into issues with documents sometimes getting created with "undefined" lifecycle state. Please note the state is the text string "undefined", not an undefined value.
This is being observed on LTS 2023 HF06 and HF09.
I found the likely cause of in an (IMO) incorrect synchronization code for initializing the lifecycle states in method LifeCycleRegistry.lookup().
protected volatile Map<String, LifeCycle> lookup; . . . protected Map<String, LifeCycle> lookup() { if (lookup == null) { synchronized (this) { if (lookup == null) { lookup = new HashMap<>(); for (var desc : toMap().values()) { if (desc.isEnabled()) { lookup.put(desc.getName(), getLifeCycle(desc)); } } } } } return lookup; }
The condition for checking the volatile lookup variable is outside the synchronized block: https://github.com/nuxeo/nuxeo/blob/a5880ac462f9d7538a8ef43e5ac7800d602fb1e3/modules/core/nuxeo-core/src/main/java/org/nuxeo/ecm/core/lifecycle/impl/LifeCycleRegistry.java#L114.
Although lookup gets initialized inside of the synchronized block, at the time of initialization it is an empty map, though, but already visible to other threads due to the use of the volatile keyword. https://github.com/nuxeo/nuxeo/blob/a5880ac462f9d7538a8ef43e5ac7800d602fb1e3/modules/core/nuxeo-core/src/main/java/org/nuxeo/ecm/core/lifecycle/impl/LifeCycleRegistry.java#L117
I.e. other threads checking condition in line 114 (outside of the synchronized block) may see the map in a state before the for loop has completed, e.g. either empty or partially initialized.
The volatile lookup variable should be set after the for loop when the map is fully initialized and not before the loop.