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,6 +157,8 @@ 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 () {
@@ -160,15 +168,18 @@ public void run() {
160168					display .syncExec (() -> {
161169						synchronized  (RCPTestWorkbenchAdvisor .class ) {
162170							if  (callDisplayAccess )
163- 								syncWithDisplayAccess  = !isSTARTED () ?  Boolean . TRUE  :  Boolean . FALSE ;
171+ 								syncWithDisplayAccess  = !isSTARTED ();
164172							else 
165- 								syncWithoutDisplayAccess  = !isSTARTED () ?  Boolean . TRUE  :  Boolean . FALSE ;
173+ 								syncWithoutDisplayAccess  = !isSTARTED ();
166174						}
167175					});
168176				} catch  (SWTException  e ) {
169177					// this can happen because we shut down the workbench just 
170178					// as soon as we're initialized - ie: when we're trying to 
171179					// run this runnable in the deferred case. 
180+ 				} finally  {
181+ 					// Signal that this operation has completed 
182+ 					asyncPhaser .arriveAndDeregister ();
172183				}
173184			}
174185		};
@@ -177,19 +188,27 @@ public void run() {
177188	}
178189
179190	private  void  setupAsyncDisplayThread (final  boolean  callDisplayAccess , final  Display  display ) {
191+ 		// Register this thread with the phaser 
192+ 		asyncPhaser .register ();
180193		Thread  asyncThread  = new  Thread () {
181194			@ Override 
182195			public  void  run () {
183196				if  (callDisplayAccess )
184197					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- 				});
198+ 				try  {
199+ 					display .asyncExec (() -> {
200+ 						synchronized  (RCPTestWorkbenchAdvisor .class ) {
201+ 							if  (callDisplayAccess )
202+ 								asyncWithDisplayAccess  = !isSTARTED ();
203+ 							else 
204+ 								asyncWithoutDisplayAccess  = !isSTARTED ();
205+ 						}
206+ 					});
207+ 				} finally  {
208+ 					// Signal that this operation has been scheduled (not necessarily executed yet) 
209+ 					// This avoids deadlock since we're not waiting for execution on the UI thread 
210+ 					asyncPhaser .arriveAndDeregister ();
211+ 				}
193212			}
194213		};
195214		asyncThread .setDaemon (true );
@@ -199,6 +218,27 @@ public void run() {
199218	@ Override 
200219	public  void  postStartup () {
201220		super .postStartup ();
221+ 
222+ 		// Wait for all async/sync operations to be scheduled (not blocking UI thread) 
223+ 		try  {
224+ 			// Wait up to 5 seconds for all operations to be scheduled 
225+ 			// The main thread arrives and deregisters, waiting for all other registered threads 
226+ 			asyncPhaser .awaitAdvanceInterruptibly (asyncPhaser .arrive (), 5 , TimeUnit .SECONDS );
227+ 		} catch  (TimeoutException  e ) {
228+ 			throw  new  AssertionError ("Not all async/sync operations were scheduled within timeout" , e );
229+ 		} catch  (InterruptedException  e ) {
230+ 			Thread .currentThread ().interrupt ();
231+ 			throw  new  RuntimeException ("Interrupted while waiting for async/sync operations" , e );
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 
237+ 		UITestUtil .processEventsUntil (() -> syncWithDisplayAccess  != null  && asyncWithDisplayAccess  != null , 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