|
@@ -32,19 +32,16 @@
|
|
|
|
|
|
package com.jme3.network.base;
|
|
|
|
|
|
-import java.io.IOException;
|
|
|
import java.nio.ByteBuffer;
|
|
|
+import java.util.concurrent.ArrayBlockingQueue;
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
-import java.util.concurrent.Executors;
|
|
|
-import java.util.concurrent.ExecutorService;
|
|
|
|
|
|
import com.jme3.network.ErrorListener;
|
|
|
import com.jme3.network.Message;
|
|
|
import com.jme3.network.MessageListener;
|
|
|
import com.jme3.network.kernel.Connector;
|
|
|
import com.jme3.network.kernel.ConnectorException;
|
|
|
-import com.jme3.network.kernel.NamedThreadFactory;
|
|
|
-import com.jme3.network.serializing.Serializer;
|
|
|
+import java.util.concurrent.BlockingQueue;
|
|
|
|
|
|
/**
|
|
|
* Wraps a single Connector and forwards new messages
|
|
@@ -64,18 +61,22 @@ import com.jme3.network.serializing.Serializer;
|
|
|
*/
|
|
|
public class ConnectorAdapter extends Thread
|
|
|
{
|
|
|
+ private static final int OUTBOUND_BACKLOG = 16000;
|
|
|
+
|
|
|
private Connector connector;
|
|
|
private MessageListener<Object> dispatcher;
|
|
|
private ErrorListener<Object> errorHandler;
|
|
|
private AtomicBoolean go = new AtomicBoolean(true);
|
|
|
-
|
|
|
+
|
|
|
+ private BlockingQueue<ByteBuffer> outbound;
|
|
|
+
|
|
|
// Writes messages out on a background thread
|
|
|
- private ExecutorService writer;
|
|
|
+ private WriterThread writer;
|
|
|
|
|
|
// Marks the messages as reliable or not if they came
|
|
|
// through this connector.
|
|
|
private boolean reliable;
|
|
|
-
|
|
|
+
|
|
|
public ConnectorAdapter( Connector connector, MessageListener<Object> dispatcher,
|
|
|
ErrorListener<Object> errorHandler, boolean reliable )
|
|
|
{
|
|
@@ -84,9 +85,34 @@ public class ConnectorAdapter extends Thread
|
|
|
this.dispatcher = dispatcher;
|
|
|
this.errorHandler = errorHandler;
|
|
|
this.reliable = reliable;
|
|
|
- setDaemon(true);
|
|
|
- writer = Executors.newFixedThreadPool(1,
|
|
|
- new NamedThreadFactory(String.valueOf(connector) + "-writer", true));
|
|
|
+ setDaemon(true);
|
|
|
+
|
|
|
+ // The backlog makes sure that the outbound channel blocks once
|
|
|
+ // a certain backlog level is reached. It is set high so that it
|
|
|
+ // is only reached in the worst cases... which are usually things like
|
|
|
+ // raw throughput tests. Technically, a saturated TCP channel could
|
|
|
+ // back up quite a bit if the buffers are full and the socket has
|
|
|
+ // stalled but 16,000 messages is still a big backlog.
|
|
|
+ outbound = new ArrayBlockingQueue<ByteBuffer>(OUTBOUND_BACKLOG);
|
|
|
+
|
|
|
+ // Note: this technically adds a potential deadlock case
|
|
|
+ // with the above code where there wasn't one before. For example,
|
|
|
+ // if a TCP outbound queue fills to capacity and a client sends
|
|
|
+ // in such a way that they block TCP message handling then if the HostedConnection
|
|
|
+ // on the server is similarly blocked then the TCP network buffers may
|
|
|
+ // all get full and no outbound messages move and we forever block
|
|
|
+ // on the queue.
|
|
|
+ // However, in practice this can't really happen... or at least it's
|
|
|
+ // the sign of other really bad things.
|
|
|
+ // First, currently the server-side outbound queues are all unbounded and
|
|
|
+ // so won't ever block the handling of messages if the outbound channel is full.
|
|
|
+ // Second, there would have to be a huge amount of data backlog for this
|
|
|
+ // to ever occur anyway.
|
|
|
+ // Third, it's a sign of a really poor architecture if 16,000 messages
|
|
|
+ // can go out in a way that blocks reads.
|
|
|
+
|
|
|
+ writer = new WriterThread();
|
|
|
+ writer.start();
|
|
|
}
|
|
|
|
|
|
public void close()
|
|
@@ -107,7 +133,11 @@ public class ConnectorAdapter extends Thread
|
|
|
|
|
|
public void write( ByteBuffer data )
|
|
|
{
|
|
|
- writer.execute( new MessageWriter(data) );
|
|
|
+ try {
|
|
|
+ outbound.put( data );
|
|
|
+ } catch( InterruptedException e ) {
|
|
|
+ throw new RuntimeException( "Interrupted while waiting for queue to drain", e );
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
protected void handleError( Exception e )
|
|
@@ -147,28 +177,40 @@ public class ConnectorAdapter extends Thread
|
|
|
handleError( e );
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- protected class MessageWriter implements Runnable
|
|
|
+
|
|
|
+ protected class WriterThread extends Thread
|
|
|
{
|
|
|
- private ByteBuffer data;
|
|
|
-
|
|
|
- public MessageWriter( ByteBuffer data )
|
|
|
+ public WriterThread()
|
|
|
{
|
|
|
- this.data = data;
|
|
|
+ super( String.valueOf(connector) + "-writer" );
|
|
|
}
|
|
|
-
|
|
|
- public void run()
|
|
|
+
|
|
|
+ public void shutdown()
|
|
|
+ {
|
|
|
+ interrupt();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void write( ByteBuffer data )
|
|
|
{
|
|
|
- if( !go.get() )
|
|
|
- return;
|
|
|
-
|
|
|
try {
|
|
|
connector.write(data);
|
|
|
} catch( Exception e ) {
|
|
|
handleError( e );
|
|
|
}
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
+ }
|
|
|
|
|
|
+ public void run()
|
|
|
+ {
|
|
|
+ while( go.get() ) {
|
|
|
+ try {
|
|
|
+ ByteBuffer data = outbound.take();
|
|
|
+ write(data);
|
|
|
+ } catch( InterruptedException e ) {
|
|
|
+ if( !go.get() )
|
|
|
+ return;
|
|
|
+ throw new RuntimeException( "Interrupted waiting for data", e );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|