CANCEL_HISTORY_SEC = 60
};
+const tr_peer_event TR_PEER_EVENT_INIT = { 0, 0, NULL, 0, 0, 0.0f, 0, FALSE, 0 };
/**
***
int16_t requestCount;
};
+enum piece_sort_state
+{
+ PIECES_UNSORTED,
+ PIECES_SORTED_BY_INDEX,
+ PIECES_SORTED_BY_WEIGHT
+};
+
/** @brief Opaque, per-torrent data structure for peer connection information */
typedef struct tr_torrent_peers
{
struct weighted_piece * pieces;
int pieceCount;
+ enum piece_sort_state pieceSortState;
+
+ /* An array of pieceCount items stating how many peers have each piece.
+ This is used to help us for downloading pieces "rarest first."
+ This may be NULL if we don't have metainfo yet, or if we're not
+ downloading and don't care about rarity */
+ uint16_t * pieceReplication;
+ size_t pieceReplicationSize;
int interestedCount;
int maxPeers;
tr_free( peer );
}
-static void
-removePeer( Torrent * t, tr_peer * peer )
+static tr_bool
+replicationExists( const Torrent * t )
{
- tr_peer * removed;
- struct peer_atom * atom = peer->atom;
-
- assert( torrentIsLocked( t ) );
- assert( atom );
-
- atom->time = tr_time( );
+ return t->pieceReplication != NULL;
+}
- removed = tr_ptrArrayRemoveSorted( &t->peers, peer, peerCompare );
- assert( removed == peer );
- peerDestructor( t, removed );
+static void
+replicationFree( Torrent * t )
+{
+ tr_free( t->pieceReplication );
+ t->pieceReplication = NULL;
+ t->pieceReplicationSize = 0;
}
static void
-removeAllPeers( Torrent * t )
+replicationNew( Torrent * t )
{
- while( !tr_ptrArrayEmpty( &t->peers ) )
- removePeer( t, tr_ptrArrayNth( &t->peers, 0 ) );
+ tr_piece_index_t piece_i;
+ const tr_piece_index_t piece_count = t->tor->info.pieceCount;
+ tr_peer ** peers = (tr_peer**) tr_ptrArrayBase( &t->peers );
+ const int peer_count = tr_ptrArraySize( &t->peers );
+
+ assert( !replicationExists( t ) );
+
+ t->pieceReplicationSize = piece_count;
+ t->pieceReplication = tr_new0( uint16_t, piece_count );
+
+ for( piece_i=0; piece_i<piece_count; ++piece_i )
+ {
+ int peer_i;
+ uint16_t r = 0;
+
+ for( peer_i=0; peer_i<peer_count; ++peer_i )
+ if( tr_bitsetHasFast( &peers[peer_i]->have, piece_i ) )
+ ++r;
+
+ t->pieceReplication[piece_i] = r;
+ }
}
static void
tr_ptrArrayDestruct( &t->outgoingHandshakes, NULL );
tr_ptrArrayDestruct( &t->peers, NULL );
+ replicationFree( t );
+
tr_free( t->requests );
tr_free( t->pieces );
tr_free( t );
}
}
-/**
-*** struct weighted_piece
-**/
+static int
+countActiveWebseeds( const Torrent * t )
+{
+ int activeCount = 0;
+ const tr_webseed ** w = (const tr_webseed **) tr_ptrArrayBase( &t->webseeds );
+ const tr_webseed ** const wend = w + tr_ptrArraySize( &t->webseeds );
-enum
+ for( ; w!=wend; ++w )
+ if( tr_webseedIsActive( *w ) )
+ ++activeCount;
+
+ return activeCount;
+}
+
+static void
+updateEndgame( Torrent * t )
{
- PIECES_UNSORTED,
- PIECES_SORTED_BY_INDEX,
- PIECES_SORTED_BY_WEIGHT
-};
+ const tr_torrent * tor = t->tor;
+ const tr_block_index_t missing = tr_cpBlocksMissing( &tor->completion );
+
+ assert( t->requestCount >= 0 );
+
+ if( (tr_block_index_t) t->requestCount < missing )
+ {
+ /* not in endgame */
+ t->endgame = 0;
+ }
+ else if( !t->endgame ) /* only recalculate when endgame first begins */
+ {
+ int numDownloading = 0;
+ const tr_peer ** p = (const tr_peer **) tr_ptrArrayBase( &t->peers );
+ const tr_peer ** const pend = p + tr_ptrArraySize( &t->peers );
+
+ /* add the active bittorrent peers... */
+ for( ; p!=pend; ++p )
+ if( (*p)->pendingReqsToPeer > 0 )
+ ++numDownloading;
+
+ /* add the active webseeds... */
+ numDownloading += countActiveWebseeds( t );
+
+ /* average number of pending requests per downloading peer */
+ t->endgame = t->requestCount / MAX( numDownloading, 1 );
+ }
+}
+
+
+/****
+*****
+***** Piece List Manipulation / Accessors
+*****
+****/
+
+static inline void
+invalidatePieceSorting( Torrent * t )
+{
+ t->pieceSortState = PIECES_UNSORTED;
+}
const tr_torrent * weightTorrent;
+const uint16_t * weightReplication;
+
+static void
+setComparePieceByWeightTorrent( Torrent * t )
+{
+ if( !replicationExists( t ) )
+ replicationNew( t );
+
+ weightTorrent = t->tor;
+ weightReplication = t->pieceReplication;
+}
+
/* we try to create a "weight" s.t. high-priority pieces come before others,
* and that partially-complete pieces come before empty ones. */
static int
const struct weighted_piece * b = vb;
int ia, ib, missing, pending;
const tr_torrent * tor = weightTorrent;
+ const uint16_t * rep = weightReplication;
/* primary key: weight */
missing = tr_cpMissingBlocksInPiece( &tor->completion, a->index );
if( ia > ib ) return -1;
if( ia < ib ) return 1;
- /* tertiary key: random */
+ /* tertiary key: rarest first. */
+ ia = rep[a->index];
+ ib = rep[b->index];
+ if( ia < ib ) return -1;
+ if( ia > ib ) return 1;
+
+ /* quaternary key: random */
if( a->salt < b->salt ) return -1;
if( a->salt > b->salt ) return 1;
}
static void
-pieceListSort( Torrent * t, int mode )
+pieceListSort( Torrent * t, enum piece_sort_state state )
{
- assert( mode==PIECES_SORTED_BY_INDEX
- || mode==PIECES_SORTED_BY_WEIGHT );
+ assert( state==PIECES_SORTED_BY_INDEX
+ || state==PIECES_SORTED_BY_WEIGHT );
- weightTorrent = t->tor;
- if( mode == PIECES_SORTED_BY_WEIGHT )
+ if( state == PIECES_SORTED_BY_WEIGHT )
+ {
+ setComparePieceByWeightTorrent( t );
qsort( t->pieces, t->pieceCount, sizeof( struct weighted_piece ), comparePieceByWeight );
+ }
else
qsort( t->pieces, t->pieceCount, sizeof( struct weighted_piece ), comparePieceByIndex );
-}
-static int
-countActiveWebseeds( const Torrent * t )
-{
- int activeCount = 0;
- const tr_webseed ** w = (const tr_webseed **) tr_ptrArrayBase( &t->webseeds );
- const tr_webseed ** const wend = w + tr_ptrArraySize( &t->webseeds );
-
- for( ; w!=wend; ++w )
- if( tr_webseedIsActive( *w ) )
- ++activeCount;
-
- return activeCount;
-}
-
-static void
-updateEndgame( Torrent * t )
-{
- const tr_torrent * tor = t->tor;
- const tr_block_index_t missing = tr_cpBlocksMissing( &tor->completion );
-
- assert( t->requestCount >= 0 );
-
- if( (tr_block_index_t) t->requestCount < missing )
- {
- /* not in endgame */
- t->endgame = 0;
- }
- else if( !t->endgame ) /* only recalculate when endgame first begins */
- {
- int numDownloading = 0;
- const tr_peer ** p = (const tr_peer **) tr_ptrArrayBase( &t->peers );
- const tr_peer ** const pend = p + tr_ptrArraySize( &t->peers );
-
- /* add the active bittorrent peers... */
- for( ; p!=pend; ++p )
- if( (*p)->pendingReqsToPeer > 0 )
- ++numDownloading;
-
- /* add the active webseeds... */
- numDownloading += countActiveWebseeds( t );
-
- /* average number of pending requests per downloading peer */
- t->endgame = t->requestCount / MAX( numDownloading, 1 );
- }
+ t->pieceSortState = state;
}
/**
- * This function is useful for sanity checking,
- * but is too expensive even for nightly builds...
+ * These functions are useful for testing, but too expensive for nightly builds.
* let's leave it disabled but add an easy hook to compile it back in
*/
#if 0
+#define assertWeightedPiecesAreSorted(t)
+#define assertReplicationCountIsExact(t)
+#else
static void
assertWeightedPiecesAreSorted( Torrent * t )
{
if( !t->endgame )
{
int i;
- weightTorrent = t->tor;
+ setComparePieceByWeightTorrent( t );
for( i=0; i<t->pieceCount-1; ++i )
assert( comparePieceByWeight( &t->pieces[i], &t->pieces[i+1] ) <= 0 );
}
}
-#else
-#define assertWeightedPiecesAreSorted(t)
+static void
+assertReplicationCountIsExact( Torrent * t )
+{
+ /* This assert might fail due to errors of implementations in other
+ * clients. It happens when receiving duplicate bitfields/HaveAll/HaveNone
+ * from a client. If a such a behavior is noticed,
+ * a bug report should be filled to the faulty client. */
+
+ size_t piece_i;
+ const uint16_t * rep = t->pieceReplication;
+ const size_t piece_count = t->pieceReplicationSize;
+ const tr_peer ** peers = (const tr_peer**) tr_ptrArrayBase( &t->peers );
+ const int peer_count = tr_ptrArraySize( &t->peers );
+
+ assert( piece_count == t->tor->info.pieceCount );
+
+ for( piece_i=0; piece_i<piece_count; ++piece_i )
+ {
+ int peer_i;
+ uint16_t r = 0;
+
+ for( peer_i=0; peer_i<peer_count; ++peer_i )
+ if( tr_bitsetHasFast( &peers[peer_i]->have, piece_i ) )
+ ++r;
+
+ assert( rep[piece_i] == r );
+ }
+}
#endif
static struct weighted_piece *
static void
pieceListRebuild( Torrent * t )
{
- assertWeightedPiecesAreSorted( t );
if( !tr_torrentIsSeed( t->tor ) )
{
{
struct weighted_piece * p;
- assertWeightedPiecesAreSorted( t );
-
if(( p = pieceListLookup( t, piece )))
{
const int pos = p - t->pieces;
t->pieces = NULL;
}
}
-
- assertWeightedPiecesAreSorted( t );
}
static void
/* is the torrent already sorted? */
pos = p - t->pieces;
- weightTorrent = t->tor;
+ setComparePieceByWeightTorrent( t );
if( isSorted && ( pos > 0 ) && ( comparePieceByWeight( p-1, p ) > 0 ) )
isSorted = FALSE;
if( isSorted && ( pos < t->pieceCount - 1 ) && ( comparePieceByWeight( p, p+1 ) > 0 ) )
isSorted = FALSE;
+ if( t->pieceSortState != PIECES_SORTED_BY_WEIGHT )
+ {
+ pieceListSort( t, PIECES_SORTED_BY_WEIGHT);
+ isSorted = TRUE;
+ }
+
/* if it's not sorted, move it around */
if( !isSorted )
{
struct weighted_piece * p;
const tr_piece_index_t index = tr_torBlockPiece( t->tor, block );
- assertWeightedPiecesAreSorted( t );
-
if( ((p = pieceListLookup( t, index ))) && ( p->requestCount > 0 ) )
{
--p->requestCount;
pieceListResortPiece( t, p );
}
+}
- assertWeightedPiecesAreSorted( t );
+
+/****
+*****
+***** Replication count ( for rarest first policy )
+*****
+****/
+
+/**
+ * Increase the replication count of this piece and sort it if the
+ * piece list is already sorted
+ */
+static void
+tr_incrReplicationOfPiece( Torrent * t, const size_t index )
+{
+ assert( replicationExists( t ) );
+ assert( t->pieceReplicationSize == t->tor->info.pieceCount );
+
+ /* One more replication of this piece is present in the swarm */
+ ++t->pieceReplication[index];
+
+ /* we only resort the piece if the list is already sorted */
+ if( t->pieceSortState == PIECES_SORTED_BY_WEIGHT )
+ pieceListResortPiece( t, pieceListLookup( t, index ) );
+}
+
+/**
+ * Increases the replication count of pieces present in the bitfield
+ */
+static void
+tr_incrReplicationFromBitfield( Torrent * t, const tr_bitfield * b )
+{
+ size_t i;
+ uint16_t * rep = t->pieceReplication;
+ const size_t n = t->tor->info.pieceCount;
+
+ assert( replicationExists( t ) );
+ assert( n == t->pieceReplicationSize );
+ assert( tr_bitfieldTestFast( b, n-1 ) );
+
+ if( tr_bitfieldTestFast( b, n-1 ) )
+ for( i=0; i<n; ++i )
+ if( tr_bitfieldHasFast( b, i ) )
+ ++rep[i];
+
+ if( t->pieceSortState == PIECES_SORTED_BY_WEIGHT )
+ invalidatePieceSorting( t );
+}
+
+/**
+ * Increase the replication count of every piece
+ */
+static void
+tr_incrReplication( Torrent * t )
+{
+ int i;
+ const int n = t->pieceReplicationSize;
+
+ assert( replicationExists( t ) );
+ assert( t->pieceReplicationSize == t->tor->info.pieceCount );
+
+ for( i=0; i<n; ++i )
+ ++t->pieceReplication[i];
+}
+
+/**
+ * Decrease the replication count of pieces present in the bitset.
+ */
+static void
+tr_decrReplicationFromBitset( Torrent * t, const tr_bitset * bitset )
+{
+ int i;
+ const int n = t->pieceReplicationSize;
+
+ assert( replicationExists( t ) );
+ assert( t->pieceReplicationSize == t->tor->info.pieceCount );
+
+ if( bitset->haveAll )
+ {
+ for( i=0; i<n; ++i )
+ --t->pieceReplication[i];
+ }
+ else if ( !bitset->haveNone )
+ {
+ const tr_bitfield * const b = &bitset->bitfield;
+
+ if( tr_bitfieldTestFast( b, n-1 ) )
+ for( i=0; i<n; ++i )
+ if( tr_bitfieldHasFast( b, i ) )
+ --t->pieceReplication[i];
+
+ if( t->pieceSortState == PIECES_SORTED_BY_WEIGHT )
+ invalidatePieceSorting( t );
+ }
}
/**
/* walk through the pieces and find blocks that should be requested */
got = 0;
t = tor->torrentPeers;
- assertWeightedPiecesAreSorted( t );
/* prep the pieces list */
if( t->pieces == NULL )
pieceListRebuild( t );
+ if( t->pieceSortState != PIECES_SORTED_BY_WEIGHT )
+ pieceListSort( t, PIECES_SORTED_BY_WEIGHT );
+
+ assertReplicationCountIsExact( t );
+ assertWeightedPiecesAreSorted( t );
+
updateEndgame( t );
pieces = t->pieces;
for( i=0; i<t->pieceCount && got<numwant; ++i )
/* not enough requests || last piece modified */
if ( i == t->pieceCount ) --i;
- weightTorrent = t->tor;
+ setComparePieceByWeightTorrent( t );
while( --i >= 0 )
{
tr_bool exact;
break;
}
+ case TR_PEER_CLIENT_GOT_HAVE:
+ if( replicationExists( t ) ) {
+ tr_incrReplicationOfPiece( t, e->pieceIndex );
+ assertReplicationCountIsExact( t );
+ }
+ break;
+
+ case TR_PEER_CLIENT_GOT_HAVE_ALL:
+ if( replicationExists( t ) ) {
+ tr_incrReplication( t );
+ assertReplicationCountIsExact( t );
+ }
+ break;
+
+ case TR_PEER_CLIENT_GOT_HAVE_NONE:
+ /* noop */
+ break;
+
+ case TR_PEER_CLIENT_GOT_BITFIELD:
+ assert( e->bitfield != NULL );
+ if( replicationExists( t ) ) {
+ tr_incrReplicationFromBitfield( t, e->bitfield );
+ assertReplicationCountIsExact( t );
+ }
+ break;
+
case TR_PEER_CLIENT_GOT_REJ:
removeRequestFromTables( t, _tr_block( t->tor, e->pieceIndex, e->offset ), peer );
break;
t->isRunning = TRUE;
t->maxPeers = t->tor->maxConnectedPeers;
+ t->pieceSortState = PIECES_UNSORTED;
rechokePulse( 0, 0, t->manager );
}
t->isRunning = FALSE;
+ replicationFree( t );
+ invalidatePieceSorting( t );
+
/* disconnect the peers. */
for( i=0, n=tr_ptrArraySize( &t->peers ); i<n; ++i )
peerDestructor( t, tr_ptrArrayNth( &t->peers, i ) );
return sec;
}
+static void
+removePeer( Torrent * t, tr_peer * peer )
+{
+ tr_peer * removed;
+ struct peer_atom * atom = peer->atom;
+
+ assert( torrentIsLocked( t ) );
+ assert( atom );
+
+ atom->time = tr_time( );
+
+ removed = tr_ptrArrayRemoveSorted( &t->peers, peer, peerCompare );
+
+ if( replicationExists( t ) )
+ tr_decrReplicationFromBitset( t, &peer->have );
+
+ assert( removed == peer );
+ peerDestructor( t, removed );
+}
+
static void
closePeer( Torrent * t, tr_peer * peer )
{
removePeer( t, peer );
}
+static void
+removeAllPeers( Torrent * t )
+{
+ while( !tr_ptrArrayEmpty( &t->peers ) )
+ removePeer( t, tr_ptrArrayNth( &t->peers, 0 ) );
+}
+
static void
closeBadPeers( Torrent * t, const uint64_t now_msec, const time_t now_sec )
{
*/
struct tr_peermsgs
{
+ tr_bool got_a_bitfield_or_have_all_or_have_none;
tr_bool peerSupportsPex;
tr_bool peerSupportsMetadataXfer;
tr_bool clientSentLtepHandshake;
*** EVENTS
**/
-static const tr_peer_event blankEvent = { 0, 0, 0, 0, 0.0f, 0, 0, 0 };
-
static void
publish( tr_peermsgs * msgs, tr_peer_event * e )
{
static void
fireError( tr_peermsgs * msgs, int err )
{
- tr_peer_event e = blankEvent;
+ tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_ERROR;
e.err = err;
publish( msgs, &e );
static void
firePeerProgress( tr_peermsgs * msgs )
{
- tr_peer_event e = blankEvent;
+ tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_PEER_PROGRESS;
e.progress = msgs->peer->progress;
publish( msgs, &e );
static void
fireGotBlock( tr_peermsgs * msgs, const struct peer_request * req )
{
- tr_peer_event e = blankEvent;
+ tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_BLOCK;
e.pieceIndex = req->index;
e.offset = req->offset;
static void
fireGotRej( tr_peermsgs * msgs, const struct peer_request * req )
{
- tr_peer_event e = blankEvent;
+ tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_REJ;
e.pieceIndex = req->index;
e.offset = req->offset;
static void
fireGotChoke( tr_peermsgs * msgs )
{
- tr_peer_event e = blankEvent;
+ tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_CHOKE;
publish( msgs, &e );
}
+static void
+fireClientGotHaveAll( tr_peermsgs * msgs )
+{
+ tr_peer_event e = TR_PEER_EVENT_INIT;
+ e.eventType = TR_PEER_CLIENT_GOT_HAVE_ALL;
+ publish( msgs, &e );
+}
+
+static void
+fireClientGotHaveNone( tr_peermsgs * msgs )
+{
+ tr_peer_event e = TR_PEER_EVENT_INIT;
+ e.eventType = TR_PEER_CLIENT_GOT_HAVE_NONE;
+ publish( msgs, &e );
+}
+
static void
fireClientGotData( tr_peermsgs * msgs,
uint32_t length,
int wasPieceData )
{
- tr_peer_event e = blankEvent;
+ tr_peer_event e = TR_PEER_EVENT_INIT;
e.length = length;
e.eventType = TR_PEER_CLIENT_GOT_DATA;
static void
fireClientGotSuggest( tr_peermsgs * msgs, uint32_t pieceIndex )
{
- tr_peer_event e = blankEvent;
+ tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_SUGGEST;
e.pieceIndex = pieceIndex;
publish( msgs, &e );
static void
fireClientGotPort( tr_peermsgs * msgs, tr_port port )
{
- tr_peer_event e = blankEvent;
+ tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_PORT;
e.port = port;
publish( msgs, &e );
static void
fireClientGotAllowedFast( tr_peermsgs * msgs, uint32_t pieceIndex )
{
- tr_peer_event e = blankEvent;
+ tr_peer_event e = TR_PEER_EVENT_INIT;
e.eventType = TR_PEER_CLIENT_GOT_ALLOWED_FAST;
e.pieceIndex = pieceIndex;
publish( msgs, &e );
}
+static void
+fireClientGotBitfield( tr_peermsgs * msgs, tr_bitfield * bitfield )
+{
+ tr_peer_event e = TR_PEER_EVENT_INIT;
+ e.eventType = TR_PEER_CLIENT_GOT_BITFIELD;
+ e.bitfield = bitfield;
+ publish( msgs, &e );
+}
+
+static void
+fireClientGotHave( tr_peermsgs * msgs, tr_piece_index_t index )
+{
+ tr_peer_event e = TR_PEER_EVENT_INIT;
+ e.eventType = TR_PEER_CLIENT_GOT_HAVE;
+ e.pieceIndex = index;
+ publish( msgs, &e );
+}
+
static void
firePeerGotData( tr_peermsgs * msgs,
uint32_t length,
int wasPieceData )
{
- tr_peer_event e = blankEvent;
+ tr_peer_event e = TR_PEER_EVENT_INIT;
e.length = length;
e.eventType = TR_PEER_PEER_GOT_DATA;
fireError( msgs, ERANGE );
return READ_ERR;
}
- if( tr_bitsetAdd( &msgs->peer->have, ui32 ) )
- {
- fireError( msgs, ERANGE );
- return READ_ERR;
- }
+
+ /* a peer can send the same HAVE message twice... */
+ if( !tr_bitsetHas( &msgs->peer->have, ui32 ) )
+ if( !tr_bitsetAdd( &msgs->peer->have, ui32 ) )
+ fireClientGotHave( msgs, ui32 );
updatePeerProgress( msgs );
break;
const size_t bitCount = tr_torrentHasMetadata( msgs->torrent )
? msgs->torrent->info.pieceCount
: msglen * 8;
+assert( !msgs->got_a_bitfield_or_have_all_or_have_none );
+msgs->got_a_bitfield_or_have_all_or_have_none = TRUE;
dbgmsg( msgs, "got a bitfield" );
tr_bitsetReserve( &msgs->peer->have, bitCount );
tr_peerIoReadBytes( msgs->peer->io, inbuf,
msgs->peer->have.bitfield.bits, msglen );
+ fireClientGotBitfield( msgs, &msgs->peer->have.bitfield );
updatePeerProgress( msgs );
break;
}
case BT_FEXT_HAVE_ALL:
dbgmsg( msgs, "Got a BT_FEXT_HAVE_ALL" );
if( fext ) {
+assert( !msgs->got_a_bitfield_or_have_all_or_have_none );
+msgs->got_a_bitfield_or_have_all_or_have_none = TRUE;
tr_bitsetSetHaveAll( &msgs->peer->have );
+ fireClientGotHaveAll( msgs );
updatePeerProgress( msgs );
} else {
fireError( msgs, EMSGSIZE );
case BT_FEXT_HAVE_NONE:
dbgmsg( msgs, "Got a BT_FEXT_HAVE_NONE" );
if( fext ) {
+assert( !msgs->got_a_bitfield_or_have_all_or_have_none );
+msgs->got_a_bitfield_or_have_all_or_have_none = TRUE;
tr_bitsetSetHaveNone( &msgs->peer->have );
+ fireClientGotHaveNone( msgs );
updatePeerProgress( msgs );
} else {
fireError( msgs, EMSGSIZE );