Skip to content

Commit 95a8680

Browse files
author
clay_shooter
committed
Sourceforge 1986987 Deadlock between ComThread and ROT. Also added thread name to Jacob log output.
1 parent 2406511 commit 95a8680

File tree

4 files changed

+169
-19
lines changed

4 files changed

+169
-19
lines changed

jacob/docs/ReleaseNotes.html

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ <h3>Tracked Changes</h3>
1010
<tr>
1111
<td width="13%" valign="top">2011706</td>
1212
<td width="87%" valign="top">Fixed windows memory corruption unhooking
13-
call back proxies</td>
13+
call back proxy</td>
14+
</tr>
15+
<tr>
16+
<td width="13%" valign="top">1986987 </td>
17+
<td width="87%" valign="top">Possible deadlock when multiple threads starting
18+
and stopping that rely on implicit ComThread.InitMTA</td>
1419
</tr>
1520
<tr>
1621
<td width="13%" valign="top">&nbsp;</td>
@@ -148,6 +153,7 @@ <h3>Tracked Changes</h3>
148153
<td width="87%" valign="top">(M7) Jacob DLL name can now be customized to
149154
support bundling of Jacob in other products.</td>
150155
</tr>
156+
<tr>
151157
<td width="13%" valign="top">1845039 </td>
152158
<td width="87%" valign="top">(M6) Jacob DLL names are now qualified by platform and
153159
release. The JacobLibraryLoader now determines the correct 32bit or 64bit

jacob/src/com/jacob/com/JacobObject.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ public static String getBuildVersion() {
9696
*/
9797
protected static void debug(String istrMessage) {
9898
if (isDebugEnabled()) {
99-
System.out.println(istrMessage + " in thread "
100-
+ Thread.currentThread().getName());
99+
System.out.println(Thread.currentThread().getName() + ": "
100+
+ istrMessage);
101101
}
102102
}
103103

jacob/src/com/jacob/com/ROT.java

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ protected synchronized static Map<JacobObject, String> addThread() {
9595
}
9696

9797
/**
98-
* returns the pool for this thread if it exists. can create a new one if
98+
* Returns the pool for this thread if it exists. can create a new one if
9999
* you wish by passing in TRUE
100100
*
101101
* @param createIfDoesNotExist
@@ -115,19 +115,20 @@ protected synchronized static Map<JacobObject, String> getThreadObjects(
115115
* Iterates across all of the entries in the Hashmap in the rot that
116116
* corresponds to this thread. This calls safeRelease() on each entry and
117117
* then clears the map when done and removes it from the rot. All traces of
118-
* this thread's objects will disapear. This is called by COMThread in the
118+
* this thread's objects will disappear. This is called by COMThread in the
119119
* tear down and provides a synchronous way of releasing memory
120120
*/
121-
protected synchronized static void clearObjects() {
122-
Map<JacobObject, String> tab = getThreadObjects(false);
121+
protected static void clearObjects() {
123122
if (JacobObject.isDebugEnabled()) {
124123
JacobObject.debug("ROT: " + rot.keySet().size()
125124
+ " thread tables exist");
126125
}
126+
127+
Map<JacobObject, String> tab = getThreadObjects(false);
127128
if (tab != null) {
128129
if (JacobObject.isDebugEnabled()) {
129130
JacobObject.debug("ROT: " + tab.keySet().size()
130-
+ " objects to clear in this thread ");
131+
+ " objects to clear in this thread's ROT ");
131132
}
132133
// walk the values
133134
Iterator<JacobObject> it = tab.keySet().iterator();
@@ -155,13 +156,11 @@ protected synchronized static void clearObjects() {
155156
}
156157
o.safeRelease();
157158
}
158-
// used to be an iterator.remove() but why bother when we're
159-
// nuking them all anyway?
160159
}
161160
// empty the collection
162161
tab.clear();
163162
// remove the collection from rot
164-
rot.remove(Thread.currentThread().getName());
163+
ROT.removeThread();
165164
if (JacobObject.isDebugEnabled()) {
166165
JacobObject.debug("ROT: thread table cleared and removed");
167166
}
@@ -172,31 +171,52 @@ protected synchronized static void clearObjects() {
172171
}
173172
}
174173

174+
/**
175+
* Removes the map from the rot that is associated with the current thread.
176+
*/
177+
private synchronized static void removeThread() {
178+
// should this see if it exists first?
179+
rot.remove(Thread.currentThread().getName());
180+
}
181+
175182
/**
176183
* @deprecated the java model leave the responsibility of clearing up
177184
* objects to the Garbage Collector. Our programming model
178185
* should not require that the user specifically remove object
179-
* from the thread.
180-
*
181-
* This will remove an object from the ROT
186+
* from the thread. <br>
187+
* This will remove an object from the ROT <br>
188+
* This does not need to be synchronized because only the rot
189+
* modification related methods need to synchronized. Each
190+
* individual map is only modified in a single thread.
182191
* @param o
183192
*/
184193
@Deprecated
185-
protected synchronized static void removeObject(JacobObject o) {
186-
String t_name = Thread.currentThread().getName();
187-
Map<JacobObject, String> tab = rot.get(t_name);
194+
protected static void removeObject(JacobObject o) {
195+
Map<JacobObject, String> tab = ROT.getThreadObjects(false);
188196
if (tab != null) {
189197
tab.remove(o);
190198
}
191199
o.safeRelease();
192200
}
193201

194202
/**
195-
* adds an object to the HashMap for the current thread
203+
* Adds an object to the HashMap for the current thread. <br>
204+
* <p>
205+
* This method does not need to be threaded because the only concurrent
206+
* modification risk is on the hash map that contains all of the thread
207+
* related hash maps. The individual thread related maps are only used on a
208+
* per thread basis so there isn't a locking issue.
209+
* <p>
210+
* In addition, this method cannot be threaded because it calls
211+
* ComThread.InitMTA. The ComThread object has some methods that call ROT so
212+
* we could end up deadlocked. This method should be safe without the
213+
* synchronization because the ROT works on per thread basis and the methods
214+
* that add threads and remove thread related entries are all synchronized
215+
*
196216
*
197217
* @param o
198218
*/
199-
protected synchronized static void addObject(JacobObject o) {
219+
protected static void addObject(JacobObject o) {
200220
// check the system property to see if this class is put in the ROT
201221
// the default value is "true" which simulates the old behavior
202222
String shouldIncludeClassInROT = System.getProperty(o.getClass()
@@ -208,18 +228,22 @@ protected synchronized static void addObject(JacobObject o) {
208228
+ o.getClass().getName() + " not added to ROT");
209229
}
210230
} else {
231+
// first see if we have a table for this thread
211232
Map<JacobObject, String> tab = getThreadObjects(false);
212233
if (tab == null) {
213234
// this thread has not been initialized as a COM thread
214235
// so make it part of MTA for backwards compatibility
215236
ComThread.InitMTA(false);
237+
// don't really need the "true" because the InitMTA will have
238+
// called back to the ROT to create a table for this thread
216239
tab = getThreadObjects(true);
217240
}
218241
if (JacobObject.isDebugEnabled()) {
219242
JacobObject.debug("ROT: adding " + o + "->"
220243
+ o.getClass().getName()
221244
+ " table size prior to addition:" + tab.size());
222245
}
246+
// add the object to the table that is specific to this thread
223247
if (tab != null) {
224248
tab.put(o, null);
225249
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.jacob.com;
2+
3+
import com.jacob.test.BaseTestCase;
4+
5+
/**
6+
* Sourceforge defect report 1986987 July 2008. This test case demonstrated a
7+
* deadlock issue.
8+
* <UL>
9+
* <LI>One thread attempts to create an object in a thread where InitMTA has
10+
* not been called. This results in ROT.addObject being called which then calls
11+
* ComThread.InitMTA
12+
* <LI>One thread attempts to call ComThread.release() which then calls ROT
13+
* .clear objects.
14+
* </UL>
15+
* The result is one thread with a call sequence ComThread--ROT and the other
16+
* with a sequence ROT--ComThread resulting in deadlock.
17+
* <p>
18+
* This test will fail with debug logging turned on because of the amount of
19+
* time it takes to write the debug output.
20+
*
21+
* @author jsamarziya
22+
*
23+
*/
24+
public class JacobDeadlockTest extends BaseTestCase {
25+
private static final long TIMEOUT = 5000l;
26+
27+
/** Thread component */
28+
public static class TestThread extends Thread {
29+
private final int id;
30+
private final boolean initCOM;
31+
private final boolean writeOutput;
32+
33+
/**
34+
* constructor for ThestThread
35+
*
36+
* @param id
37+
* @param initCOM
38+
* @param writeOutput
39+
*
40+
*/
41+
public TestThread(int id, boolean initCOM, boolean writeOutput) {
42+
this.id = id;
43+
this.initCOM = initCOM;
44+
this.writeOutput = writeOutput;
45+
}
46+
47+
@Override
48+
public void run() {
49+
for (int i = 0; i < 1000; i++) {
50+
log("iteration " + i);
51+
if (initCOM) {
52+
log("Initializing COM thread");
53+
ComThread.InitMTA(false);
54+
}
55+
log("Creating JacobObject");
56+
new JacobObject();
57+
log("Releasing COM thread");
58+
ComThread.Release();
59+
}
60+
log("Exiting Java Thread");
61+
}
62+
63+
private void log(String message) {
64+
if (writeOutput) {
65+
System.out.println(Thread.currentThread().getName()
66+
+ ": TestThread[" + id + "] " + " " + " - " + message);
67+
}
68+
}
69+
}
70+
71+
/**
72+
* This test shows that if ComThread.Init() is called explicitly, no problem
73+
* occurs.
74+
*
75+
* @throws InterruptedException
76+
*/
77+
public void testShowNoProblemIfCOMIsInitialized()
78+
throws InterruptedException {
79+
runTest(2, true, false);
80+
runTest(100, true, false);
81+
}
82+
83+
/**
84+
* This test shows that if only one thread is creating COM objects, no
85+
* problem occurs.
86+
*
87+
* @throws InterruptedException
88+
*/
89+
public void testShowNoProblemIfSingleThreaded() throws InterruptedException {
90+
runTest(1, false, false);
91+
runTest(1, true, false);
92+
}
93+
94+
/**
95+
* Runs the test with two threads, which don't initialize the COM thread.
96+
*
97+
* This test will always fail.
98+
*
99+
* @throws InterruptedException
100+
*/
101+
public void testShowDeadlockProblem() throws InterruptedException {
102+
runTest(2, false, true);
103+
}
104+
105+
private void runTest(int numberOfThreads, boolean initCOM,
106+
boolean writeOutput) throws InterruptedException {
107+
Thread[] threads = new Thread[numberOfThreads];
108+
for (int i = 0; i < threads.length; i++) {
109+
threads[i] = new TestThread(i, initCOM, writeOutput);
110+
threads[i].start();
111+
}
112+
for (int i = 0; i < threads.length; i++) {
113+
threads[i].join(TIMEOUT);
114+
if (threads[i].isAlive()) {
115+
fail("thread " + i + " failed to finish in " + TIMEOUT
116+
+ " milliseconds");
117+
}
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)