@dxos/echo-pipeline 0.1.34 → 0.1.36-next.ef27157

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.
@@ -36,6 +36,7 @@ export declare class PipelineState {
36
36
  get endTimeframe(): Timeframe;
37
37
  get timeframe(): Timeframe;
38
38
  get targetTimeframe(): Timeframe;
39
+ get feeds(): FeedWrapper<any>[];
39
40
  waitUntilTimeframe(target: Timeframe): Promise<void>;
40
41
  setTargetTimeframe(target: Timeframe): void;
41
42
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/pipeline/pipeline.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,EAAmB,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,OAAO,EAAwD,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEzG,oBAAY,4BAA4B,GAAG;IACzC;;OAEG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF;;GAEG;AACH,qBAAa,aAAa;IAatB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,eAAe;IAZzB,SAAgB,eAAe,yCAA+B;IAE9D;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAAwB;IAEhD,OAAO,CAAC,qBAAqB,CAA4B;gBAI/C,SAAS,EAAE,eAAe,CAAC,GAAG,CAAC,EAC/B,eAAe,EAAE,cAAc;IAGzC;;;OAGG;IACH,IAAI,YAAY,cASf;IAED,IAAI,SAAS,cAEZ;IAED,IAAI,eAAe,cAElB;IAEK,kBAAkB,CAAC,MAAM,EAAE,SAAS;IAI1C,kBAAkB,CAAC,MAAM,EAAE,SAAS;IAIpC;;;;;;OAMG;IACG,+BAA+B,CAAC,EACpC,GAAmB,EACnB,OAAO,EACP,YAAmB,EACpB,GAAE,4BAAiC;CAuCrC;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,QAAS,YAAW,gBAAgB;IAsB7C,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IArBpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA8C;IAG9E,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAG9B;IAGH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiF;IAGxG,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAwB;IAG3D,OAAO,CAAC,OAAO,CAA8C;IAE7D,OAAO,CAAC,OAAO,CAAS;gBAIL,iBAAiB,GAAE,SAA2B;IAOjE,IAAI,KAAK,kBAER;IAED,IAAI,MAAM,IAAI,UAAU,CAAC,WAAW,CAAC,OAAO,CAAC,CAG5C;IAED,QAAQ,IAAI,WAAW,CAAC,WAAW,CAAC,EAAE;IAIhC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,WAAW,CAAC;IAI5C,OAAO,CAAC,OAAO,EAAE,SAAS;IAI1B,YAAY,CAAC,IAAI,EAAE,WAAW,CAAC,WAAW,CAAC;IAarC,KAAK;IAML,IAAI;IAOV;;;OAGG;IACI,OAAO,IAAI,aAAa,CAAC,gBAAgB,CAAC;CAelD"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/pipeline/pipeline.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,EAAmB,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,OAAO,EAAyC,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE1F,oBAAY,4BAA4B,GAAG;IACzC;;OAEG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF;;GAEG;AACH,qBAAa,aAAa;IAatB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,eAAe;IAZzB,SAAgB,eAAe,yCAA+B;IAE9D;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAAwB;IAEhD,OAAO,CAAC,qBAAqB,CAA4B;gBAI/C,SAAS,EAAE,eAAe,CAAC,GAAG,CAAC,EAC/B,eAAe,EAAE,cAAc;IAGzC;;;OAGG;IAEH,IAAI,YAAY,cASf;IAED,IAAI,SAAS,cAEZ;IAED,IAAI,eAAe,cAElB;IAED,IAAI,KAAK,uBAER;IAEK,kBAAkB,CAAC,MAAM,EAAE,SAAS;IAI1C,kBAAkB,CAAC,MAAM,EAAE,SAAS;IAIpC;;;;;;OAMG;IACG,+BAA+B,CAAC,EACpC,GAAmB,EACnB,OAAO,EACP,YAAmB,EACpB,GAAE,4BAAiC;CAuCrC;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,QAAS,YAAW,gBAAgB;IAsB7C,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IArBpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA8C;IAG9E,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAG9B;IAGH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiF;IAGxG,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAwB;IAG3D,OAAO,CAAC,OAAO,CAA8C;IAE7D,OAAO,CAAC,OAAO,CAAS;gBAIL,iBAAiB,GAAE,SAA2B;IAOjE,IAAI,KAAK,kBAER;IAED,IAAI,MAAM,IAAI,UAAU,CAAC,WAAW,CAAC,OAAO,CAAC,CAG5C;IAED,QAAQ,IAAI,WAAW,CAAC,WAAW,CAAC,EAAE;IAMhC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,WAAW,CAAC;IAI5C,OAAO,CAAC,OAAO,EAAE,SAAS;IAI1B,YAAY,CAAC,IAAI,EAAE,WAAW,CAAC,WAAW,CAAC;IAcrC,KAAK;IAOL,IAAI;IAOV;;;OAGG;IACI,OAAO,IAAI,aAAa,CAAC,gBAAgB,CAAC;CAelD"}
@@ -4,6 +4,7 @@ import { PublicKey } from '@dxos/keys';
4
4
  import { Timeframe } from '@dxos/timeframe';
5
5
  export declare const mapTimeframeToFeedIndexes: (timeframe: Timeframe) => FeedIndex[];
6
6
  export declare const mapFeedIndexesToTimeframe: (indexes: FeedIndex[]) => Timeframe;
7
+ export declare const startAfter: (timeframe: Timeframe) => FeedIndex[];
7
8
  /**
8
9
  * Keeps state of the last timeframe that was processed by ECHO.
9
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"timeframe-clock.d.ts","sourceRoot":"","sources":["../../../../src/pipeline/timeframe-clock.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,eAAO,MAAM,yBAAyB,cAAe,SAAS,KAAG,SAAS,EACN,CAAC;AAErE,eAAO,MAAM,yBAAyB,YAAa,SAAS,EAAE,KAAG,SACK,CAAC;AAEvE;;GAEG;AACH,qBAAa,cAAc;IAKvB,OAAO,CAAC,UAAU;IAJpB,QAAQ,CAAC,MAAM,mBAA0B;gBAI/B,UAAU,YAAkB;IAGtC,IAAI,SAAS,cAEZ;IAED,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM;IAK3C,OAAO,CAAC,SAAS,EAAE,SAAS;IAMtB,gBAAgB,CAAC,MAAM,EAAE,SAAS;CAYzC"}
1
+ {"version":3,"file":"timeframe-clock.d.ts","sourceRoot":"","sources":["../../../../src/pipeline/timeframe-clock.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,eAAO,MAAM,yBAAyB,cAAe,SAAS,KAAG,SAAS,EACN,CAAC;AAErE,eAAO,MAAM,yBAAyB,YAAa,SAAS,EAAE,KAAG,SACK,CAAC;AAEvE,eAAO,MAAM,UAAU,cAAe,SAAS,KAAG,SAAS,EACoB,CAAC;AAEhF;;GAEG;AACH,qBAAa,cAAc;IAKvB,OAAO,CAAC,UAAU;IAJpB,QAAQ,CAAC,MAAM,mBAA0B;gBAI/B,UAAU,YAAkB;IAGtC,IAAI,SAAS,cAEZ;IAED,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM;IAK3C,OAAO,CAAC,SAAS,EAAE,SAAS;IAMtB,gBAAgB,CAAC,MAAM,EAAE,SAAS;CAYzC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/echo-pipeline",
3
- "version": "0.1.34",
3
+ "version": "0.1.36-next.ef27157",
4
4
  "description": "ECHO database.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -20,40 +20,41 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "debug": "^4.3.3",
23
- "@dxos/async": "0.1.34",
24
- "@dxos/codec-protobuf": "0.1.34",
25
- "@dxos/context": "0.1.34",
26
- "@dxos/credentials": "0.1.34",
27
- "@dxos/crypto": "0.1.34",
28
- "@dxos/debug": "0.1.34",
29
- "@dxos/document-model": "0.1.34",
30
- "@dxos/echo-db": "0.1.34",
31
- "@dxos/feed-store": "0.1.34",
32
- "@dxos/hypercore": "0.1.34",
33
- "@dxos/keyring": "0.1.34",
34
- "@dxos/keys": "0.1.34",
35
- "@dxos/log": "0.1.34",
36
- "@dxos/messaging": "0.1.34",
37
- "@dxos/model-factory": "0.1.34",
38
- "@dxos/network-manager": "0.1.34",
39
- "@dxos/node-std": "0.1.34",
40
- "@dxos/protocols": "0.1.34",
41
- "@dxos/random-access-storage": "0.1.34",
42
- "@dxos/rpc": "0.1.34",
43
- "@dxos/teleport": "0.1.34",
44
- "@dxos/teleport-extension-gossip": "0.1.34",
45
- "@dxos/teleport-extension-object-sync": "0.1.34",
46
- "@dxos/teleport-extension-replicator": "0.1.34",
47
- "@dxos/timeframe": "0.1.34",
48
- "@dxos/typings": "0.1.34",
49
- "@dxos/util": "0.1.34"
23
+ "@dxos/async": "0.1.36-next.ef27157",
24
+ "@dxos/codec-protobuf": "0.1.36-next.ef27157",
25
+ "@dxos/context": "0.1.36-next.ef27157",
26
+ "@dxos/credentials": "0.1.36-next.ef27157",
27
+ "@dxos/crypto": "0.1.36-next.ef27157",
28
+ "@dxos/debug": "0.1.36-next.ef27157",
29
+ "@dxos/document-model": "0.1.36-next.ef27157",
30
+ "@dxos/echo-db": "0.1.36-next.ef27157",
31
+ "@dxos/feed-store": "0.1.36-next.ef27157",
32
+ "@dxos/hypercore": "0.1.36-next.ef27157",
33
+ "@dxos/keyring": "0.1.36-next.ef27157",
34
+ "@dxos/keys": "0.1.36-next.ef27157",
35
+ "@dxos/log": "0.1.36-next.ef27157",
36
+ "@dxos/messaging": "0.1.36-next.ef27157",
37
+ "@dxos/model-factory": "0.1.36-next.ef27157",
38
+ "@dxos/network-manager": "0.1.36-next.ef27157",
39
+ "@dxos/node-std": "0.1.36-next.ef27157",
40
+ "@dxos/protocols": "0.1.36-next.ef27157",
41
+ "@dxos/random-access-storage": "0.1.36-next.ef27157",
42
+ "@dxos/rpc": "0.1.36-next.ef27157",
43
+ "@dxos/teleport": "0.1.36-next.ef27157",
44
+ "@dxos/teleport-extension-gossip": "0.1.36-next.ef27157",
45
+ "@dxos/teleport-extension-object-sync": "0.1.36-next.ef27157",
46
+ "@dxos/teleport-extension-replicator": "0.1.36-next.ef27157",
47
+ "@dxos/timeframe": "0.1.36-next.ef27157",
48
+ "@dxos/typings": "0.1.36-next.ef27157",
49
+ "@dxos/util": "0.1.36-next.ef27157"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/debug": "^4.1.7",
53
+ "fast-check": "~3.3.0",
53
54
  "hypercore-protocol": "^8.0.7",
54
55
  "source-map-support": "^0.5.12",
55
56
  "wait-for-expect": "^3.0.2",
56
- "@dxos/text-model": "0.1.34"
57
+ "@dxos/text-model": "0.1.36-next.ef27157"
57
58
  },
58
59
  "publishConfig": {
59
60
  "access": "public"
@@ -2,7 +2,16 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
+ import expect from 'expect';
6
+ import * as fc from 'fast-check';
7
+ import { inspect } from 'util';
8
+
9
+ import { asyncTimeout } from '@dxos/async';
5
10
  import { checkType } from '@dxos/debug';
11
+ import { FeedStore, FeedWrapper } from '@dxos/feed-store';
12
+ import { PublicKey } from '@dxos/keys';
13
+ import { log } from '@dxos/log';
14
+ import { FeedMessageBlock } from '@dxos/protocols';
6
15
  import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
7
16
  import { describe, test, afterTest } from '@dxos/test';
8
17
  import { Timeframe } from '@dxos/timeframe';
@@ -76,4 +85,209 @@ describe('pipeline/Pipeline', () => {
76
85
  }
77
86
  }
78
87
  });
88
+
89
+ test
90
+ .skip('stress', async () => {
91
+ const builder = new TestFeedBuilder();
92
+
93
+ const NUM_AGENTS = 2;
94
+ const NUM_MESSAGES = 100;
95
+
96
+ class Agent {
97
+ public startingTimeframe = new Timeframe();
98
+ public pipeline!: Pipeline;
99
+ public feed!: FeedWrapper<FeedMessage>;
100
+ public messages: FeedMessageBlock[] = [];
101
+ public writePromise: Promise<any> = Promise.resolve();
102
+
103
+ constructor(public id: string, public feedStore: FeedStore<FeedMessage>) {}
104
+
105
+ async open() {
106
+ const key = await builder.keyring.createKey();
107
+ this.feed = await this.feedStore.openFeed(key, { writable: true });
108
+ }
109
+
110
+ async start() {
111
+ this.pipeline = new Pipeline(this.startingTimeframe);
112
+
113
+ await this.pipeline.start();
114
+
115
+ // NOTE: not awaiting here breaks the test.
116
+ await Promise.all(this.feedStore.feeds.map((feed) => this.pipeline.addFeed(feed)));
117
+ this.pipeline.setWriteFeed(this.feed);
118
+
119
+ // consume in async task.
120
+ void this.consume();
121
+ }
122
+
123
+ async stop() {
124
+ await this.pipeline.stop();
125
+ this.startingTimeframe = this.pipeline.state.timeframe;
126
+ }
127
+
128
+ async close() {
129
+ await this.feed.close();
130
+ }
131
+
132
+ write(message: FeedMessage.Payload) {
133
+ const prev = this.writePromise;
134
+ const promise = this.pipeline.writer!.write(message);
135
+ this.writePromise = Promise.all([prev, promise]);
136
+ }
137
+
138
+ async consume() {
139
+ for await (const msg of this.pipeline.consume()) {
140
+ this.messages.push(msg);
141
+ }
142
+ log('stopped consuming');
143
+ }
144
+ }
145
+
146
+ type Model = {};
147
+ type Real = {
148
+ feedStore: FeedStore<FeedMessage>;
149
+ agents: Map<string, Agent>;
150
+ };
151
+
152
+ class WriteCommand implements fc.AsyncCommand<Model, Real> {
153
+ constructor(public agent: string, public count: number) {}
154
+
155
+ check = () => true;
156
+
157
+ async run(model: Model, real: Real) {
158
+ console.log(`WriteCommand(${this.agent}, ${this.count})`);
159
+
160
+ const agent = real.agents.get(this.agent)!;
161
+
162
+ const toWrite = Math.min(this.count, NUM_MESSAGES - agent.feed.length);
163
+ if (toWrite > 0) {
164
+ for (const _ of range(toWrite)) {
165
+ agent.write({}); // Content is not important.
166
+ }
167
+ }
168
+ }
169
+
170
+ toString = () => `WriteCommand(${this.agent}, ${this.count})`;
171
+ }
172
+
173
+ class SyncCommand implements fc.AsyncCommand<Model, Real> {
174
+ check = () => true;
175
+
176
+ async run(model: Model, real: Real) {
177
+ console.log('SyncCommand()');
178
+ const targets: any = {};
179
+
180
+ try {
181
+ for (const agent of real.agents.values()) {
182
+ await agent.writePromise;
183
+ }
184
+
185
+ for (const agent of real.agents.values()) {
186
+ targets[agent.id] = agent.pipeline.state.endTimeframe;
187
+ if (agent.pipeline.state.endTimeframe.isEmpty()) {
188
+ console.log('empty endtimeframe', {
189
+ id: agent.id,
190
+ endTimeframe: agent.pipeline.state.endTimeframe,
191
+ feeds: agent.pipeline.getFeeds().map((feed) => [feed.key.toString(), feed.length])
192
+ });
193
+ }
194
+ await asyncTimeout(agent.pipeline.state.waitUntilTimeframe(agent.pipeline.state.endTimeframe), 1000);
195
+ }
196
+
197
+ const tf: Timeframe = real.agents.values().next().value!.pipeline.state.timeframe;
198
+ for (const agent of real.agents.values()) {
199
+ expect(agent.pipeline.state.timeframe.equals(tf)).toEqual(true);
200
+ }
201
+
202
+ const totalMessages = real.feedStore.feeds.reduce((acc, feed) => acc + feed.length, 0);
203
+ for (const agent of real.agents.values()) {
204
+ expect(agent.messages.length).toEqual(totalMessages);
205
+ }
206
+ } catch (err) {
207
+ console.log(
208
+ inspect(
209
+ {
210
+ agents: Array.from(real.agents.values()).map((agent) => ({
211
+ id: agent.id,
212
+ messages: agent.messages.length,
213
+ feeds: agent.pipeline.getFeeds().map((feed) => [feed.key, feed.length]),
214
+ timeframe: agent.pipeline.state.timeframe,
215
+ endTimeframe: agent.pipeline.state.endTimeframe
216
+ })),
217
+ feeds: real.feedStore.feeds.map((feed) => [feed.key, feed.length]),
218
+ targets
219
+ },
220
+ false,
221
+ null,
222
+ true
223
+ )
224
+ );
225
+ throw err;
226
+ }
227
+ }
228
+
229
+ toString = () => 'SyncCommand()';
230
+ }
231
+
232
+ class RestartCommand implements fc.AsyncCommand<Model, Real> {
233
+ constructor(public agent: string) {}
234
+
235
+ check = () => true;
236
+
237
+ async run(model: Model, real: Real) {
238
+ console.log(`RestartCommand(${this.agent})`);
239
+
240
+ const agent = real.agents.get(this.agent)!;
241
+
242
+ await agent.writePromise;
243
+ await agent.stop();
244
+ await agent.start();
245
+ }
246
+
247
+ toString = () => `RestartCommand(${this.agent})`;
248
+ }
249
+
250
+ const agentIds = range(NUM_AGENTS).map(() => PublicKey.random().toHex().slice(0, 8));
251
+ const anAgentId = fc.constantFrom(...agentIds);
252
+
253
+ const commands = fc.commands(
254
+ [
255
+ fc.tuple(anAgentId, fc.integer({ min: 1, max: 10 })).map(([agent, count]) => new WriteCommand(agent, count)),
256
+ fc.constant(new SyncCommand()),
257
+ anAgentId.map((agent) => new RestartCommand(agent))
258
+ ],
259
+ { size: 'large' }
260
+ );
261
+
262
+ const model = fc.asyncProperty(commands, async (commands) => {
263
+ const feedStore = builder.createFeedStore();
264
+
265
+ const agents = new Map(agentIds.map((id) => [id, new Agent(id, feedStore)]));
266
+ await Promise.all(Array.from(agents.values()).map((agent) => agent.open()));
267
+ await Promise.all(Array.from(agents.values()).map((agent) => agent.start()));
268
+
269
+ const setup: fc.ModelRunSetup<Model, Real> = () => ({
270
+ model: {},
271
+ real: {
272
+ feedStore,
273
+ agents
274
+ }
275
+ });
276
+
277
+ try {
278
+ await fc.asyncModelRun(setup, [...commands, new SyncCommand()]);
279
+ } finally {
280
+ await Promise.all(Array.from(agents.values()).map((agent) => agent.stop()));
281
+ await Promise.all(Array.from(agents.values()).map((agent) => agent.close()));
282
+ }
283
+ });
284
+
285
+ const examples: [commands: Iterable<fc.AsyncCommand<Model, Real, boolean>>][] = [
286
+ [[new WriteCommand(agentIds[0], 10), new WriteCommand(agentIds[1], 10), new SyncCommand()]],
287
+ [[new WriteCommand(agentIds[0], 4), new RestartCommand(agentIds[0]), new SyncCommand()]]
288
+ ];
289
+
290
+ await fc.assert(model, { examples });
291
+ })
292
+ .timeout(60_000);
79
293
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  import assert from 'node:assert';
6
6
 
7
- import { sleep, Trigger } from '@dxos/async';
7
+ import { sleep, synchronized, Trigger } from '@dxos/async';
8
8
  import { Context, rejectOnDispose } from '@dxos/context';
9
9
  import { FeedSetIterator, FeedWrapper, FeedWriter } from '@dxos/feed-store';
10
10
  import { PublicKey } from '@dxos/keys';
@@ -15,7 +15,7 @@ import { Timeframe } from '@dxos/timeframe';
15
15
 
16
16
  import { createMappedFeedWriter } from '../common';
17
17
  import { createMessageSelector } from './message-selector';
18
- import { mapFeedIndexesToTimeframe, mapTimeframeToFeedIndexes, TimeframeClock } from './timeframe-clock';
18
+ import { mapFeedIndexesToTimeframe, startAfter, TimeframeClock } from './timeframe-clock';
19
19
 
20
20
  export type WaitUntilReachedTargetParams = {
21
21
  /**
@@ -54,13 +54,14 @@ export class PipelineState {
54
54
  * Latest theoretical timeframe based on the last mutation in each feed.
55
55
  * NOTE: This might never be reached if the mutation dependencies
56
56
  */
57
+ // TODO(dmaretskyi): Rename `totalTimeframe`? or `lastTimeframe`.
57
58
  get endTimeframe() {
58
59
  return mapFeedIndexesToTimeframe(
59
60
  this._iterator.feeds
60
- .filter((feed) => feed.properties.length > 0)
61
+ .filter((feed) => feed.length > 0)
61
62
  .map((feed) => ({
62
63
  feedKey: feed.key,
63
- index: feed.properties.length - 1
64
+ index: feed.length - 1
64
65
  }))
65
66
  );
66
67
  }
@@ -73,6 +74,10 @@ export class PipelineState {
73
74
  return this._targetTimeframe ? this._targetTimeframe : new Timeframe();
74
75
  }
75
76
 
77
+ get feeds() {
78
+ return this._iterator.feeds;
79
+ }
80
+
76
81
  async waitUntilTimeframe(target: Timeframe) {
77
82
  await this._timeframeClock.waitUntilReached(target);
78
83
  }
@@ -175,7 +180,7 @@ export class Pipeline implements PipelineAccessor {
175
180
 
176
181
  // Inbound feed stream.
177
182
  private readonly _feedSetIterator = new FeedSetIterator<FeedMessage>(createMessageSelector(this._timeframeClock), {
178
- start: mapTimeframeToFeedIndexes(this._initialTimeframe),
183
+ start: startAfter(this._initialTimeframe),
179
184
  stallTimeout: 1000
180
185
  });
181
186
 
@@ -212,6 +217,8 @@ export class Pipeline implements PipelineAccessor {
212
217
  return this._feedSetIterator.feeds;
213
218
  }
214
219
 
220
+ // NOTE: This cannot be synchronized with `stop` because stop waits for the mutation processing to complete,
221
+ // which might be opening feeds during the mutation processing, which w
215
222
  async addFeed(feed: FeedWrapper<FeedMessage>) {
216
223
  await this._feedSetIterator.addFeed(feed);
217
224
  }
@@ -233,12 +240,14 @@ export class Pipeline implements PipelineAccessor {
233
240
  );
234
241
  }
235
242
 
243
+ @synchronized
236
244
  async start() {
237
245
  log('starting...', {});
238
246
  await this._feedSetIterator.open();
239
247
  log('started');
240
248
  }
241
249
 
250
+ @synchronized
242
251
  async stop() {
243
252
  log('stopping...', {});
244
253
  await this._feedSetIterator.close();
@@ -15,6 +15,9 @@ export const mapTimeframeToFeedIndexes = (timeframe: Timeframe): FeedIndex[] =>
15
15
  export const mapFeedIndexesToTimeframe = (indexes: FeedIndex[]): Timeframe =>
16
16
  new Timeframe(indexes.map(({ feedKey, index }) => [feedKey, index]));
17
17
 
18
+ export const startAfter = (timeframe: Timeframe): FeedIndex[] =>
19
+ timeframe.frames().map(([feedKey, index]) => ({ feedKey, index: index + 1 }));
20
+
18
21
  /**
19
22
  * Keeps state of the last timeframe that was processed by ECHO.
20
23
  */
@@ -42,7 +45,7 @@ export class TimeframeClock {
42
45
 
43
46
  @timed(5_000)
44
47
  async waitUntilReached(target: Timeframe) {
45
- log.debug('waitUntilReached', { target, current: this._timeframe });
48
+ log.info('waitUntilReached', { target, current: this._timeframe });
46
49
  await this.update.waitForCondition(() => {
47
50
  log('check if reached', {
48
51
  target,
@@ -53,7 +53,7 @@ const TIMEFRAME_SAVE_DEBOUNCE_INTERVAL = 500;
53
53
  /**
54
54
  * Flag to disable automatic local snapshots.
55
55
  */
56
- const DISABLE_SNAPSHOT_CACHE = false;
56
+ const DISABLE_SNAPSHOT_CACHE = true;
57
57
 
58
58
  /**
59
59
  * Controls data pipeline in the space.
@@ -106,7 +106,7 @@ export class DataPipeline {
106
106
  }
107
107
 
108
108
  this._spaceContext = spaceContext;
109
- if (this._params.snapshotId) {
109
+ if (this._params.snapshotId && !DISABLE_SNAPSHOT_CACHE) {
110
110
  this._snapshot = await this._params.snapshotManager.load(this._params.snapshotId);
111
111
  this._lastAutomaticSnapshotTimeframe = this._snapshot?.timeframe ?? new Timeframe();
112
112
  }