Fix hangs when receiving relay messages in background for open game by

adding a static feedMessages method like the one used by SMS and BT
games.  For that to work, rowid and relayid need to be fetched and
tracked together -- so do that in RelayService.
This commit is contained in:
Eric House 2012-12-13 06:57:12 -08:00
parent 91ac04b896
commit 6060d5e8bd
7 changed files with 129 additions and 91 deletions

View file

@ -189,6 +189,27 @@ public class BoardActivity extends XWActivity
return delivered;
}
public static boolean feedMessages( long rowid, byte[][] msgs )
{
boolean delivered = false;
Assert.assertNotNull( msgs );
synchronized( s_thisLocker ) {
if ( null != s_this ) {
Assert.assertNotNull( s_this.m_gi );
Assert.assertNotNull( s_this.m_gameLock );
Assert.assertNotNull( s_this.m_jniThread );
if ( rowid == s_this.m_rowid ) {
delivered = true; // even if no messages!
for ( byte[] msg : msgs ) {
s_this.m_jniThread.handle( JNICmd.CMD_RECEIVE, msg,
null );
}
}
}
}
return delivered;
}
private static void setThis( BoardActivity self )
{
synchronized( s_thisLocker ) {
@ -509,6 +530,7 @@ public class BoardActivity extends XWActivity
Intent intent = getIntent();
m_rowid = intent.getLongExtra( GameUtils.INTENT_KEY_ROWID, -1 );
DbgUtils.logf( "BoardActivity: opening rowid %d", m_rowid );
m_haveInvited = intent.getBooleanExtra( GameUtils.INVITED, false );
m_overNotShown = true;

View file

@ -573,7 +573,7 @@ public class DBUtils {
return result;
}
public static String[] getRelayIDs( Context context, boolean noMsgs )
public static String[] getRelayIDs( Context context, long[][] rowIDs )
{
String[] result = null;
initDB( context );
@ -581,26 +581,31 @@ public class DBUtils {
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
String[] columns = { DBHelper.RELAYID };
String[] columns = { ROW_ID, DBHelper.RELAYID };
String selection = DBHelper.RELAYID + " NOT null";
if ( noMsgs ) {
selection += " AND NOT " + DBHelper.HASMSGS;
}
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, null, null, null, null );
int count = cursor.getCount();
if ( 0 < count ) {
result = new String[count];
if ( null != rowIDs ) {
rowIDs[0] = new long[count];
}
while ( cursor.moveToNext() ) {
ids.add( cursor.getString( cursor.
getColumnIndex(DBHelper.RELAYID)) );
int idIndex = cursor.getColumnIndex(DBHelper.RELAYID);
int rowIndex = cursor.getColumnIndex(ROW_ID);
for ( int ii = 0; cursor.moveToNext(); ++ii ) {
result[ii] = cursor.getString( idIndex );
if ( null != rowIDs ) {
rowIDs[0][ii] = cursor.getLong( rowIndex );
}
}
}
cursor.close();
db.close();
}
if ( 0 < ids.size() ) {
result = ids.toArray( new String[ids.size()] );
}
return result;
}

View file

@ -242,7 +242,7 @@ public class DlgDelegate {
public void doSyncMenuitem()
{
if ( null == DBUtils.getRelayIDs( m_activity, false ) ) {
if ( null == DBUtils.getRelayIDs( m_activity, null ) ) {
showOKOnlyDialog( R.string.no_games_to_refresh );
} else {
RelayReceiver.RestartTimer( m_activity, true );

View file

@ -111,16 +111,21 @@ public class GameUtils {
final long assertTime = 2000;
Assert.assertTrue( maxMillis < assertTime );
long sleptTime = 0;
// DbgUtils.logf( "GameLock.lock(%s)", m_path );
// Utils.printStack();
if ( XWApp.DEBUG_LOCKS ) {
DbgUtils.logf( "lock %H (rowid:%d, maxMillis=%d)",
this, m_rowid, maxMillis );
}
for ( ; ; ) {
if ( tryLock() ) {
result = this;
break;
}
if ( XWApp.DEBUG_LOCKS ) {
DbgUtils.logf( "GameLock.lock() %H failed; sleeping", this );
DbgUtils.printStack();
DbgUtils.logf( "GameLock.lock() %H failed (rowid:%d); sleeping",
this, m_rowid );
// DbgUtils.printStack();
}
try {
Thread.sleep( 25 ); // milliseconds
@ -130,12 +135,17 @@ public class GameUtils {
break;
}
if ( XWApp.DEBUG_LOCKS ) {
DbgUtils.logf( "GameLock.lock() %H awake; "
+ "sleptTime now %d millis", this, sleptTime );
}
if ( 0 < maxMillis && sleptTime >= maxMillis ) {
break;
} else if ( sleptTime >= assertTime ) {
if ( XWApp.DEBUG_LOCKS ) {
DbgUtils.logf( "lock %H overlocked. lock holding stack:",
this );
DbgUtils.logf( "lock %H overlocked waiting for rowid:%d. "
+ "lock holding stack:", m_rowid, this );
DbgUtils.printStack( m_lockTrace );
DbgUtils.logf( "lock %H seeking stack:", this );
DbgUtils.printStack();
@ -158,8 +168,12 @@ public class GameUtils {
Assert.assertTrue( !m_isForWrite );
}
--m_lockCount;
if ( XWApp.DEBUG_LOCKS ) {
DbgUtils.logf( "GameLock.unlock: this: %H (rowid:%d) "
+ "unlocked", this, m_rowid );
}
}
// DbgUtils.logf( "GameLock.unlock(%s) done", m_path );
}
public long getRowid()
@ -707,41 +721,46 @@ public class GameUtils {
}
}
private static boolean feedMessages( Context context, long rowid,
byte[][] msgs, CommsAddrRec ret,
MultiMsgSink sink )
public static boolean feedMessages( Context context, long rowid,
byte[][] msgs, CommsAddrRec ret,
MultiMsgSink sink )
{
boolean draw = false;
Assert.assertTrue( -1 != rowid );
GameLock lock = new GameLock( rowid, true ).lock();
if ( null != msgs ) {
// timed lock: If a game is opened by BoardActivity just
// as we're trying to deliver this message to it it'll
// have the lock and we'll never get it. Better to drop
// the message than fire the hung-lock assert. Messages
// belong in local pre-delivery storage anyway.
GameLock lock = new GameLock( rowid, true ).lock( 150 );
if ( null != lock ) {
CurGameInfo gi = new CurGameInfo( context );
FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid );
int gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock );
if ( 0 != gamePtr ) {
XwJNI.comms_resendAll( gamePtr, false, false );
CurGameInfo gi = new CurGameInfo( context );
FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, rowid );
int gamePtr = loadMakeGame( context, gi, feedImpl, sink, lock );
if ( 0 != gamePtr ) {
XwJNI.comms_resendAll( gamePtr, false, false );
for ( byte[] msg : msgs ) {
draw = XwJNI.game_receiveMessage( gamePtr, msg, ret )
|| draw;
}
XwJNI.comms_ackAny( gamePtr );
if ( null != msgs ) {
for ( byte[] msg : msgs ) {
draw = XwJNI.game_receiveMessage( gamePtr, msg, ret )
|| draw;
// update gi to reflect changes due to messages
XwJNI.game_getGi( gamePtr, gi );
saveGame( context, gamePtr, gi, lock, false );
summarizeAndClose( context, lock, gamePtr, gi, feedImpl );
int flags = setFromFeedImpl( feedImpl );
if ( GameSummary.MSG_FLAGS_NONE != flags ) {
draw = true;
DBUtils.setMsgFlags( rowid, flags );
}
}
}
XwJNI.comms_ackAny( gamePtr );
// update gi to reflect changes due to messages
XwJNI.game_getGi( gamePtr, gi );
saveGame( context, gamePtr, gi, lock, false );
summarizeAndClose( context, lock, gamePtr, gi, feedImpl );
int flags = setFromFeedImpl( feedImpl );
if ( GameSummary.MSG_FLAGS_NONE != flags ) {
draw = true;
DBUtils.setMsgFlags( rowid, flags );
lock.unlock();
}
}
lock.unlock();
return draw;
} // feedMessages
@ -754,21 +773,6 @@ public class GameUtils {
return feedMessages( context, rowid, msgs, ret, sink );
}
// Current assumption: this is the relay case where return address
// can be null.
public static boolean feedMessages( Context context, String relayID,
byte[][] msgs, MultiMsgSink sink )
{
boolean draw = false;
long[] rowids = DBUtils.getRowIDsFor( context, relayID );
if ( null != rowids ) {
for ( long rowid : rowids ) {
draw = feedMessages( context, rowid, msgs, null, sink ) || draw;
}
}
return draw;
}
// This *must* involve a reset if the language is changing!!!
// Which isn't possible right now, so make sure the old and new
// dict have the same langauge code.

View file

@ -130,8 +130,7 @@ public class NetUtils {
}
}
public static byte[][][] queryRelay( Context context, String[] ids,
int nBytes )
public static byte[][][] queryRelay( Context context, String[] ids )
{
byte[][][] msgs = null;
try {
@ -141,6 +140,7 @@ public class NetUtils {
new DataOutputStream( socket.getOutputStream() );
// total packet size
int nBytes = sumStrings( ids );
outStream.writeShort( 2 + nBytes + ids.length + 1 );
outStream.writeByte( NetUtils.PROTOCOL_VERSION );
@ -263,4 +263,16 @@ public class NetUtils {
DbgUtils.logf( "sendToRelay: null msgs" );
}
} // sendToRelay
private static int sumStrings( final String[] strs )
{
int len = 0;
if ( null != strs ) {
for ( String str : strs ) {
len += str.length();
}
}
return len;
}
}

View file

@ -74,45 +74,40 @@ public class RelayService extends Service {
}
}
}
private String[] collectIDs( int[] nBytes )
{
String[] ids = DBUtils.getRelayIDs( this, false );
int len = 0;
if ( null != ids ) {
for ( String id : ids ) {
len += id.length();
}
}
nBytes[0] = len;
return ids;
}
private void fetchAndProcess()
{
int[] nBytes = new int[1];
String[] ids = collectIDs( nBytes );
if ( null != ids && 0 < ids.length ) {
RelayMsgSink sink = new RelayMsgSink();
byte[][][] msgs =
NetUtils.queryRelay( this, ids, nBytes[0] );
long[][] rowIDss = new long[1][];
String[] relayIDs = DBUtils.getRelayIDs( this, rowIDss );
if ( null != relayIDs && 0 < relayIDs.length ) {
long[] rowIDs = rowIDss[0];
byte[][][] msgs = NetUtils.queryRelay( this, relayIDs );
if ( null != msgs ) {
int nameCount = ids.length;
RelayMsgSink sink = new RelayMsgSink();
int nameCount = relayIDs.length;
ArrayList<String> idsWMsgs =
new ArrayList<String>( nameCount );
for ( int ii = 0; ii < nameCount; ++ii ) {
byte[][] forOne = msgs[ii];
// if game has messages, open it and feed 'em
// to it.
if ( GameUtils.feedMessages( this, ids[ii],
msgs[ii], sink ) ) {
idsWMsgs.add( ids[ii] );
if ( null == forOne ) {
// Nothing for this relayID
} else if ( BoardActivity.feedMessages( rowIDs[ii], forOne )
|| GameUtils.feedMessages( this, rowIDs[ii],
forOne, null,
sink ) ) {
idsWMsgs.add( relayIDs[ii] );
} else {
DbgUtils.logf( "dropping message for %s (rowid %d)",
relayIDs[ii], rowIDs[ii] );
}
}
if ( 0 < idsWMsgs.size() ) {
String[] relayIDs = new String[idsWMsgs.size()];
idsWMsgs.toArray( relayIDs );
setupNotification( relayIDs );
String[] tmp = new String[idsWMsgs.size()];
idsWMsgs.toArray( tmp );
setupNotification( tmp );
}
sink.send( this );
}

View file

@ -28,13 +28,13 @@ import java.util.UUID;
import org.eehouse.android.xw4.jni.XwJNI;
public class XWApp extends Application {
public static final boolean DEBUG_LOCKS = false;
public static final boolean BTSUPPORTED = false;
public static final boolean SMSSUPPORTED = true;
public static final boolean GCMSUPPORTED = true;
public static final boolean ATTACH_SUPPORTED = true;
public static final boolean REMATCH_SUPPORTED = false;
public static final boolean DEBUG = true; // DON'T SHIP THIS WAY
public static final boolean DEBUG = true; // DON'T SHIP THIS WAY
public static final boolean DEBUG_LOCKS = false && DEBUG;
public static final String SMS_PUBLIC_HEADER = "-XW4";