2004-12-08 jrandom

* Revised the buffering when reading from the SAM client and writing
      to the stream.  Also added a thread (sigh) so we don't block the
      SAM client from giving us more messages for abnormally long periods
      of time.
    * Display the router version in the logs on startup (oft requested)
    * Fix a race during the closing of a messageOutputStream
This commit is contained in:
jrandom
2004-12-08 17:16:16 +00:00
committed by zzz
parent 4c5f7b9451
commit d88396c1e2
8 changed files with 183 additions and 93 deletions

View File

@ -15,8 +15,10 @@ import java.io.InterruptedIOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.NoRouteToHostException; import java.net.NoRouteToHostException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
@ -29,6 +31,7 @@ import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.client.streaming.I2PSocketOptions; import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.Base64; import net.i2p.data.Base64;
import net.i2p.data.ByteArray; import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.util.ByteCache; import net.i2p.util.ByteCache;
@ -53,7 +56,10 @@ public class SAMStreamSession {
private I2PSocketManager socketMgr = null; private I2PSocketManager socketMgr = null;
private Object handlersMapLock = new Object(); private Object handlersMapLock = new Object();
/** stream id (Long) to SAMStreamSessionSocketReader */
private HashMap handlersMap = new HashMap(); private HashMap handlersMap = new HashMap();
/** stream id (Long) to StreamSender */
private HashMap sendersMap = new HashMap();
private Object idLock = new Object(); private Object idLock = new Object();
private int lastNegativeId = 0; private int lastNegativeId = 0;
@ -61,6 +67,14 @@ public class SAMStreamSession {
// Can we create outgoing connections? // Can we create outgoing connections?
private boolean canCreate = false; private boolean canCreate = false;
/**
* should we flush every time we get a STREAM SEND, or leave that up to
* the streaming lib to decide?
*/
private boolean forceFlush = false;
public static String PROP_FORCE_FLUSH = "sam.forceFlush";
public static String DEFAULT_FORCE_FLUSH = "false";
/** /**
* Create a new SAM STREAM session. * Create a new SAM STREAM session.
* *
@ -109,9 +123,6 @@ public class SAMStreamSession {
} catch (NumberFormatException nfe) { } catch (NumberFormatException nfe) {
throw new SAMException("Invalid I2CP port specified [" + port + "]"); throw new SAMException("Invalid I2CP port specified [" + port + "]");
} }
// streams MUST be mode=guaranteed (though i think the socket manager
// enforces this anyway...
allprops.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
_log.debug("Creating I2PSocketManager..."); _log.debug("Creating I2PSocketManager...");
socketMgr = I2PSocketManagerFactory.createManager(destStream, socketMgr = I2PSocketManagerFactory.createManager(destStream,
@ -122,6 +133,8 @@ public class SAMStreamSession {
throw new SAMException("Error creating I2PSocketManager"); throw new SAMException("Error creating I2PSocketManager");
} }
forceFlush = Boolean.valueOf(allprops.getProperty(PROP_FORCE_FLUSH, DEFAULT_FORCE_FLUSH)).booleanValue();
boolean canReceive = false; boolean canReceive = false;
if (dir.equals("BOTH")) { if (dir.equals("BOTH")) {
canCreate = true; canCreate = true;
@ -199,14 +212,14 @@ public class SAMStreamSession {
* *
* @param data Bytes to be sent * @param data Bytes to be sent
* *
* @return True if the data was sent, false otherwise * @return True if the data was queued for sending, false otherwise
*/ */
public boolean sendBytes(int id, InputStream in, int size) throws IOException { public boolean sendBytes(int id, InputStream in, int size) throws IOException {
SAMStreamSessionSocketHandler handler = getSocketHandler(id); StreamSender sender = getSender(id);
if (handler == null) { if (sender == null) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.error("Trying to send bytes through inexistent handler " +id); _log.warn("Trying to send bytes through nonexistent handler " +id);
// even though it failed, we need to read those bytes! // even though it failed, we need to read those bytes!
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
int c = in.read(); int c = in.read();
@ -216,7 +229,8 @@ public class SAMStreamSession {
return false; return false;
} }
return handler.sendBytes(in, size); sender.sendBytes(in, size);
return true;
} }
/** /**
@ -256,13 +270,15 @@ public class SAMStreamSession {
* @return An id associated to the socket handler * @return An id associated to the socket handler
*/ */
private int createSocketHandler(I2PSocket s, int id) { private int createSocketHandler(I2PSocket s, int id) {
SAMStreamSessionSocketHandler handler; SAMStreamSessionSocketReader reader = null;
StreamSender sender = null;
if (id == 0) { if (id == 0) {
id = createUniqueId(); id = createUniqueId();
} }
try { try {
handler = new SAMStreamSessionSocketHandler(s, id); reader = new SAMStreamSessionSocketReader(s, id);
sender = new StreamSender(s, id);
} catch (IOException e) { } catch (IOException e) {
_log.error("IOException when creating SAM STREAM session socket handler", e); _log.error("IOException when creating SAM STREAM session socket handler", e);
recv.stopStreamReceiving(); recv.stopStreamReceiving();
@ -270,10 +286,13 @@ public class SAMStreamSession {
} }
synchronized (handlersMapLock) { synchronized (handlersMapLock) {
handlersMap.put(new Integer(id), handler); handlersMap.put(new Integer(id), reader);
sendersMap.put(new Integer(id), sender);
} }
I2PThread t = new I2PThread(handler, "SAMStreamSessionSocketHandler"); I2PThread t = new I2PThread(reader, "SAMReader" + id);
t.start();
t = new I2PThread(sender, "SAMSender" + id);
t.start(); t.start();
return id; return id;
@ -291,9 +310,14 @@ public class SAMStreamSession {
* *
* @param id Handler id * @param id Handler id
*/ */
private SAMStreamSessionSocketHandler getSocketHandler(int id) { private SAMStreamSessionSocketReader getSocketReader(int id) {
synchronized (handlersMapLock) { synchronized (handlersMapLock) {
return (SAMStreamSessionSocketHandler)handlersMap.get(new Integer(id)); return (SAMStreamSessionSocketReader)handlersMap.get(new Integer(id));
}
}
private StreamSender getSender(int id) {
synchronized (handlersMapLock) {
return (StreamSender)sendersMap.get(new Integer(id));
} }
} }
@ -314,18 +338,19 @@ public class SAMStreamSession {
* @param id Handler id to be removed * @param id Handler id to be removed
*/ */
private void removeSocketHandler(int id) { private void removeSocketHandler(int id) {
SAMStreamSessionSocketHandler removed; SAMStreamSessionSocketReader reader = null;
StreamSender sender = null;
synchronized (handlersMapLock) { synchronized (handlersMapLock) {
removed = (SAMStreamSessionSocketHandler)handlersMap.remove(new Integer(id)); reader = (SAMStreamSessionSocketReader)handlersMap.remove(new Integer(id));
sender = (StreamSender)sendersMap.remove(new Integer(id));
} }
if (removed == null) { if (reader != null)
// ignore - likely the socket handler was already removed by removeAllSocketHandlers reader.stopRunning();
} else { if (sender != null)
removed.stopRunning(); sender.stopRunning();
_log.debug("Removed SAM STREAM session socket handler " + id); _log.debug("Removed SAM STREAM session socket handler " + id);
}
} }
/** /**
@ -344,9 +369,11 @@ public class SAMStreamSession {
while (iter.hasNext()) { while (iter.hasNext()) {
id = (Integer)iter.next(); id = (Integer)iter.next();
((SAMStreamSessionSocketHandler)handlersMap.get(id)).stopRunning(); ((SAMStreamSessionSocketReader)handlersMap.get(id)).stopRunning();
((StreamSender)sendersMap.get(id)).stopRunning();
} }
handlersMap.clear(); handlersMap.clear();
sendersMap.clear();
} }
} }
@ -440,10 +467,9 @@ public class SAMStreamSession {
* *
* @author human * @author human
*/ */
public class SAMStreamSessionSocketHandler implements Runnable { public class SAMStreamSessionSocketReader implements Runnable {
private I2PSocket i2pSocket = null; private I2PSocket i2pSocket = null;
private OutputStream i2pSocketOS = null;
private Object runningLock = new Object(); private Object runningLock = new Object();
private boolean stillRunning = true; private boolean stillRunning = true;
@ -451,72 +477,20 @@ public class SAMStreamSession {
private int id; private int id;
/** /**
* Create a new SAM STREAM session socket handler * Create a new SAM STREAM session socket reader
* *
* @param s Socket to be handled * @param s Socket to be handled
* @param id Unique id assigned to the handler * @param id Unique id assigned to the handler
*/ */
public SAMStreamSessionSocketHandler(I2PSocket s, int id) throws IOException { public SAMStreamSessionSocketReader(I2PSocket s, int id) throws IOException {
_log.debug("Instantiating new SAM STREAM session socket handler"); _log.debug("Instantiating new SAM STREAM session socket handler");
i2pSocket = s; i2pSocket = s;
i2pSocketOS = s.getOutputStream();
this.id = id; this.id = id;
} }
/** /**
* Send bytes through the SAM STREAM session socket handler * Stop a SAM STREAM session socket reader
*
* @param data Data to be sent
*
* @return True if data has been sent without errors, false otherwise
*/
public boolean sendBytes(InputStream in, int size) throws IOException {
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Handler " + id + ": sending " + size
+ " bytes");
}
ByteCache cache = ByteCache.getInstance(1024, 4);
ByteArray ba = cache.acquire();
int remaining = size;
try {
byte buf[] = ba.getData();
while (remaining > 0) {
int read = in.read(buf, 0, remaining > buf.length ? buf.length : remaining);
if (read == -1) {
throw new IOException("Insufficient data from the SAM client (" + remaining + "/" + size + ")");
} else if (read > 0) {
remaining -= read;
try {
i2pSocketOS.write(buf, 0, read);
} catch (IOException ioe) {
// ok, the stream failed, but the SAM client didn't
if (_log.shouldLog(Log.WARN))
_log.warn("Stream failed", ioe);
removeSocketHandler(id);
// emtpy the remaining payload so we can continue
for (int i = remaining; i > 0; i--) {
int c = in.read();
if (c == -1)
throw new IOException("Stream closed, but the SAM client didn't send enough anyway ("
+ i + " remaining)");
}
return false;
}
}
}
} finally {
cache.release(ba);
}
return true;
}
/**
* Stop a SAM STREAM session socket handler
* *
*/ */
public void stopRunning() { public void stopRunning() {
@ -575,4 +549,99 @@ public class SAMStreamSession {
_log.debug("Shutting down SAM STREAM session socket handler " +id); _log.debug("Shutting down SAM STREAM session socket handler " +id);
} }
} }
/**
* Lets us push data through the stream without blocking, (even after exceeding
* the I2PSocket's buffer)
*/
private class StreamSender implements Runnable {
private List _data;
private int _id;
private ByteCache _cache;
private OutputStream _out = null;
private boolean _stillRunning;
public StreamSender(I2PSocket s, int id) throws IOException {
_data = new ArrayList(1);
_id = id;
_cache = ByteCache.getInstance(4, 32*1024);
_out = s.getOutputStream();
_stillRunning = true;
}
/**
* Send bytes through the SAM STREAM session socket sender
*
* @param data Data to be sent
*
* @throws IOException if the client didnt provide enough data
*/
public void sendBytes(InputStream in, int size) throws IOException {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handler " + _id + ": sending " + size + " bytes");
ByteArray ba = _cache.acquire();
int read = DataHelper.read(in, ba.getData(), 0, size);
if (read != size)
throw new IOException("Insufficient data from the SAM client (" + read + "/" + size + ")");
ba.setValid(read);
synchronized (_data) {
_data.add(ba);
_data.notifyAll();
}
}
/**
* Stop a SAM STREAM session socket sender
*
*/
public void stopRunning() {
_log.debug("stopRunning() invoked on socket sender " + _id);
_stillRunning = false;
synchronized (_data) {
_data.clear();
_data.notifyAll();
}
}
public void run() {
ByteArray data = null;
while (_stillRunning) {
data = null;
try {
synchronized (_data) {
if (_data.size() > 0)
data = (ByteArray)_data.remove(0);
else
_data.wait(5000);
}
if (data != null) {
try {
_out.write(data.getData(), 0, data.getValid());
if (forceFlush) {
// i dont like doing this, but it clears the buffer issues
_out.flush();
}
} catch (IOException ioe) {
// ok, the stream failed, but the SAM client didn't
if (_log.shouldLog(Log.WARN))
_log.warn("Stream failed", ioe);
removeSocketHandler(_id);
stopRunning();
} finally {
_cache.release(data);
}
}
} catch (InterruptedException ie) {}
}
synchronized (_data) {
_data.clear();
}
}
}
} }

View File

@ -394,15 +394,12 @@ public class Connection {
if (_socket != null) if (_socket != null)
_socket.destroy(); _socket.destroy();
_socket = null; _socket = null;
_inputStream = null;
if (_outputStream != null) if (_outputStream != null)
_outputStream.destroy(); _outputStream.destroy();
_outputStream = null;
_outboundQueue = null;
if (_receiver != null) if (_receiver != null)
_receiver.destroy(); _receiver.destroy();
if (_activityTimer != null) if (_activityTimer != null)
SimpleTimer.getInstance().addEvent(_activityTimer, 1); SimpleTimer.getInstance().removeEvent(_activityTimer);
_activityTimer = null; _activityTimer = null;
if (!_disconnectScheduled) { if (!_disconnectScheduled) {

View File

@ -155,12 +155,13 @@ public class MessageOutputStream extends OutputStream {
} }
public void timeReached() { public void timeReached() {
_enqueued = false; _enqueued = false;
DataReceiver rec = _dataReceiver;
long timeLeft = (_lastBuffered + _passiveFlushDelay - _context.clock().now()); long timeLeft = (_lastBuffered + _passiveFlushDelay - _context.clock().now());
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("flusher time reached: left = " + timeLeft); _log.debug("flusher time reached: left = " + timeLeft);
if (timeLeft > 0) if (timeLeft > 0)
enqueue(); enqueue();
else if (_dataReceiver.writeInProcess()) else if ( (rec != null) && (rec.writeInProcess()) )
enqueue(); // don't passive flush if there is a write being done (unacked outbound) enqueue(); // don't passive flush if there is a write being done (unacked outbound)
else else
doFlush(); doFlush();

View File

@ -18,6 +18,7 @@ import java.io.Serializable;
*/ */
public class ByteArray implements Serializable, Comparable { public class ByteArray implements Serializable, Comparable {
private byte[] _data; private byte[] _data;
private int _valid;
public ByteArray() { public ByteArray() {
this(null); this(null);
@ -25,6 +26,7 @@ public class ByteArray implements Serializable, Comparable {
public ByteArray(byte[] data) { public ByteArray(byte[] data) {
_data = data; _data = data;
_valid = 0;
} }
public final byte[] getData() { public final byte[] getData() {
@ -34,6 +36,14 @@ public class ByteArray implements Serializable, Comparable {
public void setData(byte[] data) { public void setData(byte[] data) {
_data = data; _data = data;
} }
/**
* how many of the bytes in the array are 'valid'?
* this property does not necessarily have meaning for all byte
* arrays.
*/
public final int getValid() { return _valid; }
public final void setValid(int valid) { _valid = valid; }
public final boolean equals(Object o) { public final boolean equals(Object o) {
if (o == null) return false; if (o == null) return false;

View File

@ -658,12 +658,14 @@ public class DataHelper {
} }
public static int read(InputStream in, byte target[]) throws IOException { public static int read(InputStream in, byte target[]) throws IOException {
int cur = 0; return read(in, target, 0, target.length);
while (cur < target.length) { }
int numRead = in.read(target, cur, target.length - cur); public static int read(InputStream in, byte target[], int offset, int length) throws IOException {
int cur = offset;
while (cur < length) {
int numRead = in.read(target, cur, length - cur);
if (numRead == -1) { if (numRead == -1) {
if (cur == 0) return -1; // throw new EOFException("EOF Encountered during reading"); if (cur == offset) return -1; // throw new EOFException("EOF Encountered during reading");
return cur; return cur;
} }
cur += numRead; cur += numRead;
@ -671,6 +673,7 @@ public class DataHelper {
return cur; return cur;
} }
/** /**
* Read a newline delimited line from the stream, returning the line (without * Read a newline delimited line from the stream, returning the line (without
* the newline), or null if EOF reached before the newline was found * the newline), or null if EOF reached before the newline was found

View File

@ -1,4 +1,12 @@
$Id: history.txt,v 1.99 2004/12/06 00:03:58 jrandom Exp $ $Id: history.txt,v 1.100 2004/12/06 20:09:20 jrandom Exp $
2004-12-08 jrandom
* Revised the buffering when reading from the SAM client and writing
to the stream. Also added a thread (sigh) so we don't block the
SAM client from giving us more messages for abnormally long periods
of time.
* Display the router version in the logs on startup (oft requested)
* Fix a race during the closing of a messageOutputStream
2004-12-06 jrandom 2004-12-06 jrandom
* Don't do a 'passive flush' while there are already outbound messages * Don't do a 'passive flush' while there are already outbound messages

View File

@ -792,6 +792,8 @@ public class Router {
} }
public static void main(String args[]) { public static void main(String args[]) {
System.out.println("Starting I2P " + RouterVersion.VERSION + "-" + RouterVersion.BUILD);
System.out.println(RouterVersion.ID);
installUpdates(); installUpdates();
verifyWrapperConfig(); verifyWrapperConfig();
Router r = new Router(); Router r = new Router();

View File

@ -15,9 +15,9 @@ import net.i2p.CoreVersion;
* *
*/ */
public class RouterVersion { public class RouterVersion {
public final static String ID = "$Revision: 1.104 $ $Date: 2004/12/06 00:03:57 $"; public final static String ID = "$Revision: 1.105 $ $Date: 2004/12/06 20:09:20 $";
public final static String VERSION = "0.4.2.2"; public final static String VERSION = "0.4.2.2";
public final static long BUILD = 8; public final static long BUILD = 9;
public static void main(String args[]) { public static void main(String args[]) {
System.out.println("I2P Router version: " + VERSION); System.out.println("I2P Router version: " + VERSION);
System.out.println("Router ID: " + RouterVersion.ID); System.out.println("Router ID: " + RouterVersion.ID);