/*==========================================================================*\
| $Id: LockErrorScreamerEditingContext.java,v 1.2 2012/03/28 13:48:08 stedwar2 Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2006-2012 Virginia Tech
|
| This file is part of Web-CAT.
|
| Web-CAT is free software; you can redistribute it and/or modify
| it under the terms of the GNU Affero General Public License as published
| by the Free Software Foundation; either version 3 of the License, or
| (at your option) any later version.
|
| Web-CAT is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU Affero General Public License
| along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package org.webcat.core;
import com.webobjects.eocontrol.*;
import com.webobjects.foundation.*;
import java.io.StringWriter;
import java.io.PrintWriter;
import org.webcat.core.LockErrorScreamerEditingContext;
import org.apache.log4j.Logger;
// -------------------------------------------------------------------------
/**
* EOEditingContext subclass that tracks locking activity and
* complains about mismatching locking calls. Based on the original
* class of the same name by Jonathan 'Wolf' Rentzsch (jon at redshed dot net)
* enhanced by Anthony Ingraldi (a.m.ingraldi at larc.nasa.gov).
*
* @author Jonathan 'Wolf' Rentzsch
* @author Last changed by $Author: stedwar2 $
* @version $Revision: 1.2 $, $Date: 2012/03/28 13:48:08 $
*/
public class LockErrorScreamerEditingContext
extends er.extensions.eof.ERXEC
{
//~ Constructors ..........................................................
// ----------------------------------------------------------
/**
* Creates a new LockErrorScreamerEditingContext object.
*/
public LockErrorScreamerEditingContext()
{
super();
}
// ----------------------------------------------------------
/**
* Constructor for nested editing contexts.
* @param parent EOEditingContext that this editing context is a child of
*/
public LockErrorScreamerEditingContext( EOObjectStore parent )
{
super( parent );
}
//~ Methods ...............................................................
// ----------------------------------------------------------
/**
* Overridden to capture trace of where lock was taken and to show error
* and trace if the editing context is already lock by by a thread other
* than the one asking for the lock. For editing contexts associated
* with sessions this usually results in deadlock for the session and
* also for the application if requrests are not being dispatched
* concurrently.
*/
public void lock()
{
String nameOfCurrentThread = Thread.currentThread().getName();
// If we have not recorded any traces this editing context is not
// currently locked and this results in a the lock being taken by
// a new thread.
if ( stackTraces.count() == 0 )
{
stackTraces.addObject( trace() );
nameOfLockingThread = nameOfCurrentThread;
log.debug( "+++ Lock number (" + stackTraces.count()
+ ") in " + nameOfCurrentThread );
}
else
{
// This editing context has already been locked.
// If the thread is the same then this is a secondary call that
// results in an increased recursionCount() for the
// NSRecursiveLock.
if ( nameOfCurrentThread.equals( nameOfLockingThread ) )
{
stackTraces.addObject( trace() );
log.debug( "+++ Lock number (" + stackTraces.count()
+ ") in " + nameOfCurrentThread );
}
// If the thread is not the same it will block. For editing
// contexts in a session this results in deadlock so an error
// message is output. The error includes the trace of the most
// recent lock taken which is probably the offending unreleased
// lock. It might not be if your lock and unlocks are not
// cleanly nested. In that case you might need to capture and
// display traces of all the lock and unlock calls to find what
// is not nested correctly.
else
{
log.error( "!!! Attempting to lock editing context from "
+ nameOfCurrentThread
+ " that was previously locked in "
+ nameOfLockingThread );
log.error( "!!! Current stack trace:\n" + trace() );
log.error( "!!! Stack trace for most recent lock:\n"
+ stackTraces.lastObject() );
}
}
super.lock();
}
// ----------------------------------------------------------
/**
* Overridden to capture trace of where lock was taken and to show error
* and trace if the editing context is already lock by by a thread other
* than the one asking for the lock.
*/
public void unlock()
{
// This will throw an IllegalStateException if the editing context
// is not locked, or if the unlocking thread is not the thread with
// the lock.
super.unlock();
// This editing context is already locked, so remove the trace for the
// latest lock(), assuming that it corresponds to this unlock().
if ( stackTraces.count() > 0 )
{
stackTraces.removeLastObject();
}
if ( stackTraces.count() == 0 )
{
// No more traces means that we are no longer locked so
// dis-associate ourselves with the thread that had us locked.
nameOfLockingThread = null;
}
String nameOfCurrentThread = Thread.currentThread().getName();
log.debug( "--- Unlocked in " + nameOfCurrentThread + " ("
+ stackTraces.count() + " remaining)" );
}
// ----------------------------------------------------------
/**
* Support method with check common to dispose() and finalize().
* Allowing locked editing contexts to be garbage collected or
* disposed is not a good practice. It is probably OK for peer
* editing contexts, but this is not a really good coding practice.
* This method outputs a warning message if this happens. If you want
* to follow this bad practice, change the test below to:<br>
* <code>( (_stackTraces.count() != 0)
* && (parent() instanceof EOEditingContext) )</code>
*/
public void goodbye()
{
if ( stackTraces.count() != 0 )
{
log.error( "!!! editing context being disposed with "
+ stackTraces.count() + " locks." );
log.error( "!!! Most recently locked by:\n"
+ stackTraces.lastObject() );
}
}
// ----------------------------------------------------------
/**
* Allowing locked editing contexts to be disposed is not a good
* practice. This method calls goodbye() to output a warning message
* if this happens.
*/
public void dispose()
{
try
{
goodbye();
}
finally
{
super.dispose();
}
}
// ----------------------------------------------------------
/**
* Allowing locked editing contexts to be garbage collected is not a good
* practice. This method calls goodbye() to outputs a warning message if
* this happens.
* @throws Throwable if the superclass implementation produces an error
*/
public void finalize()
throws Throwable
{
try
{
goodbye();
}
finally
{
super.finalize();
}
}
// ----------------------------------------------------------
/**
* Utility method to return the stack trace from the current location as
* a string.
* @return the stack trace from the current location as a string
*/
private String trace()
{
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter( stringWriter );
( new Throwable() ).printStackTrace( printWriter );
return stringWriter.toString();
}
//~ Instance/static variables .............................................
private String nameOfLockingThread = null;
private NSMutableArray<String> stackTraces = new NSMutableArray<String>();
static Logger log =
Logger.getLogger( LockErrorScreamerEditingContext.class );
}