Skip to content

Fix race condition in AbstractReconciler.reset() event ordering#3794

Merged
vogella merged 2 commits intoeclipse-platform:masterfrom
vogella:fast
Mar 27, 2026
Merged

Fix race condition in AbstractReconciler.reset() event ordering#3794
vogella merged 2 commits intoeclipse-platform:masterfrom
vogella:fast

Conversation

@vogella
Copy link
Copy Markdown
Contributor

@vogella vogella commented Mar 18, 2026

Summary

  • Fixes flaky FastAbstractReconcilerTest.testReplacingDocumentWhenClean by reordering BackgroundWorker.reset() so that the reconcilerReset() hook executes before fIsDirty is set to true and before any queue notifications wake the background thread
  • Adds ReconcilerResetOrderingTest that deterministically reproduces the race condition by widening the race window with a 50ms delay — this test always fails without the fix and always passes with it

Root Cause

In BackgroundWorker.reset(), reconcilerReset() was called after fIsDirty was set and after notifyAll()/informNotFinished(). Two race windows existed:

  1. aboutToBeReconciledInternal() (called before reset() in both documentChanged and inputDocumentChanged) triggers signalWaitForFinish()notifyAll(), waking the background thread. It could then see fIsDirty=true (set at the start of reset()), consume the fReset flag, skip delay (waitFinish=true), and reach process() before reconcilerReset() ran.

  2. Within reset() itself, notifyAll() and informNotFinished() were called before reconcilerReset(), providing additional wake-up points.

Fix

Move reconcilerReset() to the very beginning of reset(), before fIsDirty is set. The background thread cannot find work (isDirty() returns false) and blocks in delay() until after the hook completes and state flags are set.

User-visible impact

The AbstractReconciler drives background processing in text editors (syntax validation, spell checking, error/warning markers, code analysis). The race occurs when the document changes or is replaced (e.g., switching files, reverting). Without the reset hook firing first, process() can run with stale state, causing brief flickers of incorrect error markers or squiggles from a previous document. Being a race condition, this is intermittent and self-corrects on the next reconciling pass.

Test plan

  • AbstractReconcilerTest (7 tests) — base reconciler tests, all pass
  • FastAbstractReconcilerTest (7 tests) — fast-mode tests including previously flaky testReplacingDocumentWhenClean, all pass
  • ReconcilerResetOrderingTest (7 tests) — new regression test with 50ms delay, deterministically fails without fix, passes with fix
  • Verified test fails without fix by temporarily reverting production code

Fixes #2708

@eclipse-platform-bot
Copy link
Copy Markdown
Contributor

This pull request changes some projects for the first time in this development cycle.
Therefore the following files need a version increment:

tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF

An additional commit containing all the necessary changes was pushed to the top of this PR's branch. To obtain these changes (for example if you want to push more changes) either fetch from your fork or apply the git patch.

Git patch
From da52f3ac4b9cbb464137b71d8187d3295f67b5c9 Mon Sep 17 00:00:00 2001
From: Eclipse Platform Bot <platform-bot@eclipse.org>
Date: Wed, 18 Mar 2026 14:49:58 +0000
Subject: [PATCH] Version bump(s) for 4.40 stream


diff --git a/tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF
index 7cc57c18bf..0c0524f3eb 100644
--- a/tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF
+++ b/tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-Name: %Plugin.name
 Bundle-SymbolicName: org.eclipse.jface.text.tests
-Bundle-Version: 3.14.0.qualifier
+Bundle-Version: 3.14.100.qualifier
 Bundle-Vendor: %Plugin.providerName
 Bundle-Localization: plugin
 Export-Package: 
-- 
2.53.0

Further information are available in Common Build Issues - Missing version increments.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 18, 2026

Test Results

   852 files  ±0     852 suites  ±0   51m 36s ⏱️ - 1m 41s
 7 874 tests +7   7 631 ✅ +7  243 💤 ±0  0 ❌ ±0 
20 127 runs  ±0  19 471 ✅ ±0  656 💤 ±0  0 ❌ ±0 

Results for commit 8d01b63. ± Comparison against base commit 1c1d72e.

♻️ This comment has been updated with latest results.

@vogella vogella marked this pull request as ready for review March 18, 2026 16:05
@vogella
Copy link
Copy Markdown
Contributor Author

vogella commented Mar 20, 2026

Test failure is reported here: #3796

vogella and others added 2 commits March 23, 2026 10:39
In BackgroundWorker.reset(), reconcilerReset() was called after
fIsDirty was set to true and after fDirtyRegionQueue.notifyAll() and
informNotFinished(). This created two race windows:

1. aboutToBeReconciledInternal() (called before reset() in both
   documentChanged and inputDocumentChanged) triggers
   signalWaitForFinish() which wakes the background thread via
   notifyAll(). The background thread could then see fIsDirty=true
   (set at the start of reset()), consume the fReset flag, loop back,
   skip delay (waitFinish=true), and reach process() — all before the
   main thread reached reconcilerReset().

2. Within reset() itself, notifyAll() and informNotFinished() (which
   also triggers signalWaitForFinish → notifyAll) were called before
   reconcilerReset(), providing additional wake-up points for the
   background thread.

The fix moves reconcilerReset() to the very beginning of reset(),
before fIsDirty is set to true. This way, even if the background
thread is already awake from an earlier notification, it cannot find
work to do (isDirty() returns false) and blocks in delay() until after
reconcilerReset() completes and the state flags are set.

The reordered reset() is now:
1. reconcilerReset() — hook runs while isDirty is still false
2. Set state flags (fIsDirty, fReset) in synchronized block
3. informNotFinished() — may wake background thread
4. fDirtyRegionQueue.notifyAll() — wakes background thread

This guarantees the reconcilerReset hook completes before the
background thread can proceed to process().

User-visible impact: The AbstractReconciler drives background
processing in text editors (syntax validation, spell checking,
error/warning markers, code analysis). The race condition occurs when
the document changes or is replaced (e.g., switching files, reverting).
Without the reset happening first, process() can run with stale state,
causing brief flickers of incorrect error markers or squiggles from
the previous document on the new content. Being a race condition, this
is intermittent and self-corrects on the next reconciling pass.

Test coverage: Added ReconcilerResetOrderingTest which extends
FastAbstractReconcilerTest with a 50ms delay before logging
reconcilerReset. This widens the race window so that without the fix,
the background thread deterministically reaches process() before the
reset hook logs — causing testReplacingDocumentWhenClean and
testDirtyingWhenClean to always fail. With the fix, all tests pass
because the background thread cannot find work (isDirty=false) during
the delay. The full reconciler test suite (AbstractReconcilerTest +
FastAbstractReconcilerTest + ReconcilerResetOrderingTest, 21 tests)
covers reset() through all code paths.

See eclipse-platform#2708
@vogella
Copy link
Copy Markdown
Contributor Author

vogella commented Mar 27, 2026

Merging so that this gets in early and we can spot issue with it.

@vogella vogella merged commit 0b514c7 into eclipse-platform:master Mar 27, 2026
18 checks passed
@vogella vogella deleted the fast branch March 27, 2026 11: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.

Random failing org.eclipse.jface.text.tests.reconciler.FastAbstractReconcilerTest#testReplacingDocumentWhenClean()

2 participants