Skip to content

Commit bb9cb4a

Browse files
committed
Scope batchAppend promise bank per stream
1 parent 8c95da3 commit bb9cb4a

2 files changed

Lines changed: 63 additions & 7 deletions

File tree

packages/db-client/src/streams/appendToStream/batchAppend.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,19 @@ import {
3030

3131
import type { AppendToStreamOptions } from ".";
3232

33+
type PromiseBank = Map<
34+
string,
35+
[resolve: (r: AppendResult) => void, reject: (error: Error) => void]
36+
>;
37+
3338
const streamCache = new WeakMap<
3439
StreamsClient,
3540
Promise<ReturnType<StreamsClient["batchAppend"]>>
3641
>();
3742

38-
const promiseBank = new Map<
39-
string,
40-
[resolve: (r: AppendResult) => void, reject: (error: Error) => void]
43+
const promiseBanks = new WeakMap<
44+
ReturnType<StreamsClient["batchAppend"]>,
45+
PromiseBank
4146
>();
4247

4348
export const batchAppend = async function (
@@ -55,17 +60,22 @@ export const batchAppend = async function (
5560
const stream = await this.GRPCStreamCreator(
5661
StreamsClient,
5762
"appendToStream",
58-
(client) =>
59-
client
63+
(client) => {
64+
const promiseBank: PromiseBank = new Map();
65+
66+
const batchStream = client
6067
.batchAppend(
6168
...this.callArguments(baseOptions, {
6269
deadline: Infinity,
6370
})
6471
)
6572
.on("data", (resp: BatchAppendResp) => {
6673
const resultingId = parseUUID(resp.getCorrelationId()!);
67-
const [resolve, reject] = promiseBank.get(resultingId)!;
74+
const entry = promiseBank.get(resultingId);
6875

76+
if (!entry) return;
77+
78+
const [resolve, reject] = entry;
6979
promiseBank.delete(resultingId);
7080

7181
if (resp.hasError()) {
@@ -120,10 +130,22 @@ export const batchAppend = async function (
120130
reject(convertToCommandError(error));
121131
}
122132
promiseBank.clear();
123-
}),
133+
});
134+
135+
promiseBanks.set(batchStream, promiseBank);
136+
137+
return batchStream;
138+
},
124139
streamCache
125140
)();
126141

142+
const promiseBank = promiseBanks.get(stream);
143+
if (!promiseBank) {
144+
throw new Error(
145+
"batchAppend could not find the promise bank for the stream."
146+
);
147+
}
148+
127149
return new Promise(async (...batchPromise) => {
128150
promiseBank.set(correlationId, batchPromise);
129151

packages/test/src/streams/appendToStream-batch-append.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,40 @@ describe("appendToStream - batch append", () => {
111111
(128 * 5_000) / 1024
112112
);
113113
});
114+
115+
test("A stream error does not reject in-flight appends on sibling streams", async () => {
116+
const clientA = KurrentDBClient.connectionString(node.connectionString());
117+
const clientB = KurrentDBClient.connectionString(node.connectionString());
118+
const aSpy = jest.spyOn(
119+
clientA,
120+
"GRPCStreamCreator" as never
121+
) as unknown as jest.SpiedFunction<KurrentDBClient["GRPCStreamCreator"]>;
122+
123+
try {
124+
await clientA.appendToStream("sibling_a_warmup", jsonTestEvents());
125+
await clientB.appendToStream("sibling_b_warmup", jsonTestEvents());
126+
127+
const aStream = await extractBatchStream.call(
128+
clientA,
129+
...aSpy.mock.calls[0]
130+
);
131+
132+
const bAppend = clientB.appendToStream(
133+
"sibling_b_during_a_error",
134+
jsonTestEvents()
135+
);
136+
137+
await new Promise((r) => setImmediate(r));
138+
139+
aStream.emit("error", new Error("simulated transport error"));
140+
141+
const bResult = await bAppend;
142+
expect(bResult.success).toBe(true);
143+
} finally {
144+
await clientA.dispose();
145+
await clientB.dispose();
146+
}
147+
});
114148
});
115149
});
116150

0 commit comments

Comments
 (0)