use of net.i2p.router.OutNetMessage in project i2p.i2p by i2p.
the class NTCPConnection method locked_close.
/**
* @return a second connection with the same peer...
*/
private synchronized NTCPConnection locked_close(boolean allowRequeue) {
if (_chan != null)
try {
_chan.close();
} catch (IOException ioe) {
}
if (_conKey != null)
_conKey.cancel();
_establishState = EstablishState.FAILED;
NTCPConnection old = _transport.removeCon(this);
_transport.getReader().connectionClosed(this);
_transport.getWriter().connectionClosed(this);
for (FIFOBandwidthLimiter.Request req : _bwInRequests) {
req.abort();
// we would like to return read ByteBuffers via EventPumper.releaseBuf(),
// but we can't risk releasing it twice
}
_bwInRequests.clear();
for (FIFOBandwidthLimiter.Request req : _bwOutRequests) {
req.abort();
}
_bwOutRequests.clear();
_writeBufs.clear();
ByteBuffer bb;
while ((bb = _readBufs.poll()) != null) {
EventPumper.releaseBuf(bb);
}
List<OutNetMessage> pending = new ArrayList<OutNetMessage>();
// _outbound.drainAllTo(pending);
_outbound.drainTo(pending);
for (OutNetMessage msg : pending) _transport.afterSend(msg, false, allowRequeue, msg.getLifetime());
OutNetMessage msg = getCurrentOutbound();
if (msg != null)
_transport.afterSend(msg, false, allowRequeue, msg.getLifetime());
return old;
}
use of net.i2p.router.OutNetMessage in project i2p.i2p by i2p.
the class BuildHandler method handleReq.
/**
* If we are dropping lots of requests before even trying to handle them,
* I suppose you could call us "overloaded"
*/
/**
** unused, see handleReq() below
* private final static int MAX_PROACTIVE_DROPS = 240;
*
* private int countProactiveDrops() {
* int dropped = 0;
* dropped += countEvents("tunnel.dropLoadProactive", 60*1000);
* dropped += countEvents("tunnel.dropLoad", 60*1000);
* dropped += countEvents("tunnel.dropLoadBacklog", 60*1000);
* dropped += countEvents("tunnel.dropLoadDelay", 60*1000);
* return dropped;
* }
*
* private int countEvents(String stat, long period) {
* RateStat rs = _context.statManager().getRate(stat);
* if (rs != null) {
* Rate r = rs.getRate(period);
* if (r != null)
* return (int)r.getCurrentEventCount();
* }
* return 0;
* }
***
*/
/**
* Actually process the request and send the reply.
*
* Todo: Replies are not subject to RED for bandwidth reasons,
* and the bandwidth is not credited to any tunnel.
* If we did credit the reply to the tunnel, it would
* prevent the classification of the tunnel as 'inactive' on tunnels.jsp.
*/
private void handleReq(RouterInfo nextPeerInfo, BuildMessageState state, BuildRequestRecord req, Hash nextPeer) {
long ourId = req.readReceiveTunnelId();
long nextId = req.readNextTunnelId();
boolean isInGW = req.readIsInboundGateway();
boolean isOutEnd = req.readIsOutboundEndpoint();
Hash from = state.fromHash;
if (from == null && state.from != null)
from = state.from.calculateHash();
if (isInGW && isOutEnd) {
_context.statManager().addRateData("tunnel.rejectHostile", 1);
_log.error("Dropping build request, IBGW+OBEP: " + req);
if (from != null)
_context.commSystem().mayDisconnect(from);
return;
}
if (ourId <= 0 || ourId > TunnelId.MAX_ID_VALUE || nextId <= 0 || nextId > TunnelId.MAX_ID_VALUE) {
_context.statManager().addRateData("tunnel.rejectHostile", 1);
if (_log.shouldWarn())
_log.warn("Dropping build request, bad tunnel ID: " + req);
if (from != null)
_context.commSystem().mayDisconnect(from);
return;
}
// Loop checks
if ((!isOutEnd) && _context.routerHash().equals(nextPeer)) {
_context.statManager().addRateData("tunnel.rejectHostile", 1);
// old i2pd
if (_log.shouldWarn())
_log.warn("Dropping build request, we are the next hop: " + req);
if (from != null)
_context.commSystem().mayDisconnect(from);
return;
}
if (!isInGW) {
// but if not, something is seriously wrong here.
if (from == null || _context.routerHash().equals(from)) {
_context.statManager().addRateData("tunnel.rejectHostile", 1);
if (_log.shouldWarn())
_log.warn("Dropping build request, we are the previous hop: " + req);
return;
}
}
if ((!isOutEnd) && (!isInGW)) {
// A-B-C-A is not preventable
if (nextPeer.equals(from)) {
// i2pd does this
_context.statManager().addRateData("tunnel.rejectHostile", 1);
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping build request with the same previous and next hop: " + req);
_context.commSystem().mayDisconnect(from);
return;
}
}
// time is in hours, rounded down.
// tunnel-alt-creation.html specifies that this is enforced +/- 1 hour but it was not.
// As of 0.9.16, allow + 5 minutes to - 65 minutes.
long time = req.readRequestTime();
long now = (_context.clock().now() / (60l * 60l * 1000l)) * (60 * 60 * 1000);
long timeDiff = now - time;
if (timeDiff > MAX_REQUEST_AGE) {
_context.statManager().addRateData("tunnel.rejectTooOld", 1);
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping build request too old... replay attack? " + DataHelper.formatDuration(timeDiff) + ": " + req);
if (from != null)
_context.commSystem().mayDisconnect(from);
return;
}
if (timeDiff < 0 - MAX_REQUEST_FUTURE) {
_context.statManager().addRateData("tunnel.rejectFuture", 1);
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping build request too far in future " + DataHelper.formatDuration(0 - timeDiff) + ": " + req);
if (from != null)
_context.commSystem().mayDisconnect(from);
return;
}
int response;
if (_context.router().isHidden()) {
_context.throttle().setTunnelStatus(_x("Rejecting tunnels: Hidden mode"));
response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
} else {
response = _context.throttle().acceptTunnelRequest();
}
// This only checked OUR tunnels, so the log message was wrong.
// Now checked by TunnelDispatcher.joinXXX()
// and returned as success value, checked below.
// if (_context.tunnelManager().getTunnelInfo(new TunnelId(ourId)) != null) {
// if (_log.shouldLog(Log.ERROR))
// _log.error("Already participating in a tunnel with the given Id (" + ourId + "), so gotta reject");
// if (response == 0)
// response = TunnelHistory.TUNNEL_REJECT_PROBABALISTIC_REJECT;
// }
// if ( (response == 0) && (_context.random().nextInt(50) <= 1) )
// response = TunnelHistory.TUNNEL_REJECT_PROBABALISTIC_REJECT;
long recvDelay = _context.clock().now() - state.recvTime;
if (response == 0) {
// unused
// int proactiveDrops = countProactiveDrops();
float pDrop = ((float) recvDelay) / (float) (BuildRequestor.REQUEST_TIMEOUT * 3);
pDrop = (float) Math.pow(pDrop, 16);
if (_context.random().nextFloat() < pDrop) {
// || (proactiveDrops > MAX_PROACTIVE_DROPS) ) ) {
_context.statManager().addRateData("tunnel.rejectOverloaded", recvDelay);
_context.throttle().setTunnelStatus(_x("Rejecting tunnels: Request overload"));
// if (true || (proactiveDrops < MAX_PROACTIVE_DROPS*2))
response = TunnelHistory.TUNNEL_REJECT_TRANSIENT_OVERLOAD;
// else
// response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
} else {
_context.statManager().addRateData("tunnel.acceptLoad", recvDelay);
}
}
/*
* Being a IBGW or OBEP generally leads to more connections, so if we are
* approaching our connection limit (i.e. !haveCapacity()),
* reject this request.
*
* Don't do this for class N or O, under the assumption that they are already talking
* to most of the routers, so there's no reason to reject. This may drive them
* to their conn. limits, but it's hopefully a temporary solution to the
* tunnel build congestion. As the net grows this will have to be revisited.
*/
RouterInfo ri = _context.router().getRouterInfo();
if (response == 0) {
if (ri == null) {
// ?? We should always have a RI
response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
} else {
char bw = ri.getBandwidthTier().charAt(0);
if (bw != 'O' && bw != 'N' && bw != 'P' && bw != 'X' && ((isInGW && !_context.commSystem().haveInboundCapacity(87)) || (isOutEnd && !_context.commSystem().haveOutboundCapacity(87)))) {
_context.statManager().addRateData("tunnel.rejectConnLimits", 1);
_context.throttle().setTunnelStatus(_x("Rejecting tunnels: Connection limit"));
response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
}
}
}
// We may need another counter above for requests.
if (response == 0 && !isInGW) {
if (from != null && _throttler.shouldThrottle(from)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Rejecting tunnel (hop throttle), previous hop: " + from + ": " + req);
// no setTunnelStatus() indication
_context.statManager().addRateData("tunnel.rejectHopThrottle", 1);
response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
}
}
if (response == 0 && (!isOutEnd) && _throttler.shouldThrottle(nextPeer)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Rejecting tunnel (hop throttle), next hop: " + req);
_context.statManager().addRateData("tunnel.rejectHopThrottle", 1);
// no setTunnelStatus() indication
response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
}
HopConfig cfg = null;
if (response == 0) {
cfg = new HopConfig();
cfg.setCreation(_context.clock().now());
cfg.setExpiration(_context.clock().now() + 10 * 60 * 1000);
cfg.setIVKey(req.readIVKey());
cfg.setLayerKey(req.readLayerKey());
if (isInGW) {
// default
// cfg.setReceiveFrom(null);
} else {
if (from != null) {
cfg.setReceiveFrom(from);
} else {
// b0rk
return;
}
}
cfg.setReceiveTunnelId(DataHelper.toLong(4, ourId));
if (isOutEnd) {
// default
// cfg.setSendTo(null);
// cfg.setSendTunnelId(null);
} else {
cfg.setSendTo(nextPeer);
cfg.setSendTunnelId(DataHelper.toLong(4, nextId));
}
// now "actually" join
boolean success;
if (isOutEnd)
success = _context.tunnelDispatcher().joinOutboundEndpoint(cfg);
else if (isInGW)
success = _context.tunnelDispatcher().joinInboundGateway(cfg);
else
success = _context.tunnelDispatcher().joinParticipant(cfg);
if (success) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Joining: " + req);
} else {
// Dup Tunnel ID. This can definitely happen (birthday paradox).
// Probability in 11 minutes (per hop type):
// 0.1% for 2900 tunnels; 1% for 9300 tunnels
response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH;
_context.statManager().addRateData("tunnel.rejectDupID", 1);
if (_log.shouldLog(Log.WARN))
_log.warn("DUP ID failure: " + req);
}
}
if (response != 0) {
_context.statManager().addRateData("tunnel.reject." + response, 1);
_context.messageHistory().tunnelRejected(from, new TunnelId(ourId), nextPeer, // (isOutEnd ? "outbound endpoint" : isInGW ? "inbound gw" : "participant"));
Integer.toString(response));
if (from != null)
_context.commSystem().mayDisconnect(from);
// 81% = between 75% control measures in Transports and 87% rejection above
if ((!_context.routerHash().equals(nextPeer)) && (!_context.commSystem().haveOutboundCapacity(81)) && (!_context.commSystem().isEstablished(nextPeer))) {
_context.statManager().addRateData("tunnel.dropConnLimits", 1);
if (_log.shouldLog(Log.WARN))
_log.warn("Not sending rejection due to conn limits: " + req);
return;
}
} else if (isInGW && from != null) {
// we're the start of the tunnel, no use staying connected
_context.commSystem().mayDisconnect(from);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Responding to " + state.msg.getUniqueId() + " after " + recvDelay + " with " + response + " from " + (from != null ? from : "tunnel") + ": " + req);
EncryptedBuildRecord reply = BuildResponseRecord.create(_context, response, req.readReplyKey(), req.readReplyIV(), state.msg.getUniqueId());
int records = state.msg.getRecordCount();
int ourSlot = -1;
for (int j = 0; j < records; j++) {
if (state.msg.getRecord(j) == null) {
ourSlot = j;
state.msg.setRecord(j, reply);
// + ": " + Base64.encode(reply));
break;
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read slot " + ourSlot + " containing: " + req + " accepted? " + response + " recvDelay " + recvDelay + " replyMessage " + req.readReplyMessageId());
// now actually send the response
long expires = _context.clock().now() + NEXT_HOP_SEND_TIMEOUT;
if (!isOutEnd) {
state.msg.setUniqueId(req.readReplyMessageId());
state.msg.setMessageExpiration(expires);
OutNetMessage msg = new OutNetMessage(_context, state.msg, expires, PRIORITY, nextPeerInfo);
if (response == 0)
msg.setOnFailedSendJob(new TunnelBuildNextHopFailJob(_context, cfg));
_context.outNetMessagePool().add(msg);
} else {
// We are the OBEP.
// send it to the reply tunnel on the reply peer within a new TunnelBuildReplyMessage
// (enough layers jrandom?)
TunnelBuildReplyMessage replyMsg;
if (records == TunnelBuildMessage.MAX_RECORD_COUNT)
replyMsg = new TunnelBuildReplyMessage(_context);
else
replyMsg = new VariableTunnelBuildReplyMessage(_context, records);
for (int i = 0; i < records; i++) replyMsg.setRecord(i, state.msg.getRecord(i));
replyMsg.setUniqueId(req.readReplyMessageId());
replyMsg.setMessageExpiration(expires);
TunnelGatewayMessage m = new TunnelGatewayMessage(_context);
m.setMessage(replyMsg);
m.setMessageExpiration(expires);
m.setTunnelId(new TunnelId(nextId));
if (_context.routerHash().equals(nextPeer)) {
// ok, we are the gateway, so inject it
if (_log.shouldLog(Log.DEBUG))
_log.debug("We are the reply gateway for " + nextId + " when replying to replyMessage " + req);
_context.tunnelDispatcher().dispatch(m);
} else {
// ok, the gateway is some other peer, shove 'er across
OutNetMessage outMsg = new OutNetMessage(_context, m, expires, PRIORITY, nextPeerInfo);
if (response == 0)
outMsg.setOnFailedSendJob(new TunnelBuildNextHopFailJob(_context, cfg));
_context.outNetMessagePool().add(outMsg);
}
}
}
use of net.i2p.router.OutNetMessage in project i2p.i2p by i2p.
the class BuildRequestor method request.
/**
* Send out a build request message.
*
* @param cfg ReplyMessageId must be set
* @return success
*/
public static boolean request(RouterContext ctx, TunnelPool pool, PooledTunnelCreatorConfig cfg, BuildExecutor exec) {
// new style crypto fills in all the blanks, while the old style waits for replies to fill in the next hop, etc
prepare(ctx, cfg);
if (cfg.getLength() <= 1) {
buildZeroHop(ctx, pool, cfg, exec);
return true;
}
Log log = ctx.logManager().getLog(BuildRequestor.class);
cfg.setTunnelPool(pool);
TunnelInfo pairedTunnel = null;
Hash farEnd = cfg.getFarEnd();
TunnelManagerFacade mgr = ctx.tunnelManager();
boolean isInbound = pool.getSettings().isInbound();
if (pool.getSettings().isExploratory() || !usePairedTunnels(ctx)) {
if (isInbound)
pairedTunnel = mgr.selectOutboundExploratoryTunnel(farEnd);
else
pairedTunnel = mgr.selectInboundExploratoryTunnel(farEnd);
} else {
// building a client tunnel
if (isInbound)
pairedTunnel = mgr.selectOutboundTunnel(pool.getSettings().getDestination(), farEnd);
else
pairedTunnel = mgr.selectInboundTunnel(pool.getSettings().getDestination(), farEnd);
if (pairedTunnel == null) {
if (isInbound) {
// random more reliable than closest ??
// pairedTunnel = mgr.selectOutboundExploratoryTunnel(farEnd);
pairedTunnel = mgr.selectOutboundTunnel();
if (pairedTunnel != null && pairedTunnel.getLength() <= 1 && mgr.getOutboundSettings().getLength() > 0 && mgr.getOutboundSettings().getLength() + mgr.getOutboundSettings().getLengthVariance() > 0) {
// don't build using a zero-hop expl.,
// as it is both very bad for anonomyity,
// and it takes a build slot away from exploratory
pairedTunnel = null;
}
} else {
// random more reliable than closest ??
// pairedTunnel = mgr.selectInboundExploratoryTunnel(farEnd);
pairedTunnel = mgr.selectInboundTunnel();
if (pairedTunnel != null && pairedTunnel.getLength() <= 1 && mgr.getInboundSettings().getLength() > 0 && mgr.getInboundSettings().getLength() + mgr.getInboundSettings().getLengthVariance() > 0) {
// ditto
pairedTunnel = null;
}
}
if (pairedTunnel != null && log.shouldLog(Log.INFO))
log.info("Couldn't find a paired tunnel for " + cfg + ", using exploratory tunnel");
}
}
if (pairedTunnel == null) {
if (log.shouldLog(Log.WARN))
log.warn("Tunnel build failed, as we couldn't find a paired tunnel for " + cfg);
exec.buildComplete(cfg, pool);
// Not even an exploratory tunnel? We are in big trouble.
// Let's not spin through here too fast.
// But don't let a client tunnel waiting for exploratories slow things down too much,
// as there may be other tunnel pools who can build
int ms = pool.getSettings().isExploratory() ? 250 : 25;
try {
Thread.sleep(ms);
} catch (InterruptedException ie) {
}
return false;
}
// long beforeCreate = System.currentTimeMillis();
TunnelBuildMessage msg = createTunnelBuildMessage(ctx, pool, cfg, pairedTunnel, exec);
// long createTime = System.currentTimeMillis()-beforeCreate;
if (msg == null) {
if (log.shouldLog(Log.WARN))
log.warn("Tunnel build failed, as we couldn't create the tunnel build message for " + cfg);
exec.buildComplete(cfg, pool);
return false;
}
// long beforeDispatch = System.currentTimeMillis();
if (cfg.isInbound()) {
if (log.shouldLog(Log.INFO))
log.info("Sending the tunnel build request " + msg.getUniqueId() + " out the tunnel " + pairedTunnel + " to " + cfg.getPeer(0) + " for " + cfg + " waiting for the reply of " + cfg.getReplyMessageId());
// send it out a tunnel targetting the first hop
// TODO - would be nice to have a TunnelBuildFirstHopFailJob queued if the
// pairedTunnel is zero-hop, but no way to do that?
ctx.tunnelDispatcher().dispatchOutbound(msg, pairedTunnel.getSendTunnelId(0), cfg.getPeer(0));
} else {
if (log.shouldLog(Log.INFO))
log.info("Sending the tunnel build request directly to " + cfg.getPeer(1) + " for " + cfg + " waiting for the reply of " + cfg.getReplyMessageId() + " with msgId=" + msg.getUniqueId());
// send it directly to the first hop
// Add some fuzz to the TBM expiration to make it harder to guess how many hops
// or placement in the tunnel
msg.setMessageExpiration(ctx.clock().now() + BUILD_MSG_TIMEOUT + ctx.random().nextLong(20 * 1000));
// We set the OutNetMessage expiration much shorter, so that the
// TunnelBuildFirstHopFailJob fires before the 13s build expiration.
RouterInfo peer = ctx.netDb().lookupRouterInfoLocally(cfg.getPeer(1));
if (peer == null) {
if (log.shouldLog(Log.WARN))
log.warn("Could not find the next hop to send the outbound request to: " + cfg);
exec.buildComplete(cfg, pool);
return false;
}
OutNetMessage outMsg = new OutNetMessage(ctx, msg, ctx.clock().now() + FIRST_HOP_TIMEOUT, PRIORITY, peer);
outMsg.setOnFailedSendJob(new TunnelBuildFirstHopFailJob(ctx, pool, cfg, exec));
ctx.outNetMessagePool().add(outMsg);
}
// + "ms and dispatched in " + (System.currentTimeMillis()-beforeDispatch));
return true;
}
use of net.i2p.router.OutNetMessage in project i2p.i2p by i2p.
the class FloodfillNetworkDatabaseFacade method flood.
/**
* Send to a subset of all floodfill peers.
* We do this to implement Kademlia within the floodfills, i.e.
* we flood to those closest to the key.
*/
public void flood(DatabaseEntry ds) {
Hash key = ds.getHash();
RouterKeyGenerator gen = _context.routerKeyGenerator();
Hash rkey = gen.getRoutingKey(key);
FloodfillPeerSelector sel = (FloodfillPeerSelector) getPeerSelector();
List<Hash> peers = sel.selectFloodfillParticipants(rkey, MAX_TO_FLOOD, getKBuckets());
// todo key cert skip?
long until = gen.getTimeTillMidnight();
if (until < NEXT_RKEY_LS_ADVANCE_TIME || (ds.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO && until < NEXT_RKEY_RI_ADVANCE_TIME)) {
// to avoid lookup faulures after midnight, also flood to some closest to the
// next routing key for a period of time before midnight.
Hash nkey = gen.getNextRoutingKey(key);
List<Hash> nextPeers = sel.selectFloodfillParticipants(nkey, NEXT_FLOOD_QTY, getKBuckets());
int i = 0;
for (Hash h : nextPeers) {
// But other implementations may not...
if (h.equals(key))
continue;
// todo key cert skip?
if (!peers.contains(h)) {
peers.add(h);
i++;
}
}
if (i > 0 && _log.shouldLog(Log.INFO))
_log.info("Flooding the entry for " + key + " to " + i + " more, just before midnight");
}
int flooded = 0;
for (int i = 0; i < peers.size(); i++) {
Hash peer = peers.get(i);
RouterInfo target = lookupRouterInfoLocally(peer);
if ((target == null) || (_context.banlist().isBanlisted(peer)))
continue;
// But other implementations may not...
if (ds.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO && peer.equals(key))
continue;
if (peer.equals(_context.routerHash()))
continue;
DatabaseStoreMessage msg = new DatabaseStoreMessage(_context);
msg.setEntry(ds);
OutNetMessage m = new OutNetMessage(_context, msg, _context.clock().now() + FLOOD_TIMEOUT, FLOOD_PRIORITY, target);
Job floodFail = new FloodFailedJob(_context, peer);
m.setOnFailedSendJob(floodFail);
// we want to give credit on success, even if we aren't sure,
// because otherwise no use noting failure
Job floodGood = new FloodSuccessJob(_context, peer);
m.setOnSendJob(floodGood);
_context.commSystem().processMessage(m);
flooded++;
if (_log.shouldLog(Log.INFO))
_log.info("Flooding the entry for " + key.toBase64() + " to " + peer.toBase64());
}
if (_log.shouldLog(Log.INFO))
_log.info("Flooded the data to " + flooded + " of " + peers.size() + " peers");
}
use of net.i2p.router.OutNetMessage in project i2p.i2p by i2p.
the class OutboundMessageRegistry method registerPending.
/**
* @param allowEmpty is msg.getMessage() allowed to be null?
*/
@SuppressWarnings("unchecked")
private void registerPending(OutNetMessage msg, boolean allowEmpty) {
if ((!allowEmpty) && (msg.getMessage() == null))
throw new IllegalArgumentException("OutNetMessage doesn't contain an I2NPMessage? Impossible?");
MessageSelector sel = msg.getReplySelector();
if (sel == null)
throw new IllegalArgumentException("No reply selector? Impossible?");
if (!_activeMessages.add(msg))
// dont add dups
return;
synchronized (_selectorToMessage) {
Object oldMsg = _selectorToMessage.put(sel, msg);
if (oldMsg != null) {
List<OutNetMessage> multi = null;
if (oldMsg instanceof OutNetMessage) {
// multi = Collections.synchronizedList(new ArrayList(4));
multi = new ArrayList<OutNetMessage>(4);
multi.add((OutNetMessage) oldMsg);
multi.add(msg);
_selectorToMessage.put(sel, multi);
} else if (oldMsg instanceof List) {
multi = (List<OutNetMessage>) oldMsg;
multi.add(msg);
_selectorToMessage.put(sel, multi);
}
if (_log.shouldLog(Log.WARN))
_log.warn("a single message selector [" + sel + "] with multiple messages (" + multi + ")");
}
}
synchronized (_selectors) {
_selectors.add(sel);
}
_cleanupTask.scheduleExpiration(sel);
}
Aggregations