/*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.impl.endpoint.tls;
import net.jxta.endpoint.ByteArrayMessageElement;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.MessageElement;
import net.jxta.endpoint.StringMessageElement;
import net.jxta.impl.endpoint.tls.TlsConn.HandshakeState;
import net.jxta.impl.util.TimeUtils;
import net.jxta.logging.Logging;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Acts as the output for TLS. Accepts ciphertext from TLS and packages it into
* messages for sending to the remote. The messages are kept in a retry queue
* until the remote peer acknowledges receipt of the message.
**/
class JTlsOutputStream extends OutputStream {
/**
* Log4J Logger
**/
private static final Logger LOG = Logger.getLogger(JTlsOutputStream.class.getName());
// constants
/**
* This maximum is only enforced if we have not heard
* from the remote for RETRMAXAGE.
**/
private static final int MAXRETRQSIZE = 100;
/**
* Initial estimated Round Trip Time
**/
private static final long initRTT = 1 * TimeUtils.ASECOND;
private static final MessageElement RETELT = new StringMessageElement(JTlsDefs.RETR, "TLSRET", null);
/**
* Retrans window. When reached, we up the RTO.
**/
private static final int RWINDOW = 5;
/**
* If true then the stream has been closed.
**/
private volatile boolean closed = false;
/**
* If true then the stream is being closed.
* It means that it still works completely for all messages already
* queued, but no new message may be enqueued.
**/
private volatile boolean closing = false;
/**
* Sequence number of the message we most recently sent out.
**/
private volatile int sequenceNumber = 0;
/**
* Sequence number of highest sequential ACK.
**/
private volatile int maxACK = 0;
/**
* Transport we are working for
**/
private TlsTransport tp = null;
/**
* connection we are working for
**/
private TlsConn conn = null;
private Retransmitter retransmitter = null;
// for retransmission
/**
* Average round trip time in milliseconds.
**/
private volatile long aveRTT = initRTT;
private volatile long remRTT = 0;
/**
* Number of ACK message received.
**/
private final AtomicInteger nACKS = new AtomicInteger(0);
/**
* Retry Time Out measured in milliseconds.
**/
private volatile long RTO = 0;
/**
* Minimum Retry Timeout measured in milliseconds.
**/
private volatile long minRTO = initRTT;
/**
* Maximum Retry Timeout measured in milliseconds.
**/
private volatile long maxRTO = initRTT * 5;
/**
* absolute time in milliseconds of last sequential ACK.
**/
private volatile long lastACKTime = 0;
/**
* absolute time in milliseconds of last SACK based retransmit.
**/
private volatile long sackRetransTime = 0;
/**
* The collection of messages available for re-transmission.
*/
final List<RetrQElt> retrQ = new Vector<RetrQElt>(25, 5);
// running average of receipients Input Queue
private int nIQTests = 0;
private int aveIQSize = 0;
/**
* Our estimation of the current free space in the remote input queue.
**/
private volatile int mrrIQFreeSpace = 0;
/**
* Our estimation of the maximum sise of the remote input queue.
**/
private int rmaxQSize = 0;
/**
* Number of acknowledged sends (round trips) before the connection is regarded as 'stable'
* Once stabilisation is established, downward tracking of RTO is suspended
* Set to zero to defeat this behaviour.
*/
private volatile int stabalizationAckCount = 0;
/**
* retrans queue element
**/
private static class RetrQElt {
int seqnum; // sequence number of this message.
long enqueuedAt; // absolute time of original enqueing.
volatile Message msg; // the message
int marked; // has been marked as retransmission
long sentAt; // when this msg was last transmitted
public RetrQElt(int seqnum, Message msg) {
this.seqnum = seqnum;
this.msg = msg;
this.enqueuedAt = TimeUtils.timeNow();
this.sentAt = this.enqueuedAt;
this.marked = 0;
}
}
JTlsOutputStream(TlsTransport tp, TlsConn conn) {
String maxrto = System.getProperty( "net.jxta.tlsout.maxrto" );
if( null != maxrto ){
this.maxRTO = Integer.parseInt( maxrto );
}
String minrto = System.getProperty( "net.jxta.tlsout.minrto" );
if( null != minrto ){
this.minRTO = Integer.parseInt( minrto );
}
String ackStabilizaton = System.getProperty( "net.jxta.tlsout.stablizeacks" );
if( null != ackStabilizaton ){
this.stabalizationAckCount = Integer.parseInt( ackStabilizaton );
}
this.conn = conn; // TlsConnection.
this.tp = tp; // our transport
this.RTO = minRTO; // initial RTO
// input free queue size
this.rmaxQSize = 20;
this.mrrIQFreeSpace = rmaxQSize;
// Init last ACK Time to now
this.lastACKTime = TimeUtils.timeNow();
this.sackRetransTime = TimeUtils.timeNow();
// Start retransmission thread
this.retransmitter = new Retransmitter();
}
/**
* {@inheritDoc}
*
* <p/>We don't current support linger.
**/
@Override
public void close() throws IOException {
synchronized (this) {
super.close();
closed = true;
}
synchronized (retrQ) {
retrQ.notifyAll();
retrQ.clear();
}
}
/**
* indicate that we're in the process of closing. To respect the semantics
* of close()/isClosed(), we do not set the closed flag, yet. Instead, we
* set the flag "closing", which simply garantees that no new message
* will be queued.
* This, in combination with getSequenceNumber and getMaxAck, and
* waitQevent, enables fine grain control of the tear down process.
**/
public void setClosing() {
synchronized (retrQ) {
closing = true;
retrQ.clear();
retrQ.notifyAll();
}
}
/**
* {@inheritDoc}
**/
@Override
public void write(int c) throws IOException {
byte[] a = new byte[1];
a[0] = (byte) (c & 0xFF);
write(a, 0, 1);
}
/**
* {@inheritDoc}
*
* <p/>We override the write(byte[], offset, length);
* method which is called by SSLRecord.send(SSLConn conn)
* via tos.writeTo(conn.sock_out), tos a ByteArrayOutputStream
* which has buffered the TLS output record in the byte array.
* The actual call is write(byte[] b, 0, length);
*
* <p/>We put this TLS record into a msssage element for the output
* pipe to send along.
*
* <p/>This is reasonable since in fact, if more than 16K bytes of
* application data are sent, then the max TLS Record is a little
* larger than 16K bytes including the TLS overhead.
*
* <p/>Therefore, an app. message is N+r TLS Records,
* Message length = Nx16K + r, N >= 0, r >= 0,
* N > 0 || r > 0 true.
**/
@Override
public void write(byte[] b, int off, int len) throws IOException {
// flag to allow connection closure in finally block
// Connection can not be closed when holding a lock on this
boolean closeStale = false;
// allocate new message
Message jmsg = new Message();
try {
if (closed) {
throw new IOException("stream is closed");
}
if (closing) {
throw new IOException("stream is being closed");
}
if (b == null) {
throw new IllegalArgumentException("buffer is null");
}
if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
}
if (len == 0) {
return;
}
// Copy the data since it will be queued, and caller may
// overwrite the same byte[] buffer.
byte[] data = new byte[len];
System.arraycopy(b, off, data, 0, len);
// sync so that writes don't get out of order.
synchronized (retrQ) {
// add TLS record as element
MessageElement ciphertext = new ByteArrayMessageElement(Integer.toString(++sequenceNumber), JTlsDefs.BLOCKS, data
,
null);
jmsg.addMessageElement(JTlsDefs.TLSNameSpace, ciphertext);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("TLS CT WRITE : seqn#" + sequenceNumber + " length=" + len);
}
// (1) See if the most recent remote input queue size is close to
// it's maximum input queue size
// Send only if at least 20% or more of the queue is free.
// (2) Also, if our retransQ is larger than the remotes inputQ,
// wait until we've received an ack.
// We assume some msgs are in transit or the remote system buffers
// We do not want to overrun the receiver.
// (3) We need to release from the loop because of possible deadlocks
// EG: retrQ.size() == 0 and mrrIQFreeSpace forces looping
// forever because the most recent SACK cleared it, and the receiver
// is waiting for more data.
// max of ??? wait
int maxwait = (int)Math.min(RTO, maxRTO);
// iterations to wait (max 3, min 1)
int waitCt = Math.max(maxwait / 250, 1);
// check if the queue has gone dead.
if (retrQ.size() > 0) {
long inQueue = TimeUtils.toRelativeTimeMillis(TimeUtils.timeNow(), retrQ.get(0).enqueuedAt);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("write : Retry queue idle for " + inQueue);
}
if (inQueue > tp.RETRMAXAGE) {
if (inQueue > (2 * tp.RETRMAXAGE)) {
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("Closing stale connection " + conn);
}
// SPT - set flag for connection close in finally block
closeStale = true;
throw new IOException("Stale connection closure in progress");
} else if (retrQ.size() >= MAXRETRQSIZE) {
// if the the queue is "full" and we are long idle, delay new writes forever.
waitCt = Integer.MAX_VALUE;
}
}
}
int i = 0;
while (!closed && ((mrrIQFreeSpace < rmaxQSize / 5) || (retrQ.size() > rmaxQSize))) {
// see if max. wait has arrived.
if (i++ == waitCt) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("write() wait for ACK, maxwait timer expired while enqueuing seqn#" + sequenceNumber);
}
break;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("write() wait 60ms for ACK while enqueuing seqn#" + sequenceNumber + "\n\tremote IQ free space = "
+ mrrIQFreeSpace + "\n\tMIN free space to continue = " + (rmaxQSize / 5) + "" + "\n\tretQ.size()="
+ retrQ.size());
}
// Less than 20% free queue space is left. Wait.
try {
retrQ.wait(250);
} catch (InterruptedException ignored) {
Thread.interrupted();
}
}
// place copy on retransmission queue
RetrQElt r = new RetrQElt(sequenceNumber, jmsg.clone());
retrQ.add(r);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("Retrans Enqueue added seqn#" + sequenceNumber + " retQ.size()=" + retrQ.size());
}
}
// Here we will send the message to the transport
conn.sendToRemoteTls(jmsg);
// assume we have now taken a slot
mrrIQFreeSpace--;
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("TLS CT SENT : seqn#" + sequenceNumber + " length=" + len);
}
} finally {
if (closeStale) {
// The retry queue has really gone stale.
try {
setClosing();
// in this we close ourself
conn.close(HandshakeState.CONNECTIONDEAD);
} catch (IOException ignored) {
;
}
}
}
}
private void calcRTT(long enqueuedAt) {
long dt = TimeUtils.toRelativeTimeMillis(TimeUtils.timeNow(), enqueuedAt);
if (dt == 0) {
dt += 1;
}
// Use the same single exponential smoothing as ReliableOutputStream
if( nACKS.incrementAndGet() > 2 ){
long tmp = (6 * aveRTT) + ((6 * remRTT) / 9) + (3 * dt);
aveRTT = tmp / 9;
remRTT = tmp - aveRTT * 9;
}
long newRTO = aveRTT * 2;
// Unless stabalizationAckCount is zero, after a period of stream stabilisation, do not reduce the RTO value further.
// This avoids the situation where a few small message sends reduce the RTO so much that when a large
// message is sent it immediately requires repetitive retransmission until the value of RTO climbs again.
// This is most apparent when using 'slow' relayed streams and large MTUs.
if( 0 != this.stabalizationAckCount && nACKS.get() > this.stabalizationAckCount ){
RTO = Math.max( RTO, newRTO );
} else {
// Enforce a min/max
RTO = Math.max(newRTO, minRTO);
RTO = Math.min(RTO, maxRTO);
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("TLS!! RTT = " + dt + "ms aveRTT = " + aveRTT + "ms" + " RTO = " + RTO + "ms" + " maxRTO = " + maxRTO + "ms");
}
}
private int calcAVEIQ(int iq) {
int n = nIQTests;
nIQTests += 1;
aveIQSize = ((n * aveIQSize) + iq) / nIQTests;
return aveIQSize;
}
/**
* Process an ACK Message. We remove ACKed messages from the retry queue.
* We only acknowledge messages received in sequence.
*
* The seqnum is for the largest unacknowledged seqnum
* the receipient has received.
*
* The sackList is a sequence of all of the received
* messages in the sender's input Q. All will be sequence numbers higher
* than the sequential ACK seqnum.
*
* Recepients are passive and only ack upon the receipt
* of an in sequence message.
*
* They depend on our RTO to fill holes in message
* sequences.
**/
void ackReceived(int seqnum, int[] sackList) {
lastACKTime = TimeUtils.timeNow();
int numberACKed = 0;
// remove acknowledged messages from retrans Q.
synchronized (retrQ) {
maxACK = Math.max(maxACK, seqnum);
// dump the current Retry queue and the SACK list
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
StringBuilder dumpRETRQ = new StringBuilder("ACK RECEIVE : " + Integer.toString(seqnum));
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
dumpRETRQ.append('\n');
}
dumpRETRQ.append("\tRETRQ (size=").append(retrQ.size()).append(")");
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
dumpRETRQ.append(" : ");
for (int y = 0; y < retrQ.size(); y++) {
if (0 != y) {
dumpRETRQ.append(", ");
}
RetrQElt r = retrQ.get(y);
dumpRETRQ.append(r.seqnum);
}
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
dumpRETRQ.append('\n');
}
dumpRETRQ.append("\tSACKLIST (size=").append(sackList.length).append(")");
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
dumpRETRQ.append(" : ");
for (int y = 0; y < sackList.length; y++) {
if (0 != y) {
dumpRETRQ.append(", ");
}
dumpRETRQ.append(sackList[y]);
}
}
LOG.fine(dumpRETRQ.toString());
}
Iterator eachRetryQueueEntry = retrQ.iterator();
// First remove monotonically increasing seq#s in retrans vector
while (eachRetryQueueEntry.hasNext()) {
RetrQElt r = (RetrQElt) eachRetryQueueEntry.next();
if (r.seqnum > seqnum) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("r.seqnum :" + r.seqnum + " > seqnum :" + seqnum);
}
break;
}
// Acknowledged
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("seqnum :" + seqnum);
LOG.fine("Removing :" + r.seqnum + " from retransmit queue");
}
eachRetryQueueEntry.remove();
// Update RTT, RTO
if (0 != r.enqueuedAt) {
calcRTT(r.enqueuedAt);
}
r.msg.clear();
r.msg = null;
r = null;
numberACKed++;
}
// Update last accessed time in response to getting seq acks.
if (numberACKed > 0) {
conn.lastAccessed = TimeUtils.timeNow();
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("TLS!! SEQUENTIALLY ACKD SEQN = " + seqnum + ", (" + numberACKed + " acked)");
}
// most recent remote IQ free space
rmaxQSize = Math.max(rmaxQSize, sackList.length);
mrrIQFreeSpace = rmaxQSize - sackList.length;
// let's look at average sacs.size(). If it is big, then this
// probably means we must back off because the system is slow.
// Our retrans Queue can be large and we can overwhelm the
// receiver with retransmissions.
// We will keep the rwin <= ave real input queue size.
int aveIQ = calcAVEIQ(sackList.length);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("remote IQ free space = " + mrrIQFreeSpace + " remote avg IQ occupancy = " + aveIQ);
}
int retrans = 0;
if (sackList.length > 0) {
Iterator eachRetrQElement = retrQ.iterator();
int currentSACK = 0;
while (eachRetrQElement.hasNext()) {
RetrQElt r = (RetrQElt) eachRetrQElement.next();
while (sackList[currentSACK] < r.seqnum) {
currentSACK++;
if (currentSACK == sackList.length) {
break;
}
}
if (currentSACK == sackList.length) {
break;
}
if (sackList[currentSACK] == r.seqnum) {
eachRetrQElement.remove();
// ack counter
numberACKed++;
// for aveRTT calculation
long enqueuetime = r.enqueuedAt;
// Update RTT, RTO
if (enqueuetime != 0) {
calcRTT(enqueuetime);
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("TLS!! SACKD SEQN = " + r.seqnum);
}
// GC this stuff
r.msg.clear();
r.msg = null;
r = null;
} else {
// Retransmit? Only if there is a hole in the selected
// acknowledgement list. Otherwise let RTO deal.
// Given that this SACK acknowledged messages still
// in the retrQ:
// seqnum is the max consectively SACKD message.
// seqnum < r.seqnum means a message has not reached
// receiver. EG: sacklist == 10,11,13 seqnum == 11
// We retransmit 12.
if (seqnum < r.seqnum) {
retrans++;
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("RETR: Fill hole, SACK, seqn#" + r.seqnum + ", Window =" + retrans);
}
}
}
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("TLS!! SELECTIVE ACKD (" + numberACKed + ") " + retrans + " retrans wanted");
}
// retransmit 1 retq mem. only
if (retrans > 0) {
retransmit(Math.min(RWINDOW, retrans), lastACKTime);
sackRetransTime = TimeUtils.timeNow();
}
}
retrQ.notify();
}
}
/**
* retransmit unacknowledged messages
*
* @param rwin max number of messages to retransmit
* @return number of messages retransmitted.
**/
private int retransmit(int rwin, long triggerTime) {
List retransMsgs = new ArrayList();
int numberToRetrans;
// build a list of retries.
synchronized (retrQ) {
numberToRetrans = Math.min(retrQ.size(), rwin);
if (numberToRetrans > 0 && Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("RETRANSMITING [rwindow = " + numberToRetrans + "]");
}
for (int j = 0; j < numberToRetrans; j++) {
RetrQElt r = retrQ.get(j);
// Mark message as retransmission
// need to know if a msg was retr or not for RTT eval
if (r.marked == 0) {
// First time: we're here because this message has not arrived, but
// the next one has. It may be an out of order message.
// Experience shows that such a message rarely arrives older than
// 1.2 * aveRTT. Beyond that, it's lost. It is also rare that we
// detect a hole within that delay. So, often enough, as soon as
// a hole is detected, it's time to resend...but not always.
if (TimeUtils.toRelativeTimeMillis(triggerTime, r.sentAt) < (6 * aveRTT) / 5) {
// Nothing to worry about, yet.
continue;
}
} else {
// That one has been retransmitted at least once already.
// So, we don't have much of a clue other than the age of the
// last transmission. It is unlikely that it arrives before aveRTT/2
// but we have to anticipate its loss at the risk of making dupes.
// Otherwise the receiver will reach the hole, and that's really
// expensive. (Think that we've been trying for a while already.)
if (TimeUtils.toRelativeTimeMillis(triggerTime, r.sentAt) < aveRTT) {
// Nothing to worry about, yet.
continue;
}
}
r.marked++;
// Make a copy to for sending
retransMsgs.add(r);
}
}
// send the retries.
int retransmitted = 0;
Iterator eachRetrans = retransMsgs.iterator();
while (eachRetrans.hasNext()) {
RetrQElt r = (RetrQElt) eachRetrans.next();
eachRetrans.remove();
try {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("TLS!! RETRANSMIT seqn#" + r.seqnum);
}
Message sending = r.msg;
// its possible that the message was acked while we were working
// in this case r.msg will have been nulled.
if (null != sending) {
sending = sending.clone();
sending.replaceMessageElement(JTlsDefs.TLSNameSpace, RETELT);
if (conn.sendToRemoteTls(sending)) {
mrrIQFreeSpace--; // assume we have now taken a slot
retransmitted++;
} else {
break;
} // don't bother continuing.
}
} catch (IOException e) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "FAILED RETRANS seqn#" + r.seqnum, e);
}
break; // don't bother continuing.
}
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("RETRANSMITED " + retransmitted + " of " + numberToRetrans);
}
return retransmitted;
}
/**
* Retransmission daemon thread
**/
private class Retransmitter implements Runnable {
Thread retransmitterThread;
volatile int nretransmitted = 0;
int nAtThisRTO = 0;
public Retransmitter() {
this.retransmitterThread = new Thread(tp.myThreadGroup, this, "JXTA TLS Retransmiter for " + conn.destAddr);
retransmitterThread.setDaemon(true);
retransmitterThread.start();
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("STARTED TLS Retransmit thread, RTO = " + RTO);
}
}
public int getRetransCount() {
return nretransmitted;
}
/**
* {@inheritDoc]
**/
public void run() {
try {
int idleCounter = 0;
while (!closed) {
long conn_idle = TimeUtils.toRelativeTimeMillis(TimeUtils.timeNow(), conn.lastAccessed);
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("RETRANS : " + conn + " idle for " + conn_idle);
}
// check to see if we have not idled out.
if (tp.CONNECTION_IDLE_TIMEOUT < conn_idle) {
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("RETRANS : Shutting down idle connection: " + conn);
}
try {
setClosing();
// the following call eventually closes this stream
conn.close(HandshakeState.CONNECTIONDEAD);
// Leave. Otherwise we'll be spinning forever
return;
} catch (IOException ignored) {
;
}
continue;
}
synchronized (retrQ) {
try {
retrQ.wait(RTO);
} catch (InterruptedException e) {
Thread.interrupted();
}
}
if (closed) {
break;
}
// see if we recently did a retransmit triggered by a SACK
long sinceLastSACKRetr = TimeUtils.toRelativeTimeMillis(TimeUtils.timeNow(), sackRetransTime);
if (sinceLastSACKRetr < RTO) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("RETRANS : SACK retrans " + sinceLastSACKRetr + "ms ago");
}
continue;
}
// See how long we've waited since RTO was set
long sinceLastACK = TimeUtils.toRelativeTimeMillis(TimeUtils.timeNow(), lastACKTime);
long oldestInQueueWait;
synchronized (retrQ) {
if (retrQ.size() > 0) {
oldestInQueueWait = TimeUtils.toRelativeTimeMillis(TimeUtils.timeNow(), retrQ.get(0).enqueuedAt);
} else {
oldestInQueueWait = 0;
}
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("RETRANS : Last ACK " + sinceLastACK + "ms ago. Age of oldest in Queue " + oldestInQueueWait + "ms");
}
// see if the queue has gone dead
if (oldestInQueueWait > (tp.RETRMAXAGE * 2)) {
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("RETRANS : Shutting down stale connection: " + conn);
}
try {
setClosing();
conn.close(HandshakeState.CONNECTIONDEAD);
// Leave. Otherwise we'll be spinning forever.
return;
} catch (IOException ignored) {
;
}
continue;
}
// get real wait as max of age of oldest in retrQ and
// lastAck time
long realWait = Math.max(oldestInQueueWait, sinceLastACK);
// Retransmit only if RTO has expired.
// a. real wait time is longer than RTO
// b. oldest message on Q has been there longer
// than RTO. This is necessary because we may
// have just sent a message, and we do not
// want to overrun the receiver. Also, we
// do not want to restransmit a message that
// has not been idle for the RTO.
if ((realWait >= RTO) && (oldestInQueueWait >= RTO)) {
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("RETRANS : RTO RETRANSMISSION [" + RWINDOW + "]");
}
// retrasmit
int retransed = retransmit(RWINDOW, TimeUtils.timeNow());
// Total
nretransmitted += retransed;
// number at this RTO
nAtThisRTO += retransed;
// See if real wait is too long and queue is non-empty
// Remote may be dead - double until max.
// Double after window restransmitted msgs at this RTO
// exceeds the RWINDOW, and we've had no response for
// twice the current RTO.
if ((retransed > 0) && (realWait >= 2 * RTO) && (nAtThisRTO >= 2 * RWINDOW)) {
RTO = (realWait > maxRTO ? maxRTO : 2 * RTO);
nAtThisRTO = 0;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("RETRANS : RETRANSMISSION " + retransed + " retrans " + nAtThisRTO + " at this RTO (" + RTO
+ ") " + nretransmitted + " total retrans");
}
} else {
idleCounter += 1;
// reset RTO to min if we are idle
if (idleCounter == 2) {
RTO = minRTO;
idleCounter = 0;
nAtThisRTO = 0;
}
if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
LOG.fine("RETRANS : IDLE : RTO=" + RTO + " WAIT=" + realWait);
}
}
}
} catch (Throwable all) {
if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
LOG.log(Level.SEVERE, "Uncaught Throwable in thread :" + Thread.currentThread().getName(), all);
}
} finally {
if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
LOG.info("STOPPED TLS Retransmit thread");
}
retransmitterThread = null;
retransmitter = null;
}
}
}
}