telemersive-bus 0.6.15 → 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 +97 -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
|
|
@@ -178,6 +196,8 @@ class BusManager {
|
|
|
178
196
|
console.log(` -> housekeeping -> pinging all rooms ...`);
|
|
179
197
|
const keys = Object.keys(this.rooms);
|
|
180
198
|
|
|
199
|
+
// clean up auto-generated subscriptions from prior cycles before subscribing again
|
|
200
|
+
await this.communicator.unsubscribe(this.busTopics.baseRoomAlivePing("+").build());
|
|
181
201
|
// subscribe to all room ping alive topic
|
|
182
202
|
await this.communicator.subscribe(new BusMsgSub(this.busTopics.baseRoomAlivePing("+").build(), this.onRoomAlive, PeerCredentials, 0));
|
|
183
203
|
|
|
@@ -273,79 +293,82 @@ class BusManager {
|
|
|
273
293
|
*/
|
|
274
294
|
cleanOut = async (myId) => {
|
|
275
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.
|
|
276
299
|
if (myId !== this.housekeepingId) {
|
|
277
300
|
return;
|
|
278
301
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
peerId: currentPeer.peerId,
|
|
304
|
-
peerName: currentPeer.peerName,
|
|
305
|
-
roomName: currentPeer.roomName
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
// sends the left message to all peers joined in the same room
|
|
309
|
-
await this.communicator.publish(
|
|
310
|
-
new BusMsgPub(
|
|
311
|
-
this.busTopics.roomPeerLeft(currentPeer.roomName, currentPeer.peerId).build(),
|
|
312
|
-
PeerInfo, 2, true).encode(infoPayload));
|
|
313
|
-
}
|
|
314
|
-
const peerCleanup = async () => {
|
|
315
|
-
for (const peer of Object.values(this.peerHousekeep)) {
|
|
316
|
-
await kickOutPeer(peer);
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
await peerCleanup();
|
|
320
|
-
|
|
321
|
-
// And now remove the rooms
|
|
322
|
-
const cleanupRooms = async (currentRoom) => await this.deleteRoom(currentRoom.roomName);
|
|
323
|
-
const housekeeping = async () => {
|
|
324
|
-
for (const room of Object.values(this.roomHousekeep)) {
|
|
325
|
-
await cleanupRooms(room);
|
|
326
|
-
}
|
|
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
|
|
327
326
|
};
|
|
328
|
-
await housekeeping();
|
|
329
327
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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));
|
|
333
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();
|
|
334
349
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
await this.communicator.unsubscribe(this.busTopics.baseRoomAlivePing("+").build());
|
|
339
|
-
// cleanup the communicator
|
|
340
|
-
this.communicator.clearAutoGeneratedSubscriptions();
|
|
341
|
-
// set this flag to false at the end to make sure a new houskeeping cycle can only be started once
|
|
342
|
-
// all topics are unsubscribed - otherwise it won't gather the joined peers inside the room.
|
|
343
|
-
this.houseKeepingInProgress = false;
|
|
344
|
-
} finally {
|
|
345
|
-
this.cleanOutInProgress = null;
|
|
350
|
+
console.log(` -> housekeeping DONE`);
|
|
351
|
+
} else {
|
|
352
|
+
console.log(` -> housekeeping was interrupted`);
|
|
346
353
|
}
|
|
347
|
-
|
|
348
|
-
|
|
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
|
+
}
|
|
349
372
|
}
|
|
350
373
|
|
|
351
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": {
|