Sourceforge 1986987 Deadlock between ComThread and ROT. Also added thread name to Jacob log output.
This commit is contained in:
@@ -10,7 +10,12 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td width="13%" valign="top">2011706</td>
|
<td width="13%" valign="top">2011706</td>
|
||||||
<td width="87%" valign="top">Fixed windows memory corruption unhooking
|
<td width="87%" valign="top">Fixed windows memory corruption unhooking
|
||||||
call back proxies</td>
|
call back proxy</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="13%" valign="top">1986987 </td>
|
||||||
|
<td width="87%" valign="top">Possible deadlock when multiple threads starting
|
||||||
|
and stopping that rely on implicit ComThread.InitMTA</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="13%" valign="top"> </td>
|
<td width="13%" valign="top"> </td>
|
||||||
@@ -148,6 +153,7 @@
|
|||||||
<td width="87%" valign="top">(M7) Jacob DLL name can now be customized to
|
<td width="87%" valign="top">(M7) Jacob DLL name can now be customized to
|
||||||
support bundling of Jacob in other products.</td>
|
support bundling of Jacob in other products.</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
<td width="13%" valign="top">1845039 </td>
|
<td width="13%" valign="top">1845039 </td>
|
||||||
<td width="87%" valign="top">(M6) Jacob DLL names are now qualified by platform and
|
<td width="87%" valign="top">(M6) Jacob DLL names are now qualified by platform and
|
||||||
release. The JacobLibraryLoader now determines the correct 32bit or 64bit
|
release. The JacobLibraryLoader now determines the correct 32bit or 64bit
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ public class JacobObject {
|
|||||||
*/
|
*/
|
||||||
protected static void debug(String istrMessage) {
|
protected static void debug(String istrMessage) {
|
||||||
if (isDebugEnabled()) {
|
if (isDebugEnabled()) {
|
||||||
System.out.println(istrMessage + " in thread "
|
System.out.println(Thread.currentThread().getName() + ": "
|
||||||
+ Thread.currentThread().getName());
|
+ istrMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public abstract class ROT {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns the pool for this thread if it exists. can create a new one if
|
* Returns the pool for this thread if it exists. can create a new one if
|
||||||
* you wish by passing in TRUE
|
* you wish by passing in TRUE
|
||||||
*
|
*
|
||||||
* @param createIfDoesNotExist
|
* @param createIfDoesNotExist
|
||||||
@@ -115,19 +115,20 @@ public abstract class ROT {
|
|||||||
* Iterates across all of the entries in the Hashmap in the rot that
|
* Iterates across all of the entries in the Hashmap in the rot that
|
||||||
* corresponds to this thread. This calls safeRelease() on each entry and
|
* corresponds to this thread. This calls safeRelease() on each entry and
|
||||||
* then clears the map when done and removes it from the rot. All traces of
|
* then clears the map when done and removes it from the rot. All traces of
|
||||||
* this thread's objects will disapear. This is called by COMThread in the
|
* this thread's objects will disappear. This is called by COMThread in the
|
||||||
* tear down and provides a synchronous way of releasing memory
|
* tear down and provides a synchronous way of releasing memory
|
||||||
*/
|
*/
|
||||||
protected synchronized static void clearObjects() {
|
protected static void clearObjects() {
|
||||||
Map<JacobObject, String> tab = getThreadObjects(false);
|
|
||||||
if (JacobObject.isDebugEnabled()) {
|
if (JacobObject.isDebugEnabled()) {
|
||||||
JacobObject.debug("ROT: " + rot.keySet().size()
|
JacobObject.debug("ROT: " + rot.keySet().size()
|
||||||
+ " thread tables exist");
|
+ " thread tables exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<JacobObject, String> tab = getThreadObjects(false);
|
||||||
if (tab != null) {
|
if (tab != null) {
|
||||||
if (JacobObject.isDebugEnabled()) {
|
if (JacobObject.isDebugEnabled()) {
|
||||||
JacobObject.debug("ROT: " + tab.keySet().size()
|
JacobObject.debug("ROT: " + tab.keySet().size()
|
||||||
+ " objects to clear in this thread ");
|
+ " objects to clear in this thread's ROT ");
|
||||||
}
|
}
|
||||||
// walk the values
|
// walk the values
|
||||||
Iterator<JacobObject> it = tab.keySet().iterator();
|
Iterator<JacobObject> it = tab.keySet().iterator();
|
||||||
@@ -155,13 +156,11 @@ public abstract class ROT {
|
|||||||
}
|
}
|
||||||
o.safeRelease();
|
o.safeRelease();
|
||||||
}
|
}
|
||||||
// used to be an iterator.remove() but why bother when we're
|
|
||||||
// nuking them all anyway?
|
|
||||||
}
|
}
|
||||||
// empty the collection
|
// empty the collection
|
||||||
tab.clear();
|
tab.clear();
|
||||||
// remove the collection from rot
|
// remove the collection from rot
|
||||||
rot.remove(Thread.currentThread().getName());
|
ROT.removeThread();
|
||||||
if (JacobObject.isDebugEnabled()) {
|
if (JacobObject.isDebugEnabled()) {
|
||||||
JacobObject.debug("ROT: thread table cleared and removed");
|
JacobObject.debug("ROT: thread table cleared and removed");
|
||||||
}
|
}
|
||||||
@@ -172,19 +171,28 @@ public abstract class ROT {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the map from the rot that is associated with the current thread.
|
||||||
|
*/
|
||||||
|
private synchronized static void removeThread() {
|
||||||
|
// should this see if it exists first?
|
||||||
|
rot.remove(Thread.currentThread().getName());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated the java model leave the responsibility of clearing up
|
* @deprecated the java model leave the responsibility of clearing up
|
||||||
* objects to the Garbage Collector. Our programming model
|
* objects to the Garbage Collector. Our programming model
|
||||||
* should not require that the user specifically remove object
|
* should not require that the user specifically remove object
|
||||||
* from the thread.
|
* from the thread. <br>
|
||||||
*
|
* This will remove an object from the ROT <br>
|
||||||
* This will remove an object from the ROT
|
* This does not need to be synchronized because only the rot
|
||||||
|
* modification related methods need to synchronized. Each
|
||||||
|
* individual map is only modified in a single thread.
|
||||||
* @param o
|
* @param o
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
protected synchronized static void removeObject(JacobObject o) {
|
protected static void removeObject(JacobObject o) {
|
||||||
String t_name = Thread.currentThread().getName();
|
Map<JacobObject, String> tab = ROT.getThreadObjects(false);
|
||||||
Map<JacobObject, String> tab = rot.get(t_name);
|
|
||||||
if (tab != null) {
|
if (tab != null) {
|
||||||
tab.remove(o);
|
tab.remove(o);
|
||||||
}
|
}
|
||||||
@@ -192,11 +200,23 @@ public abstract class ROT {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* adds an object to the HashMap for the current thread
|
* Adds an object to the HashMap for the current thread. <br>
|
||||||
|
* <p>
|
||||||
|
* This method does not need to be threaded because the only concurrent
|
||||||
|
* modification risk is on the hash map that contains all of the thread
|
||||||
|
* related hash maps. The individual thread related maps are only used on a
|
||||||
|
* per thread basis so there isn't a locking issue.
|
||||||
|
* <p>
|
||||||
|
* In addition, this method cannot be threaded because it calls
|
||||||
|
* ComThread.InitMTA. The ComThread object has some methods that call ROT so
|
||||||
|
* we could end up deadlocked. This method should be safe without the
|
||||||
|
* synchronization because the ROT works on per thread basis and the methods
|
||||||
|
* that add threads and remove thread related entries are all synchronized
|
||||||
|
*
|
||||||
*
|
*
|
||||||
* @param o
|
* @param o
|
||||||
*/
|
*/
|
||||||
protected synchronized static void addObject(JacobObject o) {
|
protected static void addObject(JacobObject o) {
|
||||||
// check the system property to see if this class is put in the ROT
|
// check the system property to see if this class is put in the ROT
|
||||||
// the default value is "true" which simulates the old behavior
|
// the default value is "true" which simulates the old behavior
|
||||||
String shouldIncludeClassInROT = System.getProperty(o.getClass()
|
String shouldIncludeClassInROT = System.getProperty(o.getClass()
|
||||||
@@ -208,11 +228,14 @@ public abstract class ROT {
|
|||||||
+ o.getClass().getName() + " not added to ROT");
|
+ o.getClass().getName() + " not added to ROT");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// first see if we have a table for this thread
|
||||||
Map<JacobObject, String> tab = getThreadObjects(false);
|
Map<JacobObject, String> tab = getThreadObjects(false);
|
||||||
if (tab == null) {
|
if (tab == null) {
|
||||||
// this thread has not been initialized as a COM thread
|
// this thread has not been initialized as a COM thread
|
||||||
// so make it part of MTA for backwards compatibility
|
// so make it part of MTA for backwards compatibility
|
||||||
ComThread.InitMTA(false);
|
ComThread.InitMTA(false);
|
||||||
|
// don't really need the "true" because the InitMTA will have
|
||||||
|
// called back to the ROT to create a table for this thread
|
||||||
tab = getThreadObjects(true);
|
tab = getThreadObjects(true);
|
||||||
}
|
}
|
||||||
if (JacobObject.isDebugEnabled()) {
|
if (JacobObject.isDebugEnabled()) {
|
||||||
@@ -220,6 +243,7 @@ public abstract class ROT {
|
|||||||
+ o.getClass().getName()
|
+ o.getClass().getName()
|
||||||
+ " table size prior to addition:" + tab.size());
|
+ " table size prior to addition:" + tab.size());
|
||||||
}
|
}
|
||||||
|
// add the object to the table that is specific to this thread
|
||||||
if (tab != null) {
|
if (tab != null) {
|
||||||
tab.put(o, null);
|
tab.put(o, null);
|
||||||
}
|
}
|
||||||
|
|||||||
120
unittest/com/jacob/com/JacobDeadlockTest.java
Normal file
120
unittest/com/jacob/com/JacobDeadlockTest.java
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package com.jacob.com;
|
||||||
|
|
||||||
|
import com.jacob.test.BaseTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sourceforge defect report 1986987 July 2008. This test case demonstrated a
|
||||||
|
* deadlock issue.
|
||||||
|
* <UL>
|
||||||
|
* <LI>One thread attempts to create an object in a thread where InitMTA has
|
||||||
|
* not been called. This results in ROT.addObject being called which then calls
|
||||||
|
* ComThread.InitMTA
|
||||||
|
* <LI>One thread attempts to call ComThread.release() which then calls ROT
|
||||||
|
* .clear objects.
|
||||||
|
* </UL>
|
||||||
|
* The result is one thread with a call sequence ComThread--ROT and the other
|
||||||
|
* with a sequence ROT--ComThread resulting in deadlock.
|
||||||
|
* <p>
|
||||||
|
* This test will fail with debug logging turned on because of the amount of
|
||||||
|
* time it takes to write the debug output.
|
||||||
|
*
|
||||||
|
* @author jsamarziya
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class JacobDeadlockTest extends BaseTestCase {
|
||||||
|
private static final long TIMEOUT = 5000l;
|
||||||
|
|
||||||
|
/** Thread component */
|
||||||
|
public static class TestThread extends Thread {
|
||||||
|
private final int id;
|
||||||
|
private final boolean initCOM;
|
||||||
|
private final boolean writeOutput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* constructor for ThestThread
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @param initCOM
|
||||||
|
* @param writeOutput
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public TestThread(int id, boolean initCOM, boolean writeOutput) {
|
||||||
|
this.id = id;
|
||||||
|
this.initCOM = initCOM;
|
||||||
|
this.writeOutput = writeOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
log("iteration " + i);
|
||||||
|
if (initCOM) {
|
||||||
|
log("Initializing COM thread");
|
||||||
|
ComThread.InitMTA(false);
|
||||||
|
}
|
||||||
|
log("Creating JacobObject");
|
||||||
|
new JacobObject();
|
||||||
|
log("Releasing COM thread");
|
||||||
|
ComThread.Release();
|
||||||
|
}
|
||||||
|
log("Exiting Java Thread");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void log(String message) {
|
||||||
|
if (writeOutput) {
|
||||||
|
System.out.println(Thread.currentThread().getName()
|
||||||
|
+ ": TestThread[" + id + "] " + " " + " - " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test shows that if ComThread.Init() is called explicitly, no problem
|
||||||
|
* occurs.
|
||||||
|
*
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void testShowNoProblemIfCOMIsInitialized()
|
||||||
|
throws InterruptedException {
|
||||||
|
runTest(2, true, false);
|
||||||
|
runTest(100, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test shows that if only one thread is creating COM objects, no
|
||||||
|
* problem occurs.
|
||||||
|
*
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void testShowNoProblemIfSingleThreaded() throws InterruptedException {
|
||||||
|
runTest(1, false, false);
|
||||||
|
runTest(1, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the test with two threads, which don't initialize the COM thread.
|
||||||
|
*
|
||||||
|
* This test will always fail.
|
||||||
|
*
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void testShowDeadlockProblem() throws InterruptedException {
|
||||||
|
runTest(2, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runTest(int numberOfThreads, boolean initCOM,
|
||||||
|
boolean writeOutput) throws InterruptedException {
|
||||||
|
Thread[] threads = new Thread[numberOfThreads];
|
||||||
|
for (int i = 0; i < threads.length; i++) {
|
||||||
|
threads[i] = new TestThread(i, initCOM, writeOutput);
|
||||||
|
threads[i].start();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < threads.length; i++) {
|
||||||
|
threads[i].join(TIMEOUT);
|
||||||
|
if (threads[i].isAlive()) {
|
||||||
|
fail("thread " + i + " failed to finish in " + TIMEOUT
|
||||||
|
+ " milliseconds");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user