decouple networking threads from RelayService

Oreo's creating a new service instance for every single intent passed to
enqueueWork, meaning a brand new set of threads with a new queue, empty
set of messages to be retried, etc. was created every time, and all
attempts to optimize and retry were broken. So: make the threads a
static singleton that are given a Service instance from onCreate() and
told to drop it from onDestroy(). The threads proceed until they need an
instance, then block until one's available. Seems to work on Oreo and an
older Android as well.
This commit is contained in:
Eric House 2018-12-09 18:21:30 -08:00
parent 450c39ed57
commit 5284dce9b8

View file

@ -114,13 +114,14 @@ public class RelayService extends JobIntentService
static { resetBackoffTimer(); } static { resetBackoffTimer(); }
private Thread m_fetchThread = null; // no longer used private Thread m_fetchThread = null; // no longer used
private AtomicReference<UDPThreads> m_UDPThreadsRef = new AtomicReference<>(); private static final AtomicReference<UDPThreads> sUDPThreadsRef = new AtomicReference<>();
private Handler m_handler; private Handler m_handler;
private UDPThreads mThreads;
private Runnable m_onInactivity; private Runnable m_onInactivity;
private int m_maxIntervalSeconds = 0; private int m_maxIntervalSeconds = 0;
private long m_lastGamePacketReceived; private long m_lastGamePacketReceived;
private int m_nativeFailScore; private static AtomicInteger sNativeFailScore = new AtomicInteger();;
private boolean m_skipUPDSet; private static boolean sSkipUPDSet;
private RelayServiceHelper mHelper; private RelayServiceHelper mHelper;
private static DevIDType s_curType = DevIDType.ID_TYPE_NONE; private static DevIDType s_curType = DevIDType.ID_TYPE_NONE;
private static long s_regStartTime = 0; private static long s_regStartTime = 0;
@ -358,7 +359,7 @@ public class RelayService extends JobIntentService
@Override @Override
public void onCreate() public void onCreate()
{ {
Log.d( TAG, "onCreate()" ); Log.d( TAG, "%s.onCreate()", this );
super.onCreate(); super.onCreate();
mHelper = new RelayServiceHelper( this ); mHelper = new RelayServiceHelper( this );
m_lastGamePacketReceived = m_lastGamePacketReceived =
@ -378,31 +379,20 @@ public class RelayService extends JobIntentService
} }
} }
}; };
mThreads = startUDPThreadsOnce();
m_skipUPDSet = XWPrefs.getSkipToWebAPI( this ); if ( null == mThreads ) {
} stopSelf();
@Override
public int onStartCommand( Intent intent, int flags, int startId )
{
Log.d( TAG, "onStartCommand(%s)", intent );
super.onStartCommand( intent, flags, startId );
Integer result = handleCommand( intent );
if ( null == result ) {
result = Service.START_STICKY_COMPATIBILITY;
} }
sSkipUPDSet = XWPrefs.getSkipToWebAPI( this );
NetStateCache.register( this, this );
resetExitTimer();
return result;
} }
@Override @Override
public void onHandleWork( Intent intent ) public void onHandleWork( Intent intent )
{ {
Log.d( TAG, "onHandleWork(%s)", intent ); Log.d( TAG, "onHandleWork(%s)", intent );
/*void*/ handleCommand( intent ); DbgUtils.assertOnUIThread( false );
handleCommand( intent );
resetExitTimer();
} }
@Override @Override
@ -412,8 +402,11 @@ public class RelayService extends JobIntentService
long interval_millis = getMaxIntervalSeconds() * 1000; long interval_millis = getMaxIntervalSeconds() * 1000;
RelayReceiver.setTimer( this, interval_millis ); RelayReceiver.setTimer( this, interval_millis );
} }
stopThreads(); if ( null != mThreads ) {
mThreads.unsetService();
}
super.onDestroy(); super.onDestroy();
Log.d( TAG, "%s.onDestroy() DONE", this );
} }
// NetStateCache.StateChangedIf interface // NetStateCache.StateChangedIf interface
@ -422,9 +415,8 @@ public class RelayService extends JobIntentService
startService( this ); // bad name: will *stop* threads too startService( this ); // bad name: will *stop* threads too
} }
private Integer handleCommand( Intent intent ) private void handleCommand( Intent intent )
{ {
Integer result = null;
MsgCmds cmd; MsgCmds cmd;
try { try {
cmd = MsgCmds.values()[intent.getIntExtra( CMD_STR, -1 )]; cmd = MsgCmds.values()[intent.getIntExtra( CMD_STR, -1 )];
@ -432,7 +424,7 @@ public class RelayService extends JobIntentService
cmd = null; cmd = null;
} }
if ( null != cmd ) { if ( null != cmd ) {
Log.d( TAG, "onStartCommand(): cmd=%s", cmd.toString() ); Log.d( TAG, "handleCommand(): cmd=%s", cmd.toString() );
switch( cmd ) { switch( cmd ) {
case PROCESS_GAME_MSGS: case PROCESS_GAME_MSGS:
String[] relayIDs = new String[1]; String[] relayIDs = new String[1];
@ -470,7 +462,7 @@ public class RelayService extends JobIntentService
case SEND: case SEND:
case RECEIVE: case RECEIVE:
case SENDNOCONN: case SENDNOCONN:
startUDPThreadsIfNot(); startUDPThreadsOnce();
long rowid = intent.getLongExtra( ROWID, -1 ); long rowid = intent.getLongExtra( ROWID, -1 );
byte[] msg = intent.getByteArrayExtra( BINBUFFER ); byte[] msg = intent.getByteArrayExtra( BINBUFFER );
if ( MsgCmds.SEND == cmd ) { if ( MsgCmds.SEND == cmd ) {
@ -483,7 +475,7 @@ public class RelayService extends JobIntentService
} }
break; break;
case INVITE: case INVITE:
startUDPThreadsIfNot(); startUDPThreadsOnce();
srcDevID = intent.getIntExtra( DEV_ID_SRC, 0 ); srcDevID = intent.getIntExtra( DEV_ID_SRC, 0 );
int destDevID = intent.getIntExtra( DEV_ID_DEST, 0 ); int destDevID = intent.getIntExtra( DEV_ID_DEST, 0 );
String relayID = intent.getStringExtra( RELAY_ID ); String relayID = intent.getStringExtra( RELAY_ID );
@ -505,12 +497,9 @@ public class RelayService extends JobIntentService
stopSelf(); stopSelf();
break; break;
default: default:
Assert.fail(); Assert.assertFalse( BuildConfig.DEBUG );
} }
result = Service.START_STICKY;
} }
return result;
} }
private void setupNotifications( String[] relayIDs, BackMoveResult[] bmrs, private void setupNotifications( String[] relayIDs, BackMoveResult[] bmrs,
@ -553,6 +542,7 @@ public class RelayService extends JobIntentService
{ {
while ( null != m_fetchThread ) { while ( null != m_fetchThread ) {
Log.w( TAG, "2: m_fetchThread NOT NULL; WHAT TO DO???" ); Log.w( TAG, "2: m_fetchThread NOT NULL; WHAT TO DO???" );
Assert.assertFalse( BuildConfig.DEBUG );
try { try {
Thread.sleep( 20 ); Thread.sleep( 20 );
} catch( java.lang.InterruptedException ie ) { } catch( java.lang.InterruptedException ie ) {
@ -561,24 +551,38 @@ public class RelayService extends JobIntentService
} }
} }
private void startUDPThreadsIfNot() private UDPThreads startUDPThreadsOnce()
{ {
UDPThreads threads = null;
if ( XWApp.UDP_ENABLED && relayEnabled( this ) ) { if ( XWApp.UDP_ENABLED && relayEnabled( this ) ) {
synchronized ( m_UDPThreadsRef ) { synchronized ( sUDPThreadsRef ) {
if ( null == m_UDPThreadsRef.get() ) { threads = sUDPThreadsRef.get();
UDPThreads threads = new UDPThreads(); if ( null == threads ) {
m_UDPThreadsRef.set( threads ); threads = new UDPThreads();
sUDPThreadsRef.set( threads );
threads.start(); threads.start();
} }
threads.setService( this );
} }
} else { } else {
Log.i( TAG, "startUDPThreadsIfNot(): UDP disabled" ); Log.i( TAG, "startUDPThreadsOnce(): UDP disabled" );
} }
} // startUDPThreadsIfNot return threads;
} // startUDPThreadsOnce
private boolean skipNativeSend() private void stopUDPThreads()
{ {
boolean skip = m_nativeFailScore > UDP_FAIL_LIMIT || m_skipUPDSet; synchronized ( sUDPThreadsRef ) {
UDPThreads threads = sUDPThreadsRef.getAndSet( null );
if ( null != threads ) {
threads.stop();
}
}
}
private static boolean skipNativeSend()
{
boolean skip = sNativeFailScore.get() > UDP_FAIL_LIMIT || sSkipUPDSet;
// Log.d( TAG, "skipNativeSend(score=%d)) => %b", m_nativeFailScore, skip ); // Log.d( TAG, "skipNativeSend(score=%d)) => %b", m_nativeFailScore, skip );
return skip; return skip;
} }
@ -615,14 +619,6 @@ public class RelayService extends JobIntentService
Log.d( TAG, "noteSent(fromUDP=%b): size after: %d", fromUDP, map.size() ); Log.d( TAG, "noteSent(fromUDP=%b): size after: %d", fromUDP, map.size() );
} }
private void stopUDPThreadsIf()
{
UDPThreads threads = m_UDPThreadsRef.getAndSet( null );
if ( null != threads ) {
threads.stop();
}
}
// MIGHT BE Running on reader thread // MIGHT BE Running on reader thread
private void gotPacket( byte[] data, boolean skipAck, boolean fromUDP ) private void gotPacket( byte[] data, boolean skipAck, boolean fromUDP )
{ {
@ -687,7 +683,7 @@ public class RelayService extends JobIntentService
break; break;
case XWPDEV_UPGRADE: case XWPDEV_UPGRADE:
intent = getIntentTo( this, MsgCmds.UPGRADE ); intent = getIntentTo( this, MsgCmds.UPGRADE );
startService( intent ); startService( this, intent );
break; break;
case XWPDEV_GOTINVITE: case XWPDEV_GOTINVITE:
resetBackoff = true; resetBackoff = true;
@ -700,7 +696,7 @@ public class RelayService extends JobIntentService
String asStr = nli.toString(); String asStr = nli.toString();
Log.d( TAG, "got invitation: %s", asStr ); Log.d( TAG, "got invitation: %s", asStr );
intent.putExtra( NLI_DATA, asStr ); intent.putExtra( NLI_DATA, asStr );
startService( intent ); startService( this, intent );
break; break;
case XWPDEV_ACK: case XWPDEV_ACK:
noteAck( vli2un( dis ), fromUDP ); noteAck( vli2un( dis ), fromUDP );
@ -932,8 +928,7 @@ public class RelayService extends JobIntentService
private void postPacket( ByteArrayOutputStream bas, XWRelayReg cmd ) private void postPacket( ByteArrayOutputStream bas, XWRelayReg cmd )
{ {
startUDPThreadsIfNot(); UDPThreads threads = startUDPThreadsOnce();
UDPThreads threads = m_UDPThreadsRef.get();
if ( threads != null ) { if ( threads != null ) {
threads.add( new PacketData( bas, cmd ) ); threads.add( new PacketData( bas, cmd ) );
} }
@ -1001,14 +996,111 @@ public class RelayService extends JobIntentService
} }
} }
private class UDPThreads { private static class UDPThreads {
private DatagramSocket m_UDPSocket; private DatagramSocket m_UDPSocket;
private LinkedBlockingQueue<PacketData> m_queue = private LinkedBlockingQueue<PacketData> m_queue =
new LinkedBlockingQueue<PacketData>(); new LinkedBlockingQueue<PacketData>();
private Thread m_UDPReadThread; private Thread m_UDPReadThread;
private Thread m_UDPWriteThread; private Thread m_UDPWriteThread;
private RelayService[] mServiceHolder = {null};
private AtomicInteger mHaveServiceCount = new AtomicInteger();
UDPThreads() {} // Here's the problem: In the Oreo world, RelayService instances come
// and go, typically lasting only long enough to disptach a single
// onHandleWork() call. So the UDPThreads instance is now a static
// singleton that lasts a really long time, across the lives of
// hundreds of RelayService instances. It and its threads, however,
// have to make calls on RelayService instances. So I need a way to
// block them when there's no instance available AND to block
// onDestroy() returning until all references to the current instance
// have been forgotten.
//
// The solution is to set and clear the instance through
// [un]setService(). And to wrap the code inside that needs
// references in a AutoCloseable that when instantiated blocks when
// the instance is null. Thus when setService() is called with a new
// instance, all the requests for an instance are unblocked and each
// of them increments a counter. When the AutoCloseable's close() is
// called, the counter is decremented. unsetService() waits until that
// counter is 0 (and so the close() method calls notifyAll() when the
// counter's dropped to 0.)
//
// It works like this: the threads run. When they need a reference to
// the current RelayService they block until there's one
// available. They're able to get the reference as long as it's
// non-null, but once it's null they start to block again. Once the
// last that didn't block finishes with its instance unsetService()
// returns (and so onDestroy() can as well.) This clears it so the OS
// can create another RelayService instance that will eventually get
// passed to setService() again.
private class ServiceWrapper implements AutoCloseable {
private final RelayService mService;
ServiceWrapper( RelayService service ) { mService = service; }
RelayService get() { return mService; }
@Override
public void close()
{
synchronized ( mServiceHolder ) {
int newVal = mHaveServiceCount.decrementAndGet();
Log.d( TAG, "close(): count now %d", newVal );
if ( 0 == newVal ) {
mServiceHolder.notifyAll();
}
}
}
}
void setService( RelayService service )
{
synchronized ( mServiceHolder ) {
mServiceHolder[0] = service;
mServiceHolder.notifyAll();
}
}
// When service is cleared, DO NOT RETURN until all threads using the
// old value of the service have relinquished it. This is called from
// onDestroy(), and once onDestroy() had returned nobody should
// reference its RelayService instance again.
void unsetService()
{
Log.d( TAG, "unsetService()" );
synchronized ( mServiceHolder ) {
mServiceHolder[0] = null;
// Now block until the used count drops to 0
while ( mHaveServiceCount.get() > 0 ) {
try {
mServiceHolder.wait();
} catch (InterruptedException ie) {
Log.e( TAG, "wait() threw: %s", ie.getMessage() );
// Assert.assertFalse( BuildConfig.DEBUG );
}
}
}
Log.d( TAG, "unsetService() DONE" );
}
// called by various threads that need to run
private ServiceWrapper acquireWrapper()
{
synchronized ( mServiceHolder ) {
while ( null == mServiceHolder[0] ) {
try {
mServiceHolder.wait();
} catch (InterruptedException ie) {
Log.e( TAG, "wait() threw: %s", ie.getMessage() );
// InterruptedException is how we get unblocked!
// Assert.assertFalse( BuildConfig.DEBUG );
}
}
int newVal = mHaveServiceCount.incrementAndGet();
Log.d( TAG, "acquireWrapper(): count now %d", newVal );
return new ServiceWrapper( mServiceHolder[0] );
}
}
void start() void start()
{ {
@ -1025,8 +1117,10 @@ public class RelayService extends JobIntentService
new DatagramPacket( buf, buf.length ); new DatagramPacket( buf, buf.length );
try { try {
m_UDPSocket.receive( packet ); m_UDPSocket.receive( packet );
resetExitTimer(); try (ServiceWrapper wrapper = acquireWrapper() ) {
gotPacket( packet ); wrapper.get().resetExitTimer();
wrapper.get().gotPacket( packet );
}
} catch ( java.io.InterruptedIOException iioe ) { } catch ( java.io.InterruptedIOException iioe ) {
// DbgUtils.logf( "FYI: udp receive timeout" ); // DbgUtils.logf( "FYI: udp receive timeout" );
} catch( java.io.IOException ioe ) { } catch( java.io.IOException ioe ) {
@ -1052,8 +1146,12 @@ public class RelayService extends JobIntentService
private void connectSocket() private void connectSocket()
{ {
if ( null == m_UDPSocket ) { if ( null == m_UDPSocket ) {
int port = XWPrefs.getDefaultRelayPort( RelayService.this ); int port;
String host = XWPrefs.getDefaultRelayHost( RelayService.this ); String host;
try ( ServiceWrapper wrapper = acquireWrapper() ) {
port = XWPrefs.getDefaultRelayPort( wrapper.get() );
host = XWPrefs.getDefaultRelayHost( wrapper.get() );
}
try { try {
m_UDPSocket = new DatagramSocket(); m_UDPSocket = new DatagramSocket();
m_UDPSocket.setSoTimeout(30 * 1000); // timeout so we can log m_UDPSocket.setSoTimeout(30 * 1000); // timeout so we can log
@ -1108,7 +1206,9 @@ public class RelayService extends JobIntentService
sendViaWeb( dataListWeb ); sendViaWeb( dataListWeb );
sendViaUDP( dataListUDP ); sendViaUDP( dataListUDP );
resetExitTimer(); try ( ServiceWrapper wrapper = acquireWrapper() ) {
wrapper.get().resetExitTimer();
}
runUDPAckTimer(); runUDPAckTimer();
ConnStatusHandler.showSuccessOut(); ConnStatusHandler.showSuccessOut();
@ -1135,47 +1235,52 @@ public class RelayService extends JobIntentService
Log.d( TAG, "sendViaWeb(): sending %d at once", packets.size() ); Log.d( TAG, "sendViaWeb(): sending %d at once", packets.size() );
int sentLen = 0; int sentLen = 0;
if ( packets.size() > 0 ) { if ( packets.size() > 0 ) {
HttpURLConnection conn = NetUtils.makeHttpRelayConn( RelayService.this, "post" ); try ( ServiceWrapper wrapper = acquireWrapper() ) {
if ( null == conn ) { RelayService service = wrapper.get();
Log.e( TAG, "sendViaWeb(): null conn for POST" ); HttpURLConnection conn = NetUtils
} else { .makeHttpRelayConn( service, "post" );
try { if ( null == conn ) {
JSONArray dataArray = new JSONArray(); Log.e( TAG, "sendViaWeb(): null conn for POST" );
for ( PacketData packet : packets ) { } else {
Assert.assertFalse( packet instanceof EOQPacketData ); try {
byte[] datum = packet.assemble(); JSONArray dataArray = new JSONArray();
dataArray.put( Utils.base64Encode(datum) ); for ( PacketData packet : packets ) {
sentLen += datum.length; Assert.assertFalse( packet instanceof EOQPacketData );
} byte[] datum = packet.assemble();
JSONObject params = new JSONObject(); dataArray.put( Utils.base64Encode(datum) );
params.put( "data", dataArray ); sentLen += datum.length;
String result = NetUtils.runConn( conn, params );
boolean succeeded = null != result;
if ( succeeded ) {
Log.d( TAG, "sendViaWeb(): POST(%s) => %s", params, result );
JSONObject resultObj = new JSONObject( result );
JSONArray resData = resultObj.getJSONArray( "data" );
int nReplies = resData.length();
// Log.d( TAG, "sendViaWeb(): got %d replies", nReplies );
noteSent( packets, false ); // before we process the acks below :-)
for ( int ii = 0; ii < nReplies; ++ii ) {
byte[] datum = Utils.base64Decode( resData.getString( ii ) );
// PENDING: skip ack or not
gotPacket( datum, false, false );
} }
} else { JSONObject params = new JSONObject();
Log.e( TAG, "sendViaWeb(): failed result for POST" ); params.put( "data", dataArray );
String result = NetUtils.runConn( conn, params );
boolean succeeded = null != result;
if ( succeeded ) {
Log.d( TAG, "sendViaWeb(): POST(%s) => %s", params, result );
JSONObject resultObj = new JSONObject( result );
JSONArray resData = resultObj.getJSONArray( "data" );
int nReplies = resData.length();
// Log.d( TAG, "sendViaWeb(): got %d replies", nReplies );
service
.noteSent( packets, false ); // before we process the acks below :-)
for ( int ii = 0; ii < nReplies; ++ii ) {
byte[] datum = Utils.base64Decode( resData.getString( ii ) );
// PENDING: skip ack or not
service.gotPacket( datum, false, false );
}
} else {
Log.e( TAG, "sendViaWeb(): failed result for POST" );
}
ConnStatusHandler.updateStatus( service, null,
CommsConnType.COMMS_CONN_RELAY,
succeeded );
} catch ( JSONException ex ) {
Assert.assertFalse( BuildConfig.DEBUG );
} }
ConnStatusHandler.updateStatus( RelayService.this, null,
CommsConnType.COMMS_CONN_RELAY,
succeeded );
} catch ( JSONException ex ) {
Assert.assertFalse( BuildConfig.DEBUG );
} }
} }
} }
@ -1187,43 +1292,47 @@ public class RelayService extends JobIntentService
int sentLen = 0; int sentLen = 0;
if ( packets.size() > 0 ) { if ( packets.size() > 0 ) {
noteSent( packets, true ); try ( ServiceWrapper wrapper = acquireWrapper() ) {
for ( PacketData packet : packets ) { RelayService service = wrapper.get();
boolean getOut = true; service.noteSent( packets, true );
byte[] data = packet.assemble(); for ( PacketData packet : packets ) {
try { boolean getOut = true;
DatagramPacket udpPacket = new DatagramPacket( data, data.length ); byte[] data = packet.assemble();
m_UDPSocket.send( udpPacket ); try {
DatagramPacket udpPacket = new DatagramPacket( data, data.length );
m_UDPSocket.send( udpPacket );
sentLen += udpPacket.getLength(); sentLen += udpPacket.getLength();
// packet.setSentMS( nowMS ); // packet.setSentMS( nowMS );
getOut = false; getOut = false;
} catch ( java.net.SocketException se ) { } catch ( java.net.SocketException se ) {
Log.ex( TAG, se ); Log.ex( TAG, se );
Log.i( TAG, "Restarting threads to force new socket" ); Log.i( TAG, "Restarting threads to force new socket" );
ConnStatusHandler.updateStatusOut( RelayService.this, null, ConnStatusHandler.updateStatusOut( service, null,
CommsConnType.COMMS_CONN_RELAY, CommsConnType.COMMS_CONN_RELAY,
true ); true );
m_handler.post( new Runnable() { service.m_handler.post( new Runnable() {
public void run() { public void run() {
stopUDPThreadsIf(); Assert.assertFalse( BuildConfig.DEBUG );
} // stopUDPThreadsIf();
} ); }
break; } );
} catch ( java.io.IOException ioe ) { break;
Log.ex( TAG, ioe ); } catch ( java.io.IOException ioe ) {
} catch ( NullPointerException npe ) { Log.ex( TAG, ioe );
Log.w( TAG, "network problem; dropping packet" ); } catch ( NullPointerException npe ) {
} Log.w( TAG, "network problem; dropping packet" );
if ( getOut ) { }
break; if ( getOut ) {
break;
}
} }
ConnStatusHandler.updateStatus( service, null,
CommsConnType.COMMS_CONN_RELAY,
sentLen > 0 );
} }
ConnStatusHandler.updateStatus( RelayService.this, null,
CommsConnType.COMMS_CONN_RELAY,
sentLen > 0 );
} }
return sentLen; return sentLen;
@ -1256,7 +1365,7 @@ public class RelayService extends JobIntentService
forResend.add( packet ); forResend.add( packet );
if ( packet.m_cmd != XWRelayReg.XWPDEV_ACK ) { if ( packet.m_cmd != XWRelayReg.XWPDEV_ACK ) {
foundNonAck = true; foundNonAck = true;
++m_nativeFailScore; sNativeFailScore.incrementAndGet();
} }
iter.remove(); iter.remove();
} }
@ -1433,7 +1542,7 @@ public class RelayService extends JobIntentService
// Log.d( TAG, "noteAck(fromUDP=%b): removed for id %d: %s", // Log.d( TAG, "noteAck(fromUDP=%b): removed for id %d: %s",
// fromUDP, packetID, packet ); // fromUDP, packetID, packet );
if ( fromUDP ) { if ( fromUDP ) {
--m_nativeFailScore; sNativeFailScore.decrementAndGet();
} }
} else { } else {
Log.w( TAG, "Weird: got ack %d but never sent", packetID ); Log.w( TAG, "Weird: got ack %d but never sent", packetID );
@ -1462,6 +1571,7 @@ public class RelayService extends JobIntentService
// Called from any thread // Called from any thread
private void resetExitTimer() private void resetExitTimer()
{ {
Log.d( TAG, "resetExitTimer()" );
m_handler.removeCallbacks( m_onInactivity ); m_handler.removeCallbacks( m_onInactivity );
// UDP socket's no good as a return address after several // UDP socket's no good as a return address after several
@ -1477,10 +1587,11 @@ public class RelayService extends JobIntentService
stopThreads(); stopThreads();
} else if ( XWApp.UDP_ENABLED ) { } else if ( XWApp.UDP_ENABLED ) {
stopFetchThreadIf(); stopFetchThreadIf();
startUDPThreadsIfNot(); startUDPThreadsOnce();
registerWithRelay(); registerWithRelay();
} else { } else {
stopUDPThreadsIf(); Assert.assertFalse( BuildConfig.DEBUG );
stopUDPThreads();
startFetchThreadIfNotUDP(); startFetchThreadIfNotUDP();
} }
} }
@ -1489,7 +1600,7 @@ public class RelayService extends JobIntentService
{ {
Log.d( TAG, "stopThreads()" ); Log.d( TAG, "stopThreads()" );
stopFetchThreadIf(); stopFetchThreadIf();
stopUDPThreadsIf(); stopUDPThreads();
} }
private static void un2vli( int nn, OutputStream os ) private static void un2vli( int nn, OutputStream os )
@ -1656,7 +1767,7 @@ public class RelayService extends JobIntentService
} }
} }
private class PacketData { private static class PacketData {
public ByteArrayOutputStream m_bas; public ByteArrayOutputStream m_bas;
public XWRelayReg m_cmd; public XWRelayReg m_cmd;
public byte[] m_header; public byte[] m_header;
@ -1745,5 +1856,6 @@ public class RelayService extends JobIntentService
} }
// Exits only to exist, so instanceof can distinguish // Exits only to exist, so instanceof can distinguish
private class EOQPacketData extends PacketData {} // Can this be replaced by a final Object tested with ==?
private static class EOQPacketData extends PacketData {}
} }