From 984a5d615449b7bb3eff6425f0f63552937ca13e Mon Sep 17 00:00:00 2001 From: Andy2 Date: Thu, 2 Dec 2010 22:37:21 -0800 Subject: [PATCH 1/4] Fix yet again what shouldn't have been checked in. --- xwords4/android/XWords4/jni/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/XWords4/jni/Android.mk b/xwords4/android/XWords4/jni/Android.mk index 6277a98a6..f14529b93 100644 --- a/xwords4/android/XWords4/jni/Android.mk +++ b/xwords4/android/XWords4/jni/Android.mk @@ -10,7 +10,7 @@ local_C_INCLUDES+= \ local_LDLIBS += -llog -local_DEBUG = -DMEM_DEBUG -DDEBUG -DENABLE_LOGGING +# local_DEBUG = -DMEM_DEBUG -DDEBUG -DENABLE_LOGGING local_DEFINES += \ $(local_DEBUG) \ -DXWFEATURE_RELAY \ From 426f9901565fe4bd54a927a42e719f79cdcd9b30 Mon Sep 17 00:00:00 2001 From: Andy2 Date: Fri, 3 Dec 2010 06:43:40 -0800 Subject: [PATCH 2/4] fix infinite loop creating networked game: util_rand() can't call into java in some cases so needs to fall back to returning rand(). --- xwords4/android/XWords4/jni/utilwrapper.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/xwords4/android/XWords4/jni/utilwrapper.c b/xwords4/android/XWords4/jni/utilwrapper.c index 58a33f9eb..93abf160b 100644 --- a/xwords4/android/XWords4/jni/utilwrapper.c +++ b/xwords4/android/XWords4/jni/utilwrapper.c @@ -369,9 +369,14 @@ static XP_U16 and_util_rand( XW_UtilCtxt* uc ) { jint result = 0; - UTIL_CBK_HEADER("rand", "()I" ); - result = (*env)->CallIntMethod( env, util->jutil, mid ); - UTIL_CBK_TAIL(); + AndUtil* util = (AndUtil*)uc; + JNIEnv* env = *util->env; + if ( NULL != util->jutil ) { + jmethodID mid = getMethodID( env, util->jutil, "rand", "()I" ); + result = (*env)->CallIntMethod( env, util->jutil, mid ); + } else { + result = rand(); + } return result; } #endif From fda21e8d15a24137b6c718db50c351e7b39ec1b8 Mon Sep 17 00:00:00 2001 From: Andy2 Date: Fri, 3 Dec 2010 06:44:34 -0800 Subject: [PATCH 3/4] save room and player names if leaving simple config dialog via the advanced config button (but still drop if back button hit.) --- .../android/xw4/RelayGameActivity.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java index c10074919..303d5dcbf 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayGameActivity.java @@ -88,20 +88,16 @@ public class RelayGameActivity extends XWActivity @Override public void onClick( View view ) { + String room = Utils.getText( this, R.id.room_edit ).trim(); if ( view == m_playButton ) { - String room = Utils.getText( this, R.id.room_edit ).trim(); if ( room.length() == 0 ) { showOKOnlyDialog( R.string.no_empty_rooms ); } else { - m_car.ip_relay_invite = room; - String name = Utils.getText( this, R.id.local_name_edit ); - if ( name.length() > 0 ) { - m_gi.setFirstLocalName( name ); - } - GameUtils.applyChanges( this, m_gi, m_car, m_path, false ); + saveRoomAndName( room ); GameUtils.launchGame( this, m_path ); } } else if ( view == m_configButton ) { + saveRoomAndName( room ); GameUtils.doConfig( this, m_path, GameConfig.class ); finish(); } @@ -112,4 +108,14 @@ public class RelayGameActivity extends XWActivity return summary.nPlayers == 2; } + private void saveRoomAndName( String room ) + { + String name = Utils.getText( this, R.id.local_name_edit ); + if ( name.length() > 0 ) { // don't wipe existing + m_gi.setFirstLocalName( name ); + } + m_car.ip_relay_invite = room; + GameUtils.applyChanges( this, m_gi, m_car, m_path, false ); + } + } // class RelayGameActivity From b23de9a95890482523d704b03accd35603c589c1 Mon Sep 17 00:00:00 2001 From: Andy2 Date: Fri, 3 Dec 2010 18:48:14 -0800 Subject: [PATCH 4/4] switch from nio to regular old io, with separate reader and writer threads. Greatly simplifies things, but only tested for the simplest case, and I know it won't correctly reconnect when relay goes down. But folks recommend against nio, and I'd like to see if I'll get notifications in the form of blocking socket calls returning or raising exceptions when the network goes down, something that isn't happenening with nio. --- .../eehouse/android/xw4/CommsTransport.java | 296 +++++------------- 1 file changed, 85 insertions(+), 211 deletions(-) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java index 316eab952..457313b71 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/CommsTransport.java @@ -20,13 +20,8 @@ package org.eehouse.android.xw4; -import java.nio.channels.Selector; -import java.nio.channels.SocketChannel; -import java.nio.channels.SelectionKey; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.UnresolvedAddressException; -import java.nio.ByteBuffer; import java.net.InetSocketAddress; +import java.net.Socket; import java.util.Vector; import java.util.Iterator; import junit.framework.Assert; @@ -37,6 +32,11 @@ import android.content.Context; import android.os.Build; import android.os.Handler; import android.os.Message; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.io.DataInputStream; +import java.io.DataOutputStream; + import org.eehouse.android.xw4.jni.*; import org.eehouse.android.xw4.jni.JNIThread.*; @@ -66,141 +66,82 @@ public class CommsTransport implements TransportProcs { public int m_nMissing; } - private Selector m_selector; - private SocketChannel m_socketChannel; private int m_jniGamePtr; private CommsAddrRec m_addr; private JNIThread m_jniThread; - private CommsThread m_thread; private Handler m_handler; - private boolean m_done = false; - private Vector m_buffersOut; - private ByteBuffer m_bytesOut; - private ByteBuffer m_bytesIn; + private Socket m_socket; + private ReaderThread m_reader; + private WriterThread m_writer; + BlockingQueue m_queue; private Context m_context; - // assembling inbound packet - private byte[] m_packetIn; - private int m_haveLen = -1; - public CommsTransport( int jniGamePtr, Context context, Handler handler, DeviceRole role ) { m_jniGamePtr = jniGamePtr; m_context = context; m_handler = handler; - m_buffersOut = new Vector(); - m_bytesIn = ByteBuffer.allocate( 2048 ); } - public class CommsThread extends Thread { - - @Override + public class WriterThread extends Thread { public void run() { - try { - if ( Build.PRODUCT.contains("sdk") ) { - System.setProperty("java.net.preferIPv6Addresses", "false"); - } - - m_selector = Selector.open(); - - loop(); - - closeSocket(); + DataOutputStream os; + try { + os = new DataOutputStream( m_socket.getOutputStream() ); } catch ( java.io.IOException ioe ) { - Utils.logf( ioe.toString() ); - } catch ( UnresolvedAddressException uae ) { - Utils.logf( "bad address: name: %s; port: %s; exception: %s", - m_addr.ip_relay_hostName, m_addr.ip_relay_port, - uae.toString() ); + Utils.logf( "%s", ioe.toString() ); + return; } - m_thread = null; - } - - private void loop() - { - while ( !m_done ) { + + for ( ; ; ) { try { - synchronized( this ) { + byte[] buf = m_queue.take(); // blocks - // if we have data and no socket, try to connect. - if ( null == m_socketChannel - && 0 < m_buffersOut.size() ) { - try { - m_socketChannel = SocketChannel.open(); - m_socketChannel.configureBlocking( false ); - Utils.logf( "connecting to %s:%d", - m_addr.ip_relay_hostName, - m_addr.ip_relay_port ); - InetSocketAddress isa - = new InetSocketAddress( m_addr.ip_relay_hostName, - m_addr.ip_relay_port ); - m_socketChannel.connect( isa ); - } catch ( java.io.IOException ioe ) { - Utils.logf( ioe.toString() ); - } - } - - if ( null != m_socketChannel ) { - int ops = figureOps(); - // Utils.logf( "calling with ops=%x", ops ); - m_socketChannel.register( m_selector, ops ); - } - } - m_selector.select(); - } catch ( ClosedChannelException cce ) { - // we get this when relay goes down. Need to notify! - m_jniThread.handle( JNICmd.CMD_TRANSFAIL ); - closeSocket(); - Utils.logf( "exiting: " + cce.toString() ); - break; // don't try again - } catch ( java.io.IOException ioe ) { - closeSocket(); - Utils.logf( "exiting: " + ioe.toString() ); - Utils.logf( ioe.toString() ); - } - - Iterator iter = m_selector.selectedKeys().iterator(); - while ( iter.hasNext() ) { - SelectionKey key = (SelectionKey)iter.next(); - SocketChannel channel = (SocketChannel)key.channel(); - iter.remove(); - try { - if (key.isValid() && key.isConnectable()) { - if ( !channel.finishConnect() ) { - key.cancel(); - } - } - if (key.isValid() && key.isReadable()) { - m_bytesIn.clear(); // will wipe any pending data! - // Utils.logf( "socket is readable; buffer has space for " - // + m_bytesIn.remaining() ); - int nRead = channel.read( m_bytesIn ); - if ( nRead == -1 ) { - channel.close(); - } else { - addIncoming(); - } - } - if (key.isValid() && key.isWritable()) { - getOut(); - if ( null != m_bytesOut ) { - int nWritten = channel.write( m_bytesOut ); - //Utils.logf( "wrote " + nWritten + " bytes" ); - } - } - } catch ( java.io.IOException ioe ) { - key.cancel(); - } + os.writeShort( buf.length ); + os.write( buf ); + Utils.logf( "wrote %d bytes to socket", buf.length ); + } catch ( InterruptedException inte ) { + Utils.logf( "%s", inte.toString() ); + break; + } catch( java.io.IOException ioe ) { + Utils.logf( "%s", ioe.toString() ); + break; } } - } // loop - + } } - + + public class ReaderThread extends Thread { + public void run() + { + DataInputStream dis; + try { + dis = new DataInputStream( m_socket.getInputStream() ); + } catch ( java.io.IOException ioe ) { + Utils.logf( "%s", ioe.toString() ); + return; + } + + for ( ; ; ) { + try { + short len = dis.readShort(); + Utils.logf( "ReaderThread: read length short: %d", len ); + byte[] buf = new byte[len]; + dis.readFully( buf ); + Utils.logf( "returned from readFully()" ); + m_jniThread.handle( JNICmd.CMD_RECEIVE, buf ); + } catch( java.io.IOException ioe ) { + Utils.logf( "%s", ioe.toString() ); + break; + } + } + } + } + public void setReceiver( JNIThread jnit ) { m_jniThread = jnit; @@ -208,104 +149,35 @@ public class CommsTransport implements TransportProcs { public void waitToStop() { - m_done = true; - if ( null != m_selector ) { - m_selector.wakeup(); - } - if ( null != m_thread ) { // synchronized this? Or use Thread method + if ( null != m_socket ) { try { - m_thread.join(100); // wait up to 1/10 second - } catch ( java.lang.InterruptedException ie ) { - Utils.logf( "got InterruptedException: " + ie.toString() ); + m_socket.close(); + m_socket = null; + } catch ( java.io.IOException ioe ) { + Utils.logf( "%s", ioe.toString() ); } - m_thread = null; } } - private synchronized void putOut( final byte[] buf ) + private void startThreadsIf() { - int len = buf.length; - ByteBuffer netbuf = ByteBuffer.allocate( len + 2 ); - netbuf.putShort( (short)len ); - netbuf.put( buf ); - m_buffersOut.add( netbuf ); - Assert.assertEquals( netbuf.remaining(), 0 ); - - if ( null != m_selector ) { - m_selector.wakeup(); // tell it it's got some writing to do - } - } - - private synchronized void closeSocket() - { - if ( null != m_socketChannel ) { + if ( null == m_socket ) { try { - m_socketChannel.close(); - } catch ( Exception e ) { - Utils.logf( "closing socket: %s", e.toString() ); - } - m_socketChannel = null; - } - } - - private synchronized void getOut() - { - if ( null != m_bytesOut && m_bytesOut.remaining() == 0 ) { - m_bytesOut = null; - } - - if ( null == m_bytesOut && m_buffersOut.size() > 0 ) { - m_bytesOut = m_buffersOut.remove(0); - m_bytesOut.flip(); - } - } - - private synchronized int figureOps() { - int ops; - if ( null == m_socketChannel ) { - ops = 0; - } else if ( m_socketChannel.isConnected() ) { - ops = SelectionKey.OP_READ; - if ( (null != m_bytesOut && m_bytesOut.hasRemaining()) - || m_buffersOut.size() > 0 ) { - ops |= SelectionKey.OP_WRITE; - } - } else { - ops = SelectionKey.OP_CONNECT; - } - return ops; - } - - private void addIncoming( ) - { - m_bytesIn.flip(); - - for ( ; ; ) { - int len = m_bytesIn.remaining(); - if ( len <= 0 ) { - break; - } - - if ( null == m_packetIn ) { // we're not mid-packet - Assert.assertTrue( len > 1 ); // tell me if I see this case - if ( len == 1 ) { // half a length byte... - break; // can I leave it in the buffer? - } else { - m_packetIn = new byte[m_bytesIn.getShort()]; - m_haveLen = 0; - } - } else { // we're mid-packet - int wantLen = m_packetIn.length - m_haveLen; - if ( wantLen > len ) { - wantLen = len; - } - m_bytesIn.get( m_packetIn, m_haveLen, wantLen ); - m_haveLen += wantLen; - if ( m_haveLen == m_packetIn.length ) { - // send completed packet - m_jniThread.handle( JNICmd.CMD_RECEIVE, m_packetIn ); - m_packetIn = null; + m_socket = new Socket( m_addr.ip_relay_hostName, + m_addr.ip_relay_port ); + if ( null != m_socket ) { + m_queue = new ArrayBlockingQueue(16); + m_reader = new ReaderThread(); + m_reader.start(); + m_writer = new WriterThread(); + m_writer.start(); } + } catch ( java.net.UnknownHostException uhe ) { + Utils.logf( "%s", uhe.toString() ); + m_socket = null; + } catch ( java.io.IOException ioe ) { + Utils.logf( "%s", ioe.toString() ); + m_socket = null; // need to notify user on some of these } } } @@ -327,12 +199,14 @@ public class CommsTransport implements TransportProcs { switch ( m_addr.conType ) { case COMMS_CONN_RELAY: - putOut( buf ); // add to queue - if ( null == m_thread ) { - m_thread = new CommsThread(); - m_thread.start(); + startThreadsIf(); + try { + // add(), not put(): don't block thread in comms if full + m_queue.add( buf ); + nSent = buf.length; + } catch ( IllegalStateException ise ) { + Utils.logf( "%s", ise.toString() ); } - nSent = buf.length; break; case COMMS_CONN_SMS: Assert.fail();