forked from kohsuke/com4j
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathComThreadMulti.java
More file actions
338 lines (293 loc) · 10.5 KB
/
ComThreadMulti.java
File metadata and controls
338 lines (293 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
package com4j;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* Thread managed by com4j.
*
* <p>
* For each user thread that works with COM objects,
* one {@link ComThreadMulti} is created to manage those objects.
*
* <p>
* This is because COM objects are inherently tied to the thread that created it,
* and therefore all the invocations must be routed through the creator thread.
* See http://msdn.microsoft.com/en-us/library/ms809971.aspx for more discussions.
*
* <p>
* This model is rather alien to Java developers, where objects can be passed between
* threads more freely. (This is a separate issue from whether those objects can be
* safely accessed concurrently.)
*
* <p>
* To bridge these gaps, we don't let application threads touch COM objects at all,
* and instead create {@link ComThreadMulti} as a shadow thread for each application thread who wants to
* create a COM object.
*
* @author Kohsuke Kawaguchi (kk@kohsuke.org)
* @author Michael Schnell (ScM, (C) 2008, 2009, Michael-Schnell@gmx.de)
* @author mpoindexter (staticsnow@gmail.com)
*/
public final class ComThreadMulti extends Thread implements ComThread {
public static int GARBAGE_COLLECTION_INTERVAL = 10;
/**
* Used to associate a {@link ComThreadMulti} for every thread.
*/
private static final ThreadLocal<ComThreadMulti> map = new ThreadLocal<ComThreadMulti>() {
public ComThreadMulti initialValue() {
if( Thread.currentThread() instanceof ComThreadMulti)
return (ComThreadMulti)Thread.currentThread();
else
return new ComThreadMulti(Thread.currentThread());
}
};
/**
* Gets the {@link ComThreadMulti} associated with the current thread.
*/
static ComThreadMulti get() {
return map.get();
}
/**
* Detaches the {@link ComThreadMulti} for the current thread (peer) by calling {@link #kill()}
*/
static void detach() {
map.get().kill();
try {
map.get().join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
map.remove();
}
/**
* Constructs a new ComThread for the given peer and starts it.
* @param peer The peer thread.
*/
private ComThreadMulti(Thread peer) {
super("ComThread for "+peer.getName());
this.peer = peer;
setDaemon(true); // we don't want to block the JVM from exiting
start();
}
/**
* The peer thread.
*/
private final Thread peer;
/**
* Tasks that need to be processed.
*/
private final List<Task<?>> taskList = Collections.synchronizedList((new LinkedList<Task<?>>()));// com4j issue 70
/**
* COM objects that this thread is managing. This thread needs to stick around until they are all gone,
* even when the peer is dead, because other threads might still want to talk to these objects.
*/
private Set<NativePointerPhantomReference> liveComObjects = new HashSet<NativePointerPhantomReference>();
/**
* Keeps track of wrappers that should be IUnknown::release-d.
*/
final ReferenceQueue<Wrapper> collectableObjects = new ReferenceQueue<Wrapper>();
public ReferenceQueue<Wrapper> getCollectableObjects() {
return collectableObjects;
}
/**
* Listeners attached to this thread.
*/
private final List<ComObjectListener> listeners = new ArrayList<ComObjectListener>();
/**
* If set to true, this thread will commit suicide.
*/
private volatile boolean die = false;
/**
* Used instead of the monitor of an object, so that we can run
* a message loop while waiting.
*/
private final Win32Lock lock = new Win32Lock();
/**
* Returns true if this thread can exit.
* <p>
* If die is true, this method returns true. Otherwise it returns true, if the peer thread is not
* alive any more and all liveObjects have been removed.
* </p>
*/
private boolean canExit() {
// lhs:forcible death <-> rhs:natural death
return die || (!peer.isAlive() && liveComObjects.isEmpty());
}
/**
* Kills this {@link ComThreadMulti} gracefully
* and blocks until a thread dies.
*/
public void kill() {
die = true;
lock.activate(); // wake up the sleeping thread.
// wait for it to die. if someone interrupts us, process that later.
try {
join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void run() {
threads.add(this);
try {
run0();
} finally {
threads.remove(this);
}
}
private void run0() {
Native.coInitialize();
while(!canExit()) {
lock.suspend(GARBAGE_COLLECTION_INTERVAL);
//Clean up any com objects that need releasing
collectGarbage();
// do any scheduled tasks that need to be done
while (!taskList.isEmpty()) {
Task<?> task = taskList.get(0);
taskList.remove(0);
task.invoke();
// wake up the waiting thread
lock.activate();
//Maybe the task produced some garbage...clean that up
collectGarbage();
}
}
collectGarbage();
//And clobber any live COM objects that have not been dispose()'d to avoid
//leaking these objects on die
for(NativePointerPhantomReference ref : liveComObjects) {
ref.clear();
ref.releaseNative();
}
liveComObjects.clear();
//Kill the event handle we are holding in the lock.
lock.dispose();
Native.coUninitialize();
}
/**
* Cleans up any left over references
*/
private void collectGarbage() {
// dispose unused objects if any
NativePointerPhantomReference toCollect;
while((toCollect = (NativePointerPhantomReference)collectableObjects.poll()) != null) {
liveComObjects.remove(toCollect);
toCollect.clear();
toCollect.releaseNative();
}
}
/**
* Executes a {@link Task} in a {@link ComThreadMulti}
* and returns its result.
* @param task The task to be executed
* @param <T> The type of the return value.
* @return The result of the Task
*/
public <T> T execute(Task<T> task) {
synchronized(task) {
task.reset();
// add it to the tail
taskList.add(task);
// invoke the execution
lock.activate();
// wait for the completion
try {
while (!task.isDone()) {
//Native.pumpWaitingMessages();
task.wait();
}
}
catch (InterruptedException e) {
task.exception = e;
}
if(task.exception!=null) {
Throwable e = task.exception;
task.exception = null;
throw new ExecutionException(e);
} else {
T r = task.result;
task.result = null;
return r;
}
}
}
/**
* Adds a {@link Com4jObject} to the live objects of this {@link ComThreadMulti}
* <p>
* This method increases the live object count of this thread and fires an
* {@link ComObjectListener#onNewObject(Com4jObject)} event to all listeners.
* </p>
* @param r The new {@link Com4jObject}
*/
public synchronized void addLiveObject( Com4jObject r ) {// TODO: why is this public?
if(r instanceof Wrapper) {
liveComObjects.add(((Wrapper)r).ref);
}
if(!listeners.isEmpty()) {
for( int i=listeners.size()-1; i>=0; i-- )
listeners.get(i).onNewObject(r);
}
}
/**
* Checks if the current thread is this instance of ComThreadSafe;
*/
public boolean isCurrentThread() {
return Thread.currentThread() == this;
}
/**
* Adds a {@link ComObjectListener} to this {@link ComThreadMulti}
* @param listener the new listener
* @throws IllegalArgumentException if the <code>listener</code> is <code>null</code> or if the listener is already registered.
*/
public void addListener(ComObjectListener listener) {
if(listener==null)
throw new IllegalArgumentException("listener is null");
if(listeners.contains(listener))
throw new IllegalArgumentException("can't register the same listener twice");
listeners.add(listener);
}
/**
* Removes the {@link ComObjectListener} from this {@link ComThreadMulti}
* @param listener The listener to remove
* @throws IllegalArgumentException if the listener was not registered to this {@link ComThreadMulti}
*/
public void removeListener(ComObjectListener listener) {
if(!listeners.remove(listener))
throw new IllegalArgumentException("listener isn't registered");
}
/**
* All living and running {@link ComThreadMulti}s.
*/
static final Set<ComThreadMulti> threads = Collections.synchronizedSet(new HashSet<ComThreadMulti>());
static {
// before shut-down clean up all ComThreads
COM4J.addCom4JShutdownTask(new Runnable() {
public void run() {
// we need to synchronize the access to threads.
// Not just to avoid concurrent modification, but also to make sure, that a kill-task is not waiting forever for the
// thread to execute the task. (The thread might want to shut down itself concurrently, because the liveObjects dropped to zero.)
ComThreadMulti[] threadsSnapshot;
synchronized(threads) {
threadsSnapshot = threads.toArray(new ComThreadMulti[threads.size()]);
}
for (ComThreadMulti thread : threadsSnapshot) {
thread.kill();
}
}
});
}
/**
* This method calls System.gc() and executes a dummy task to initiate the corresponding
* ComThread to call dispose0() on all waiting objects.
*
* This method is mainly for debug purposes.
*/
public static void flushFreeList() {
System.gc();
ComThreadMulti.get().lock.activate();
}
}