package net.i2p.router.tunnel;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.SimpleTimer;
import net.i2p.util.SystemVersion;
/**
* Run through the tunnel gateways that have had messages added to them and push
* those messages through the preprocessing and sending process.
*
* TODO do we need this many threads?
* TODO this combines IBGWs and OBGWs, do we wish to separate the two
* and/or prioritize OBGWs (i.e. our outbound traffic) over IBGWs (participating)?
*/
class TunnelGatewayPumper implements Runnable {
private final RouterContext _context;
private final Set<PumpedTunnelGateway> _wantsPumping;
private final Set<PumpedTunnelGateway> _backlogged;
private volatile boolean _stop;
private static final int MIN_PUMPERS = 1;
private static final int MAX_PUMPERS = 4;
private final int _pumpers;
/**
* Wait just a little, but this lets the pumper queue back up.
* See additional comments in PTG.
*/
private static final long REQUEUE_TIME = 50;
/** Creates a new instance of TunnelGatewayPumper */
public TunnelGatewayPumper(RouterContext ctx) {
_context = ctx;
_wantsPumping = new LinkedHashSet<PumpedTunnelGateway>(16);
_backlogged = new HashSet<PumpedTunnelGateway>(16);
if (ctx.getBooleanProperty("i2p.dummyTunnelManager")) {
_pumpers = 1;
} else {
long maxMemory = SystemVersion.getMaxMemory();
_pumpers = (int) Math.max(MIN_PUMPERS, Math.min(MAX_PUMPERS, 1 + (maxMemory / (32*1024*1024))));
}
for (int i = 0; i < _pumpers; i++)
new I2PThread(this, "Tunnel GW pumper " + (i+1) + '/' + _pumpers, true).start();
}
public void stopPumping() {
_stop=true;
_wantsPumping.clear();
for (int i = 0; i < _pumpers; i++) {
PumpedTunnelGateway poison = new PoisonPTG(_context);
wantsPumping(poison);
}
for (int i = 1; i <= 5 && !_wantsPumping.isEmpty(); i++) {
try {
Thread.sleep(i * 50);
} catch (InterruptedException ie) {}
}
_wantsPumping.clear();
}
public void wantsPumping(PumpedTunnelGateway gw) {
if (!_stop) {
synchronized (_wantsPumping) {
if ((!_backlogged.contains(gw)) && _wantsPumping.add(gw))
_wantsPumping.notify();
}
}
}
public void run() {
PumpedTunnelGateway gw = null;
List<PendingGatewayMessage> queueBuf = new ArrayList<PendingGatewayMessage>(32);
boolean requeue = false;
while (!_stop) {
try {
synchronized (_wantsPumping) {
if (requeue && gw != null) {
// in case another packet came in
_wantsPumping.remove(gw);
if (_backlogged.add(gw))
_context.simpleTimer2().addEvent(new Requeue(gw), REQUEUE_TIME);
}
gw = null;
if (_wantsPumping.isEmpty()) {
_wantsPumping.wait();
} else {
Iterator<PumpedTunnelGateway> iter = _wantsPumping.iterator();
gw = iter.next();
iter.remove();
}
}
} catch (InterruptedException ie) {}
if (gw != null) {
if (gw.getMessagesSent() == POISON_PTG)
break;
requeue = gw.pump(queueBuf);
}
}
}
private class Requeue implements SimpleTimer.TimedEvent {
private final PumpedTunnelGateway _ptg;
public Requeue(PumpedTunnelGateway ptg) {
_ptg = ptg;
}
public void timeReached() {
synchronized (_wantsPumping) {
_backlogged.remove(_ptg);
if (_wantsPumping.add(_ptg))
_wantsPumping.notify();
}
}
}
private static final int POISON_PTG = -99999;
private static class PoisonPTG extends PumpedTunnelGateway {
public PoisonPTG(RouterContext ctx) {
super(ctx, null, null, null, null);
}
@Override
public int getMessagesSent() { return POISON_PTG; }
}
}