Skip to content

Commit fb20fc7

Browse files
committed
Merge pull request #87 from scijava/get-parent-thread
Introduce ThreadService@getParent(Thread)
2 parents df69ca7 + df3a6c5 commit fb20fc7

File tree

3 files changed

+261
-4
lines changed

3 files changed

+261
-4
lines changed

src/main/java/org/scijava/thread/DefaultThreadService.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import java.awt.EventQueue;
3535
import java.lang.reflect.InvocationTargetException;
36+
import java.util.WeakHashMap;
3637
import java.util.concurrent.Callable;
3738
import java.util.concurrent.ExecutorService;
3839
import java.util.concurrent.Executors;
@@ -63,18 +64,20 @@ public final class DefaultThreadService extends AbstractService implements
6364

6465
private boolean disposed;
6566

67+
private WeakHashMap<Thread, Thread> parents = new WeakHashMap<Thread, Thread>();
68+
6669
// -- ThreadService methods --
6770

6871
@Override
6972
public <V> Future<V> run(final Callable<V> code) {
7073
if (disposed) return null;
71-
return executor().submit(code);
74+
return executor().submit(wrap(code));
7275
}
7376

7477
@Override
7578
public Future<?> run(final Runnable code) {
7679
if (disposed) return null;
77-
return executor().submit(code);
80+
return executor().submit(wrap(code));
7881
}
7982

8083
@Override
@@ -92,13 +95,18 @@ public void invoke(final Runnable code) throws InterruptedException,
9295
}
9396
else {
9497
// invoke on the EDT
95-
EventQueue.invokeAndWait(code);
98+
EventQueue.invokeAndWait(wrap(code));
9699
}
97100
}
98101

99102
@Override
100103
public void queue(final Runnable code) {
101-
EventQueue.invokeLater(code);
104+
EventQueue.invokeLater(wrap(code));
105+
}
106+
107+
@Override
108+
public Thread getParent(final Thread thread) {
109+
return parents.get(thread != null ? thread : Thread.currentThread());
102110
}
103111

104112
// -- Disposable methods --
@@ -128,4 +136,37 @@ private ExecutorService executor() {
128136
return executor;
129137
}
130138

139+
private Runnable wrap(final Runnable r) {
140+
final Thread parent = Thread.currentThread();
141+
return new Runnable() {
142+
@Override
143+
public void run() {
144+
final Thread thread = Thread.currentThread();
145+
try {
146+
if (parent != thread) parents.put(thread, parent);
147+
r.run();
148+
}
149+
finally {
150+
if (parent != thread) parents.remove(thread);
151+
}
152+
}
153+
};
154+
}
155+
156+
private <V> Callable<V> wrap(final Callable<V> c) {
157+
final Thread parent = Thread.currentThread();
158+
return new Callable<V>() {
159+
@Override
160+
public V call() throws Exception {
161+
final Thread thread = Thread.currentThread();
162+
try {
163+
if (parent != thread) parents.put(thread, parent);
164+
return c.call();
165+
}
166+
finally {
167+
if (parent != thread) parents.remove(thread);
168+
}
169+
}
170+
};
171+
}
131172
}

src/main/java/org/scijava/thread/ThreadService.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,14 @@ void invoke(final Runnable code) throws InterruptedException,
113113
*/
114114
void queue(final Runnable code);
115115

116+
/**
117+
* Returns the thread that called the specified thread.
118+
* <p>
119+
* This works only on threads which the thread service knows about, of course.
120+
* </p>
121+
*
122+
* @param thread the managed thread, null refers to the current thread
123+
* @return the thread that asked the {@link ThreadService} to spawn the specified thread
124+
*/
125+
Thread getParent(final Thread thread);
116126
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2014 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
7+
* Institute of Molecular Cell Biology and Genetics.
8+
* %%
9+
* Redistribution and use in source and binary forms, with or without
10+
* modification, are permitted provided that the following conditions are met:
11+
*
12+
* 1. Redistributions of source code must retain the above copyright notice,
13+
* this list of conditions and the following disclaimer.
14+
* 2. Redistributions in binary form must reproduce the above copyright notice,
15+
* this list of conditions and the following disclaimer in the documentation
16+
* and/or other materials provided with the distribution.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
* POSSIBILITY OF SUCH DAMAGE.
29+
* #L%
30+
*/
31+
32+
package org.scijava.thread;
33+
34+
import static org.junit.Assert.assertFalse;
35+
import static org.junit.Assert.assertNotSame;
36+
import static org.junit.Assert.assertSame;
37+
import static org.junit.Assert.assertTrue;
38+
39+
import java.lang.reflect.InvocationTargetException;
40+
import java.util.concurrent.Callable;
41+
import java.util.concurrent.ExecutionException;
42+
43+
import org.junit.After;
44+
import org.junit.Before;
45+
import org.junit.Test;
46+
import org.scijava.Context;
47+
48+
/**
49+
* Tests the {@link ThreadService}.
50+
*
51+
* @author Johannes Schindelin
52+
*/
53+
public class ThreadServiceTest {
54+
55+
private Context context;
56+
private ThreadService threadService;
57+
58+
@Before
59+
public void setUp() {
60+
context = new Context(ThreadService.class);
61+
threadService = context.getService(ThreadService.class);
62+
}
63+
64+
@After
65+
public void tearDown() {
66+
context.dispose();
67+
}
68+
69+
/** Tests {@link ThreadService#run(Callable)}. */
70+
@Test
71+
public void testRunCallable() throws InterruptedException, ExecutionException
72+
{
73+
final Thread result = threadService.run(new Callable<Thread>() {
74+
75+
@Override
76+
public Thread call() {
77+
return Thread.currentThread();
78+
}
79+
}).get();
80+
assertNotSame(Thread.currentThread(), result);
81+
}
82+
83+
/** Tests {@link ThreadService#run(Runnable)}. */
84+
@Test
85+
public void testRunRunnable() throws InterruptedException, ExecutionException
86+
{
87+
final Thread[] results = new Thread[1];
88+
threadService.run(new Runnable() {
89+
90+
@Override
91+
public void run() {
92+
results[0] = Thread.currentThread();
93+
}
94+
}).get();
95+
assertNotSame(Thread.currentThread(), results[0]);
96+
}
97+
98+
/**
99+
* Tests {@link ThreadService#invoke(Runnable)} and
100+
* {@link ThreadService#isDispatchThread()}.
101+
*/
102+
@Test
103+
public void testInvoke() throws InterruptedException,
104+
InvocationTargetException
105+
{
106+
final boolean[] results = new boolean[1];
107+
threadService.invoke(new Runnable() {
108+
109+
@Override
110+
public void run() {
111+
results[0] = threadService.isDispatchThread();
112+
}
113+
});
114+
assertTrue(results[0]);
115+
assertFalse(threadService.isDispatchThread());
116+
}
117+
118+
/**
119+
* Tests {@link ThreadService#queue(Runnable)} and
120+
* {@link ThreadService#isDispatchThread()}.
121+
*/
122+
@Test
123+
public void testQueue() throws InterruptedException {
124+
final Object sync = new Object();
125+
final boolean[] results = new boolean[1];
126+
synchronized (sync) {
127+
threadService.queue(new Runnable() {
128+
129+
@Override
130+
public void run() {
131+
results[0] = threadService.isDispatchThread();
132+
synchronized (sync) {
133+
sync.notifyAll();
134+
}
135+
}
136+
});
137+
sync.wait();
138+
}
139+
assertTrue(results[0]);
140+
assertFalse(threadService.isDispatchThread());
141+
}
142+
143+
/**
144+
* Tests {@link ThreadService#getParent(Thread)} when called after
145+
* {@link ThreadService#invoke(Runnable)}.
146+
*/
147+
@Test
148+
public void testGetParentInvoke() throws Exception {
149+
final AskForParentR ask = new AskForParentR(threadService);
150+
threadService.invoke(ask);
151+
assertSame(Thread.currentThread(), ask.parent);
152+
}
153+
154+
/**
155+
* Tests {@link ThreadService#getParent(Thread)} when called after
156+
* {@link ThreadService#run(Callable)}.
157+
*/
158+
@Test
159+
public void testGetParentRunCallable() throws Exception {
160+
final AskForParentC ask = new AskForParentC(threadService);
161+
final Thread parent = threadService.run(ask).get();
162+
assertSame(Thread.currentThread(), parent);
163+
}
164+
165+
/**
166+
* Tests {@link ThreadService#getParent(Thread)} when called after
167+
* {@link ThreadService#run(Runnable)}.
168+
*/
169+
@Test
170+
public void testGetParentRunRunnable() throws Exception {
171+
final AskForParentR ask = new AskForParentR(threadService);
172+
threadService.run(ask).get();
173+
assertSame(Thread.currentThread(), ask.parent);
174+
}
175+
176+
private static class AskForParentR implements Runnable {
177+
178+
private final ThreadService threadService;
179+
180+
private Thread parent;
181+
182+
public AskForParentR(final ThreadService threadService) {
183+
this.threadService = threadService;
184+
}
185+
186+
@Override
187+
public void run() {
188+
parent = threadService.getParent(null);
189+
}
190+
}
191+
192+
private static class AskForParentC implements Callable<Thread> {
193+
194+
private final ThreadService threadService;
195+
196+
public AskForParentC(final ThreadService threadService) {
197+
this.threadService = threadService;
198+
}
199+
200+
@Override
201+
public Thread call() {
202+
return threadService.getParent(null);
203+
}
204+
}
205+
206+
}

0 commit comments

Comments
 (0)