telemersive-bus 0.6.16 → 0.6.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/BusManager.js +95 -74
- package/package.json +1 -1
package/lib/BusManager.js
CHANGED
|
@@ -32,10 +32,19 @@ class BusManager {
|
|
|
32
32
|
this.roomHousekeep = {};
|
|
33
33
|
this.houseKeepingInProgress = false;
|
|
34
34
|
this.housekeepingId = 0;
|
|
35
|
-
// promise set while
|
|
36
|
-
//
|
|
37
|
-
// cannot start
|
|
38
|
-
|
|
35
|
+
// promise set while a housekeeping cycle is actively running (from subscribe
|
|
36
|
+
// through cleanOut's unsubscribe), null otherwise. startHousekeeping awaits it
|
|
37
|
+
// so a new cycle cannot start its own subscribe/ping sequence while a prior
|
|
38
|
+
// cycle — even an interrupted one — is still unwinding. without this, an
|
|
39
|
+
// interrupted cycle A and a new cycle B overlap: B resets peerHousekeep and
|
|
40
|
+
// re-subscribes, but retained peer-join messages arrive after B's ping timer,
|
|
41
|
+
// causing every alive peer to be rejected as "not joined anymore".
|
|
42
|
+
this.housekeepingCycleInProgress = null;
|
|
43
|
+
// resolver for the current cycle's promise. pingAllPeersAlive schedules
|
|
44
|
+
// cleanOut which calls this when the cycle is fully done (subscriptions
|
|
45
|
+
// cleaned up). if a cycle is interrupted, cleanOut still runs its unsubscribe
|
|
46
|
+
// path and resolves the promise.
|
|
47
|
+
this.resolveHousekeepingCycle = null;
|
|
39
48
|
this.chatManagers = {};
|
|
40
49
|
this.switchBoardURI = null;
|
|
41
50
|
}
|
|
@@ -124,11 +133,15 @@ class BusManager {
|
|
|
124
133
|
* when peer leaves a room
|
|
125
134
|
*/
|
|
126
135
|
startHousekeeping = async () => {
|
|
127
|
-
// wait for any in-flight
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
// wait for any in-flight cycle to fully unwind (subscriptions cleaned up and
|
|
137
|
+
// rooms mutations settled) before starting a new one. this guards two races:
|
|
138
|
+
// 1. a still-running cleanOut's slow stopServerSidePortScripts finishes (and
|
|
139
|
+
// deletes from this.rooms) after a new cycle already snapshotted them.
|
|
140
|
+
// 2. an interrupted cycle's pingAllPeersAlive/cleanOut is still pending while
|
|
141
|
+
// a new cycle resets peerHousekeep and pings — every response then gets
|
|
142
|
+
// rejected as "not joined anymore" because peerHousekeep was wiped.
|
|
143
|
+
if (this.housekeepingCycleInProgress) {
|
|
144
|
+
await this.housekeepingCycleInProgress;
|
|
132
145
|
}
|
|
133
146
|
// we dont want to start another housekeeping process until the previous one has finished
|
|
134
147
|
if(!this.houseKeepingInProgress){
|
|
@@ -137,6 +150,11 @@ class BusManager {
|
|
|
137
150
|
// they no longer own the housekeeping state and bail out.
|
|
138
151
|
this.housekeepingId += 1;
|
|
139
152
|
const myId = this.housekeepingId;
|
|
153
|
+
// claim the cycle promise up front so any overlapping startHousekeeping call
|
|
154
|
+
// will await this cycle's completion rather than racing it.
|
|
155
|
+
this.housekeepingCycleInProgress = new Promise((resolve) => {
|
|
156
|
+
this.resolveHousekeepingCycle = resolve;
|
|
157
|
+
});
|
|
140
158
|
console.log(` -> starting housekeeping -> gather all peers ...`);
|
|
141
159
|
this.peerHousekeep = {};
|
|
142
160
|
this.roomHousekeep = Object.assign({}, this.rooms); // clone the current room list
|
|
@@ -275,79 +293,82 @@ class BusManager {
|
|
|
275
293
|
*/
|
|
276
294
|
cleanOut = async (myId) => {
|
|
277
295
|
// if a newer cycle has started, this callback belongs to an interrupted cycle — drop it.
|
|
296
|
+
// note: this can only happen if housekeepingId was bumped without awaiting the prior
|
|
297
|
+
// cycle's promise (shouldn't happen with the await in startHousekeeping), but we keep
|
|
298
|
+
// the guard as defense in depth.
|
|
278
299
|
if (myId !== this.housekeepingId) {
|
|
279
300
|
return;
|
|
280
301
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
peerId: currentPeer.peerId,
|
|
306
|
-
peerName: currentPeer.peerName,
|
|
307
|
-
roomName: currentPeer.roomName
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
// sends the left message to all peers joined in the same room
|
|
311
|
-
await this.communicator.publish(
|
|
312
|
-
new BusMsgPub(
|
|
313
|
-
this.busTopics.roomPeerLeft(currentPeer.roomName, currentPeer.peerId).build(),
|
|
314
|
-
PeerInfo, 2, true).encode(infoPayload));
|
|
315
|
-
}
|
|
316
|
-
const peerCleanup = async () => {
|
|
317
|
-
for (const peer of Object.values(this.peerHousekeep)) {
|
|
318
|
-
await kickOutPeer(peer);
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
await peerCleanup();
|
|
322
|
-
|
|
323
|
-
// And now remove the rooms
|
|
324
|
-
const cleanupRooms = async (currentRoom) => await this.deleteRoom(currentRoom.roomName);
|
|
325
|
-
const housekeeping = async () => {
|
|
326
|
-
for (const room of Object.values(this.roomHousekeep)) {
|
|
327
|
-
await cleanupRooms(room);
|
|
328
|
-
}
|
|
302
|
+
try {
|
|
303
|
+
if (this.houseKeepingInProgress) {
|
|
304
|
+
console.log(` -> housekeeping -> cleaning out ...`);
|
|
305
|
+
// flag the room as being in the process to be removed. the entry may already have
|
|
306
|
+
// been deleted from this.rooms by an earlier overlapping cleanOut whose slow
|
|
307
|
+
// stopServerSidePortScripts only just finished — in that case, nothing to flag.
|
|
308
|
+
const flag4cleanupRooms = (currentRoom) => {
|
|
309
|
+
const room = this.rooms[currentRoom.roomName];
|
|
310
|
+
if (room) room.flagRoom4cleanup = true;
|
|
311
|
+
};
|
|
312
|
+
Object.values(this.roomHousekeep).forEach(flag4cleanupRooms);
|
|
313
|
+
|
|
314
|
+
// kick out the non-responding peers
|
|
315
|
+
const kickOutPeer = async (currentPeer) => {
|
|
316
|
+
console.log(` <- remove peer '${currentPeer.peerName}' from room '${currentPeer.roomName}' `);
|
|
317
|
+
// first we send clear retain message to peer joined addresses that have not replied:
|
|
318
|
+
await this.communicator.clearRetain(this.busTopics.roomPeerJoin(currentPeer.roomName, currentPeer.peerId).build());
|
|
319
|
+
|
|
320
|
+
// create the payload for the leave message
|
|
321
|
+
const infoPayload = {
|
|
322
|
+
timestamp: Math.round(new Date().getTime() / 1000),
|
|
323
|
+
peerId: currentPeer.peerId,
|
|
324
|
+
peerName: currentPeer.peerName,
|
|
325
|
+
roomName: currentPeer.roomName
|
|
329
326
|
};
|
|
330
|
-
await housekeeping();
|
|
331
327
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
328
|
+
// sends the left message to all peers joined in the same room
|
|
329
|
+
await this.communicator.publish(
|
|
330
|
+
new BusMsgPub(
|
|
331
|
+
this.busTopics.roomPeerLeft(currentPeer.roomName, currentPeer.peerId).build(),
|
|
332
|
+
PeerInfo, 2, true).encode(infoPayload));
|
|
335
333
|
}
|
|
334
|
+
const peerCleanup = async () => {
|
|
335
|
+
for (const peer of Object.values(this.peerHousekeep)) {
|
|
336
|
+
await kickOutPeer(peer);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
await peerCleanup();
|
|
340
|
+
|
|
341
|
+
// And now remove the rooms
|
|
342
|
+
const cleanupRooms = async (currentRoom) => await this.deleteRoom(currentRoom.roomName);
|
|
343
|
+
const housekeeping = async () => {
|
|
344
|
+
for (const room of Object.values(this.roomHousekeep)) {
|
|
345
|
+
await cleanupRooms(room);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
await housekeeping();
|
|
336
349
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
await this.communicator.unsubscribe(this.busTopics.baseRoomAlivePing("+").build());
|
|
341
|
-
// cleanup the communicator
|
|
342
|
-
this.communicator.clearAutoGeneratedSubscriptions();
|
|
343
|
-
// set this flag to false at the end to make sure a new houskeeping cycle can only be started once
|
|
344
|
-
// all topics are unsubscribed - otherwise it won't gather the joined peers inside the room.
|
|
345
|
-
this.houseKeepingInProgress = false;
|
|
346
|
-
} finally {
|
|
347
|
-
this.cleanOutInProgress = null;
|
|
350
|
+
console.log(` -> housekeeping DONE`);
|
|
351
|
+
} else {
|
|
352
|
+
console.log(` -> housekeeping was interrupted`);
|
|
348
353
|
}
|
|
349
|
-
|
|
350
|
-
|
|
354
|
+
|
|
355
|
+
// and we stop by unsubscribing to all peer info messages inside all rooms
|
|
356
|
+
await this.communicator.unsubscribe(this.busTopics.roomPeerJoin('+', '+').build());
|
|
357
|
+
// unsubscribing to all room ping alive topic
|
|
358
|
+
await this.communicator.unsubscribe(this.busTopics.baseRoomAlivePing("+").build());
|
|
359
|
+
// cleanup the communicator
|
|
360
|
+
this.communicator.clearAutoGeneratedSubscriptions();
|
|
361
|
+
// set this flag to false at the end to make sure a new houskeeping cycle can only be started once
|
|
362
|
+
// all topics are unsubscribed - otherwise it won't gather the joined peers inside the room.
|
|
363
|
+
this.houseKeepingInProgress = false;
|
|
364
|
+
} finally {
|
|
365
|
+
// resolve the cycle promise so any awaiting startHousekeeping can proceed.
|
|
366
|
+
// done in finally so an exception inside the try block cannot deadlock the next cycle.
|
|
367
|
+
const resolve = this.resolveHousekeepingCycle;
|
|
368
|
+
this.housekeepingCycleInProgress = null;
|
|
369
|
+
this.resolveHousekeepingCycle = null;
|
|
370
|
+
if (resolve) resolve();
|
|
371
|
+
}
|
|
351
372
|
}
|
|
352
373
|
|
|
353
374
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "telemersive-bus",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.17",
|
|
4
4
|
"description": "MQTT based data protocol to manage unlimited peers, connected by rooms, where all peers joined to a room can exchange private data. It provides a simple chat mechanism, latency pinging, publish-subscribe mechanism (based on mqtt) and a OSC-like data stream. ",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|