exit threads when BT off; add util to report deadlocks

I've seen a deadlock in the BT stuff. Now they'll be caught and reported
via Crashlytics on DEBUG builds.
This commit is contained in:
Eric House 2019-02-17 12:49:17 -08:00
parent e23129c0e7
commit 1505a443ac
2 changed files with 193 additions and 83 deletions

View file

@ -248,6 +248,8 @@ public class BTService extends XWJIService {
if ( BTEnabled() ) { if ( BTEnabled() ) {
enqueueWork( context, BTService.class, sJobID, intent ); enqueueWork( context, BTService.class, sJobID, intent );
// Log.d( TAG, "enqueueWork(%s)", cmdFrom( intent, BTAction.values() ) ); // Log.d( TAG, "enqueueWork(%s)", cmdFrom( intent, BTAction.values() ) );
} else {
Log.d( TAG, "enqueueWork(): BT disabled so doing nothing" );
} }
} }
@ -498,6 +500,8 @@ public class BTService extends XWJIService {
default: default:
Assert.fail(); Assert.fail();
} }
} else {
Log.d( TAG, "onHandleWorkImpl(): BT disabled so doing nothing" );
} }
} // onHandleWorkImpl() } // onHandleWorkImpl()
@ -525,7 +529,6 @@ public class BTService extends XWJIService {
static void startYourself() static void startYourself()
{ {
Log.d( TAG, "startYourself()" );
synchronized ( s_listener ) { synchronized ( s_listener ) {
if ( s_listener[0] == null ) { if ( s_listener[0] == null ) {
s_listener[0] = new BTListenerThread(); s_listener[0] = new BTListenerThread();
@ -732,7 +735,7 @@ public class BTService extends XWJIService {
final String className = getClass().getSimpleName(); final String className = getClass().getSimpleName();
final AtomicInteger nDone = new AtomicInteger(); final AtomicInteger nDone = new AtomicInteger();
Log.d( TAG, "%s.run() starting", className ); Log.d( TAG, "%s.run() starting", className );
while ( !mFinishing ) { while ( !mFinishing && BTEnabled() ) {
try { try {
List<PacketAccumulator> pas = getHasData(); // blocks List<PacketAccumulator> pas = getHasData(); // blocks
Thread[] threads = new Thread[pas.size()]; Thread[] threads = new Thread[pas.size()];
@ -1096,6 +1099,7 @@ public class BTService extends XWJIService {
{ {
long waitFromNow; long waitFromNow;
// Log.d( TAG, "getNextReadyMS() IN" ); // Log.d( TAG, "getNextReadyMS() IN" );
try ( DbgUtils.DeadlockWatch dw = new DbgUtils.DeadlockWatch( this ) ) {
synchronized ( this ) { synchronized ( this ) {
if ( 0 == mCmds.size() ) { // nothing to send if ( 0 == mCmds.size() ) { // nothing to send
waitFromNow = Long.MAX_VALUE; waitFromNow = Long.MAX_VALUE;
@ -1109,16 +1113,19 @@ public class BTService extends XWJIService {
Log.d( TAG, "%s.getNextReadyMS() => %dms", this, waitFromNow ); Log.d( TAG, "%s.getNextReadyMS() => %dms", this, waitFromNow );
} }
}
return waitFromNow; return waitFromNow;
} }
void setNoHost() void setNoHost()
{ {
// Log.d( TAG, "setNoHost() IN" ); // Log.d( TAG, "setNoHost() IN" );
try ( DbgUtils.DeadlockWatch dw = new DbgUtils.DeadlockWatch( this ) ) {
synchronized ( this ) { synchronized ( this ) {
mLastFailTime = System.currentTimeMillis(); mLastFailTime = System.currentTimeMillis();
++mFailCount; ++mFailCount;
} }
}
// Log.d( TAG, "setNoHost() OUT" ); // Log.d( TAG, "setNoHost() OUT" );
} }
@ -1140,6 +1147,7 @@ public class BTService extends XWJIService {
List<BTCmd> localCmds = null; List<BTCmd> localCmds = null;
List<Integer> localGameIDs = null; List<Integer> localGameIDs = null;
try ( DbgUtils.DeadlockWatch dw = new DbgUtils.DeadlockWatch( this ) ) {
synchronized ( this ) { synchronized ( this ) {
byte[] data = mOP.bos.toByteArray(); byte[] data = mOP.bos.toByteArray();
if ( 0 < data.length ) { if ( 0 < data.length ) {
@ -1184,6 +1192,7 @@ public class BTService extends XWJIService {
} }
} }
} }
}
Log.d( TAG, "writeAndCheck(): reading replies" ); Log.d( TAG, "writeAndCheck(): reading replies" );
int nDone = 0; int nDone = 0;
@ -1328,16 +1337,13 @@ public class BTService extends XWJIService {
private void append( BTCmd cmd, OutputPair op ) throws IOException private void append( BTCmd cmd, OutputPair op ) throws IOException
{ {
// Log.d( TAG, "append() IN" );
synchronized ( this ) {
append( cmd, 0, op ); append( cmd, 0, op );
} }
// Log.d( TAG, "append() OUT" );
}
private void append( BTCmd cmd, int gameID, OutputPair op ) throws IOException private void append( BTCmd cmd, int gameID, OutputPair op ) throws IOException
{ {
// Log.d( TAG, "append() IN" ); // Log.d( TAG, "append() IN" );
try ( DbgUtils.DeadlockWatch dw = new DbgUtils.DeadlockWatch( this ) ) {
synchronized ( this ) { synchronized ( this ) {
if ( 0 == mCmds.size() ) { if ( 0 == mCmds.size() ) {
mStamp = System.currentTimeMillis(); mStamp = System.currentTimeMillis();
@ -1353,15 +1359,18 @@ public class BTService extends XWJIService {
tellSomebody(); tellSomebody();
} }
}
// Log.d( TAG, "append(%s): now %s", cmd, this ); // Log.d( TAG, "append(%s): now %s", cmd, this );
} }
void resetBackoff() void resetBackoff()
{ {
// Log.d( TAG, "resetBackoff() IN" ); // Log.d( TAG, "resetBackoff() IN" );
try ( DbgUtils.DeadlockWatch dw = new DbgUtils.DeadlockWatch( this ) ) {
synchronized ( this ) { synchronized ( this ) {
mFailCount = 0; mFailCount = 0;
} }
}
// Log.d( TAG, "resetBackoff() OUT" ); // Log.d( TAG, "resetBackoff() OUT" );
} }
@ -1379,9 +1388,11 @@ public class BTService extends XWJIService {
private void tellSomebody() private void tellSomebody()
{ {
// Log.d( TAG, "tellSomebody() IN" ); // Log.d( TAG, "tellSomebody() IN" );
try ( DbgUtils.DeadlockWatch dw = new DbgUtils.DeadlockWatch( sBlocker ) ) {
synchronized ( sBlocker ) { synchronized ( sBlocker ) {
sBlocker.notifyAll(); sBlocker.notifyAll();
} }
}
// Log.d( TAG, "tellSomebody() OUT" ); // Log.d( TAG, "tellSomebody() OUT" );
} }
@ -1483,6 +1494,7 @@ public class BTService extends XWJIService {
List<PacketAccumulator> result = new ArrayList<>(); List<PacketAccumulator> result = new ArrayList<>();
while ( 0 == result.size() ) { while ( 0 == result.size() ) {
long newMin = 5 * 60 * 1000; long newMin = 5 * 60 * 1000;
try ( DbgUtils.DeadlockWatch dw = new DbgUtils.DeadlockWatch( sSenders ) ) {
synchronized ( sSenders ) { synchronized ( sSenders ) {
for ( String addr : sSenders.keySet() ) { for ( String addr : sSenders.keySet() ) {
PacketAccumulator pa = sSenders.get( addr ); PacketAccumulator pa = sSenders.get( addr );
@ -1494,6 +1506,7 @@ public class BTService extends XWJIService {
} }
} }
} }
}
if ( result.size() == 0 ) { if ( result.size() == 0 ) {
synchronized ( sBlocker ) { synchronized ( sBlocker ) {

View file

@ -31,7 +31,6 @@ import android.text.format.Time;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Formatter; import java.util.Formatter;
import java.util.List; import java.util.List;
import java.util.Set;
import org.eehouse.android.xw4.loc.LocUtils; import org.eehouse.android.xw4.loc.LocUtils;
@ -168,4 +167,102 @@ public class DbgUtils {
// return dump.toString(); // return dump.toString();
// } // }
private static List<DeadlockWatch> sLockHolders = new ArrayList<>();
public static class DeadlockWatch extends Thread implements AutoCloseable {
private static final long DEFAULT_SLEEP_MS = 10 * 1000;
final private Object mOwner;
private long mStartStamp;
// private long mGotItTime = 0;
private boolean mCloseFired = false;
private String mStartStack;
// There's a race between this constructor and the synchronized()
// block that follows its try-with-resources. Oh well.
DeadlockWatch( Object syncObj )
{
mOwner = BuildConfig.DEBUG ? syncObj : null;
if ( BuildConfig.DEBUG ) {
mStartStack = android.util.Log.getStackTraceString(new Exception());
// Log.d( TAG, "__init(owner=%d): %s", mOwner.hashCode(), mStartStack );
mStartStamp = System.currentTimeMillis();
synchronized ( sLockHolders ) {
sLockHolders.add( this );
// Log.d( TAG, "added for owner %d", mOwner.hashCode() );
}
start();
}
}
// public void gotIt( Object obj )
// {
// if ( BuildConfig.DEBUG ) {
// Assert.assertTrue( obj == mOwner );
// mGotItTime = System.currentTimeMillis();
// // Log.d( TAG, "%s got lock after %dms", obj, mGotItTime - mStartStamp );
// }
// }
@Override
public void close()
{
if ( BuildConfig.DEBUG ) {
mCloseFired = true;
// Assert.assertTrue( 0 < mGotItTime ); // did you forget to call gotIt? :-)
}
}
@Override
public void run()
{
if ( BuildConfig.DEBUG ) {
long sleepMS = DEFAULT_SLEEP_MS;
try {
Thread.sleep( sleepMS );
if ( !mCloseFired ) {
DeadlockWatch likelyCulprit = null;
synchronized ( sLockHolders ) {
for ( DeadlockWatch sc : sLockHolders ) {
if ( sc.mOwner == mOwner && sc != this ) {
likelyCulprit = sc;
break;
}
}
}
String msg = new StringBuilder()
.append("timer fired!!!!")
.append( "lock sought by: " )
.append( mStartStack )
.append( "lock likely held by: " )
.append( likelyCulprit.mStartStack )
.toString();
CrashTrack.logAndSend( TAG, msg );
}
removeSelf();
} catch ( InterruptedException ie ) {
}
}
}
private void removeSelf()
{
if ( BuildConfig.DEBUG ) {
synchronized ( sLockHolders ) {
int start = sLockHolders.size();
// Log.d( TAG, "removing for owner %d", mOwner.hashCode() );
sLockHolders.remove( this );
Assert.assertTrue( start - 1 == sLockHolders.size() );
}
}
}
@Override
public String toString()
{
return super.toString() + "; startStack: " + mStartStack;
}
}
} }