Serge Huber created UNOMI-964:
---------------------------------
Summary: Flaky GraphQLListIT.testCRUD: stale static
condition-factory singletons produce match-none profile queries
Key: UNOMI-964
URL: https://issues.apache.org/jira/browse/UNOMI-964
Project: Apache Unomi
Issue Type: Bug
Components: unomi(-core)
Affects Versions: unomi-2.7.0, unomi-3.0.0, unomi-3.1.0
Reporter: Serge Huber
Assignee: Serge Huber
Fix For: unomi-3.1.0
h2. Symptom
{{org.apache.unomi.itests.graphql.GraphQLListIT.testCRUD}} fails
intermittently, both locally and in CI, with:
{noformat}
java.lang.AssertionError: Failed waiting for profile in list query
at org.apache.unomi.itests.BaseIT.keepTrying(BaseIT.java:848)
at
org.apache.unomi.itests.graphql.GraphQLListIT.testCRUD(GraphQLListIT.java:83)
{noformat}
The test retries ~30 times over ~60s and never sees the profile as a member of
the list. Re-running the job usually passes.
h2. Root cause
The GraphQL condition factories ({{ProfileConditionFactory}},
{{EventConditionFactory}}, {{ProfileAliasConditionFactory}},
{{TopicConditionFactory}}) are lazy *static singletons*. The base
{{ConditionFactory}} snapshots {{definitionsService.getAllConditionTypes()}}
once into {{conditionTypesMap}} and never refreshes it.
Because {{instance}} is {{static}}, that snapshot is frozen for the entire
PaxExam {{@PerSuite}} container lifetime, captured under whatever tenant/warmup
context triggered the *first* {{get()}} call in the suite. If that first call
happens before the condition-type cache is fully warm,
{{getConditionType("profilePropertyCondition")}} returns {{null}}, producing a
{{Condition}} with a null type. The ES dispatcher then logs and short-circuits:
{noformat}
TypeResolutionServiceImpl: Condition has no type ID for query builder
ConditionESQueryBuilderDispatcher: Condition type is null for condition
typeID=null, returning match-none query
{noformat}
So the {{findLists.active}} member query returns zero rows for the rest of the
run.
This explains every observed detail:
* {{totalCount == 1}} still passes because the list-metadata query uses a
*fresh* {{ConditionFactory}}, while {{active.edges}} is always empty because
the member query uses the *stale singleton*.
* It is a structural {{match-none}}, not an indexing delay — which is why prior
timing-based fixes (deferring {{refreshPersistence}}, tripling retries in
commit {{9eeac6d21}}) never worked.
* Intermittency depends purely on suite/test ordering and warmup timing at
first singleton initialization; a fresh container (re-run) usually initializes
it at a "good" time.
h2. Scope of fix
# Remove the static singletons from the four factories; return a fresh instance
per {{get()}}. (A local stash already implements this.)
# In the same PR, drop the {{conditionTypesMap}} snapshot in
{{ConditionFactory}} and resolve types live via
{{definitionsService.getConditionType(id)}}. This also removes the leaked
first-request {{DataFetchingEnvironment}}/{{ServiceManager}}.
# Harden {{AddProfileToListCommand}}: replace {{send(event) ==
EventService.PROFILE_UPDATED}} with {{(send(event) & PROFILE_UPDATED) ==
PROFILE_UPDATED}} (latent bitmask bug on the same code path).
# Revert the misleading timing workarounds and comment in
{{GraphQLListIT.testCRUD}}.
h2. Performance
Negligible. All condition-type lookups are in-memory cache reads (no
Elasticsearch/IO). Net-positive: removes a {{synchronized}} static bottleneck
and a per-container memory retention. The high-frequency ingestion path
({{/context.json}}, {{/eventcollector}}, rule/segment evaluation) does not use
these factories and is unaffected.
h2. Acceptance criteria
* {{GraphQLListIT.testCRUD}} passes reliably (e.g. 20 consecutive local runs on
the Elasticsearch profile).
* No recurring {{returning match-none query}} warnings during the run.
* Full {{graphql}} IT package is green.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)