package com.robonobo.mina.instance; import java.io.IOException; import java.util.*; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import com.robonobo.common.concurrent.Attempt; import com.robonobo.common.concurrent.CatchingRunnable; import com.robonobo.core.api.proto.CoreApi.EndPoint; import com.robonobo.core.api.proto.CoreApi.Node; import com.robonobo.mina.message.proto.MinaProtocol.SourceStatus; import com.robonobo.mina.network.*; /** * @syncpriority 180 */ public class StreamConnsMgr { private final Log log; private final MinaInstance mina; /** Map<sid, Set<nid>> */ private final Map<String, Set<String>> pendingCons; private final Map<String, Set<LCPair>> lcPairs; private final Map<String, Set<BCPair>> bcPairs; StreamConnsMgr(MinaInstance mina) { this.mina = mina; log = mina.getLogger(getClass()); pendingCons = new HashMap<String, Set<String>>(); lcPairs = new HashMap<String, Set<LCPair>>(); bcPairs = new HashMap<String, Set<BCPair>>(); } public synchronized void abort() { List<ConnectionPair> cpList = new ArrayList<ConnectionPair>(); for (String sid : lcPairs.keySet()) { cpList.addAll(lcPairs.get(sid)); } for(String sid : bcPairs.keySet()) { cpList.addAll(bcPairs.get(sid)); } for (ConnectionPair cp : cpList) { cp.abort(); } bcPairs.clear(); lcPairs.clear(); pendingCons.clear(); } /** * @syncpriority 180 */ public void closeAllStreamConns() { log.debug("Closing down all stream conns"); Set<BCPair> bcpSet = new HashSet<BCPair>(); Set<LCPair> lcpSet = new HashSet<LCPair>(); synchronized (this) { for (String sid : bcPairs.keySet()) { for (BCPair bcp : bcPairs.get(sid)) { bcpSet.add(bcp); } } for (String sid : lcPairs.keySet()) { for (LCPair lcp : lcPairs.get(sid)) { lcpSet.add(lcp); } } } for (BCPair bcp : bcpSet) { bcp.die(); } for (LCPair lcp : lcpSet) { lcp.die(); } } /** * Close everyone we are broadcasting to * * @syncpriority 180 */ public void closeAllBroadcastConns(String sid) { for (BCPair bcp : getBroadcastConns(sid)) { bcp.die(); } synchronized (this) { if(bcPairs.containsKey(sid)) bcPairs.get(sid).clear(); } } /** * Close everyone we are listening to * * @syncpriority 180 */ public void closeAllListenConns(String sid) { for (LCPair lcp : getListenConns(sid)) { lcp.die(); } synchronized (this) { if(lcPairs.containsKey(sid)) lcPairs.get(sid).clear(); } } /** * Copied out, so safe to iterate over * * @syncpriority 180 */ public synchronized LCPair[] getListenConns(String sid) { Set<LCPair> lcpSet = lcPairs.get(sid); if(lcpSet == null) return new LCPair[0]; LCPair[] lcpArr = new LCPair[lcpSet.size()]; lcpSet.toArray(lcpArr); return lcpArr; } /** * Copied out, so safe to iterate over * * @syncpriority 180 */ public synchronized BCPair[] getBroadcastConns(String sid) { Set<BCPair> bcpSet = bcPairs.get(sid); if(bcpSet == null) return new BCPair[0]; BCPair[] bcpArr = new BCPair[bcpSet.size()]; bcpSet.toArray(bcpArr); return bcpArr; } /** * The number of people we are broadcasting to * * @syncpriority 180 */ public synchronized int getNumBroadcastConns(String sid) { Set<BCPair> bcpSet = bcPairs.get(sid); if(bcpSet == null) return 0; return bcpSet.size(); } /** * The number of people we are listening to * * @syncpriority 180 */ public synchronized int getNumListenConns(String sid) { Set<LCPair> lcpSet = lcPairs.get(sid); if(lcpSet == null) return 0; return lcpSet.size(); } /** * @syncpriority 180 */ public synchronized int getNumPendingCons() { return pendingCons.size(); } // public StreamingDetails getStreamingDetails(String sid) { // StreamingNode[] bNodes = getBroadcastStreamingNodes(sid); // StreamingNode[] lNodes = getListenStreamingNodes(sid); // StreamingDetails sd = new StreamingDetails(sid); // sd.setReceivingFromNodes(lNodes); // sd.setSendingToNodes(bNodes); // PageBuffer pb = mina.getPageBufProvider().getPageBuf(sid); // if(pb != null) // sd.setBytesDownloaded(pb.getBytesReceived()); // return sd; // } /** * Don't call this directly - call StreamMgr.broadcastTo() instead * @syncpriority 180 */ public synchronized void makeBroadcastConnectionTo(String sid, ControlConnection cc, EndPoint listenEp, List<Long> pages) { if (mina.getCCM().isShuttingDown()) { log.debug("Not making broadcast connection to " + cc.getNodeId() + ": shutting down"); return; } if (cc.getBCPair(sid) != null) { log.error("Error: not creating broadcasting connection to already-receiving node " + cc.getNodeId()); return; } if(!bcPairs.containsKey(sid)) bcPairs.put(sid, new HashSet<BCPair>()); bcPairs.get(sid).add(new BCPair(mina, sid, cc, listenEp, pages)); } /** * @syncpriority 200 */ public void makeListenConnectionTo(final String sid, final SourceStatus ss) { Node node = ss.getFromNode(); final String nodeId = node.getId(); if (mina.getCCM().isShuttingDown()) { log.debug("Not making listen connection to " + nodeId + ": shutting down"); return; } // If we have no currency client, wait til we do if (mina.getConfig().isAgoric() && !mina.getCurrencyClient().isReady()) { log.debug("Not making lcpair to " + nodeId + " for "+sid+" as currency client is not ready - waiting 5s"); mina.getExecutor().schedule(new CatchingRunnable() { public void doRun() throws Exception { makeListenConnectionTo(sid, ss); } }, 5, TimeUnit.SECONDS); return; } ControlConnection cc = mina.getCCM().getCCWithId(nodeId); if (cc == null) { synchronized (this) { if(!pendingCons.containsKey(sid)) pendingCons.put(sid, new HashSet<String>()); if(pendingCons.get(sid).contains(nodeId)) { log.debug("Not making listen conn to "+nodeId+" for stream "+sid+" - attempt already in progress"); return; } Attempt getCCAttempt = new GetCCAttempt(sid, ss); pendingCons.get(sid).add(nodeId); getCCAttempt.start(); log.debug("Making connection to " + nodeId + " for listening to " + sid); mina.getCCM().makeCCTo(node, getCCAttempt); } } else { if(cc.isClosing()) { // We have a CC, but we're already shutting it down (probably cleaning up the end of a previous stream), so try again in a little while log.debug("Not making lcpair to " + nodeId + " for "+sid+" as existing CC is closing - waiting 5s"); mina.getExecutor().schedule(new CatchingRunnable() { public void doRun() throws Exception { makeListenConnectionTo(sid, ss); } }, 5, TimeUnit.SECONDS); return; } try { startListeningTo(cc, sid, ss); } catch (IOException e) { log.error("Caught exception setting up lcp to "+cc.getNodeId()+" for "+sid, e); mina.getSourceMgr().cachePossiblyDeadSource(cc.getNode(), sid); // Request more if we need them mina.getStreamMgr().requestCachedSources(sid); } } } /** * @throws IOException * @syncpriority 200 */ private void startListeningTo(final ControlConnection cc, String sid, SourceStatus ss) throws IOException { String nodeId = cc.getNodeId(); if (mina.getCCM().isShuttingDown()) { log.debug("Not making listen connection to " + nodeId + ": shutting down"); return; } if(cc.getLCPair(sid) != null) { log.error("Error: Asked to make ListenConnection to already-listening node " + nodeId); return; } log.info("Starting listening to " + nodeId + " for stream " + sid); synchronized (this) { LCPair lcp = new LCPair(mina, sid, cc, ss); if(!lcPairs.containsKey(sid)) lcPairs.put(sid, new HashSet<LCPair>()); lcPairs.get(sid).add(lcp); } mina.getEventMgr().fireReceptionConnsChanged(sid); } /** * Must only be called from inside sync block */ private void removePendingConn(String sid, String nid) { Set<String> set = pendingCons.get(sid); if(set == null) return; set.remove(nid); if(set.size() == 0) pendingCons.remove(sid); } /** * @syncpriority 180 */ public synchronized void removeConnectionPair(ConnectionPair pair) { String sid = pair.getStreamId(); String nid = pair.getCC().getNodeId(); if (pair instanceof LCPair) { removePendingConn(sid, nid); Set<LCPair> set = lcPairs.get(sid); if(set != null) { set.remove(pair); if(set.size() == 0) lcPairs.remove(sid); } } else if (pair instanceof BCPair) { Set<BCPair> set = bcPairs.get(sid); if(set != null) { set.remove(pair); if(set.size() == 0) bcPairs.remove(sid); } } } // // /** // * @syncpriority 180 // */ // public synchronized void sendToBroadcastConns(String msgName, GeneratedMessage msg, String except) { // Iterator<BCPair> i = bcPairs.values().iterator(); // while (i.hasNext()) { // BCPair bcPair = i.next(); // if (bcPair.getCC().getNodeId().equals(except)) // continue; // bcPair.sendMessage(msgName, msg); // } // } // // /** // * @syncpriority 180 // */ // public synchronized void sendToListenConns(String msgName, GeneratedMessage msg) { // sendToListenConns(msgName, msg, null); // } // // /** // * @syncpriority 180 // */ // public synchronized void sendToListenConns(String msgName, GeneratedMessage msg, String except) { // Iterator<LCPair> i = lcPairs.values().iterator(); // while (i.hasNext()) { // ConnectionPair lcPair = i.next(); // if (lcPair.getCC().getNodeId().equals(except)) // continue; // lcPair.sendMessage(msgName, msg); // } // } // // /** // * @syncpriority 180 // */ // public synchronized void sendToNonLocalBroadcastConns(String msgName, GeneratedMessage msg, String except) { // Iterator<BCPair> i = bcPairs.values().iterator(); // while (i.hasNext()) { // BCPair bcPair = i.next(); // if (bcPair.getCC().getNodeId().equals(except)) // continue; // if (bcPair.getCC().isLocal()) // continue; // bcPair.sendMessage(msgName, msg); // } // } // // /** // * @syncpriority 180 // */ // private synchronized StreamingNode[] getBroadcastStreamingNodes(String sid) { // StreamingNode[] bNodes = new StreamingNode[bcPairs.size()]; // int i = 0; // for (Iterator<BCPair> iter = bcPairs.values().iterator(); iter.hasNext();) { // BCPair bcp = iter.next(); // bNodes[i++] = new StreamingNode(bcp.getCC().getNodeId().toString(), bcp.getFlowRate()); // } // return bNodes; // } // // /** // * @syncpriority 180 // */ // private synchronized StreamingNode[] getListenStreamingNodes(String sid) { // int i; // StreamingNode[] lNodes = new StreamingNode[lcPairs.size()]; // i = 0; // for (Iterator<LCPair> iter = lcPairs.values().iterator(); iter.hasNext();) { // LCPair lcp = iter.next(); // StreamingNode node = new StreamingNode(lcp.getCC().getNodeId().toString(), lcp.getFlowRate()); // node.setConnected(true); // node.setComplete(lcp.isComplete()); // lNodes[i++] = node; // } // return lNodes; // } private class GetCCAttempt extends Attempt { private SourceStatus sourceStat; private String sid; private String nid; public GetCCAttempt(String sid, SourceStatus ss) { // This should never timeout, it should be handled by CCMgr, but just in case... super(mina.getExecutor(), 2 * mina.getConfig().getConnectTimeout() * 1000, "GetCCAttempt"); this.sourceStat = ss; this.sid = sid; this.nid = ss.getFromNode().getId(); } /** * @syncpriority 200 */ protected void onFail() { log.info("Failed to connect to " + nid + " for stream '" + sid + "'"); synchronized (StreamConnsMgr.this) { removePendingConn(sid, nid); } mina.getSourceMgr().cachePossiblyDeadSource(sourceStat.getFromNode(), sid); // Request more if we need them mina.getStreamMgr().requestCachedSources(sid); } /** * @syncpriority 200 */ protected void onSuccess() { synchronized (StreamConnsMgr.this) { removePendingConn(sid, nid); } ControlConnection cc = mina.getCCM().getCCWithId(nid); if (cc == null) { // Oops, CC has disappeared due to an ill-timed network snafu onFail(); return; } log.info("Successfully got CC " + cc.getNodeId() + " for listening to stream " + sid); try { startListeningTo(cc, sid, sourceStat); } catch (IOException e) { // Oops, a network errot log.error("Caught exception starting listening to "+cc.getNodeId()+" for "+sid, e); onFail(); } } /** * @syncpriority 180 */ protected void onTimeout() { log.error("SCM.GetCCAttempt to "+nid+" for "+sid+" timed out!"); onFail(); } @Override public String toString() { return "GetCCAttempt ("+sid+"/"+nid+")"; } } }