package se.sics.gvod.ls.video; import java.util.*; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import se.sics.gvod.common.RetryComponentDelegator; import se.sics.gvod.common.Self; import se.sics.gvod.ls.system.LSConfig; import se.sics.gvod.ls.video.snapshot.VideoStats; import se.sics.gvod.net.VodAddress; import se.sics.gvod.net.VodNetwork; import se.sics.gvod.timer.*; import se.sics.gvod.timer.Timer; import se.sics.gvod.timer.UUID; import se.sics.gvod.video.msgs.EncodedSubPiece; import se.sics.gvod.video.msgs.VideoPieceMsg; import se.sics.kompics.Positive; /** * Provides three-phase gossiping of EncodedSubPieces. Improved implementation * of Three-Phase Gossip presented in [Gossip++ p.38], similar to their Claim * algorithm [p.55]. * * @author Niklas Wahlén <nwahlen@kth.se> */ public class VideoGossip { protected final Logger logger = LoggerFactory.getLogger(VideoGossip.class); // References protected RetryComponentDelegator delegator; protected Self self; protected Positive<VodNetwork> network; protected Positive<Timer> timer; protected VideoNeighbours neighbours; // Data protected Map<Integer, EncodedSubPiece> subPiecesDelivered; // Algorithm specific private int f; protected Set<Integer> subPiecesToPropose; protected Set<Integer> requestedSubPieces; // Configuration protected int ulBwCapacity, dlBwCapacity; // bytes protected int uploaded, downloaded; private boolean highUploadWarning; // congestion control private int lowUploadWarnings, highUploadWarnings; private boolean source; // Retransmission private Map<Integer, Integer> currentRequests; // <ID, requests left> private Map<Integer, BlockingQueue<VodAddress>> piecesProviders; // <ID, proviers> private Set<TimeoutId> timeoutIds; // Tools private Random random; public VideoGossip(RetryComponentDelegator delegator, Self self, Positive<VodNetwork> network, Positive<Timer> timer, VideoNeighbours neighbours, Map<Integer, EncodedSubPiece> subPieceBuffer, Set<TimeoutId> timeoutIds, boolean source) { // Algorithm specific // fanout f = ln(system size) + constant //f = ((int) Math.log(500)) + 2; subPiecesToPropose = new HashSet<Integer>(); requestedSubPieces = new HashSet<Integer>(); // References this.delegator = delegator; this.self = self; this.network = network; this.timer = timer; this.neighbours = neighbours; this.subPiecesDelivered = subPieceBuffer; // Retransmission currentRequests = new HashMap<Integer, Integer>(); piecesProviders = new HashMap<Integer, BlockingQueue<VodAddress>>(); this.timeoutIds = timeoutIds; // Configuration if (source) { ulBwCapacity = LSConfig.VIDEO_SOURCE_UPLOAD_CAPACITY; f = 5; } else { ulBwCapacity = LSConfig.VIDEO_UPLOAD_CAPACITY; // in bytes f = 8; } dlBwCapacity = Integer.MAX_VALUE; uploaded = 0; downloaded = 0; highUploadWarning = false; this.source = source; random = new Random(LSConfig.getSeed()); } public void cycle() { if (!subPiecesToPropose.isEmpty()) { gossip(new HashSet<Integer>(subPiecesToPropose)); subPiecesToPropose.clear(); } if (highUploadWarning) { highUploadWarnings++; } else { highUploadWarnings = 0; } // it is only possible to continue uploading if source or something was downloaded if ((source || (downloaded > 0)) && (ulBwCapacity - uploaded) > (10 * EncodedSubPiece.getSize())) { lowUploadWarnings++; } else { lowUploadWarnings = 0; } updateFanout(); VideoStats.instance(self).setFanout((short) f); VideoStats.instance(self).setUlBwBytes(uploaded); VideoStats.instance(self).setDlBwBytes(downloaded); // reset highUploadWarning = false; uploaded = 0; downloaded = 0; } /* * Phase 1 - Gossip chunk ids */ public void publish(EncodedSubPiece p) { if (p == null) { throw new IllegalArgumentException("null Piece not allowed"); } deliverSubPiece(p); Set<Integer> pieceIds = new HashSet<Integer>(); pieceIds.add(p.getGlobalId()); gossip(pieceIds); } /* * Phase 2 - Request chunks */ public void handleAdvertisement(VideoPieceMsg.Advertisement advertisement) { Set<Integer> wantedChunks = new HashSet<Integer>(); for (Integer id : advertisement.getAdvertisedPiecesIds()) { if (!requestedSubPieces.contains(id)) { VideoStats.instance(self).incSeenSubPieces(); addPieceProvider(id, advertisement.getVodSource()); wantedChunks.add(id); } // a request is currently outstanding for this piece. if (isBeingRetransmitted(id)) { addPieceProvider(id, advertisement.getVodSource()); } } if (!wantedChunks.isEmpty()) { requestedSubPieces.addAll(wantedChunks); VideoPieceMsg.Request request = new VideoPieceMsg.Request(self.getAddress(), advertisement.getVodSource(), wantedChunks); delegator.doRetry(request, self.getOverlayId()); startRetryTimer(request, LSConfig.VIDEO_PIECE_REQUEST_TIMEOUT); } } /* * Phase 3 - Push payload */ public void handleRequest(VideoPieceMsg.Request request) { for (Integer id : request.getPiecesIds()) { if (uploaded + EncodedSubPiece.getSize() > ulBwCapacity) { highUploadWarning = true; break; } if (random.nextDouble() < LSConfig.VIDEO_MESSAGE_DROP_RATIO) { break; } EncodedSubPiece p = getSubPiece(id); if (p != null) { delegator.doTrigger(new VideoPieceMsg.Response(self.getAddress(), request.getVodSource(), request.getTimeoutId(), p), network); incSent(request.getVodSource()); uploaded += EncodedSubPiece.getSize(); } } } public void handlePieces(VideoPieceMsg.Response response) { EncodedSubPiece p = response.getEncodedSubPiece(); if (!subPiecesDelivered.containsKey(p.getGlobalId())) { downloaded += EncodedSubPiece.getSize(); subPiecesToPropose.add(p.getGlobalId()); deliverSubPiece(p); } cancelTimer(response); } /* * Retransmission */ public void handleTimeout(VideoPieceMsg.RequestTimeout timeout) { VideoStats.instance(self).incSubPieceRequestTimeouts(); VideoPieceMsg.Request request = timeout.getRequestMsg(); if (request.getVodSource().getId() != self.getId()) { return; } Set<Integer> wantedChunks; // Check for non-received sub-pieces for (Integer id : request.getPiecesIds()) { if (!subPiecesDelivered.containsKey(id)) { wantedChunks = new HashSet(1); wantedChunks.add(id); // Get new provider VodAddress newProvider = getNextPieceProvider(id); if (newProvider != null) { VideoPieceMsg.Request newRequest = new VideoPieceMsg.Request(self.getAddress(), newProvider, wantedChunks); delegator.doRetry(newRequest, self.getOverlayId()); if (retriesLeft(id)) { long newDelay = (long) (timeout.getDelay() * LSConfig.VIDEO_PIECE_REQUEST_TIMEOUT_SCALE); if (newDelay < LSConfig.VIDEO_PIECE_REQUEST_TIMEOUT_MIN) { newDelay = LSConfig.VIDEO_PIECE_REQUEST_TIMEOUT_MIN; } startRetryTimer(newRequest, newDelay); } } } } } public boolean isBeingRetransmitted(Integer id) { return currentRequests.containsKey(id); } /* * Miscellaneous */ public Collection<VodAddress> selectNodes(int f) { return neighbours.getNRandomNeighbours(f); } public EncodedSubPiece getSubPiece(Integer id) { return subPiecesDelivered.get(id); } public void deliverSubPiece(EncodedSubPiece p) { // requestedSubPieces is the only set used for managing (sending of) // requests, and if a subpiece was encoded locally (not received) it // was never added to this set previously, so we have to make sure the // piece is added to this set requestedSubPieces.add(p.getGlobalId()); subPiecesDelivered.put(p.getGlobalId(), p); } public int getFanout() { return f; } public void gossip(Set<Integer> pieceIds) { Collection<VodAddress> communicationPartners = selectNodes(f); for (VodAddress p : communicationPartners) { delegator.doTrigger( new VideoPieceMsg.Advertisement(self.getAddress(), p, pieceIds), network); } } /* * Other */ private boolean retriesLeft(Integer id) { Integer retries = currentRequests.get(id); if (retries == null) { return true; } else if (retries > 0) { retries--; currentRequests.put(id, retries); return true; } return false; } private void startRetryTimer(VideoPieceMsg.Request request, long timeoutDelay) { ScheduleTimeout st = new ScheduleTimeout(timeoutDelay); st.setTimeoutEvent(new VideoPieceMsg.RequestTimeout(st, request)); timeoutIds.add(st.getTimeoutEvent().getTimeoutId()); for (Integer id : request.getPiecesIds()) { if (!currentRequests.containsKey(id)) { currentRequests.put(id, LSConfig.VIDEO_PIECE_REQUEST_RETRIES); } } delegator.doTrigger(st, timer); } private void cancelTimer(VideoPieceMsg.Response response) { CancelTimeout ct = new CancelTimeout(response.getTimeoutId()); delegator.doTrigger(ct, timer); timeoutIds.remove(response.getTimeoutId()); currentRequests.remove(response.getEncodedSubPiece().getGlobalId()); piecesProviders.remove(response.getEncodedSubPiece().getGlobalId()); } // Only called after local encoding, i.e. never when receiving public void handlePiece(EncodedSubPiece esp) { if (!subPiecesDelivered.containsKey(esp.getGlobalId())) { subPiecesToPropose.add(esp.getGlobalId()); deliverSubPiece(esp); } } /* * Enable record of advertisers */ private void addPieceProvider(Integer pieceId, VodAddress a) { BlockingQueue<VodAddress> pieceProviders = piecesProviders.get(pieceId); if (pieceProviders == null) { pieceProviders = new LinkedBlockingQueue<VodAddress>(); } if (!pieceProviders.contains(a)) { try { pieceProviders.put(a); } catch (InterruptedException ex) { } } piecesProviders.put(pieceId, pieceProviders); } /** * Get known providers for a piece in a round-robin manner. The first * provider in the list for the * <code>pieceId</code> is moved to the end of the queue and returned. * * @param id * @return The first provider in the queue for this encoded sub-piece id */ private VodAddress getNextPieceProvider(Integer id) { BlockingQueue<VodAddress> pieceProviders = piecesProviders.get(id); if (pieceProviders == null || pieceProviders.isEmpty()) { return null; } VodAddress provider = pieceProviders.poll(); try { pieceProviders.put(provider); } catch (InterruptedException ex) { return null; } return provider; } private void updateFanout() { if (lowUploadWarnings >= 5) { f++; lowUploadWarnings = 0; } else if (highUploadWarnings >= 5) { f--; highUploadWarnings = 0; } // the source _has_ to be able to serve a certain number of peers if(source && f < 5) { f = 5; } else if(f < 1){ f = 1; } // there is no need (nor good) to have too high fanout if(f > LSConfig.VIDEO_MAX_FANOUT) { // 20 is a resonable limit for most systems (Gossip++) f = LSConfig.VIDEO_MAX_FANOUT; } } public int getDownloaded() { return downloaded; } /* * Statistics */ private void incSent(VodAddress a) { short distance = neighbours.getDistanceTo(a); if (distance == 0) { VideoStats.instance(self).incSentSubPiecesIntraAs(); } else if (distance == 1) { VideoStats.instance(self).incSentSubPiecesNeighbourAs(); } else { VideoStats.instance(self).incSentSubPiecesOtherAs(); } } }