package er.extensions.eof; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Collections; import java.util.Map; import java.util.WeakHashMap; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.log4j.Logger; import sun.misc.Signal; import sun.misc.SignalHandler; import com.webobjects.eocontrol.EOCooperatingObjectStore; import com.webobjects.eocontrol.EOObjectStoreCoordinator; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; /** * Adds functionality to automatically close all related JDBC Connections. Also has open-lock debugging. * @author david * @author ak */ public class ERXObjectStoreCoordinator extends EOObjectStoreCoordinator { public static final Logger log = Logger.getLogger(ERXObjectStoreCoordinator.class); NSMutableDictionary<Thread, NSMutableArray<Exception>> openLockTraces; Thread lockingThread; String lockingThreadName; protected static Map<ERXObjectStoreCoordinator, String> activeDatabaseContexts = Collections.synchronizedMap(new WeakHashMap()); private long lockCount = 0; public boolean _didClose = false; public boolean _shouldClose = false; /** * @see com.webobjects.eocontrol.EOObjectStoreCoordinator * */ public ERXObjectStoreCoordinator() { if (ERXEC.markOpenLocks()) { activeDatabaseContexts.put(this, Thread.currentThread().getName()); } } public ERXObjectStoreCoordinator(boolean shouldClose) { _shouldClose = shouldClose; } private static Exception defaultTrace = new Exception("DefaultTrace"); /** * Adds the current stack trace to openLockTraces. */ private synchronized void traceLock() { if(openLockTraces == null) { openLockTraces = new NSMutableDictionary<Thread, NSMutableArray<Exception>>(); } Exception openLockTrace = defaultTrace; if(ERXEC.traceOpenLocks()) { openLockTrace = new Exception("Locked"); } Thread currentThread = Thread.currentThread(); NSMutableArray<Exception> currentTraces = openLockTraces.objectForKey(currentThread); if(currentTraces == null) { currentTraces = new NSMutableArray<>(); openLockTraces.setObjectForKey(currentTraces, currentThread); } currentTraces.addObject(openLockTrace); } /** * Removes the current trace from the openLockTraces. */ private synchronized void traceUnlock() { if (openLockTraces != null) { NSMutableArray<Exception> traces = openLockTraces.objectForKey(lockingThread); if(traces != null) { //log.error("unlock: " + lockingThread); traces.removeLastObject(); if (traces.count() == 0) { openLockTraces.removeObjectForKey(lockingThread); } } else { log.error("Missing lock: " + lockingThread); } if (openLockTraces.count() == 0) { openLockTraces = null; } } if(lockCount == 0) { lockingThread = null; lockingThreadName = null; } } /** * Overridden to emit log messages and push this instance to the locked * editing contexts in this thread. */ @Override public void lock() { boolean tracing = ERXEC.markOpenLocks(); if (tracing) { traceLock(); } super.lock(); lockCount++; lockingThread = Thread.currentThread(); lockingThreadName = lockingThread.getName(); //log.error("locked: " + lockingThread); } /** * Overridden to emit log messages and pull this instance from the locked * editing contexts in this thread. */ @Override public void unlock() { boolean tracing = ERXEC.markOpenLocks(); if (lockingThread != null && lockingThread != Thread.currentThread()) { log.fatal("Unlocking thread is not locking thread: LOCKING " + lockingThread + " vs UNLOCKING " + Thread.currentThread(), new RuntimeException("UnlockingTrace")); if (tracing) { NSMutableArray<Exception> traces = openLockTraces.objectForKey(lockingThread); if (traces != null) { for (Exception trace : traces) { log.fatal("Currenty locking threads: " + lockingThread, trace); } } else { log.fatal("Trace for locking thread is MISSING"); } } } lockCount--; if (tracing) { traceUnlock(); } super.unlock(); } @Override public void addCooperatingObjectStore(EOCooperatingObjectStore objectStore) { if (cooperatingObjectStores().indexOfIdenticalObject(objectStore) < 0) { if (objectStore.coordinator() != null) { throw new IllegalStateException("Cannot add " + objectStore + " to this EOObjectStoreCoordinator because it already has another."); } super.addCooperatingObjectStore(objectStore); } } @Override public void dispose() { if (_shouldClose) { _didClose = ERXEOAccessUtilities.closeDatabaseConnections(this); if (!_didClose && _shouldClose) { log.error("shouldClose was true but could not close all Connections!"); } } super.dispose(); } public static String outstandingLockDescription() { try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) { boolean hadLocks = false; pw.print(activeDatabaseContexts.size() + " active ObjectStoreCoordinators : "+ activeDatabaseContexts + ")"); for (ERXObjectStoreCoordinator ec : activeDatabaseContexts.keySet()) { NSMutableDictionary<Thread, NSMutableArray<Exception>> traces = ec.openLockTraces; if (traces != null && traces.count() > 0) { hadLocks = true; pw.println("\n------------------------"); pw.println("ObjectStoreCoordinator: " + ec + " Locking thread: " + ec.lockingThreadName + "->" + ec.lockingThread); for (Thread thread : traces.keySet()) { pw.println("Outstanding at @" + thread); for(Exception ex: traces.objectForKey(thread)) { if(ex == defaultTrace) { pw.println("Stack tracing is disabled"); } else { ex.printStackTrace(pw); } } } } } if(!hadLocks) { pw.print("No open ObjectStoreCoordinator (of " + activeDatabaseContexts.size() + ")"); } return sw.toString(); } catch (IOException e) { // ignore } return null; } public static class DumpLocksSignalHandler implements SignalHandler { public void handle(Signal signal) { log.info(outstandingLockDescription()); } } public static EOObjectStoreCoordinator create() { return new ERXObjectStoreCoordinator(); } public static EOObjectStoreCoordinator create(boolean shouldClose) { return new ERXObjectStoreCoordinator(shouldClose); } protected String _name = "unnamed"; /** @return a meaningful name for this OSC */ public String name() { return _name; } /** @param name a meaningful name for this OSC */ public void setName(String name) { _name = name; } @Override public String toString() { ToStringBuilder b = new ToStringBuilder(this); b.append("name", name()); return b.toString(); } }