@waku/core
Version:
TypeScript implementation of the Waku v2 protocol
118 lines • 4.46 kB
JavaScript
import { Logger } from "@waku/utils";
import { selectOpenConnection } from "./utils.js";
const STREAM_LOCK_KEY = "consumed";
export class StreamManager {
multicodec;
libp2p;
log;
ongoingCreation = new Set();
streamPool = new Map();
constructor(multicodec, libp2p) {
this.multicodec = multicodec;
this.libp2p = libp2p;
this.log = new Logger(`stream-manager:${multicodec}`);
this.libp2p.events.addEventListener("peer:update", this.handlePeerUpdateStreamPool);
}
async getStream(peerId) {
const peerIdStr = peerId.toString();
const scheduledStream = this.streamPool.get(peerIdStr);
if (scheduledStream) {
this.streamPool.delete(peerIdStr);
await scheduledStream;
}
let stream = this.getOpenStreamForCodec(peerId);
if (stream) {
this.log.info(`Found existing stream peerId=${peerIdStr} multicodec=${this.multicodec}`);
this.lockStream(peerIdStr, stream);
return stream;
}
stream = await this.createStream(peerId);
this.lockStream(peerIdStr, stream);
return stream;
}
async createStream(peerId, retries = 0) {
const connections = this.libp2p.connectionManager.getConnections(peerId);
const connection = selectOpenConnection(connections);
if (!connection) {
throw new Error(`Failed to get a connection to the peer peerId=${peerId.toString()} multicodec=${this.multicodec}`);
}
let lastError;
let stream;
for (let i = 0; i < retries + 1; i++) {
try {
this.log.info(`Attempting to create a stream for peerId=${peerId.toString()} multicodec=${this.multicodec}`);
stream = await connection.newStream(this.multicodec);
this.log.info(`Created stream for peerId=${peerId.toString()} multicodec=${this.multicodec}`);
break;
}
catch (error) {
lastError = error;
}
}
if (!stream) {
throw new Error(`Failed to create a new stream for ${peerId.toString()} -- ` + lastError);
}
return stream;
}
async createStreamWithLock(peer) {
const peerId = peer.id.toString();
if (this.ongoingCreation.has(peerId)) {
this.log.info(`Skipping creation of a stream due to lock for peerId=${peerId} multicodec=${this.multicodec}`);
return;
}
try {
this.ongoingCreation.add(peerId);
await this.createStream(peer.id);
}
catch (error) {
this.log.error(`Failed to createStreamWithLock:`, error);
}
finally {
this.ongoingCreation.delete(peerId);
}
return;
}
handlePeerUpdateStreamPool = (evt) => {
const { peer } = evt.detail;
if (!peer.protocols.includes(this.multicodec)) {
return;
}
const stream = this.getOpenStreamForCodec(peer.id);
if (stream) {
return;
}
this.scheduleNewStream(peer);
};
scheduleNewStream(peer) {
this.log.info(`Scheduling creation of a stream for peerId=${peer.id.toString()} multicodec=${this.multicodec}`);
// abandon previous attempt
if (this.streamPool.has(peer.id.toString())) {
this.streamPool.delete(peer.id.toString());
}
this.streamPool.set(peer.id.toString(), this.createStreamWithLock(peer));
}
getOpenStreamForCodec(peerId) {
const connections = this.libp2p.connectionManager.getConnections(peerId);
const connection = selectOpenConnection(connections);
if (!connection) {
return;
}
const stream = connection.streams.find((s) => s.protocol === this.multicodec);
if (!stream) {
return;
}
const isStreamUnusable = ["done", "closed", "closing"].includes(stream.writeStatus || "");
if (isStreamUnusable || this.isStreamLocked(stream)) {
return;
}
return stream;
}
lockStream(peerId, stream) {
this.log.info(`Locking stream for peerId:${peerId}\tstreamId:${stream.id}`);
stream.metadata[STREAM_LOCK_KEY] = true;
}
isStreamLocked(stream) {
return !!stream.metadata[STREAM_LOCK_KEY];
}
}
//# sourceMappingURL=stream_manager.js.map