1+ package org .scijava .ui .swing .script ;
2+
3+ import static org .junit .Assert .assertFalse ;
4+ import static org .junit .Assert .assertNotNull ;
5+ import static org .junit .Assert .assertNull ;
6+ import static org .junit .Assert .assertSame ;
7+ import static org .junit .Assert .assertTrue ;
8+
9+ import java .util .concurrent .CountDownLatch ;
10+ import java .util .concurrent .Future ;
11+ import java .util .concurrent .TimeUnit ;
12+ import java .util .concurrent .atomic .AtomicReference ;
13+
14+ import org .junit .After ;
15+ import org .junit .Before ;
16+ import org .junit .Test ;
17+ import org .scijava .Context ;
18+ import org .scijava .thread .ThreadService ;
19+
20+ public class TextEditorThreadOwnershipTest {
21+
22+ private Context context ;
23+ private ThreadService threadService ;
24+
25+ @ Before
26+ public void setUp () {
27+ context = new Context (ThreadService .class );
28+ threadService = context .getService (ThreadService .class );
29+ }
30+
31+ @ After
32+ public void tearDown () {
33+ context .dispose ();
34+ }
35+
36+ @ Test
37+ public void testOrdinaryChildThreadBelongsToRun () {
38+ final ThreadGroup runGroup = new ThreadGroup ("script-run" );
39+ final Thread child = new Thread (runGroup , () -> {});
40+ child .setDaemon (true );
41+
42+ assertTrue (TextEditor .isScriptOwnedThread (child , runGroup , threadService ));
43+ }
44+
45+ @ Test
46+ public void testIdleThreadServiceWorkerDoesNotBelongToRun () throws Exception {
47+ final ThreadGroup runGroup = new ThreadGroup ("script-run" );
48+ final AtomicReference <Thread > worker = new AtomicReference <>();
49+ final AtomicReference <Future <?>> task = new AtomicReference <>();
50+ final Thread creator = new Thread (runGroup , () -> task .set (threadService .run (
51+ () -> worker .set (Thread .currentThread ()))));
52+ creator .start ();
53+ creator .join (5000 );
54+ task .get ().get (5 , TimeUnit .SECONDS );
55+ awaitIdlePoolWorker (worker .get ());
56+
57+ assertFalse ("worker factory thread should finish" , creator .isAlive ());
58+ assertNotNull (worker .get ());
59+ assertSame (runGroup , worker .get ().getThreadGroup ());
60+ assertNull (threadService .getParent (worker .get ()));
61+ assertFalse (TextEditor .isScriptOwnedThread (worker .get (), runGroup ,
62+ threadService ));
63+ }
64+
65+ @ Test
66+ public void testActiveThreadServiceWorkBelongsToRun () throws Exception {
67+ final ThreadGroup runGroup = new ThreadGroup ("script-run" );
68+ final CountDownLatch taskStarted = new CountDownLatch (1 );
69+ final CountDownLatch releaseTask = new CountDownLatch (1 );
70+ final CountDownLatch releaseScriptThread = new CountDownLatch (1 );
71+ final AtomicReference <Thread > worker = new AtomicReference <>();
72+ final AtomicReference <Future <?>> task = new AtomicReference <>();
73+ final Thread scriptThread = new Thread (runGroup , () -> {
74+ task .set (threadService .run (() -> {
75+ worker .set (Thread .currentThread ());
76+ taskStarted .countDown ();
77+ await (releaseTask );
78+ }));
79+ await (releaseScriptThread );
80+ });
81+
82+ scriptThread .start ();
83+ try {
84+ assertTrue ("service task should start" , taskStarted .await (5 ,
85+ TimeUnit .SECONDS ));
86+ assertNotNull (worker .get ());
87+ assertTrue (TextEditor .isScriptOwnedThread (worker .get (), runGroup ,
88+ threadService ));
89+
90+ releaseTask .countDown ();
91+ task .get ().get (5 , TimeUnit .SECONDS );
92+ awaitIdlePoolWorker (worker .get ());
93+ assertFalse (TextEditor .isScriptOwnedThread (worker .get (), runGroup ,
94+ threadService ));
95+ }
96+ finally {
97+ releaseTask .countDown ();
98+ releaseScriptThread .countDown ();
99+ scriptThread .join (5000 );
100+ }
101+ assertFalse ("script parent should finish" , scriptThread .isAlive ());
102+ }
103+
104+ @ Test
105+ public void testActiveThreadServiceFactoryThreadBelongsToRun () throws Exception {
106+ final ThreadGroup runGroup = new ThreadGroup ("script-run" );
107+ final CountDownLatch taskStarted = new CountDownLatch (1 );
108+ final CountDownLatch releaseTask = new CountDownLatch (1 );
109+ final CountDownLatch releaseScriptThread = new CountDownLatch (1 );
110+ final AtomicReference <Thread > worker = new AtomicReference <>();
111+ final Thread scriptThread = new Thread (runGroup , () -> {
112+ final Thread serviceThread = threadService .newThread (() -> {
113+ worker .set (Thread .currentThread ());
114+ taskStarted .countDown ();
115+ await (releaseTask );
116+ });
117+ serviceThread .start ();
118+ await (releaseScriptThread );
119+ });
120+
121+ scriptThread .start ();
122+ try {
123+ assertTrue ("service thread should start" , taskStarted .await (5 ,
124+ TimeUnit .SECONDS ));
125+ assertNotNull (worker .get ());
126+ assertNull (threadService .getParent (worker .get ()));
127+ assertTrue (TextEditor .isScriptOwnedThread (worker .get (), runGroup ,
128+ threadService ));
129+ }
130+ finally {
131+ releaseTask .countDown ();
132+ releaseScriptThread .countDown ();
133+ scriptThread .join (5000 );
134+ if (worker .get () != null ) worker .get ().join (5000 );
135+ }
136+ assertFalse ("script parent should finish" , scriptThread .isAlive ());
137+ assertFalse ("service thread should finish" , worker .get ().isAlive ());
138+ }
139+
140+ private static void await (final CountDownLatch latch ) {
141+ try {
142+ latch .await ();
143+ }
144+ catch (final InterruptedException e ) {
145+ Thread .currentThread ().interrupt ();
146+ }
147+ }
148+
149+ private static void awaitIdlePoolWorker (final Thread worker ) throws Exception {
150+ final long deadline = System .currentTimeMillis () + 5000 ;
151+ while (!isIdlePoolWorker (worker ) && System .currentTimeMillis () < deadline ) {
152+ Thread .sleep (10 );
153+ }
154+ assertTrue ("worker should be idle in ThreadPoolExecutor.getTask" ,
155+ isIdlePoolWorker (worker ));
156+ }
157+
158+ private static boolean isIdlePoolWorker (final Thread thread ) {
159+ if (thread == null || thread .getState () != Thread .State .TIMED_WAITING ) return false ;
160+ final StackTraceElement [] stack = thread .getStackTrace ();
161+ for (int i = 0 ; i + 1 < stack .length ; i ++) {
162+ if (stack [i ].getClassName ().equals (
163+ "java.util.concurrent.ThreadPoolExecutor" ) && stack [i ].getMethodName ()
164+ .equals ("getTask" ) && stack [i + 1 ].getClassName ().equals (
165+ "java.util.concurrent.ThreadPoolExecutor" ) && stack [i + 1 ]
166+ .getMethodName ().equals ("runWorker" )) return true ;
167+ }
168+ return false ;
169+ }
170+ }
0 commit comments