Skip to content

Commit f886827

Browse files
committed
fix(race): concurrent next calls with defer/stream (#2975)
* fix(race): concurrent next calls * refactor test * use invariant * disable eslint error * fix
1 parent 5b12f8a commit f886827

File tree

2 files changed

+80
-0
lines changed

2 files changed

+80
-0
lines changed

src/execution/__tests__/stream-test.ts

+68
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,22 @@ async function complete(
190190
return result;
191191
}
192192

193+
async function completeAsync(document: DocumentNode, numCalls: number) {
194+
const schema = new GraphQLSchema({ query, enableDeferStream: true });
195+
196+
const result = await execute({ schema, document, rootValue: {} });
197+
198+
invariant(isAsyncIterable(result));
199+
200+
const iterator = result[Symbol.asyncIterator]();
201+
202+
const promises = [];
203+
for (let i = 0; i < numCalls; i++) {
204+
promises.push(iterator.next());
205+
}
206+
return Promise.all(promises);
207+
}
208+
193209
describe('Execute: stream directive', () => {
194210
it('Should ignore @stream if not enabled', async () => {
195211
const document = parse('{ scalarList @stream(initialCount: 1) }');
@@ -597,6 +613,58 @@ describe('Execute: stream directive', () => {
597613
},
598614
});
599615
});
616+
it('Can handle concurrent calls to .next() without waiting', async () => {
617+
const document = parse(`
618+
query {
619+
asyncIterableList @stream(initialCount: 2) {
620+
name
621+
id
622+
}
623+
}
624+
`);
625+
const result = await completeAsync(document, 4);
626+
expectJSON(result).toDeepEqual([
627+
{
628+
done: false,
629+
value: {
630+
data: {
631+
asyncIterableList: [
632+
{
633+
name: 'Luke',
634+
id: '1',
635+
},
636+
{
637+
name: 'Han',
638+
id: '2',
639+
},
640+
],
641+
},
642+
hasNext: true,
643+
},
644+
},
645+
{
646+
done: false,
647+
value: {
648+
data: {
649+
name: 'Leia',
650+
id: '3',
651+
},
652+
path: ['asyncIterableList', 2],
653+
hasNext: true,
654+
},
655+
},
656+
{
657+
done: false,
658+
value: {
659+
hasNext: false,
660+
},
661+
},
662+
{
663+
done: true,
664+
value: undefined,
665+
},
666+
]);
667+
});
600668
it('Handles error thrown in async iterable before initialCount is reached', async () => {
601669
const document = parse(`
602670
query {

src/execution/execute.ts

+12
Original file line numberDiff line numberDiff line change
@@ -1652,8 +1652,20 @@ export class Dispatcher {
16521652

16531653
resolved = true;
16541654

1655+
if (this._subsequentPayloads.length === 0) {
1656+
// a different call to next has exhausted all payloads
1657+
resolve({ value: undefined, done: true });
1658+
return;
1659+
}
1660+
16551661
const index = this._subsequentPayloads.indexOf(promise);
16561662

1663+
if (index === -1) {
1664+
// a different call to next has consumed this payload
1665+
resolve(this._race());
1666+
return;
1667+
}
1668+
16571669
this._subsequentPayloads.splice(index, 1);
16581670

16591671
const { value, done } = payload;

0 commit comments

Comments
 (0)