telemersive-bus 0.6.13 → 0.6.15

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.
Files changed (2) hide show
  1. package/lib/BusManager.js +97 -60
  2. package/package.json +2 -2
package/lib/BusManager.js CHANGED
@@ -31,6 +31,11 @@ class BusManager {
31
31
  this.peerHousekeep = {};
32
32
  this.roomHousekeep = {};
33
33
  this.houseKeepingInProgress = false;
34
+ this.housekeepingId = 0;
35
+ // promise set while cleanOut is actively mutating this.rooms, null otherwise.
36
+ // startHousekeeping awaits it before snapshotting this.rooms, so a new cycle
37
+ // cannot start before the previous one's room deletions have fully settled.
38
+ this.cleanOutInProgress = null;
34
39
  this.chatManagers = {};
35
40
  this.switchBoardURI = null;
36
41
  }
@@ -119,9 +124,19 @@ class BusManager {
119
124
  * when peer leaves a room
120
125
  */
121
126
  startHousekeeping = async () => {
127
+ // wait for any in-flight cleanOut to fully settle before snapshotting this.rooms.
128
+ // this prevents overlap where a still-running cleanOut's slow stopServerSidePortScripts
129
+ // finishes (and deletes from this.rooms) after a new cycle already snapshotted them.
130
+ if (this.cleanOutInProgress) {
131
+ await this.cleanOutInProgress;
132
+ }
122
133
  // we dont want to start another housekeeping process until the previous one has finished
123
134
  if(!this.houseKeepingInProgress){
124
135
  this.houseKeepingInProgress = true;
136
+ // each cycle gets a unique id so stale timers from an interrupted cycle can detect
137
+ // they no longer own the housekeeping state and bail out.
138
+ this.housekeepingId += 1;
139
+ const myId = this.housekeepingId;
125
140
  console.log(` -> starting housekeeping -> gather all peers ...`);
126
141
  this.peerHousekeep = {};
127
142
  this.roomHousekeep = Object.assign({}, this.rooms); // clone the current room list
@@ -130,7 +145,7 @@ class BusManager {
130
145
  await this.communicator.subscribe(new BusMsgSub(this.busTopics.roomPeerJoin('+', '+').build(), this.onAllPeers, PeerInfo, 0));
131
146
 
132
147
  // now we have to wait a moment
133
- setTimeout(this.pingAllPeersAlive, houseKeepingTimeOut);
148
+ setTimeout(() => this.pingAllPeersAlive(myId), houseKeepingTimeOut);
134
149
  } else {
135
150
  // if there is already a housekeeping going on,
136
151
  // we wait for some time to finish it and try again.
@@ -154,7 +169,11 @@ class BusManager {
154
169
  /**
155
170
  * ping all peers that are subscribed to each individual rooms
156
171
  */
157
- pingAllPeersAlive = async () => {
172
+ pingAllPeersAlive = async (myId) => {
173
+ // if a newer cycle has started, this callback belongs to an interrupted cycle — drop it.
174
+ if (myId !== this.housekeepingId) {
175
+ return;
176
+ }
158
177
  if(this.houseKeepingInProgress) {
159
178
  console.log(` -> housekeeping -> pinging all rooms ...`);
160
179
  const keys = Object.keys(this.rooms);
@@ -174,11 +193,11 @@ class BusManager {
174
193
  ));
175
194
  }
176
195
  // now we have to wait a moment
177
- setTimeout(this.cleanOut, houseKeepingTimeOut);
196
+ setTimeout(() => this.cleanOut(myId), houseKeepingTimeOut);
178
197
  } else {
179
198
  // this means the housekeeping got interrupted and
180
199
  // needs to finish as fast as possible to cleaning up all subscriptions.
181
- setTimeout(this.cleanOut, 0);
200
+ setTimeout(() => this.cleanOut(myId), 0);
182
201
  }
183
202
  }
184
203
 
@@ -252,63 +271,81 @@ class BusManager {
252
271
  /**
253
272
  * cleanout dead peers and rooms
254
273
  */
255
- cleanOut = async () => {
256
- if(this.houseKeepingInProgress) {
257
- console.log(` -> housekeeping -> cleaning out ...`);
258
- // flag the room as being in the process to be removed
259
- const flag4cleanupRooms = (currentRoom) => this.rooms[currentRoom.roomName].flagRoom4cleanup = true;
260
- Object.values(this.roomHousekeep).forEach(flag4cleanupRooms);
261
-
262
- // kick out the non-responding peers
263
- const kickOutPeer = async (currentPeer) => {
264
- console.log(` <- remove peer '${currentPeer.peerName}' from room '${currentPeer.roomName}' `);
265
- // first we send clear retain message to peer joined addresses that have not replied:
266
- await this.communicator.clearRetain(this.busTopics.roomPeerJoin(currentPeer.roomName, currentPeer.peerId).build());
267
-
268
- // create the payload for the leave message
269
- const infoPayload = {
270
- timestamp: Math.round(new Date().getTime() / 1000),
271
- peerId: currentPeer.peerId,
272
- peerName: currentPeer.peerName,
273
- roomName: currentPeer.roomName
274
- };
275
-
276
- // sends the left message to all peers joined in the same room
277
- await this.communicator.publish(
278
- new BusMsgPub(
279
- this.busTopics.roomPeerLeft(currentPeer.roomName, currentPeer.peerId).build(),
280
- PeerInfo, 2, true).encode(infoPayload));
281
- }
282
- const peerCleanup = async () => {
283
- for (const peer of Object.values(this.peerHousekeep)) {
284
- await kickOutPeer(peer);
285
- }
286
- };
287
- await peerCleanup();
288
-
289
- // And now remove the rooms
290
- const cleanupRooms = async (currentRoom) => await this.deleteRoom(currentRoom.roomName);
291
- const housekeeping = async () => {
292
- for (const room of Object.values(this.roomHousekeep)) {
293
- await cleanupRooms(room);
294
- }
295
- };
296
- await housekeeping();
297
-
298
- console.log(` -> housekeeping DONE`);
299
- } else {
300
- console.log(` -> housekeeping was interrupted`);
274
+ cleanOut = async (myId) => {
275
+ // if a newer cycle has started, this callback belongs to an interrupted cycle — drop it.
276
+ if (myId !== this.housekeepingId) {
277
+ return;
301
278
  }
279
+ // expose the in-flight cleanOut as a promise so the next startHousekeeping can await it
280
+ // and avoid snapshotting this.rooms while deleteRoom is still mutating it.
281
+ this.cleanOutInProgress = (async () => {
282
+ try {
283
+ if(this.houseKeepingInProgress) {
284
+ console.log(` -> housekeeping -> cleaning out ...`);
285
+ // flag the room as being in the process to be removed. the entry may already have
286
+ // been deleted from this.rooms by an earlier overlapping cleanOut whose slow
287
+ // stopServerSidePortScripts only just finished — in that case, nothing to flag.
288
+ const flag4cleanupRooms = (currentRoom) => {
289
+ const room = this.rooms[currentRoom.roomName];
290
+ if (room) room.flagRoom4cleanup = true;
291
+ };
292
+ Object.values(this.roomHousekeep).forEach(flag4cleanupRooms);
293
+
294
+ // kick out the non-responding peers
295
+ const kickOutPeer = async (currentPeer) => {
296
+ console.log(` <- remove peer '${currentPeer.peerName}' from room '${currentPeer.roomName}' `);
297
+ // first we send clear retain message to peer joined addresses that have not replied:
298
+ await this.communicator.clearRetain(this.busTopics.roomPeerJoin(currentPeer.roomName, currentPeer.peerId).build());
299
+
300
+ // create the payload for the leave message
301
+ const infoPayload = {
302
+ timestamp: Math.round(new Date().getTime() / 1000),
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
+ }
327
+ };
328
+ await housekeeping();
329
+
330
+ console.log(` -> housekeeping DONE`);
331
+ } else {
332
+ console.log(` -> housekeeping was interrupted`);
333
+ }
302
334
 
303
- // and we stop by unsubscribing to all peer info messages inside all rooms
304
- await this.communicator.unsubscribe(this.busTopics.roomPeerJoin('+', '+').build());
305
- // unsubscribing to all room ping alive topic
306
- await this.communicator.unsubscribe(this.busTopics.baseRoomAlivePing("+").build());
307
- // cleanup the communicator
308
- this.communicator.clearAutoGeneratedSubscriptions();
309
- // set this flag to false at the end to make sure a new houskeeping cycle can only be started once
310
- // all topics are unsubscribed - otherwise it won't gather the joined peers inside the room.
311
- this.houseKeepingInProgress = false;
335
+ // and we stop by unsubscribing to all peer info messages inside all rooms
336
+ await this.communicator.unsubscribe(this.busTopics.roomPeerJoin('+', '+').build());
337
+ // unsubscribing to all room ping alive topic
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;
346
+ }
347
+ })();
348
+ await this.cleanOutInProgress;
312
349
  }
313
350
 
314
351
  /**
@@ -511,7 +548,7 @@ class BusManager {
511
548
  // Generate short UUID as peerId
512
549
  let roomUUID = this.translator.new(); // mhvXdrZT4jP5T8vBxuvm75
513
550
 
514
- if(roomID === null)
551
+ if(roomID === null || roomID === undefined)
515
552
  roomID = this.findUnusedRoomId();
516
553
  this.rooms[_roomName] = {
517
554
  roomName: _roomName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "telemersive-bus",
3
- "version": "0.6.13",
3
+ "version": "0.6.15",
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": {
@@ -11,7 +11,7 @@
11
11
  "crypto-js": "^4.0.0",
12
12
  "internal-ip": "^6.2.0",
13
13
  "mqtt": "^5.3.5",
14
- "protobufjs": "^6.10.2",
14
+ "protobufjs": "^7.5.5",
15
15
  "public-ip": "^4.0.2",
16
16
  "sha1": "^1.1.1",
17
17
  "short-uuid": "^4.1.0",