From 1e6c0e9cb3db85207ebe5c4ffaef7164f65c7b96 Mon Sep 17 00:00:00 2001 From: DimaDemchenko Date: Fri, 21 Jun 2024 15:03:32 +0300 Subject: [PATCH] refactor: Improve FastTracker by optimizing peer management --- lib/fast-tracker.ts | 149 +++++++++++++++++++++----------------------- lib/tracker.ts | 9 ++- 2 files changed, 78 insertions(+), 80 deletions(-) diff --git a/lib/fast-tracker.ts b/lib/fast-tracker.ts index 4df412d..ae0ff89 100644 --- a/lib/fast-tracker.ts +++ b/lib/fast-tracker.ts @@ -20,6 +20,7 @@ import { SocketContext, PeerContext, TrackerError, + Swarm, } from "./tracker.js"; const debug = Debug("wt-tracker:fast-tracker"); @@ -49,6 +50,69 @@ export class FastTracker implements Tracker { this.startClearPeersInterval(); } + private getOrCreateSwarm(infoHash: string) { + let swarm = this.#swarms.get(infoHash); + + if (swarm === undefined) { + if (typeof infoHash !== "string") { + throw new TrackerError("announce: info_hash field is missing or wrong"); + } + + if (debugEnabled) { + debug( + "announce: swarm created:", + Buffer.from(infoHash).toString("hex"), + ); + } + + swarm = { + infoHash, + peers: [], + completedCount: 0, + }; + + this.#swarms.set(infoHash, swarm); + } + + return swarm; + } + + private addPeerToSwarm( + swarm: Swarm, + peer: PeerContext, + isPeerCompleted: boolean, + ) { + swarm.peers.push(peer); + if (isPeerCompleted) { + if (swarm.completedPeers === undefined) { + swarm.completedPeers = new Set(); + } + swarm.completedPeers.add(peer.peerId); + swarm.completedCount++; + } + } + + private removePeerFromSwarm(swarm: Swarm, peer: PeerContext) { + const peerIndex = swarm.peers.indexOf(peer); + + if (swarm.completedPeers?.delete(peer.peerId)) { + swarm.completedCount--; + } + + const lastPeer = swarm.peers.pop()!; + if (peerIndex < swarm.peers.length) { + swarm.peers[peerIndex] = lastPeer; + } + } + + private setPeerCompletedInSwarm(swarm: Swarm, peer: PeerContext) { + if (swarm.completedPeers === undefined) { + swarm.completedPeers = new Set(); + } + swarm.completedPeers.add(peer.peerId); + swarm.completedCount++; + } + private startClearPeersInterval(): void { if (this.#clearPeersInterval !== undefined) { clearInterval(this.#clearPeersInterval); @@ -92,8 +156,7 @@ export class FastTracker implements Tracker { ); } - swarm.removePeer(peer); - + this.removePeerFromSwarm(swarm, peer); this.removeSwarmIfEmpty(swarm); this.#peers.delete(peer.peerId); @@ -169,7 +232,7 @@ export class FastTracker implements Tracker { swarm, }; - swarm.addPeer(peer, isPeerCompleted); + this.addPeerToSwarm(swarm, peer, isPeerCompleted); (socket as unknown as UnknownObject)[peerId] = peer; this.#peers.set(peerId, peer); @@ -179,16 +242,17 @@ export class FastTracker implements Tracker { if (infoHash !== peer.swarm.infoHash) { const oldSwarm = peer.swarm; - oldSwarm.removePeer(peer); - + this.removePeerFromSwarm(oldSwarm, peer); this.removeSwarmIfEmpty(oldSwarm); swarm = this.getOrCreateSwarm(infoHash); peer.swarm = swarm; - swarm.addPeer(peer, isPeerCompleted); + this.addPeerToSwarm(swarm, peer, isPeerCompleted); } else { swarm = peer.swarm; - if (isPeerCompleted) swarm.setCompleted(peer); + if (isPeerCompleted) { + this.setPeerCompletedInSwarm(swarm, peer); + } } } else { throw new TrackerError("announce: peerId mismatch"); @@ -208,28 +272,6 @@ export class FastTracker implements Tracker { this.sendOffersToPeers(json, swarm.peers, peer, infoHash); } - private getOrCreateSwarm(infoHash: unknown): Swarm { - let swarm = this.#swarms.get(infoHash as string); - - if (swarm === undefined) { - if (typeof infoHash !== "string") { - throw new TrackerError("announce: info_hash field is missing or wrong"); - } - - if (debugEnabled) { - debug( - "announce: swarm created:", - Buffer.from(infoHash).toString("hex"), - ); - } - - swarm = new Swarm(infoHash); - this.#swarms.set(infoHash, swarm); - } - - return swarm; - } - private sendOffersToPeers( json: UnknownObject, peers: readonly PeerContext[], @@ -384,55 +426,6 @@ export class FastTracker implements Tracker { } } -export class Swarm { - public completedCount = 0; - private completedPeers?: Set; - - readonly #peers: PeerContext[] = []; - - public constructor(public readonly infoHash: string) {} - - public get peers(): readonly PeerContext[] { - return this.#peers; - } - - public addPeer(peer: PeerContext, completed: boolean): void { - this.#peers.push(peer); - if (completed) { - if (this.completedPeers === undefined) { - this.completedPeers = new Set(); - } - this.completedPeers.add(peer.peerId); - this.completedCount++; - } - } - - public removePeer(peer: PeerContext) { - const index = this.#peers.indexOf(peer); - - if (this.completedPeers?.delete(peer.peerId) === true) { - this.completedCount--; - } - - // Delete peerId from array without calling splice - const last = this.#peers.pop()!; - if (index < this.#peers.length) { - this.#peers[index] = last; - } - } - - public setCompleted(peer: PeerContext): void { - if (this.completedPeers === undefined) { - this.completedPeers = new Set(); - } - - if (!this.completedPeers.has(peer.peerId)) { - this.completedPeers.add(peer.peerId); - this.completedCount++; - } - } -} - function sendOffer( offerItem: unknown, fromPeerId: string, diff --git a/lib/tracker.ts b/lib/tracker.ts index 07b69ef..d983166 100644 --- a/lib/tracker.ts +++ b/lib/tracker.ts @@ -14,12 +14,17 @@ * limitations under the License. */ -import { Swarm } from "./fast-tracker.js"; - export interface SocketContext { sendMessage: (json: object, peer: SocketContext) => void; } +export type Swarm = { + infoHash: string; + completedCount: number; + completedPeers?: Set; + peers: PeerContext[]; +}; + export interface PeerContext { peerId: string; sendMessage: (json: object, peer: SocketContext) => void;