Posible memory leak
https://github.com/deeplay-io/nice-grpc/blob/223fb38b0078b32ae6cd242dbfffb077675c337b/packages/nice-grpc/src/client/createServerStreamingMethod.ts#L110
If I call the throw method from the iterator, then the next wait will not stop.
Example:
let serverStream = ...
once(AbortSignal.timeout(10), 'abort').then(() => {
return serverStream.throw!(new Error("Timeout."))
})
let result = await serverStream.next() // the first message will be received in 1 second. The timeout is 10ms, but we are still waiting for the first message...
Posible solution
async function testWithRace() {
// Create a slow generator similar to the previous test
const slowGenerator = () => ({
[Symbol.asyncIterator]() {
let yielded = false;
console.log('Generator started');
return {
// async next() {
// if (!yielded) {
// await new Promise(resolve => setTimeout(resolve, 5000));
// yielded = true;
// console.log('Generator yielded first value');
// return { value: { status: 200 }, done: false };
// }
// return { value: undefined, done: true };
// },
async return() {
return { value: undefined, done: true };
},
async throw(error) {
if (this._abortController) {
this._abortController.abort();
}
throw error;
},
_abortController: (null as unknown) as AbortController, // Initialize abort controller
async next() {
if (!yielded) {
this._abortController = new AbortController();
try {
await new Promise((resolve, reject) => {
const timeout = setTimeout(resolve, 5000);
this._abortController.signal.addEventListener('abort', () => {
clearTimeout(timeout);
reject(new Error('Operation aborted'));
});
});
yielded = true;
console.log('Generator yielded first value');
return { value: { status: 200 }, done: false };
} catch (err) {
if (err.message === 'Operation aborted') {
throw error; // Re-throw the original error from throw()
}
throw err;
}
}
return { value: undefined, done: true };
}
};
}
});
// Create the iterator
const iterator = slowGenerator()[Symbol.asyncIterator]()
const timeout = 2000 // 2 seconds timeout
try {
// Race between the iterator's next result and a timeout promise
const result = await Promise.race([
iterator.next(),
new Promise((_, reject) =>
setTimeout(() => iterator.throw!(new Error("Operation timed out after " + timeout + "ms")), timeout)
)
])
console.log('Result:', result)
return result
} catch (error) {
console.error('Error occurred:', error.message)
throw error
}
}
// Run the improved test
testWithRace().catch(err => console.error('Test failed with race pattern:', err))
Hi, sorry for the late response
From what I can see, the code is relying on calling iterator.throw(...) to inject an error into a server-streaming handler. However, throw is part of the generator protocol and is not something that's meant to be used in typical application code — certainly not in the context of gRPC streams. See MDN docs for context.
This kind of usage falls outside of what nice-grpc is designed to support. Internally, nice-grpc uses async generators as a convenient abstraction, but they're not intended to be interacted with directly from user code in this way.
If you can share what you're trying to achieve at a higher level, maybe there's a more idiomatic solution. But as it stands, this looks like fragile and unsupported behavior.