/*
* Created by Joseph Bridgewater
* Created on Jan 2, 2006
* Copyright (C) 2005, 2006 Aelitis, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package com.aelitis.azureus.core.peermanager.piecepicker.impl;
import java.util.*;
import org.gudy.azureus2.core3.config.*;
import org.gudy.azureus2.core3.disk.*;
import org.gudy.azureus2.core3.disk.impl.DiskManagerFileInfoImpl;
import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceList;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.logging.*;
import org.gudy.azureus2.core3.peer.*;
import org.gudy.azureus2.core3.peer.impl.*;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.util.*;
import com.aelitis.azureus.core.impl.AzureusCoreImpl;
import com.aelitis.azureus.core.peermanager.control.PeerControlScheduler;
import com.aelitis.azureus.core.peermanager.control.PeerControlSchedulerFactory;
import com.aelitis.azureus.core.peermanager.control.SpeedTokenDispenser;
import com.aelitis.azureus.core.peermanager.piecepicker.*;
import com.aelitis.azureus.core.peermanager.piecepicker.util.BitFlags;
import com.aelitis.azureus.core.util.CopyOnWriteList;
import edu.washington.cs.oneswarm.f2f.FileCollection;
import edu.washington.cs.oneswarm.f2f.multisource.Sha1DownloadManager;
import edu.washington.cs.oneswarm.f2f.multisource.Sha1Peer;
/**
* @author MjrTom
*
*/
public class PiecePickerImpl
implements PiecePicker
{
// *****************************************************************
/*
* EDIT: made by isdal@cs.washington.edu
*
* added support for in order download of video files
* constant deciding how much to add so pieces are downloaded in order
*
* pieces gets a boost that decreases linearly with piece number
*
* PRIORITY_IN_ORDER_FILES - ("piece number in file" * ("total piece number"/PRIORITY_IN_ORDER_FILES))
*
* should tip the balance a bit
*/
public static final int PRIORITY_IN_ORDER_FILES = 5000;
//******************************************************************
private static final boolean LOG_RTA = false;
private static final LogIDs LOGID = LogIDs.PIECES;
/** min ms for recalculating availability - reducing this has serious ramifications */
private static final long TIME_MIN_AVAILABILITY = 974;
/** min ms for recalculating base priorities */
private static final long TIME_MIN_PRIORITIES = 999;
/** min ms for forced availability rebuild */
private static final long TIME_AVAIL_REBUILD = 5 * 60 * 1000 - 24;
// The following are added to the base User setting based priorities (for all inspected pieces)
/** user select prioritize first/last */
private static final int PRIORITY_W_FIRSTLAST = 1300;
/** min # pieces in file for first/last prioritization */
private static final long FIRST_PIECE_MIN_NB = 4;
/** number of pieces for first pieces prioritization */
// private static final int FIRST_PIECE_RANGE_PERCENT= 10;
/** user sets file as "High" */
private static final int PRIORITY_W_FILE = 1000;
/** Additional boost for more completed High priority */
private static final int PRIORITY_W_COMPLETION = 2000;
// The following are only used when resuming already running pieces
/** priority boost due to being too old */
private static final int PRIORITY_W_AGE = 900;
/** ms a block is expected to complete in */
private static final int PRIORITY_DW_AGE = 60 * 1000;
/** ms since last write */
private static final int PRIORITY_DW_STALE = 120 * 1000;
/** finish pieces already almost done */
private static final int PRIORITY_W_PIECE_DONE = 900;
/** keep working on same piece */
private static final int PRIORITY_W_SAME_PIECE = 700;
/** currently webseeds + other explicit priorities are around 10000 or more - at this point we ignore rarity */
private static final int PRIORITY_OVERRIDES_RAREST = 9000;
private static final int PRIORITY_REQUEST_HINT = 3000;
/** priority at and above which pieces require real-time scheduling */
private static final int PRIORITY_REALTIME = 9999999;
//********************************************************
// Edit, for f2f we need this to be a bit more aggressive
/** Number of bytes to get from the end of the file **/
public static final int REQUESTS_LAST_BYTES = 1024 * 1024;
/** Min number of requests sent to a peer */
//private static final int REQUESTS_MIN =4;
private static final int REQUESTS_MIN = 16;
/** Max number of request sent to a peer */
public static final int REQUESTS_MAX = 768;
/** Default number of requests sent to a peer, (for each X B/s another request will be used) */
private static final int SLOPE_REQUESTS = 1 * 1024;
//private static final int SLOPE_REQUESTS = 4 * 1024;
//********************************************************
private static final long RTA_END_GAME_MODE_SIZE_TRIGGER = 16 * DiskManager.BLOCK_SIZE;
private static final long END_GAME_MODE_SIZE_TRIGGER = 20 * 1024 * 1024;
private static final long END_GAME_MODE_TIMEOUT = 60
* END_GAME_MODE_SIZE_TRIGGER
/ DiskManager.BLOCK_SIZE;
protected static volatile boolean firstPiecePriority = COConfigurationManager.getBooleanParameter("Prioritize First Piece");
protected static volatile boolean completionPriority = COConfigurationManager.getBooleanParameter("Prioritize Most Completed Files");
/** event # of user settings controlling priority changes */
protected static volatile long paramPriorityChange = Long.MIN_VALUE;
private static final int NO_REQUEST_BACKOFF_MAX_MILLIS = 5 * 1000;
private static final int NO_REQUEST_BACKOFF_MAX_LOOPS = NO_REQUEST_BACKOFF_MAX_MILLIS
/ PeerControlScheduler.SCHEDULE_PERIOD_MILLIS;
private static Random random = new Random();
private final DiskManager diskManager;
private final PEPeerControl peerControl;
private final DiskManagerListenerImpl diskManagerListener;
protected final Map peerListeners;
private final PEPeerManagerListener peerManagerListener;
protected final int nbPieces;
protected final DiskManagerPiece[] dmPieces;
protected final PEPiece[] pePieces;
private final List rarestStartedPieces; //List of pieces started as rarest first
protected final AEMonitor availabilityMon = new AEMonitor(
"PiecePicker:avail");
private final AEMonitor endGameModeChunks_mon = new AEMonitor(
"PiecePicker:EGM");
protected volatile int nbPiecesDone;
/** asyncronously updated availability */
protected volatile int[] availabilityAsynch;
/** indicates availability needs to be recomputed due to detected drift */
protected volatile long availabilityDrift;
private long timeAvailRebuild = TIME_AVAIL_REBUILD;
/** periodically updated consistent view of availability for calculating */
protected volatile int[] availability;
private long time_last_avail;
protected volatile long availabilityChange;
private volatile long availabilityComputeChange;
private long time_last_rebuild;
private long timeAvailLessThanOne;
private float globalAvail;
private float globalAvgAvail;
private int nbRarestActive;
private int globalMin;
private int globalMax;
/**
* The rarest availability level of pieces that we affirmatively want to try to request from others soonest
* ie; our prime targets for requesting rarest pieces
*/
private volatile int globalMinOthers;
/** event # of user file priority settings changes */
protected volatile long filePriorityChange;
/** last user parameter settings event # when priority bases were calculated */
private volatile long priorityParamChange;
/** last user priority event # when priority bases were calculated */
private volatile long priorityFileChange;
/** last availability event # when priority bases were calculated */
private volatile long priorityAvailChange;
private boolean priorityRTAexists;
/** time that base priorities were last computed */
private long timeLastPriorities;
/** the priority for starting each piece/base priority for resuming */
private int[] startPriorities;
protected volatile boolean hasNeededUndonePiece;
protected volatile long neededUndonePieceChange;
/** A flag to indicate when we're in endgame mode */
private volatile boolean endGameMode;
private volatile boolean endGameModeAbandoned;
private volatile long timeEndGameModeEntered;
/** The list of chunks needing to be downloaded (the mechanism change when entering end-game mode) */
private List endGameModeChunks;
private long lastProviderRecalcTime;
private final CopyOnWriteList rta_providers = new CopyOnWriteList();
private long[] provider_piece_rtas;
private final CopyOnWriteList priority_providers = new CopyOnWriteList();
private long[] provider_piece_priorities;
private int allocate_request_loop_count;
private static boolean enable_request_hints;
private static boolean includeLanPeersInReqLimiting;
private final CopyOnWriteList listeners = new CopyOnWriteList();
// this one is used to group the pieces together in chunks so that
// sha1 multisource matches better, it has to be deterministic
// for a certain user/torrent combo
private long piecePickerSha1GroupingRandomSeed;
static {
class ParameterListenerImpl
implements ParameterListener
{
@Override
public final void parameterChanged(final String parameterName) {
if (parameterName.equals("Prioritize Most Completed Files")) {
completionPriority = COConfigurationManager.getBooleanParameter(parameterName);
paramPriorityChange++; // this is a user's priority change event
} else if (parameterName.equals("Prioritize First Piece")) {
firstPiecePriority = COConfigurationManager.getBooleanParameter(parameterName);
paramPriorityChange++; // this is a user's priority change event
} else if (parameterName.equals("Piece Picker Request Hint Enabled")) {
enable_request_hints = COConfigurationManager.getBooleanParameter(parameterName);
}
includeLanPeersInReqLimiting = !COConfigurationManager.getBooleanParameter("LAN Speed Enabled");
}
}
final ParameterListenerImpl parameterListener = new ParameterListenerImpl();
COConfigurationManager.addParameterListener(
"Prioritize Most Completed Files", parameterListener);
COConfigurationManager.addAndFireParameterListener(
"Prioritize First Piece", parameterListener);
COConfigurationManager.addAndFireParameterListener(
"Piece Picker Request Hint Enabled", parameterListener);
COConfigurationManager.addAndFireParameterListener("LAN Speed Enabled",
parameterListener);
}
public PiecePickerImpl(final PEPeerControl pc) {
System.out.println("* Using in-order aware piece picker");
// class administration first
peerControl = pc;
diskManager = peerControl.getDiskManager();
dmPieces = diskManager.getPieces();
nbPieces = diskManager.getNbPieces();
nbPiecesDone = 0;
pePieces = pc.getPieces();
// now do stuff related to availability
availability = new int[nbPieces]; //always needed
hasNeededUndonePiece = false;
neededUndonePieceChange = Long.MIN_VALUE;
// ensure all periodic calculaters perform operations at least once
time_last_avail = Long.MIN_VALUE;
availabilityChange = Long.MIN_VALUE + 1;
availabilityComputeChange = Long.MIN_VALUE;
availabilityDrift = nbPieces;
// initialize each piece; on going changes will use event driven tracking
for (int i = 0; i < nbPieces; i++) {
if (dmPieces[i].isDone()) {
availability[i]++;
nbPiecesDone++;
} else {
hasNeededUndonePiece |= dmPieces[i].calcNeeded();
}
}
if (hasNeededUndonePiece)
neededUndonePieceChange++;
updateAvailability();
// with availability charged and primed, ready for peer messages
peerListeners = new HashMap();
peerManagerListener = new PEPeerManagerListenerImpl();
peerControl.addListener(peerManagerListener);
// now do stuff related to starting/continuing pieces
rarestStartedPieces = new ArrayList();
// startPriorities =new long[nbPieces];
filePriorityChange = Long.MIN_VALUE;
priorityParamChange = Long.MIN_VALUE;
priorityFileChange = Long.MIN_VALUE;
priorityAvailChange = Long.MIN_VALUE;
timeLastPriorities = Long.MIN_VALUE;
endGameMode = false;
endGameModeAbandoned = false;
timeEndGameModeEntered = 0;
// computeBasePriorities();
// with priorities charged and primed, ready for dm messages
diskManagerListener = new DiskManagerListenerImpl();
diskManager.addListener(diskManagerListener);
String id = COConfigurationManager.getStringParameter("ID", null);
if (id == null) {
id = "";
}
try{
this.piecePickerSha1GroupingRandomSeed = id.hashCode()
^ Arrays.hashCode(diskManager.getTorrent().getHash());
}catch(Exception e){
e.printStackTrace();
}
}
@Override
public final void addHavePiece(final PEPeer peer, final int pieceNumber) {
// peer is null if called from disk-manager callback
try {
availabilityMon.enter();
if (availabilityAsynch == null) {
availabilityAsynch = availability.clone();
}
++availabilityAsynch[pieceNumber];
availabilityChange++;
} finally {
availabilityMon.exit();
}
// if this is an interesting piece then clear any record of "no requests" so the peer gets
// scheduled next loop
if (peer != null && dmPieces[pieceNumber].isDownloadable()) {
peer.setConsecutiveNoRequestCount(0);
}
}
/**
* This methd will compute the pieces' overall availability (including ourself)
* and the _globalMinOthers & _globalAvail
*/
@Override
public final void updateAvailability() {
final long now = SystemTime.getCurrentTime();
if (now >= time_last_avail && now < time_last_avail + TIME_MIN_AVAILABILITY)
return;
if (availabilityDrift > 0 || now < time_last_rebuild
|| (now - time_last_rebuild) > timeAvailRebuild) {
try {
availabilityMon.enter();
time_last_rebuild = now;
final int[] new_availability = recomputeAvailability();
if (Constants.isCVSVersion()) {
final int[] old_availability = availabilityAsynch == null
? availability : availabilityAsynch;
int errors = 0;
for (int i = 0; i < new_availability.length; i++) {
if (new_availability[i] != old_availability[i]) {
errors++;
}
}
if (errors > 0 && errors != nbPieces) {
if (Logger.isEnabled())
Logger.log(new LogEvent(peerControl, LOGID, LogEvent.LT_ERROR,
"updateAvailability(): availability rebuild errors ="
+ errors + " timeAvailRebuild =" + timeAvailRebuild));
timeAvailRebuild -= errors;
} else
timeAvailRebuild++;
}
availabilityAsynch = new_availability;
availabilityDrift = 0;
availabilityChange++;
} finally {
availabilityMon.exit();
}
} else if (availabilityComputeChange >= availabilityChange) {
return;
}
try {
availabilityMon.enter();
time_last_avail = now;
availabilityComputeChange = availabilityChange;
// take a snapshot of availabilityAsynch
if (availabilityAsynch != null) {
availability = availabilityAsynch;
availabilityAsynch = null;
}
} finally {
availabilityMon.exit();
}
int i;
int allMin = Integer.MAX_VALUE;
int allMax = 0;
int rarestMin = Integer.MAX_VALUE;
for (i = 0; i < nbPieces; i++) {
final int avail = availability[i];
final DiskManagerPiece dmPiece = dmPieces[i];
final PEPiece pePiece = pePieces[i];
if (avail > 0 && avail < rarestMin && dmPiece.isDownloadable()
&& (pePiece == null || pePiece.isRequestable()))
rarestMin = avail; // most important targets for near future requests from others
if (avail < allMin)
allMin = avail;
if (avail > allMax)
allMax = avail;
}
// copy updated local variables into globals
globalMin = allMin;
globalMax = allMax;
globalMinOthers = rarestMin;
int total = 0;
int rarestActive = 0;
long totalAvail = 0;
for (i = 0; i < nbPieces; i++) {
final int avail = availability[i];
final DiskManagerPiece dmPiece = dmPieces[i];
final PEPiece pePiece = pePieces[i];
if (avail > 0) {
if (avail > allMin)
total++;
if (avail <= rarestMin && dmPiece.isDownloadable() && pePiece != null
&& !pePiece.isRequested())
rarestActive++;
totalAvail += avail;
}
}
// copy updated local variables into globals
float newGlobalAvail = (total / (float) nbPieces) + allMin;
if (globalAvail >= 1.0 && newGlobalAvail < 1.0) {
timeAvailLessThanOne = now;
} else if (newGlobalAvail >= 1.0) {
timeAvailLessThanOne = 0;
}
globalAvail = newGlobalAvail;
nbRarestActive = rarestActive;
globalAvgAvail = totalAvail / (float) (nbPieces)
/ (1 + peerControl.getNbSeeds() + peerControl.getNbPeers());
}
private final int[] recomputeAvailability() {
if (availabilityDrift > 0 && availabilityDrift != nbPieces
&& Logger.isEnabled())
Logger.log(new LogEvent(diskManager.getTorrent(), LOGID,
LogEvent.LT_INFORMATION, "Recomputing availabiliy. Drift="
+ availabilityDrift + ":" + peerControl.getDisplayName()));
final List peers = peerControl.getPeers();
final int[] newAvailability = new int[nbPieces];
int j;
int i;
// first our pieces
for (j = 0; j < nbPieces; j++)
newAvailability[j] = dmPieces[j].isDone() ? 1 : 0;
//for all peers
final int peersSize = peers.size();
for (i = 0; i < peersSize; i++) { //get the peer connection
final PEPeer peer = (PEPeerTransport) peers.get(i);
if (peer != null && peer.getPeerState() == PEPeer.TRANSFERING) {
//cycle trhough the pieces they actually have
final BitFlags peerHavePieces = peer.getAvailable();
if (peerHavePieces != null && peerHavePieces.nbSet > 0) {
for (j = peerHavePieces.start; j <= peerHavePieces.end; j++) {
if (peerHavePieces.flags[j])
++newAvailability[j];
}
}
}
}
return newAvailability;
}
@Override
public int getNumberOfPieces() {
return (nbPieces);
}
@Override
public final int[] getAvailability() {
return availability;
}
@Override
public final int getAvailability(final int pieceNumber) {
return availability[pieceNumber];
}
//this only gets called when the My Torrents view is displayed
@Override
public final float getMinAvailability() {
return globalAvail;
}
@Override
public final long getAvailWentBadTime() {
return (timeAvailLessThanOne);
}
@Override
public final int getMaxAvailability() {
return globalMax;
}
@Override
public final float getAvgAvail() {
return globalAvgAvail;
}
@Override
public int getNbPiecesDone() {
return nbPiecesDone;
}
/**
* Early-outs when finds a downloadable piece
* Either way sets hasNeededUndonePiece and neededUndonePieceChange if necessary
*/
protected final void checkDownloadablePiece() {
for (int i = 0; i < nbPieces; i++) {
if (dmPieces[i].isInteresting()) {
if (!hasNeededUndonePiece) {
hasNeededUndonePiece = true;
neededUndonePieceChange++;
}
return;
}
}
if (hasNeededUndonePiece) {
hasNeededUndonePiece = false;
neededUndonePieceChange++;
}
}
/**
* one reason requests don't stem from the individual peers is so the connections can be
* sorted by best uploaders, providing some ooprtunity to download the most important
* (ie; rarest and/or highest priority) pieces faster and more reliably
*/
@Override
public final void allocateRequests() {
//System.err.println("in allocateRequests()");
if (!hasNeededUndonePiece) {
return;
}
allocate_request_loop_count++;
final List peers = peerControl.getPeers();
final int peersSize = peers.size();
//final long[] upRates =new long[peersSize];
final ArrayList bestUploaders = new ArrayList(peersSize);
for (int i = 0; i < peersSize; i++) {
final PEPeerTransport peer = (PEPeerTransport) peers.get(i);
if (peer.isDownloadPossible()) {
int no_req_count = peer.getConsecutiveNoRequestCount();
if (no_req_count == 0
|| allocate_request_loop_count % (no_req_count + 1) == 0) {
bestUploaders.add(peer);
//final long upRate =peer.getStats().getSmoothDataReceiveRate();
//UnchokerUtil.updateLargestValueFirstSort(upRate, upRates, peer, bestUploaders, 0);
}
}
}
/* sort all peers we're currently downloading from
* with the most favorable for the next request one as 1st entry
* randomize list first to not pick the same candidates if the list of best doesn't return conclusive results
*/
Collections.shuffle(bestUploaders);
Collections.sort(bestUploaders, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
PEPeerTransport pt2 = (PEPeerTransport) o2;
PEPeerTransport pt1 = (PEPeerTransport) o1;
PEPeerStats stats2 = pt2.getStats();
PEPeerStats stats1 = pt1.getStats();
/* pt1 comes first if we want to request data from it more than from pt2
* it is "smaller", i.e. return is < 0
*/
int toReturn = 0;
// lan peers to the front of the queue as they'll ignore request limiting
if (pt1.isLANLocal() && !pt2.isLANLocal())
toReturn = -1;
else if (!pt1.isLANLocal() && pt2.isLANLocal())
toReturn = 1;
// try to download from the currently fastest, this is important for the request focusing
if (toReturn == 0)
toReturn = (int) (stats2.getSmoothDataReceiveRate() - stats1.getSmoothDataReceiveRate());
// we're here because we're not requesting anything from both peers
// which means we might have to send the next request to this peer
// first try to download from peers that we're uploading to, that should stabilize tit-for-tat a bit
if (toReturn == 0 && (!pt2.isChokedByMe() || !pt1.isChokedByMe()))
toReturn = (int) (stats2.getDataSendRate() - stats1.getDataSendRate());
// ok, we checked all downloading and uploading peers by now
// avoid snubbed ones for the next step here
if (toReturn == 0 && pt2.isSnubbed() && !pt1.isSnubbed())
toReturn = -1;
if (toReturn == 0 && !pt2.isSnubbed() && pt1.isSnubbed())
toReturn = 1;
/*
//TODO enable in next beta cycle
// try some peer we haven't downloaded from yet (this should allow us to taste all peers)
if(toReturn == 0 && stats2.getTotalDataBytesReceived() == 0 && stats1.getTotalDataBytesReceived() > 0)
toReturn = 1;
if(toReturn == 0 && stats1.getTotalDataBytesReceived() == 0 && stats2.getTotalDataBytesReceived() > 0)
toReturn = -1;
*/
/*
// still nothing, next try peers from which we have downloaded most in the past
// NO, we don't want to focus on what may have become a bad peer
if(toReturn == 0)
toReturn = (int)(stats2.getTotalDataBytesReceived() - stats1.getTotalDataBytesReceived());
*/
return toReturn;
}
});
final int uploadersSize = bestUploaders.size();
if (uploadersSize == 0) {
// no usable peers, bail out early
return;
}
boolean done_priorities = false;
if (priorityRTAexists) {
final Map[] peer_randomiser = {
null
};
// to keep the ordering consistent we need to use a fixed metric unless
// we remove + re-add a peer, at which point we need to take account of
// the fact that it has a new request allocated
final Map block_time_order_peers_metrics = new HashMap(uploadersSize);
Set block_time_order_peers = new TreeSet(new Comparator() {
@Override
public int compare(Object arg1, Object arg2) {
PEPeerTransport pt1 = (PEPeerTransport) arg1;
PEPeerTransport pt2 = (PEPeerTransport) arg2;
Integer m1 = (Integer) block_time_order_peers_metrics.get(pt1);
if (m1 == null) {
m1 = new Integer(getNextBlockETAFromNow(pt1));
block_time_order_peers_metrics.put(pt1, m1);
}
Integer m2 = (Integer) block_time_order_peers_metrics.get(pt2);
if (m2 == null) {
m2 = new Integer(getNextBlockETAFromNow(pt2));
block_time_order_peers_metrics.put(pt2, m2);
}
int result = m1.intValue() - m2.intValue();
if (result == 0) {
Map pr = peer_randomiser[0];
if (pr == null) {
pr = peer_randomiser[0] = new LightHashMap(bestUploaders.size());
}
Integer r_1 = (Integer) pr.get(pt1);
if (r_1 == null) {
r_1 = new Integer(random.nextInt());
pr.put(pt1, r_1);
}
Integer r_2 = (Integer) pr.get(pt2);
if (r_2 == null) {
r_2 = new Integer(random.nextInt());
pr.put(pt2, r_2);
}
result = r_1.intValue() - r_2.intValue();
if (result == 0) {
result = pt1.hashCode() - pt2.hashCode();
if (result == 0) {
// v unlikely - inconsistent but better than losing a peer
result = 1;
}
}
}
return (result);
}
});
block_time_order_peers.addAll(bestUploaders);
PEPeerTransport best_uploader = (PEPeerTransport) bestUploaders.get(0);
long best_block_eta = SystemTime.getCurrentTime()
+ getNextBlockETAFromNow(best_uploader);
// give priority pieces the first look-in
// we need to sort by how quickly the peer can get a block, not just its base speed
boolean allocated_request = true;
Set allocations_started = new HashSet();
try {
while (allocated_request && priorityRTAexists) {
allocated_request = false;
while (!block_time_order_peers.isEmpty()) {
Iterator it = block_time_order_peers.iterator();
PEPeerTransport pt = (PEPeerTransport) it.next();
it.remove();
if (!pt.isDownloadPossible() || pt.isSnubbed()) {
continue;
}
// ignore request number advice from peers in RTA mode, we gotta do what we can
int maxRequests = REQUESTS_MIN
+ (int) (pt.getStats().getDataReceiveRate() / SLOPE_REQUESTS)
+ 1;
if (maxRequests > REQUESTS_MAX || maxRequests < 0) {
maxRequests = REQUESTS_MAX;
}
int currentRequests = pt.getNbRequests();
int allowed_requests = maxRequests - currentRequests;
if (allowed_requests > 0) {
if (!done_priorities) {
done_priorities = true;
computeBasePriorities();
if (!priorityRTAexists) {
// might have stopped RTA as this is calculated in computeBasePriorities
break;
}
}
if (!allocations_started.contains(pt)) {
pt.requestAllocationStarts(startPriorities);
allocations_started.add(pt);
}
if (findRTAPieceToDownload(pt, pt == best_uploader,
best_block_eta)) {
// add back in to see if we can allocate a further request
if (allowed_requests > 1) {
block_time_order_peers_metrics.remove(pt);
block_time_order_peers.add(pt);
}
}
}
}
}
} finally {
Iterator it = allocations_started.iterator();
while (it.hasNext()) {
((PEPeerTransport) it.next()).requestAllocationComplete();
}
}
}
checkEndGameMode();
//dispenser.refill();
for (int i = 0; i < uploadersSize; i++) {
final PEPeerTransport pt = (PEPeerTransport) bestUploaders.get(i);
// only request when there are still free tokens in the bucket or when it's a lan peer (which get sorted to the front of the queue)
if (dispenser.peek(DiskManager.BLOCK_SIZE) < 1
&& (!pt.isLANLocal() || includeLanPeersInReqLimiting))
break;
//System.out.println("#"+i+" "+pt.getStats().getSmoothDataReceiveRate());
// can we transfer something?
if (pt.isDownloadPossible()) {
int peer_request_num = pt.getMaxNbRequests();
// If request queue is too low, enqueue another request
int maxRequests;
if (peer_request_num != -1) {
maxRequests = peer_request_num;
} else {
if (!pt.isSnubbed()) {
if (!endGameMode) {
maxRequests = REQUESTS_MIN
+ (int) (pt.getStats().getDataReceiveRate() / SLOPE_REQUESTS);
if (maxRequests > REQUESTS_MAX || maxRequests < 0)
maxRequests = REQUESTS_MAX;
} else {
maxRequests = 2;
}
} else {
maxRequests = 1;
}
}
// Only loop when 3/5 of the queue is empty, in order to make more consecutive requests,
// and improve cache efficiency
//***************************************************
/* Edit, by isdal
* For f2f we need to be more aggressive here since the bdp is much higher
* Also, peers only get scheduled every second or so forcing us to keep
* a large window to get any speed on multihop links with high bdp
*/
//if (pt.getNbRequests() <= (maxRequests * 3) / 5) {
if (pt.getNbRequests() <= maxRequests) {
//System.err.println(System.currentTimeMillis() + "\trequesting from peer; speed:"+pt.getStats().getDataReceiveRate()+" outstanding requests:"+pt.getNbRequests()+" max:"+maxRequests);
if (!done_priorities) {
done_priorities = true;
computeBasePriorities();
}
int total_allocated = 0;
try {
boolean peer_managing_requests = pt.requestAllocationStarts(startPriorities);
while (pt.isDownloadPossible() && pt.getNbRequests() < maxRequests) {
// is there anything else to download?
int allocated;
if (peer_managing_requests || !endGameMode) {
//***************************************************
// EDIT: lets request 10% of the block at the time to
// decrease the burstiness
//allocated = findPieceToDownload(pt, maxRequests );
allocated = findPieceToDownload(pt, Math.max(1,
Math.round(0.1f * maxRequests)));
//allocated = findPieceToDownload(pt, 1);
//***************************************************
} else {
allocated = findPieceInEndGameMode(pt, maxRequests);
}
if (allocated == 0)
break;
else
total_allocated += allocated;
}
} finally {
pt.requestAllocationComplete();
}
//System.err.println(System.currentTimeMillis() + "\tdone requesting from peer; allocated:"+total_allocated+" outstanding requests:"+pt.getNbRequests()+" max:"+maxRequests);
if (total_allocated == 0) {
// there are various reasons that we might not allocate any requests to a peer
// such as them not having any pieces we're interested in. Keep track of the
// number of consecutive "no requests" outcomes so we can reduce the scheduling
// frequency of such peers
int no_req_count = pt.getConsecutiveNoRequestCount();
if (no_req_count < NO_REQUEST_BACKOFF_MAX_LOOPS) {
pt.setConsecutiveNoRequestCount(no_req_count + 2);
}
// System.out.println( pt.getIp() + ": nb=" + pt.getNbRequests() + ",max=" + maxRequests + ",nrc=" + no_req_count +",loop=" + allocate_request_loop_count);
} else {
pt.setConsecutiveNoRequestCount(0);
}
}
}
}
}
protected int getNextBlockETAFromNow(PEPeerTransport pt) {
long upRate = pt.getStats().getDataReceiveRate();
if (upRate < 1) {
upRate = 1;
}
int next_block_bytes = (pt.getNbRequests() + 1) * DiskManager.BLOCK_SIZE;
return ((int) ((next_block_bytes * 1000) / upRate));
}
/**
* Count current global min avail pieces in progress
* (not counting non-rarest pieces but keep them to compensate high churn, remove completed ones, ignore idle ones)
* @return number of pieces that may be started due to the "rarest first" picking rule
*/
private int calcRarestAllowed() {
// initial adjustment depending on swarm needs
int RarestAllowed = 1;
if (globalMinOthers < 20) {
RarestAllowed = 2;
}
if (globalMinOthers < 8) {
RarestAllowed = 3;
}
if (globalMinOthers < 4) {
RarestAllowed = 4;
}
//avoid rarest start until we have finished some pieces to share
if (nbPiecesDone < 4) {
RarestAllowed = 0;
}
// avoid rarest start during startup (high churn, inaccurate data)
if (SystemTime.getCurrentTime() - peerControl.getTimeStarted() < 180 * 1000) {
RarestAllowed = 0;
}
// more churn avoidance
if (rarestStartedPieces.size() > RarestAllowed + 2) {
RarestAllowed = 0;
}
//decrement number of allowed rarest by already running rarest pieces
PEPiece rarestStarted;
for (int i = 0; i < rarestStartedPieces.size(); i++) {
rarestStarted = (PEPiece) rarestStartedPieces.get(i);
if (pePieces[rarestStarted.getPieceNumber()] == null) {
rarestStartedPieces.remove(i);
i--;
continue;
}
if ((rarestStarted.getAvailability() <= globalMinOthers || globalMinOthers > globalMin)
&& (SystemTime.getCurrentTime()
- rarestStarted.getLastDownloadTime(SystemTime.getCurrentTime()) < 60 * 1000 || rarestStarted.getNbWritten() == 0)
&& !rarestStarted.isDownloaded())
RarestAllowed--;
}
return RarestAllowed;
}
/** This computes the base priority for all pieces that need requesting if there's
* been any availability change or user priority setting changes since the last
* call, which will be most of the time since availability changes so dynamicaly
* It will change startPriorities[] (unless there was nothing to do)
*/
private final void computeBasePriorities() {
final long now = SystemTime.getCurrentTime();
if (now < lastProviderRecalcTime || now - lastProviderRecalcTime > 1000) {
lastProviderRecalcTime = now;
priorityRTAexists = computeProviderPriorities();
}
if (!priorityRTAexists) {
if (startPriorities != null
&& ((now > timeLastPriorities && now < time_last_avail
+ TIME_MIN_PRIORITIES) || (priorityParamChange >= paramPriorityChange
&& priorityFileChange >= filePriorityChange && priorityAvailChange >= availabilityChange)))
return; // *somehow* nothing changed, so nothing to do
}
// store the latest change indicators before we start making dependent calculations so that a
// further change while computing stuff doesn't get lost
timeLastPriorities = now;
priorityParamChange = paramPriorityChange;
priorityFileChange = filePriorityChange;
priorityAvailChange = availabilityChange;
boolean foundPieceToDownload = false;
final int[] newPriorities = new int[nbPieces];
// locals are a tiny bit faster
final boolean firstPiecePriorityL = firstPiecePriority;
final boolean completionPriorityL = completionPriority;
try {
final boolean rarestOverride = calcRarestAllowed() < 1;
// calculate all base (starting) priorities for all pieces needing requesting
final int nbConnects = peerControl.getNbPeers()
+ peerControl.getNbSeeds();
piece_loop:
for (int i = 0; i < nbPieces; i++) {
final DiskManagerPiece dmPiece = dmPieces[i];
if (dmPiece.isDone())
continue; // nothing to do for pieces not needing requesting
//****************************************************
// edit: by isdal
// for auto added downloads
// do not request pieces that already are downloaded by
// other sha1 peers
DownloadManager dm = AzureusCoreImpl.getSingleton().getGlobalManager().getDownloadManager(
new HashWrapper(peerControl.getHash()));
if (dm != null
&& dm.getDownloadState().getBooleanAttribute(
Sha1DownloadManager.ONESWARM_AUTO_ADDED)) {
List peers = peerControl.getPeers();
for (Object o : peers) {
if (o instanceof Sha1Peer) {
Sha1Peer p = (Sha1Peer) o;
if (p.isPieceAvailable(i)) {
dmPiece.setNeeded(false);
continue piece_loop;
}
}
}
}
//****************************************************
int priority = Integer.MIN_VALUE;
int startPriority = Integer.MIN_VALUE;
final DMPieceList pieceList = diskManager.getPieceList(dmPiece.getPieceNumber());
final int pieceListSize = pieceList.size();
//*****************************************************
// Edit: by isdal
// fixed straming, no streaming on files
boolean streaming = false;
if (dm != null) {
// only stream if the stream atttribute is set
if (dm.getDownloadState().getBooleanAttribute(
FileCollection.ONESWARM_STREAM_ATTRIBUTE)) {
streaming = true;
}
}
// *****************************************************
for (int j = 0; j < pieceListSize; j++) {
final DiskManagerFileInfoImpl fileInfo = pieceList.get(j).getFile();
final long downloaded = fileInfo.getDownloaded();
final long length = fileInfo.getLength();
if (length > 0 && downloaded < length && !fileInfo.isSkipped()) {
priority = 0;
// user option "prioritize first and last piece"
// TODO: should prioritize ~10% from edges of file
if (firstPiecePriorityL
&& fileInfo.getNbPieces() > FIRST_PIECE_MIN_NB) {
/* backed out for the moment - reverting to old first/last piece only
int lastFirstPiece = fileInfo.getFirstPieceNumber() + FIRST_PIECE_RANGE_PERCENT * (fileInfo.getLastPieceNumber() - fileInfo.getFirstPieceNumber()) / 100;
if ( (i >=fileInfo.getFirstPieceNumber() && i<= lastFirstPiece ) ) {
priority +=PRIORITY_W_FIRSTLAST + 10 * (lastFirstPiece - i) ;
}
if( i ==fileInfo.getLastPieceNumber() ) {
priority +=PRIORITY_W_FIRSTLAST;
}
*/
if (i == fileInfo.getFirstPieceNumber()
|| i == fileInfo.getLastPieceNumber())
priority += PRIORITY_W_FIRSTLAST;
}
// *****************************************************************
/*
* EDIT: made by isdal@cs.washington.edu
* prioritise first blocks of media files, see top for details
* the first file gets the highest boost and so on...
*
* EDIT: made by piatek (unless this has been overridden by the user -- this
* can make things faster in certain circumstances)
*/
if (streaming) {
//int fileIndex = fileInfo.getIndex();
// int firstPieceInFile = fileInfo.getFirstPieceNumber();
// double currentRelativePieceNumber = i - firstPieceInFile;
double totalPieceNum = diskManager.getNbPieces();
int boost = (int) Math.round(PRIORITY_IN_ORDER_FILES
- (i * (PRIORITY_IN_ORDER_FILES / totalPieceNum)));
priority += boost + PRIORITY_OVERRIDES_RAREST;
// high prio for first/last piece
int firstLastNum = REQUESTS_LAST_BYTES
/ diskManager.getPieceLength();
if (i >= fileInfo.getLastPieceNumber() - firstLastNum
&& i <= fileInfo.getLastPieceNumber()) {
priority += PRIORITY_IN_ORDER_FILES;
}
if (i <= fileInfo.getFirstPieceNumber() + firstLastNum
&& i >= fileInfo.getFirstPieceNumber()) {
priority += PRIORITY_IN_ORDER_FILES;
}
// System.out.println("boosting priority of piece: " +i +
// "(" + currentRelativePieceNumber +") with " + boost);
} else {
// have a slight prio to chunk things into larger blocks
// to improve potential sha1 multisource matching
final int totalRanges = Math.min(diskManager.getNbPieces() / 10, 50);
final int rangePrioMultiplier = PRIORITY_W_COMPLETION/totalRanges;
// create priority ranges to improve sha1 multi source
ArrayList<Integer> rangePrios = new ArrayList<Integer>(totalRanges);
for (int k = 0; k < totalRanges; k++) {
rangePrios.add(k * rangePrioMultiplier);
}
Collections.shuffle(rangePrios, new Random(piecePickerSha1GroupingRandomSeed));
double pieceCount = diskManager.getNbPieces();
int piecesPerRange = (int) Math.ceil(pieceCount / totalRanges);
int range = i / piecesPerRange;
double pieceInRange = i % piecesPerRange;
double piecePrio = pieceInRange / piecesPerRange;
priority += Math.round(rangePrios.get(range) + piecePrio);
}
//******************************************************************
// if the file is high-priority
// startPriority +=(1000 *fileInfo.getPriority()) /255;
if (fileInfo.isPriority()) {
priority += PRIORITY_W_FILE;
if (completionPriorityL) {
final long percent = (1000 * downloaded) / length;
if (percent >= 900)
priority += (PRIORITY_W_COMPLETION * downloaded)
/ diskManager.getTotalLength();
}
}
if (priority > startPriority)
startPriority = priority;
}
}
if (startPriority >= 0) {
dmPiece.setNeeded();
foundPieceToDownload = true;
final int avail = availability[i];
// nbconnects is async calculate so may be wrong - make sure we don't decrease pri by accident
if (avail > 0 && nbConnects > avail) { // boost priority for rarity
startPriority += nbConnects - avail;
// startPriority +=(PRIORITY_W_RARE +peerControl.getNbPeers()) /avail;
// Boost priority even a little more if it's a globally rarest piece
if (!rarestOverride && avail <= globalMinOthers)
startPriority += nbConnects / avail;
}
if (provider_piece_rtas != null) {
if (provider_piece_rtas[i] > 0) {
startPriority = PRIORITY_REALTIME;
}
} else if (provider_piece_priorities != null) {
startPriority += provider_piece_priorities[i];
}
} else {
dmPiece.clearNeeded();
}
newPriorities[i] = startPriority;
}
} catch (Throwable e) {
Debug.printStackTrace(e);
}
if (foundPieceToDownload != hasNeededUndonePiece) {
hasNeededUndonePiece = foundPieceToDownload;
neededUndonePieceChange++;
}
startPriorities = newPriorities;
}
private final boolean isRarestOverride() {
final int nbSeeds = peerControl.getNbSeeds();
final int nbPeers = peerControl.getNbPeers();
final int nbMost = (nbPeers > nbSeeds ? nbPeers : nbSeeds);
final int nbActive = peerControl.getNbActivePieces();
// Dont seek rarest under a few circumstances, so that other factors work better
// never seek rarest when bootstrapping torrent
boolean rarestOverride = nbPiecesDone < 4
|| endGameMode
|| (globalMinOthers > 1 && (nbRarestActive >= nbMost || nbActive >= nbMost));
if (!rarestOverride && nbRarestActive > 1 && globalMinOthers > 1) {
// if already getting some rarest, dont get more if swarm is healthy or too many pieces running
rarestOverride = globalMinOthers > globalMin
|| (globalMinOthers >= (2 * nbSeeds) && (2 * globalMinOthers) >= nbPeers);
// Interest in Rarest pieces (compared to user priority settings) could be influenced by several factors;
// less demand closer to 0% and 100% of torrent completion/farther from 50% of torrent completion
// less demand closer to 0% and 100% of peers interestd in us/farther from 50% of peers interested in us
// less demand the more pieces are in progress (compared to swarm size)
// less demand the farther ahead from absolute global minimum we're at already
// less demand the healthier a swarm is (rarity compared to # seeds and # peers)
}
return rarestOverride;
}
private final SpeedTokenDispenser dispenser = PeerControlSchedulerFactory.getSingleton().getSpeedTokenDispenser();
/**
* @param pt the PEPeerTransport we're working on
* @return int # of blocks that were requested (0 if no requests were made)
*/
protected final int findPieceToDownload(PEPeerTransport pt, int nbWanted) {
final int pieceNumber = getRequestCandidate(pt);
if (pieceNumber < 0) {
// probaly should have found something since chose to try; probably not interested anymore
// (or maybe Needed but not Done pieces are otherwise not requestable)
// pt.checkInterested();
return 0;
}
int peerSpeed = (int) pt.getStats().getDataReceiveRate() / 1000;
if (peerSpeed < 0)
peerSpeed = 0;
if (pt.isSnubbed())
peerSpeed = 0;
final PEPiece pePiece;
if (pePieces[pieceNumber] != null)
pePiece = pePieces[pieceNumber];
else { //create piece manually
int[] peer_priority_offsets = pt.getPriorityOffsets();
int this_offset = peer_priority_offsets == null ? 0
: peer_priority_offsets[pieceNumber];
//create piece manually
pePiece = new PEPieceImpl(pt.getManager(), dmPieces[pieceNumber],
peerSpeed >> 1);
// Assign the created piece to the pieces array.
peerControl.addPiece(pePiece, pieceNumber);
if (startPriorities != null) {
pePiece.setResumePriority(startPriorities[pieceNumber] + this_offset);
} else {
pePiece.setResumePriority(this_offset);
}
if (availability[pieceNumber] <= globalMinOthers)
nbRarestActive++;
}
if (!pt.isLANLocal() || includeLanPeersInReqLimiting
&& !pt.getPeerSource().startsWith("SHA1"))
nbWanted = dispenser.dispense(nbWanted, DiskManager.BLOCK_SIZE);
final int[] blocksFound = pePiece.getAndMarkBlocks(pt, nbWanted,
enable_request_hints);
final int blockNumber = blocksFound[0];
final int nbBlocks = blocksFound[1];
if ((!pt.isLANLocal() || includeLanPeersInReqLimiting)
&& nbBlocks != nbWanted)
dispenser.returnUnusedChunks(nbWanted - nbBlocks, DiskManager.BLOCK_SIZE);
if (nbBlocks <= 0)
return 0;
int requested = 0;
// really try to send the request to the peer
for (int i = 0; i < nbBlocks; i++) {
final int thisBlock = blockNumber + i;
if (pt.request(pieceNumber, thisBlock * DiskManager.BLOCK_SIZE,
pePiece.getBlockSize(thisBlock)) != null) {
requested++;
pt.setLastPiece(pieceNumber);
pePiece.setLastRequestedPeerSpeed(peerSpeed);
// have requested a block
}
}
if (requested > 0 && pePiece.getAvailability() <= globalMinOthers
&& calcRarestAllowed() > 0 && !rarestStartedPieces.contains(pePiece))
rarestStartedPieces.add(pePiece);
return requested;
}
protected final boolean findRTAPieceToDownload(PEPeerTransport pt,
boolean best_uploader, long best_uploader_next_block_eta) {
if (pt == null || pt.getPeerState() != PEPeer.TRANSFERING) {
return (false);
}
final BitFlags peerHavePieces = pt.getAvailable();
if (peerHavePieces == null || peerHavePieces.nbSet <= 0) {
return (false);
}
String rta_log_str = LOG_RTA ? pt.getIp() : null;
try {
final int peerSpeed = (int) pt.getStats().getDataReceiveRate() / 1024; // how many KB/s has the peer has been sending
final int startI = peerHavePieces.start;
final int endI = peerHavePieces.end;
int piece_min_rta_index = -1;
int piece_min_rta_block = 0;
long piece_min_rta_time = Long.MAX_VALUE;
long now = SystemTime.getCurrentTime();
long my_next_block_eta = now + getNextBlockETAFromNow(pt);
for (int i = startI; i <= endI; i++) {
long piece_rta = provider_piece_rtas[i];
if (peerHavePieces.flags[i] && startPriorities[i] == PRIORITY_REALTIME
&& piece_rta > 0) {
final DiskManagerPiece dmPiece = dmPieces[i];
if (!dmPiece.isDownloadable()) {
continue;
}
final PEPiece pePiece = pePieces[i];
if (pePiece != null && pePiece.isDownloaded()) {
continue;
}
Object realtime_data = null;
boolean try_allocate_even_though_late = my_next_block_eta > piece_rta
&& best_uploader_next_block_eta > piece_rta;
if (piece_rta >= piece_min_rta_time) {
// piece is less urgent than an already found one
} else if (my_next_block_eta > piece_rta
&& !(best_uploader || best_uploader_next_block_eta > piece_rta)) {
// only allocate if we have a chance of getting this block in time or we're
// the best uploader we've got/even the best uploader can't get it
// the second part is important for when we get to the point whereby no peers
// can get a block in time. Here we need to allocate someone to get it as
// otherwise we'll concentrate on getting lower priority pieces that we can
// get in time and leave the stuck ones for just the best uploader to get
} else if (pePiece == null
|| (realtime_data = pePiece.getRealTimeData()) == null) {
if (LOG_RTA)
rta_log_str += "{alloc_new=" + i + ",time=" + (piece_rta - now)
+ "}";
// no real-time block allocated yet
piece_min_rta_time = piece_rta;
piece_min_rta_index = i;
} else {
RealTimeData rtd = (RealTimeData) realtime_data;
// check the blocks to see if any are now lagging behind their ETA given current peer speed
List[] peer_requests = rtd.getRequests();
for (int j = 0; j < peer_requests.length; j++) {
if (pePiece.isDownloaded(j) || pePiece.isWritten(j)) {
// this block is already downloaded, ignore
continue;
}
List block_peer_requests = peer_requests[j];
long best_eta = Long.MAX_VALUE;
boolean pt_already_present = false;
// tidy up existing request data
Iterator it = block_peer_requests.iterator();
while (it.hasNext()) {
RealTimePeerRequest pr = (RealTimePeerRequest) it.next();
PEPeerTransport this_pt = pr.getPeer();
if (this_pt.getPeerState() != PEPeer.TRANSFERING) {
// peer's dead
if (LOG_RTA)
rta_log_str += "{peer_dead=" + this_pt.getIp() + "}";
it.remove();
continue;
}
DiskManagerReadRequest this_request = pr.getRequest();
int request_index = this_pt.getRequestIndex(this_request);
if (request_index == -1) {
// request's gone
if (LOG_RTA)
rta_log_str += "{request_lost="
+ this_request.getPieceNumber() + "}";
it.remove();
continue;
}
if (this_pt == pt) {
pt_already_present = true;
break;
}
long this_up_bps = this_pt.getStats().getDataReceiveRate();
if (this_up_bps < 1) {
this_up_bps = 1;
}
int next_block_bytes = (request_index + 1)
* DiskManager.BLOCK_SIZE;
long this_peer_eta = now
+ ((next_block_bytes * 1000) / this_up_bps);
best_eta = Math.min(best_eta, this_peer_eta);
}
// if we've not already requested this block
if (!pt_already_present) {
// and there are no outstanding requests or outstanding requests are lagging
if (block_peer_requests.size() == 0) {
if (LOG_RTA)
rta_log_str += "{alloc as no req=" + i + ",block=" + j
+ ",time=" + (piece_rta - now) + "}";
piece_min_rta_time = piece_rta;
piece_min_rta_index = i;
piece_min_rta_block = j;
break; // earlier blocks always have priority
} else if (best_eta > piece_rta
&& (best_uploader || !try_allocate_even_though_late)) {
// don't re-allocate when we're already running late as we'll end up
// with a lot of discard
if (LOG_RTA)
rta_log_str += "{lagging=" + i + ",block=" + j + ",time="
+ (best_eta - piece_rta) + "}";
// if we can do better than existing best effort allocate
if (my_next_block_eta < best_eta) {
if (LOG_RTA)
rta_log_str += "{taking over, time="
+ (best_eta - my_next_block_eta) + "}";
piece_min_rta_time = piece_rta;
piece_min_rta_index = i;
piece_min_rta_block = j;
break; // earlier blocks always have priority
}
}
}
}
}
}
}
if (piece_min_rta_index != -1) {
if (LOG_RTA)
rta_log_str += ",{select_piece=" + piece_min_rta_index + ",block="
+ piece_min_rta_block + ",time=" + (piece_min_rta_time - now)
+ "}";
if (dispenser.dispense(1, DiskManager.BLOCK_SIZE) == 1
|| (pt.isLANLocal() && !includeLanPeersInReqLimiting)) {
PEPiece pePiece = pePieces[piece_min_rta_index];
if (pePiece == null) {
// create piece manually
pePiece = new PEPieceImpl(pt.getManager(),
dmPieces[piece_min_rta_index], peerSpeed >> 1);
// Assign the created piece to the pieces array.
peerControl.addPiece(pePiece, piece_min_rta_index);
pePiece.setResumePriority(PRIORITY_REALTIME);
if (availability[piece_min_rta_index] <= globalMinOthers) {
nbRarestActive++;
}
}
RealTimeData rtd = (RealTimeData) pePiece.getRealTimeData();
if (rtd == null) {
rtd = new RealTimeData(pePiece);
pePiece.setRealTimeData(rtd);
}
pePiece.getAndMarkBlock(pt, piece_min_rta_block);
DiskManagerReadRequest request = pt.request(piece_min_rta_index,
piece_min_rta_block * DiskManager.BLOCK_SIZE,
pePiece.getBlockSize(piece_min_rta_block));
if (request != null) {
List real_time_requests = rtd.getRequests()[piece_min_rta_block];
real_time_requests.add(new RealTimePeerRequest(pt, request));
pt.setLastPiece(piece_min_rta_index);
pePiece.setLastRequestedPeerSpeed(peerSpeed);
return (true);
} else {
if (LOG_RTA)
rta_log_str += "{request failed}";
if (!pt.isLANLocal() || includeLanPeersInReqLimiting)
dispenser.returnUnusedChunks(1, DiskManager.BLOCK_SIZE);
return (false);
}
} else {
if (LOG_RTA)
rta_log_str += "{dispenser denied}";
return (false);
}
} else {
if (LOG_RTA)
rta_log_str += "{no piece found}";
return (false);
}
} finally {
if (LOG_RTA) {
System.out.println(rta_log_str);
}
}
}
/**
* This method is the downloading core. It decides, for a given peer,
* which block should be requested. Here is the overall algorithm :
* 0. If there a FORCED_PIECE or reserved piece, that will be started/resumed if possible
* 1. Scan all the active pieces and find the rarest piece (and highest priority among equally rarest)
* that can possibly be continued by this peer, if any
* 2. While scanning the active pieces, develop a list of equally highest priority pieces
* (and equally rarest among those) as candidates for starting a new piece
* 3. If it can't find any piece, this means all pieces are
* already downloaded/full requested
* 4. Returns int[] pieceNumber, blockNumber if a request to be made is found,
* or null if none could be found
* @param pc PEPeerTransport to work with
*
* @return int with pieceNumberto be requested or -1 if no request could be found
*/
private final int getRequestCandidate(final PEPeerTransport pt) {
if (pt == null || pt.getPeerState() != PEPeer.TRANSFERING)
return -1;
final BitFlags peerHavePieces = pt.getAvailable();
if (peerHavePieces == null || peerHavePieces.nbSet <= 0)
return -1;
// piece number and its block number that we'll try to DL
int reservedPieceNumber = pt.getReservedPieceNumber();
// If there's a piece seserved to this peer resume it and only it (if possible)
if (reservedPieceNumber >= 0) {
PEPiece pePiece = pePieces[reservedPieceNumber];
if (pePiece != null) {
String peerReserved = pePiece.getReservedBy();
if (peerReserved != null && peerReserved.equals(pt.getIp())) {
if (peerHavePieces.flags[reservedPieceNumber]
&& pePiece.isRequestable()) {
return reservedPieceNumber;
}
}
}
// reserved piece is no longer valid, dump it
pt.setReservedPieceNumber(-1);
// clear the reservation if the piece is still allocated to the peer
if (pePiece != null) {
String peerReserved = pePiece.getReservedBy();
if (peerReserved != null) {
if (peerReserved.equals(pt.getIp())) {
pePiece.setReservedBy(null);
}
}
}
// note, pieces reserved to peers that get disconnected are released in pepeercontrol
reservedPieceNumber = -1;
}
final int peerSpeed = (int) pt.getStats().getDataReceiveRate() / 1024; // how many KB/s has the peer has been sending
final int lastPiece = pt.getLastPiece();
//final boolean rarestOverride = calcRarestAllowed() > 0;
final int nbSnubbed = peerControl.getNbPeersSnubbed();
long resumeMinAvail = Long.MAX_VALUE;
int resumeMaxPriority = Integer.MIN_VALUE;
boolean resumeIsRarest = false; // can the peer continuea piece with lowest avail of all pieces we want
int secondChoiceResume = -1;
BitFlags startCandidates = null;
int startMaxPriority = Integer.MIN_VALUE;
int startMinAvail = Integer.MAX_VALUE;
boolean startIsRarest = false;
boolean forceStart = false;
int priority; // aggregate priority of piece under inspection (start priority or resume priority for pieces to be resumed)
int avail = 0; // the swarm-wide availability level of the piece under inspection
long pieceAge; // how long since the PEPiece first started downloading (requesting, actually)
final boolean rarestAllowed = calcRarestAllowed() > 0;
final int startI = peerHavePieces.start;
final int endI = peerHavePieces.end;
int i;
final int[] peerPriorities = pt.getPriorityOffsets();
final long now = SystemTime.getCurrentTime();
int[] request_hint = pt.getRequestHint();
int request_hint_piece_number;
if (request_hint != null) {
request_hint_piece_number = request_hint[0];
if (dmPieces[request_hint_piece_number].isDone()) {
pt.clearRequestHint();
request_hint_piece_number = -1;
}
} else {
request_hint_piece_number = -1;
}
// Try to continue a piece already loaded, according to priority
for (i = startI; i <= endI; i++) {
// is the piece available from this peer?
if (peerHavePieces.flags[i]) {
priority = startPriorities[i];
final DiskManagerPiece dmPiece = dmPieces[i];
if (priority >= 0 && dmPiece.isDownloadable()) {
if (peerPriorities != null)
priority += peerPriorities[i];
if (enable_request_hints && i == request_hint_piece_number) {
priority += PRIORITY_REQUEST_HINT;
PEPiece pePiece = pePieces[i];
if (pePiece == null) {
forceStart = true;
} else {
if (reservedPieceNumber != i) {
pePiece.setReservedBy(pt.getIp());
pt.setReservedPieceNumber(i);
}
}
}
final PEPiece pePiece = pePieces[i];
// if we are considering realtime pieces then don't bother with non-realtime ones
if (pePiece == null || pePiece.isRequestable()) {
// if this priority exceeds the priority-override threshold then we override rarity
boolean pieceRarestOverride = priority >= PRIORITY_OVERRIDES_RAREST
? true : rarestAllowed;
// piece is: Needed, not fully: Requested, Downloaded, Written, hash-Checking or Done
avail = availability[i];
if (avail == 0) { // maybe we didn't know we could get it before
availability[i] = 1; // but the peer says s/he has it
avail = 1;
}
// is the piece active
if (pePiece != null) {
if (priority != startPriorities[i])
pePiece.setResumePriority(priority); // maintained for display purposes only
boolean startedRarest = rarestStartedPieces.contains(pePiece);
boolean rarestPrio = avail <= globalMinOthers
&& (startedRarest || rarestAllowed);
// How many requests can still be made on this piece?
final int freeReqs = pePiece.getNbUnrequested();
if (freeReqs <= 0) {
pePiece.setRequested();
continue;
}
// Don't touch pieces reserved for others
final String peerReserved = pePiece.getReservedBy();
if (peerReserved != null) {
if (!peerReserved.equals(pt.getIp()))
continue; //reserved to somebody else
// the peer forgot this is reserved to him; re-associate it
pt.setReservedPieceNumber(i);
return i;
}
int pieceSpeed = pePiece.getSpeed();
// ### Piece/Peer speed checks
boolean mayResume = true;
if (pt.isSnubbed()) {
// snubbed peers shouldn't stall fast pieces under ANY condition
// may lead to trouble when the snubbed peer is the only seed, needs further testing
mayResume &= pieceSpeed < 1;
mayResume &= freeReqs > 2 || avail <= nbSnubbed;
} else {
// slower peers are allowed as long as there is enough free room
mayResume &= freeReqs * peerSpeed >= pieceSpeed / 2; //|| rarestPrio;
// prevent non-subbed peers from resuming on snubbed-peer-pieces but still allow them to resume stalled pieces
mayResume &= peerSpeed < 2 || pieceSpeed > 0
|| pePiece.getNbRequests() == 0;
mayResume |= i == pt.getLastPiece();
}
// find a fallback piece in case the peer could theoretically contribute
// to an existing piece but is prevented by the snubbing rules etc.
// this will prevent unecessary piece starting
if (secondChoiceResume == -1
|| avail > availability[secondChoiceResume])
secondChoiceResume = i;
if (!mayResume)
continue;
if (avail > resumeMinAvail)
continue;
priority += pieceSpeed;
priority += (i == lastPiece) ? PRIORITY_W_SAME_PIECE : 0;
// Adjust priority for purpose of continuing pieces
// how long since last written to (if written to)
priority += pePiece.getTimeSinceLastActivity()
/ PRIORITY_DW_STALE;
// how long since piece was started
pieceAge = now - pePiece.getCreationTime();
if (pieceAge > 0)
priority += PRIORITY_W_AGE * pieceAge
/ (PRIORITY_DW_AGE * dmPiece.getNbBlocks());
// how much is already written to disk
priority += (PRIORITY_W_PIECE_DONE * dmPiece.getNbWritten())
/ dmPiece.getNbBlocks();
pePiece.setResumePriority(priority); // this is only for display
if (avail < resumeMinAvail
|| (avail == resumeMinAvail && priority > resumeMaxPriority)) { // this piece seems like best choice for resuming
// Verify it's still possible to get a block to request from this piece
if (pePiece.hasUnrequestedBlock()) { // change the different variables to reflect interest in this block
reservedPieceNumber = i;
resumeMinAvail = avail;
resumeMaxPriority = priority;
resumeMinAvail = avail;
resumeIsRarest = rarestPrio;
}
}
} else if (avail <= globalMinOthers && rarestAllowed) { // rarest pieces only from now on
if (!startIsRarest) { // 1st rarest piece
if (startCandidates == null)
startCandidates = new BitFlags(nbPieces);
startMaxPriority = priority;
startMinAvail = avail;
startIsRarest = avail <= globalMinOthers;
startCandidates.setOnly(i); // clear the non-rarest bits in favor of only rarest
} else if (priority > startMaxPriority) { // continuing rarest, higher priority level
if (startCandidates == null)
startCandidates = new BitFlags(nbPieces);
startMaxPriority = priority;
startCandidates.setOnly(i);
} else if (priority == startMaxPriority) { // continuing rares, same priority level
startCandidates.setEnd(i);
}
} else if (!startIsRarest || !rarestAllowed) { // not doing rarest pieces
if (priority > startMaxPriority) { // new priority level
if (startCandidates == null)
startCandidates = new BitFlags(nbPieces);
startMaxPriority = priority;
startMinAvail = avail;
startIsRarest = avail <= globalMinOthers;
startCandidates.setOnly(i);
} else if (priority == startMaxPriority) { // continuing same priority level
if (avail < startMinAvail) { // same priority, new availability level
startMinAvail = avail;
startIsRarest = avail <= globalMinOthers;
startCandidates.setOnly(i);
} else if (avail == startMinAvail) { // same priority level, same availability level
startCandidates.setEnd(i);
}
}
}
}
}
}
}
/*
// don't start pieces when snubbed, unless it's the only peer with that piece
// returns -1 if no piece to resume is found
if(pt.isSnubbed() && startMinAvail > 1)
return pieceNumber;
*/
if (!forceStart || startCandidates == null || startCandidates.nbSet <= 0) {
// can & should or must resume a piece?
if (reservedPieceNumber >= 0
&& (resumeIsRarest || !startIsRarest || !rarestAllowed
|| startCandidates == null || startCandidates.nbSet <= 0))
return reservedPieceNumber;
if (secondChoiceResume != -1
&& (startCandidates == null || startCandidates.nbSet <= 0)) {
//System.out.println("second choice resume:"+secondChoiceResume);
return secondChoiceResume;
}
// this would allow more non-rarest pieces to be resumed so they get completed so they can be re-shared,
// which can make us intersting to more peers, and generally improve the speed of the swarm,
// however, it can sometimes be hard to get the rarest pieces, such as when a holder unchokes very infrequently
// 20060312[MjrTom] this can lead to TOO many active pieces, so do the extra check with arbitrary # of active pieces
final boolean resumeIsBetter;
if (reservedPieceNumber >= 0 && globalMinOthers > 0
&& peerControl.getNbActivePieces() > 32) // check at arbitrary figure of 32 pieces
{
resumeIsBetter = (resumeMaxPriority / resumeMinAvail) > (startMaxPriority / globalMinOthers);
if (Constants.isCVSVersion() && Logger.isEnabled())
Logger.log(new LogEvent(new Object[] {
pt,
peerControl
}, LOGID, "Start/resume choice; piece #:" + reservedPieceNumber
+ " resumeIsBetter:" + resumeIsBetter + " globalMinOthers="
+ globalMinOthers + " startMaxPriority=" + startMaxPriority
+ " startMinAvail=" + startMinAvail + " resumeMaxPriority="
+ resumeMaxPriority + " resumeMinAvail=" + resumeMinAvail + " : "
+ pt));
if (resumeIsBetter)
return reservedPieceNumber;
}
}
// start a new piece; select piece from start candidates bitfield
return getPieceToStart(startCandidates);
}
/**
* @param startCandidates BitFlags of potential candidates to choose from
* @return int the piece number that was chosen to be started. Note it's possible for
* the chosen piece to have been started already (by another thread).
* This method considers that potential to not be relevant.
*/
protected final int getPieceToStart(final BitFlags startCandidates) {
if (startCandidates == null || startCandidates.nbSet <= 0)
return -1;
if (startCandidates.nbSet == 1)
return startCandidates.start;
final int direction = RandomUtils.generateRandomPlusMinus1();
final int startI;
if (direction == 1)
startI = startCandidates.start;
else
startI = startCandidates.end;
// randomly select a bit flag to be the one
final int targetNb = RandomUtils.generateRandomIntUpto(startCandidates.nbSet);
// figure out the piece number of that selected bitflag
int foundNb = -1;
for (int i = startI; i <= startCandidates.end && i >= startCandidates.start; i += direction) {
// is piece flagged
if (startCandidates.flags[i]) {
foundNb++;
if (foundNb >= targetNb)
return i;
}
}
return -1;
}
@Override
public final boolean hasDownloadablePiece() {
return hasNeededUndonePiece;
}
@Override
public final long getNeededUndonePieceChange() {
return neededUndonePieceChange;
}
private final void checkEndGameMode() {
if (peerControl.getNbSeeds() + peerControl.getNbPeers() < 3) {
return;
}
final long now = SystemTime.getCurrentTime();
if (endGameMode || endGameModeAbandoned) {
if (!endGameModeAbandoned) {
if (now - timeEndGameModeEntered > END_GAME_MODE_TIMEOUT) {
abandonEndGameMode();
}
}
return;
}
int active_pieces = 0;
for (int i = 0; i < nbPieces; i++) {
final DiskManagerPiece dmPiece = dmPieces[i];
// If the piece isn't even Needed, or doesn't need more downloading, simply continue
if (!dmPiece.isDownloadable())
continue;
final PEPiece pePiece = pePieces[i];
if (pePiece != null && pePiece.isDownloaded()) {
continue;
}
// If the piece is being downloaded (fully requested), count it and continue
if (pePiece != null && pePiece.isRequested() && dmPiece.isNeeded()) {
active_pieces++;
continue;
}
// Else, some piece is Needed, not downloaded/fully requested; this isn't end game mode
return;
}
// when doing RTA we don't want EGM to kick in too early as it interfers with progressive
// playback by increasing discard. So we use a smaller trigger value to limit the impact
boolean use_rta_egm = rta_providers.size() > 0;
long remaining = active_pieces * diskManager.getPieceLength();
long trigger = use_rta_egm ? RTA_END_GAME_MODE_SIZE_TRIGGER
: END_GAME_MODE_SIZE_TRIGGER;
// only flip into end-game mode if < trigger size left
if (remaining <= trigger) {
try {
endGameModeChunks_mon.enter();
endGameModeChunks = new ArrayList();
timeEndGameModeEntered = now;
endGameMode = true;
computeEndGameModeChunks();
if (Logger.isEnabled())
Logger.log(new LogEvent(diskManager.getTorrent(), LOGID,
"Entering end-game mode: " + peerControl.getDisplayName()));
// System.out.println("End-Game Mode activated");
} finally {
endGameModeChunks_mon.exit();
}
}
}
private final void computeEndGameModeChunks() {
try {
endGameModeChunks_mon.enter();
for (int i = 0; i < nbPieces; i++) {
final DiskManagerPiece dmPiece = dmPieces[i];
// Pieces not Needed or not needing more downloading are of no interest
if (!dmPiece.isInteresting()) {
continue;
}
PEPiece pePiece = pePieces[i];
if (pePiece == null) {
pePiece = new PEPieceImpl(peerControl, dmPiece, 0);
peerControl.addPiece(pePiece, i);
}
final boolean written[] = dmPiece.getWritten();
if (written == null) {
if (!dmPiece.isDone()) {
for (int j = 0; j < pePiece.getNbBlocks(); j++) {
endGameModeChunks.add(new EndGameModeChunk(pePiece, j));
}
}
} else {
for (int j = 0; j < written.length; j++) {
if (!written[j])
endGameModeChunks.add(new EndGameModeChunk(pePiece, j));
}
}
}
} finally {
endGameModeChunks_mon.exit();
}
}
@Override
public final boolean isInEndGameMode() {
return endGameMode;
}
@Override
public boolean hasEndGameModeBeenAbandoned() {
return (endGameModeAbandoned);
}
/** adds every block from the piece to the list of chuncks to be selected for egm requesting
*
*/
@Override
public final void addEndGameChunks(final PEPiece pePiece) {
if (!endGameMode) {
return;
}
try {
endGameModeChunks_mon.enter();
final int nbChunks = pePiece.getNbBlocks();
for (int i = 0; i < nbChunks; i++) {
endGameModeChunks.add(new EndGameModeChunk(pePiece, i));
}
} finally {
endGameModeChunks_mon.exit();
}
}
protected final int findPieceInEndGameMode(final PEPeerTransport pt,
final int wants) {
if (pt == null || wants <= 0 || pt.getPeerState() != PEPeer.TRANSFERING) {
return 0;
}
// Ok, we try one, if it doesn't work, we'll try another next time
try {
endGameModeChunks_mon.enter();
final int nbChunks = endGameModeChunks.size();
if (nbChunks > 0) {
final int random = RandomUtils.generateRandomIntUpto(nbChunks);
final EndGameModeChunk chunk = (EndGameModeChunk) endGameModeChunks.get(random);
final int pieceNumber = chunk.getPieceNumber();
if (dmPieces[pieceNumber].isWritten(chunk.getBlockNumber())) {
endGameModeChunks.remove(chunk);
return 0;
}
final PEPiece pePiece = pePieces[pieceNumber];
if (pt.isPieceAvailable(pieceNumber)
&& pePiece != null
&& (!pt.isSnubbed() || availability[pieceNumber] <= peerControl.getNbPeersSnubbed())
&& pt.request(pieceNumber, chunk.getOffset(), chunk.getLength()) != null) {
pePiece.setRequested(pt, chunk.getBlockNumber());
pt.setLastPiece(pieceNumber);
return 1;
}
}
// we're here because there are no endgame mode chunks left
// either the torrent is done or something unusual happened
// cleanup anyway and allow a proper re-entry into endgame mode if neccessary
leaveEndGameMode();
} finally {
endGameModeChunks_mon.exit();
}
return 0;
}
@Override
public final void removeFromEndGameModeChunks(final int pieceNumber,
final int offset) {
if (!endGameMode) {
return;
}
try {
endGameModeChunks_mon.enter();
final Iterator iter = endGameModeChunks.iterator();
while (iter.hasNext()) {
EndGameModeChunk chunk = (EndGameModeChunk) iter.next();
if (chunk.equals(pieceNumber, offset)) {
iter.remove();
}
}
} finally {
endGameModeChunks_mon.exit();
}
}
@Override
public final void clearEndGameChunks() {
if (!endGameMode) {
return;
}
try {
endGameModeChunks_mon.enter();
endGameModeChunks.clear();
endGameMode = false;
} finally {
endGameModeChunks_mon.exit();
}
}
protected void leaveEndGameMode() {
try {
endGameModeChunks_mon.enter();
if (endGameMode) {
if (Logger.isEnabled()) {
Logger.log(new LogEvent(diskManager.getTorrent(), LOGID,
"Leaving end-game mode: " + peerControl.getDisplayName()));
}
endGameMode = false;
endGameModeChunks.clear();
timeEndGameModeEntered = 0;
}
} finally {
endGameModeChunks_mon.exit();
}
}
protected void abandonEndGameMode() {
if (!endGameModeAbandoned) {
try {
endGameModeChunks_mon.enter();
endGameModeAbandoned = true;
endGameMode = false;
clearEndGameChunks();
if (Logger.isEnabled())
Logger.log(new LogEvent(diskManager.getTorrent(), LOGID,
"Abandoning end-game mode: " + peerControl.getDisplayName()));
} finally {
endGameModeChunks_mon.exit();
}
}
}
private boolean computeProviderPriorities() {
List p_ps = priority_providers.getList();
if (p_ps.size() == 0) {
if (provider_piece_priorities != null) {
paramPriorityChange++;
provider_piece_priorities = null;
}
} else {
paramPriorityChange++;
provider_piece_priorities = new long[nbPieces];
for (int i = 0; i < p_ps.size(); i++) {
PiecePriorityProvider shaper = (PiecePriorityProvider) p_ps.get(i);
final long[] priorities = shaper.updatePriorities(this);
if (priorities == null) {
continue;
}
for (int j = 0; j < priorities.length; j++) {
long priority = priorities[j];
if (priority != 0) {
provider_piece_priorities[j] += priority;
}
}
}
}
List rta_ps = rta_providers.getList();
if (rta_ps.size() == 0) {
if (provider_piece_rtas != null) {
// coming out of real-time mode - clear down
for (int i = 0; i < pePieces.length; i++) {
PEPiece piece = pePieces[i];
if (piece != null) {
piece.setRealTimeData(null);
}
}
provider_piece_rtas = null;
}
return (false);
} else {
boolean has_rta = false;
// prolly more efficient to reallocate than reset to 0
provider_piece_rtas = new long[nbPieces];
for (int i = 0; i < rta_ps.size(); i++) {
PieceRTAProvider shaper = (PieceRTAProvider) rta_ps.get(i);
final long[] offsets = shaper.updateRTAs(this);
if (offsets == null) {
continue;
}
for (int j = 0; j < offsets.length; j++) {
long rta = offsets[j];
if (rta > 0) {
if (provider_piece_rtas[j] == 0) {
provider_piece_rtas[j] = rta;
} else {
provider_piece_rtas[j] = Math.min(provider_piece_rtas[j], rta);
}
has_rta = true;
}
}
}
return (has_rta);
}
}
@Override
public void addRTAProvider(PieceRTAProvider provider) {
rta_providers.add(provider);
Iterator it = listeners.iterator();
while (it.hasNext()) {
try {
((PiecePickerListener) it.next()).providerAdded(provider);
} catch (Throwable e) {
Debug.printStackTrace(e);
}
}
// we don't want end-game mode kicking in and screwing with the RTA logic
// at the end of the download. simplest way is to abandon it for this
// download. if someone gives up RT later then the download will just complete
// without EGM
leaveEndGameMode();
}
@Override
public void removeRTAProvider(PieceRTAProvider provider) {
rta_providers.remove(provider);
Iterator it = listeners.iterator();
while (it.hasNext()) {
try {
((PiecePickerListener) it.next()).providerRemoved(provider);
} catch (Throwable e) {
Debug.printStackTrace(e);
}
}
}
@Override
public List getRTAProviders() {
return (rta_providers.getList());
}
@Override
public void addPriorityProvider(PiecePriorityProvider provider) {
priority_providers.add(provider);
Iterator it = listeners.iterator();
while (it.hasNext()) {
try {
((PiecePickerListener) it.next()).providerAdded(provider);
} catch (Throwable e) {
Debug.printStackTrace(e);
}
}
}
@Override
public void removePriorityProvider(PiecePriorityProvider provider) {
priority_providers.remove(provider);
Iterator it = listeners.iterator();
while (it.hasNext()) {
try {
((PiecePickerListener) it.next()).providerRemoved(provider);
} catch (Throwable e) {
Debug.printStackTrace(e);
}
}
}
@Override
public List getPriorityProviders() {
return (rta_providers.getList());
}
@Override
public void addListener(PiecePickerListener listener) {
listeners.add(listener);
Iterator it = rta_providers.iterator();
while (it.hasNext()) {
listener.providerAdded((PieceRTAProvider) it.next());
}
}
@Override
public void removeListener(PiecePickerListener listener) {
listeners.remove(listener);
}
/**
* An instance of this listener is registered with peerControl
* Through this, we learn of peers joining and leaving
* and attach/detach listeners to them
*/
private class PEPeerManagerListenerImpl
implements PEPeerManagerListener
{
@Override
public final void peerAdded(final PEPeerManager manager, PEPeer peer) {
PEPeerListenerImpl peerListener;
peerListener = (PEPeerListenerImpl) peerListeners.get(peer);
if (peerListener == null) {
peerListener = new PEPeerListenerImpl();
peerListeners.put(peer, peerListener);
}
peer.addListener(peerListener);
}
@Override
public final void peerRemoved(final PEPeerManager manager, PEPeer peer) {
// remove this listener from list of listeners and from the peer
final PEPeerListenerImpl peerListener = (PEPeerListenerImpl) peerListeners.remove(peer);
peer.removeListener(peerListener);
}
@Override
public void destroyed() {
}
}
/**
* An instance of this listener is registered with each peer
*/
private class PEPeerListenerImpl
implements PEPeerListener
{
@Override
public final void stateChanged(PEPeer peer, final int newState) {
/*
switch (newState)
{
case PEPeer.CONNECTING:
return;
case PEPeer.HANDSHAKING:
return;
case PEPeer.TRANSFERING:
return;
case PEPeer.CLOSING:
return;
case PEPeer.DISCONNECTED:
return;
}
*/
}
@Override
public final void sentBadChunk(final PEPeer peer, final int piece_num,
final int total_bad_chunks) {
/* nothing to do here */
}
@Override
public final void addAvailability(final PEPeer peer,
final BitFlags peerHavePieces) {
if (peerHavePieces == null || peerHavePieces.nbSet <= 0)
return;
try {
availabilityMon.enter();
if (availabilityAsynch == null) {
availabilityAsynch = availability.clone();
}
for (int i = peerHavePieces.start; i <= peerHavePieces.end; i++) {
if (peerHavePieces.flags[i]) {
++availabilityAsynch[i];
}
}
availabilityChange++;
} finally {
availabilityMon.exit();
}
}
/**
* Takes away the given pieces from global availability
* @param PEPeer peer this is about
* @param peerHasPieces BitFlags of the pieces
*/
@Override
public final void removeAvailability(final PEPeer peer,
final BitFlags peerHavePieces) {
if (peerHavePieces == null || peerHavePieces.nbSet <= 0)
return;
try {
availabilityMon.enter();
if (availabilityAsynch == null) {
availabilityAsynch = availability.clone();
}
for (int i = peerHavePieces.start; i <= peerHavePieces.end; i++) {
if (peerHavePieces.flags[i]) {
if (availabilityAsynch[i] > (dmPieces[i].isDone() ? 1 : 0))
--availabilityAsynch[i];
else
availabilityDrift++;
}
}
availabilityChange++;
} finally {
availabilityMon.exit();
}
}
}
/**
* An instance of this listener is registered with peerControl
* @author MjrTom
*/
private class DiskManagerListenerImpl
implements DiskManagerListener
{
@Override
public final void stateChanged(int oldState, int newState) {
//starting torrent
}
@Override
public final void filePriorityChanged(DiskManagerFileInfo file) {
// record that user-based priorities changed
filePriorityChange++; // this is a user's priority change event
// only need to re-calc Needed on file's pieces; priority is calculated seperatly
boolean foundPieceToDownload = false;
// if didn't have anything to do before, now only need to check if we need
// to DL from this file, but if had something to do before,
// must rescan all pieces to see if now nothing to do
final int startI;
final int endI;
if (hasNeededUndonePiece) {
startI = 0;
endI = nbPieces;
} else {
startI = file.getFirstPieceNumber();
endI = file.getLastPieceNumber() + 1;
}
for (int i = startI; i < endI; i++) {
final DiskManagerPiece dmPiece = dmPieces[i];
if (!dmPiece.isDone())
foundPieceToDownload |= dmPiece.calcNeeded();
}
if (foundPieceToDownload ^ hasNeededUndonePiece) {
hasNeededUndonePiece = foundPieceToDownload;
neededUndonePieceChange++;
}
}
@Override
public final void pieceDoneChanged(DiskManagerPiece dmPiece) {
final int pieceNumber = dmPiece.getPieceNumber();
if (dmPiece.isDone()) {
addHavePiece(null, pieceNumber);
nbPiecesDone++;
if (nbPiecesDone >= nbPieces)
checkDownloadablePiece();
} else {
try {
availabilityMon.enter();
if (availabilityAsynch == null) {
availabilityAsynch = availability.clone();
}
if (availabilityAsynch[pieceNumber] > 0)
--availabilityAsynch[pieceNumber];
else
availabilityDrift++;
availabilityChange++;
} finally {
availabilityMon.exit();
}
nbPiecesDone--;
if (dmPiece.calcNeeded() && !hasNeededUndonePiece) {
hasNeededUndonePiece = true;
neededUndonePieceChange++;
}
}
}
@Override
public final void fileAccessModeChanged(DiskManagerFileInfo file,
int old_mode, int new_mode) {
//file done (write to read)
//starting to upload from the file (read to write)
}
}
@Override
public void destroy() {
}
@Override
public String getPieceString(int piece_number) {
String str;
long priority = startPriorities == null ? 0 : startPriorities[piece_number];
if (priority == PRIORITY_REALTIME) {
long[] rta = provider_piece_rtas;
str = "pri=rta:"
+ (rta == null ? "?"
: ("" + (rta[piece_number] - SystemTime.getCurrentTime())));
} else {
str = "pri=" + priority;
}
long[] exts = provider_piece_priorities;
if (exts != null) {
str += ",ext=" + exts[piece_number];
}
return (str);
}
@Override
public void generateEvidence(IndentWriter writer) {
writer.println("Piece Picker");
try {
writer.indent();
writer.println("globalAvail: " + globalAvail);
writer.println("globalAvgAvail: " + globalAvgAvail);
writer.println("nbRarestActive: " + nbRarestActive);
writer.println("globalMin: " + globalMin);
writer.println("globalMinOthers: " + globalMinOthers);
writer.println("hasNeededUndonePiece: " + hasNeededUndonePiece);
writer.println("endGameMode: " + endGameMode);
writer.println("endGameModeAbandoned: " + endGameModeAbandoned);
writer.println("endGameModeChunks: " + endGameModeChunks);
} finally {
writer.exdent();
}
}
protected class RealTimeData
{
private final List[] peer_requests;
protected RealTimeData(PEPiece piece) {
int nb = piece.getNbBlocks();
peer_requests = new List[nb];
for (int i = 0; i < peer_requests.length; i++) {
peer_requests[i] = new ArrayList(1);
}
}
public final List[] getRequests() {
return (peer_requests);
}
}
protected class RealTimePeerRequest
{
private final PEPeerTransport peer;
private final DiskManagerReadRequest request;
protected RealTimePeerRequest(PEPeerTransport _peer,
DiskManagerReadRequest _request) {
peer = _peer;
request = _request;
}
protected PEPeerTransport getPeer() {
return (peer);
}
protected DiskManagerReadRequest getRequest() {
return (request);
}
}
}