1313 *******************************************************************************/
1414package org .eclipse .ui .tests .harness .util ;
1515
16+ import java .util .concurrent .Phaser ;
17+ import java .util .concurrent .TimeUnit ;
18+ import java .util .concurrent .TimeoutException ;
19+
1620import org .eclipse .jface .preference .IPreferenceStore ;
1721import org .eclipse .swt .SWTException ;
1822import org .eclipse .swt .widgets .Display ;
@@ -44,6 +48,10 @@ public class RCPTestWorkbenchAdvisor extends WorkbenchAdvisor {
4448
4549 private static boolean started = false ;
4650
51+ // Use a Phaser to ensure async operations are scheduled before postStartup
52+ // Each thread registers itself and arrives when the operation is scheduled
53+ private static final Phaser asyncPhaser = new Phaser (1 ); // 1 for the main thread
54+
4755 public static boolean isSTARTED () {
4856 synchronized (RCPTestWorkbenchAdvisor .class ) {
4957 return started ;
@@ -122,12 +130,10 @@ public void eventLoopIdle(final Display display) {
122130 public void preStartup () {
123131 super .preStartup ();
124132 final Display display = Display .getCurrent ();
133+
125134 if (display != null ) {
126135 display .asyncExec (() -> {
127- if (isSTARTED ())
128- asyncDuringStartup = Boolean .FALSE ;
129- else
130- asyncDuringStartup = Boolean .TRUE ;
136+ asyncDuringStartup = !isSTARTED ();
131137 });
132138 }
133139
@@ -151,24 +157,28 @@ public void preStartup() {
151157 }
152158
153159 private void setupSyncDisplayThread (final boolean callDisplayAccess , final Display display ) {
160+ // Register this thread with the phaser
161+ asyncPhaser .register ();
154162 Thread syncThread = new Thread () {
155163 @ Override
156164 public void run () {
157165 if (callDisplayAccess )
158166 DisplayAccess .accessDisplayDuringStartup ();
159167 try {
160168 display .syncExec (() -> {
161- synchronized (RCPTestWorkbenchAdvisor .class ) {
162- if (callDisplayAccess )
163- syncWithDisplayAccess = !isSTARTED () ? Boolean .TRUE : Boolean .FALSE ;
164- else
165- syncWithoutDisplayAccess = !isSTARTED () ? Boolean .TRUE : Boolean .FALSE ;
169+ if (callDisplayAccess ) {
170+ syncWithDisplayAccess = !isSTARTED ();
171+ } else {
172+ syncWithoutDisplayAccess = !isSTARTED ();
166173 }
167174 });
168175 } catch (SWTException e ) {
169176 // this can happen because we shut down the workbench just
170177 // as soon as we're initialized - ie: when we're trying to
171178 // run this runnable in the deferred case.
179+ } finally {
180+ // Signal that this operation has completed
181+ asyncPhaser .arriveAndDeregister ();
172182 }
173183 }
174184 };
@@ -177,19 +187,26 @@ public void run() {
177187 }
178188
179189 private void setupAsyncDisplayThread (final boolean callDisplayAccess , final Display display ) {
190+ // Register this thread with the phaser
191+ asyncPhaser .register ();
180192 Thread asyncThread = new Thread () {
181193 @ Override
182194 public void run () {
183195 if (callDisplayAccess )
184196 DisplayAccess .accessDisplayDuringStartup ();
185- display .asyncExec (() -> {
186- synchronized (RCPTestWorkbenchAdvisor .class ) {
187- if (callDisplayAccess )
188- asyncWithDisplayAccess = !isSTARTED () ? Boolean .TRUE : Boolean .FALSE ;
189- else
190- asyncWithoutDisplayAccess = !isSTARTED () ? Boolean .TRUE : Boolean .FALSE ;
191- }
192- });
197+ try {
198+ display .asyncExec (() -> {
199+ if (callDisplayAccess ) {
200+ asyncWithDisplayAccess = !isSTARTED ();
201+ } else {
202+ asyncWithoutDisplayAccess = !isSTARTED ();
203+ }
204+ });
205+ } finally {
206+ // Signal that this operation has been scheduled (not necessarily executed yet)
207+ // This avoids deadlock since we're not waiting for execution on the UI thread
208+ asyncPhaser .arriveAndDeregister ();
209+ }
193210 }
194211 };
195212 asyncThread .setDaemon (true );
@@ -199,6 +216,29 @@ public void run() {
199216 @ Override
200217 public void postStartup () {
201218 super .postStartup ();
219+
220+ // Wait for all async/sync operations to be scheduled (not blocking UI thread)
221+ try {
222+ // Wait up to 5 seconds for all operations to be scheduled
223+ // The main thread arrives and deregisters, waiting for all other registered threads
224+ asyncPhaser .awaitAdvanceInterruptibly (asyncPhaser .arrive (), 5 , TimeUnit .SECONDS );
225+ } catch (TimeoutException e ) {
226+ // Log warning but don't throw - we need to mark as started to avoid breaking subsequent tests
227+ System .err .println ("WARNING: Not all async/sync operations were scheduled within timeout" );
228+ e .printStackTrace ();
229+ } catch (InterruptedException e ) {
230+ Thread .currentThread ().interrupt ();
231+ System .err .println ("WARNING: Interrupted while waiting for async/sync operations" );
232+ }
233+
234+ // Pump the event loop to ensure async runnables execute before marking as started
235+ // This prevents the original race condition where async variables might not be set yet
236+ // Wait until the variables that should be set during startup are actually set to TRUE
237+ UITestUtil .processEventsUntil (() -> Boolean .TRUE .equals (syncWithDisplayAccess ) && Boolean .TRUE .equals (asyncWithDisplayAccess ), 5000 );
238+ // Process any remaining events to allow variables that should NOT be set during startup
239+ // to accidentally execute (to detect regression)
240+ UITestUtil .processEvents ();
241+
202242 synchronized (RCPTestWorkbenchAdvisor .class ) {
203243 started = true ;
204244 }
0 commit comments