@procedures/mongo 0.47.1 → 0.48.0

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Materialized views driven by change streams (Graph's split plan, part P4). A view is a document
2
+ * Materialized views driven by change streams. A view is a document
3
3
  * of a views collection: an aggregation pipeline over source collections whose result goes into a
4
4
  * target collection, for one partition (for example one tenant). The materializer runs each view
5
5
  * when its sources change, and keeps the target equal to the result of the pipeline.
@@ -31,8 +31,11 @@
31
31
  * of another view. The materializer writes the rows.
32
32
  * - A pass keeps in memory the key of each row of the result (at most `maxRows`) and one group of
33
33
  * 1000 rows. It keeps nothing between passes.
34
+ * - Each source has one change stream, for all views of all partitions (roadmap CLI-92). A stream at
35
+ * rest holds one connection of the pool for its `getMore`, so the count of streams must not grow
36
+ * with the partitions. The handler of the stream gives a change to the views of its partition.
34
37
  * - A change stream that ends (for example after `dropDatabase`) opens again after `rewatch`,
35
- * and a pass catches up on the changes that the stream missed.
38
+ * and a pass of each partition of that source catches up on the changes that the stream missed.
36
39
  * - Each stream is open on the server before the materializer reads what it watches: the views
37
40
  * before the read of the views, the sources before a run. A change between the two reaches both.
38
41
  * - The status of each view (`ok`, `errored`, `cycle`) goes into the field `statusField` of its
@@ -299,9 +302,15 @@ export declare class ViewMaterializer {
299
302
  private db;
300
303
  private viewsStream;
301
304
  private readonly running;
305
+ /** The one change stream of each source that an active view reads (roadmap CLI-92). */
306
+ private readonly sources;
302
307
  /** The collections that changed since the last pass, for each partition. */
303
308
  private readonly dirty;
304
309
  private readonly timers;
310
+ /** The time of the last change of each partition with a timer: the end of the wait of the pass. */
311
+ private readonly lastChange;
312
+ /** The passes that run at one time. A burst that reaches each partition does not fill the pool. */
313
+ private passes;
305
314
  /** The timers of the streams that open again, and of the ends of the grace times. */
306
315
  private readonly retries;
307
316
  private readonly clock;
@@ -341,7 +350,7 @@ export declare class ViewMaterializer {
341
350
  * deleted item) is not a row of a source (roadmap CLI-79).
342
351
  */
343
352
  private scoped;
344
- /** The change streams that are open: the views stream and the source streams of the active views. */
353
+ /** The change streams that are open: the views stream and one stream for each source of the active views. */
345
354
  openStreamCount(): number;
346
355
  /** The passes, rebalances and status writes in flight. */
347
356
  inFlight(): number;
@@ -389,19 +398,18 @@ export declare class ViewMaterializer {
389
398
  private activate;
390
399
  private tearDown;
391
400
  /**
392
- * The filter of the change stream of a source for a partition. It only removes the events that
393
- * cannot change a view of the partition: an event whose partition is unknown passes, and the
394
- * pass of the partition reads its own documents only.
401
+ * The partitions that a change of a source can reach (roadmap CLI-92). This list only removes the
402
+ * partitions that the change cannot touch: the pass of a partition reads its own documents only.
395
403
  *
396
404
  * - An insert, a replace or an update has the document after the change (`fullDocument`).
397
405
  * - With a pre-image (a collection with `changeStreamPreAndPostImages`), each change also has the
398
406
  * document before it: a delete, and an update that moves a document out of the partition.
399
407
  * - Without a pre-image, a delete, a replace and an update of the partition field (a top-level
400
- * field) can leave any partition: they pass. In a source of `storageSources`, the handler then
401
- * reads the partition from the id (`isOtherPartition`).
402
- * - The handler checks the fields of an update against `sourceFields` (`touchesFields`).
408
+ * field) can leave any partition: the change reaches each partition of the source. In a source
409
+ * of `storageSources`, the handler then reads the partition from the id (`isOtherPartition`).
410
+ * - The handler checks the fields of an update against `sourceFields` (`touchesNoField`).
403
411
  */
404
- private sourceMatch;
412
+ private partitionsOfChange;
405
413
  /**
406
414
  * True for an update that changes no field of `fields` and not the partition field. The keys of
407
415
  * an update are paths with dots (`tags.3` after a `$push`, `a.b` after a `$set` of `a.b`), so a key
@@ -421,12 +429,36 @@ export declare class ViewMaterializer {
421
429
  */
422
430
  private isOtherPartition;
423
431
  private isCurrent;
424
- /** This method opens the stream of a source for a view. The promise resolves when the stream is open on the server. */
425
- private watchSource;
432
+ /**
433
+ * This method adds a view to the stream of each of its sources, and opens a stream that does not
434
+ * exist. The promise resolves when each stream is open on the server, so a change after that
435
+ * reaches the view.
436
+ */
437
+ private watchSources;
438
+ /** This method removes a view from its streams. The stream of a source with no view left closes. */
439
+ private unwatch;
440
+ /** This method closes the stream of a source and forgets it. */
441
+ private dropWatch;
442
+ /** This method opens the stream of a source. The promise resolves when the stream is open on the server. */
443
+ private openSource;
444
+ /**
445
+ * This method opens the stream of a source again after `rewatch`. The changes while no stream was
446
+ * open are lost, so a pass of each partition of the source catches up, after the new stream is
447
+ * open. A view that joins before then is in that pass.
448
+ */
449
+ private afterStreamEnd;
450
+ /** This method gives a change of a source to the views of its partitions. */
451
+ private route;
426
452
  /** This method runs a function after `rewatch`, unless the materializer stops. The timer does not keep the process alive. */
427
453
  private later;
428
454
  /** This method marks a collection as changed, and starts the pass of the partition after the burst. */
429
455
  private schedule;
456
+ /**
457
+ * This method starts the timer of the pass of a partition. A change during the wait moves the end
458
+ * of the wait and makes no new timer: 1,000 deletes with no pre-image at 1,000 partitions are a
459
+ * million changes. A pass waits for its place in `passes` after the work of its partition before it.
460
+ */
461
+ private waitForPass;
430
462
  /** The views of a partition, in the order of their dependencies, and the views in a cycle. */
431
463
  private order;
432
464
  /** This method moves the views of a partition between active and cyclic, and runs each view that becomes active. */
@@ -1 +1 @@
1
- {"version":3,"file":"materializer.d.ts","sourceRoot":"","sources":["../../src/storage/materializer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"materializer.d.ts","sourceRoot":"","sources":["../../src/storage/materializer.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Materialized views driven by change streams (Graph's split plan, part P4). A view is a document
2
+ * Materialized views driven by change streams. A view is a document
3
3
  * of a views collection: an aggregation pipeline over source collections whose result goes into a
4
4
  * target collection, for one partition (for example one tenant). The materializer runs each view
5
5
  * when its sources change, and keeps the target equal to the result of the pipeline.
@@ -31,8 +31,11 @@
31
31
  * of another view. The materializer writes the rows.
32
32
  * - A pass keeps in memory the key of each row of the result (at most `maxRows`) and one group of
33
33
  * 1000 rows. It keeps nothing between passes.
34
+ * - Each source has one change stream, for all views of all partitions (roadmap CLI-92). A stream at
35
+ * rest holds one connection of the pool for its `getMore`, so the count of streams must not grow
36
+ * with the partitions. The handler of the stream gives a change to the views of its partition.
34
37
  * - A change stream that ends (for example after `dropDatabase`) opens again after `rewatch`,
35
- * and a pass catches up on the changes that the stream missed.
38
+ * and a pass of each partition of that source catches up on the changes that the stream missed.
36
39
  * - Each stream is open on the server before the materializer reads what it watches: the views
37
40
  * before the read of the views, the sources before a run. A change between the two reaches both.
38
41
  * - The status of each view (`ok`, `errored`, `cycle`) goes into the field `statusField` of its
@@ -55,7 +58,7 @@
55
58
  import { createHash } from "node:crypto";
56
59
  import { isDeepStrictEqual } from "node:util";
57
60
  import { BSON, ObjectId } from "mongodb";
58
- import { collectionChangeTopic, ms, optionalMs, realClock, writeLog } from "@procedures/client";
61
+ import { collectionChangeTopic, ms, optionalMs, realClock, Semaphore, writeLog } from "@procedures/client";
59
62
  import { DEFAULT_STORED_FORM, DOCUMENT_STORAGE_FIELDS, MongoDocumentStorage, TOMBSTONE_FIELD, storageWriteTarget } from "./document-storage.js";
60
63
  import { scopePipeline } from "./pipeline-scope.js";
61
64
  /** The fields of a storage that a row never keeps: its sequence number and its tombstone flag (roadmap CLI-77). */
@@ -102,6 +105,17 @@ function groups(items) {
102
105
  const CONFLICT_RETRIES = 3;
103
106
  /** The most ids of a hint of a target not in a stored form. More ids give a reset hint. */
104
107
  const MAX_HINT_IDS = 10_000;
108
+ /** The most passes that run at one time: about one third of the default pool of the driver (100). */
109
+ const MOST_PASSES = 32;
110
+ /**
111
+ * This function gives the most passes that run at one time on a pool. A pass uses one connection at
112
+ * a time, so two thirds of the pool stay free for the other operations of the process.
113
+ */
114
+ function passLimit(db) {
115
+ // A fake database of a test has no client
116
+ const pool = db.client?.options.maxPoolSize;
117
+ return pool ? Math.max(1, Math.min(MOST_PASSES, Math.floor(pool / 3))) : MOST_PASSES;
118
+ }
105
119
  /** The `_id` of a row in the result, from the stored `_id` of `partitionTargetId`. */
106
120
  export function defaultRowId(storedId) {
107
121
  return typeof storedId === "object" && storedId !== null && "id" in storedId ? storedId.id : storedId;
@@ -192,9 +206,15 @@ export class ViewMaterializer {
192
206
  db;
193
207
  viewsStream;
194
208
  running = new Map();
209
+ /** The one change stream of each source that an active view reads (roadmap CLI-92). */
210
+ sources = new Map();
195
211
  /** The collections that changed since the last pass, for each partition. */
196
212
  dirty = new Map();
197
213
  timers = new Map();
214
+ /** The time of the last change of each partition with a timer: the end of the wait of the pass. */
215
+ lastChange = new Map();
216
+ /** The passes that run at one time. A burst that reaches each partition does not fill the pool. */
217
+ passes = new Semaphore(MOST_PASSES);
198
218
  /** The timers of the streams that open again, and of the ends of the grace times. */
199
219
  retries = new Set();
200
220
  clock;
@@ -325,11 +345,12 @@ export class ViewMaterializer {
325
345
  // No stamp: the materializer writes the rows, so the guard refuses a $merge of the pipeline
326
346
  return scopePipeline(pipeline, { match, collections: view.collections });
327
347
  }
328
- /** The change streams that are open: the views stream and the source streams of the active views. */
348
+ /** The change streams that are open: the views stream and one stream for each source of the active views. */
329
349
  openStreamCount() {
330
350
  let count = this.viewsStream ? 1 : 0;
331
- for (const view of this.running.values())
332
- count += view.streams.length;
351
+ for (const watch of this.sources.values())
352
+ if (watch.stream)
353
+ count++;
333
354
  return count;
334
355
  }
335
356
  /** The passes, rebalances and status writes in flight. */
@@ -345,6 +366,7 @@ export class ViewMaterializer {
345
366
  this.stopped = false;
346
367
  this.intake = true;
347
368
  this.db = await this.options.db();
369
+ this.passes = new Semaphore(passLimit(this.db));
348
370
  // The stream of the views opens before the read of the views: a change between the two reaches both (roadmap O63)
349
371
  await this.watchViews();
350
372
  const count = await this.resync();
@@ -356,6 +378,7 @@ export class ViewMaterializer {
356
378
  for (const cancel of this.timers.values())
357
379
  cancel();
358
380
  this.timers.clear();
381
+ this.lastChange.clear();
359
382
  this.dirty.clear();
360
383
  }
361
384
  /**
@@ -533,7 +556,7 @@ export class ViewMaterializer {
533
556
  this.log("warn", "The view needs collections, a pipeline and a targetCollection: it does not run", { view: view._id, partition }, `the view ${view._id} needs collections, a pipeline and a targetCollection: it does not run`);
534
557
  return false;
535
558
  }
536
- this.running.set(this.keyOf(partition, view._id), { view, partition, state: "active", streams: [] });
559
+ this.running.set(this.keyOf(partition, view._id), { view, partition, state: "active", watched: [] });
537
560
  return true;
538
561
  }
539
562
  /** This method makes the index of the target of a registered view, and rebalances its partition: a new view runs. */
@@ -564,37 +587,38 @@ export class ViewMaterializer {
564
587
  const view = this.running.get(key);
565
588
  if (!view)
566
589
  return;
567
- for (const stream of view.streams)
568
- stream.close().catch(() => undefined);
590
+ this.unwatch(view);
569
591
  this.running.delete(key);
570
592
  this.storageClaims.delete(key);
571
593
  }
572
594
  /**
573
- * The filter of the change stream of a source for a partition. It only removes the events that
574
- * cannot change a view of the partition: an event whose partition is unknown passes, and the
575
- * pass of the partition reads its own documents only.
595
+ * The partitions that a change of a source can reach (roadmap CLI-92). This list only removes the
596
+ * partitions that the change cannot touch: the pass of a partition reads its own documents only.
576
597
  *
577
598
  * - An insert, a replace or an update has the document after the change (`fullDocument`).
578
599
  * - With a pre-image (a collection with `changeStreamPreAndPostImages`), each change also has the
579
600
  * document before it: a delete, and an update that moves a document out of the partition.
580
601
  * - Without a pre-image, a delete, a replace and an update of the partition field (a top-level
581
- * field) can leave any partition: they pass. In a source of `storageSources`, the handler then
582
- * reads the partition from the id (`isOtherPartition`).
583
- * - The handler checks the fields of an update against `sourceFields` (`touchesFields`).
602
+ * field) can leave any partition: the change reaches each partition of the source. In a source
603
+ * of `storageSources`, the handler then reads the partition from the id (`isOtherPartition`).
604
+ * - The handler checks the fields of an update against `sourceFields` (`touchesNoField`).
584
605
  */
585
- sourceMatch(partition) {
606
+ partitionsOfChange(change, watch) {
586
607
  const field = this.partitionField;
587
- const noPreImage = { fullDocumentBeforeChange: null };
588
- return {
589
- $match: {
590
- $or: [
591
- { [`fullDocument.${field}`]: partition },
592
- { [`fullDocumentBeforeChange.${field}`]: partition },
593
- { operationType: { $in: ["delete", "replace"] }, ...noPreImage },
594
- { operationType: "update", ...noPreImage, $or: [{ [`updateDescription.updatedFields.${field}`]: { $exists: true } }, { "updateDescription.removedFields": field }] },
595
- ],
596
- },
597
- };
608
+ const type = change["operationType"];
609
+ const before = change["fullDocumentBeforeChange"];
610
+ if (before === null || before === undefined) {
611
+ const description = (change["updateDescription"] ?? {});
612
+ const movable = type === "delete" || type === "replace" || (type === "update" && (Object.hasOwn(description.updatedFields ?? {}, field) || (description.removedFields ?? []).includes(field)));
613
+ if (movable)
614
+ return watch.partitions.keys();
615
+ }
616
+ const after = change["fullDocument"];
617
+ const named = new Set();
618
+ for (const value of [after?.[field], before?.[field]].flat())
619
+ if (typeof value === "string")
620
+ named.add(value);
621
+ return named;
598
622
  }
599
623
  /**
600
624
  * True for an update that changes no field of `fields` and not the partition field. The keys of
@@ -654,57 +678,122 @@ export class ViewMaterializer {
654
678
  isCurrent(running) {
655
679
  return this.running.get(this.keyOf(running.partition, running.view._id)) === running;
656
680
  }
657
- /** This method opens the stream of a source for a view. The promise resolves when the stream is open on the server. */
658
- watchSource(running, source) {
681
+ /**
682
+ * This method adds a view to the stream of each of its sources, and opens a stream that does not
683
+ * exist. The promise resolves when each stream is open on the server, so a change after that
684
+ * reaches the view.
685
+ */
686
+ watchSources(running) {
687
+ const opened = [];
688
+ for (const source of new Set(running.view.collections)) {
689
+ let watch = this.sources.get(source);
690
+ if (!watch) {
691
+ watch = { source, stream: undefined, partitions: new Map(), opened: Promise.resolve() };
692
+ this.sources.set(source, watch);
693
+ watch.opened = this.openSource(watch);
694
+ }
695
+ let views = watch.partitions.get(running.partition);
696
+ if (!views)
697
+ watch.partitions.set(running.partition, (views = new Set()));
698
+ views.add(running);
699
+ running.watched.push(source);
700
+ opened.push(watch.opened);
701
+ }
702
+ return Promise.all(opened).then(() => undefined);
703
+ }
704
+ /** This method removes a view from its streams. The stream of a source with no view left closes. */
705
+ unwatch(running) {
706
+ for (const source of running.watched) {
707
+ const watch = this.sources.get(source);
708
+ const views = watch?.partitions.get(running.partition);
709
+ if (!watch || !views)
710
+ continue;
711
+ views.delete(running);
712
+ if (views.size === 0)
713
+ watch.partitions.delete(running.partition);
714
+ if (watch.partitions.size === 0)
715
+ this.dropWatch(watch);
716
+ }
717
+ running.watched = [];
718
+ }
719
+ /** This method closes the stream of a source and forgets it. */
720
+ dropWatch(watch) {
721
+ if (this.sources.get(watch.source) === watch)
722
+ this.sources.delete(watch.source);
723
+ const stream = watch.stream;
724
+ watch.stream = undefined;
725
+ stream?.close().catch(() => undefined);
726
+ }
727
+ /** This method opens the stream of a source. The promise resolves when the stream is open on the server. */
728
+ openSource(watch) {
659
729
  if (this.stopped || !this.db)
660
730
  return Promise.resolve();
731
+ const { source } = watch;
661
732
  let stream;
662
733
  try {
663
- stream = this.db
664
- .collection(source)
665
- .watch([this.sourceMatch(running.partition)], { fullDocument: "updateLookup", fullDocumentBeforeChange: "whenAvailable" });
734
+ stream = this.db.collection(source).watch([], { fullDocument: "updateLookup", fullDocumentBeforeChange: "whenAvailable" });
666
735
  }
667
736
  catch (error) {
668
- this.log("error", "Cannot watch a source of the view", { view: running.view._id, partition: running.partition, collection: source, error }, `cannot watch ${source} for the view ${running.view._id}: ${String(error?.message ?? error)}`);
669
- this.later(() => {
670
- if (this.isCurrent(running) && running.state === "active")
671
- void this.watchSource(running, source);
672
- });
737
+ this.log("error", "Cannot watch a source", { collection: source, error }, `cannot watch ${source}: ${String(error?.message ?? error)}`);
738
+ this.afterStreamEnd(watch);
673
739
  return Promise.resolve();
674
740
  }
675
- running.streams.push(stream);
741
+ watch.stream = stream;
676
742
  let again = false;
677
743
  const rewatch = (reason) => {
678
744
  if (again)
679
745
  return;
680
746
  again = true;
681
747
  stream.close().catch(() => undefined);
682
- const index = running.streams.indexOf(stream);
683
- // A close of a teardown also ends the stream: open again only a stream of a current active view
684
- if (index === -1 || !this.isCurrent(running) || running.state !== "active")
748
+ // A close of a teardown also ends the stream: open again only the stream of a source with views
749
+ if (watch.stream !== stream || this.sources.get(source) !== watch)
685
750
  return;
686
- running.streams.splice(index, 1);
687
- this.log("warn", "The stream of a source ended: it opens again", { view: running.view._id, partition: running.partition, collection: source, reason, delayMs: this.rewatchMs }, `the stream of ${source} for the view ${running.view._id} ${reason}: it opens again in ${this.rewatchMs} ms`);
688
- this.later(() => {
689
- if (!this.isCurrent(running) || running.state !== "active")
690
- return;
691
- // The changes while the stream was closed are lost: a pass catches up, after the new stream is open
692
- void this.watchSource(running, source).then(() => this.schedule(running.partition, source));
693
- });
751
+ watch.stream = undefined;
752
+ this.log("warn", "The stream of a source ended: it opens again", { collection: source, reason, delayMs: this.rewatchMs }, `the stream of ${source} ${reason}: it opens again in ${this.rewatchMs} ms`);
753
+ this.afterStreamEnd(watch);
694
754
  };
695
755
  stream.on("change", (change) => {
696
756
  if (change["operationType"] === "invalidate")
697
757
  return rewatch("ended (invalidate)");
698
- if (this.isOtherPartition(change, source, running.partition))
699
- return;
700
- if (this.touchesNoField(change, running.view.sourceFields?.[source]))
701
- return;
702
- this.schedule(running.partition, source);
758
+ if (watch.stream === stream)
759
+ this.route(change, watch);
703
760
  });
704
761
  stream.on("error", (error) => rewatch(`failed: ${error.message}`));
705
762
  stream.on("close", () => rewatch("closed"));
706
763
  return openOnServer(stream);
707
764
  }
765
+ /**
766
+ * This method opens the stream of a source again after `rewatch`. The changes while no stream was
767
+ * open are lost, so a pass of each partition of the source catches up, after the new stream is
768
+ * open. A view that joins before then is in that pass.
769
+ */
770
+ afterStreamEnd(watch) {
771
+ this.later(() => {
772
+ if (this.sources.get(watch.source) !== watch)
773
+ return;
774
+ void this.openSource(watch).then(() => {
775
+ if (this.sources.get(watch.source) !== watch)
776
+ return;
777
+ for (const partition of watch.partitions.keys())
778
+ this.schedule(partition, watch.source);
779
+ });
780
+ });
781
+ }
782
+ /** This method gives a change of a source to the views of its partitions. */
783
+ route(change, watch) {
784
+ const { source } = watch;
785
+ for (const partition of this.partitionsOfChange(change, watch)) {
786
+ const views = watch.partitions.get(partition);
787
+ if (!views || this.isOtherPartition(change, source, partition))
788
+ continue;
789
+ for (const running of views) {
790
+ if (this.touchesNoField(change, running.view.sourceFields?.[source]))
791
+ continue;
792
+ this.schedule(partition, source);
793
+ break;
794
+ }
795
+ }
796
+ }
708
797
  /** This method runs a function after `rewatch`, unless the materializer stops. The timer does not keep the process alive. */
709
798
  later(work) {
710
799
  const cancel = this.clock.after(this.rewatchMs, () => {
@@ -723,12 +812,27 @@ export class ViewMaterializer {
723
812
  if (!dirty)
724
813
  this.dirty.set(partition, (dirty = new Set()));
725
814
  dirty.add(collection);
726
- this.timers.get(partition)?.();
727
- const cancel = this.clock.after(this.debounceMs, () => {
815
+ this.lastChange.set(partition, this.clock.now());
816
+ if (!this.timers.has(partition))
817
+ this.waitForPass(partition, this.debounceMs);
818
+ }
819
+ /**
820
+ * This method starts the timer of the pass of a partition. A change during the wait moves the end
821
+ * of the wait and makes no new timer: 1,000 deletes with no pre-image at 1,000 partitions are a
822
+ * million changes. A pass waits for its place in `passes` after the work of its partition before it.
823
+ */
824
+ waitForPass(partition, delay) {
825
+ const cancel = this.clock.after(delay, () => {
826
+ const left = this.lastChange.get(partition) + this.debounceMs - this.clock.now();
827
+ if (left > 0) {
828
+ this.waitForPass(partition, left);
829
+ return;
830
+ }
728
831
  this.timers.delete(partition);
832
+ this.lastChange.delete(partition);
729
833
  if (!this.intake)
730
834
  return;
731
- void this.serial(partition, () => this.pass(partition)).catch((error) => this.log("error", "The pass failed", { partition, error }, `the pass of ${partition} failed: ${String(error?.message ?? error)}`));
835
+ void this.serial(partition, () => this.passes.withPermit(() => this.pass(partition))).catch((error) => this.log("error", "The pass failed", { partition, error }, `the pass of ${partition} failed: ${String(error?.message ?? error)}`));
732
836
  });
733
837
  cancel.unref?.();
734
838
  this.timers.set(partition, cancel);
@@ -771,17 +875,15 @@ export class ViewMaterializer {
771
875
  continue;
772
876
  if (cyclic.has(view._id)) {
773
877
  if (running.state === "active") {
774
- for (const stream of running.streams)
775
- stream.close().catch(() => undefined);
776
- running.streams = [];
878
+ this.unwatch(running);
777
879
  running.state = "cyclic";
778
880
  await this.writeStatus(view, partition, { state: "cycle", at: Date.now() });
779
881
  }
780
882
  }
781
- else if (running.state === "cyclic" || running.streams.length === 0) {
883
+ else if (running.state === "cyclic" || running.watched.length === 0) {
782
884
  running.state = "active";
783
885
  // The streams open before the run reads the sources: a change between the two reaches both (roadmap O63)
784
- await Promise.all(view.collections.map((source) => this.watchSource(running, source)));
886
+ await this.watchSources(running);
785
887
  // A change of the view document during the wait replaced this view: the rebalance of the new one runs it
786
888
  if (!this.isCurrent(running))
787
889
  continue;