You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I was trying to send a file with a Fastify server and upon completion of the file's transfer have some post-transfer action execute, but realized that the post-transfer action was executing too quickly. The file transfer was taking at least 10 seconds, but the post-transfer action was executed almost immediately after starting the transfer...
Upon extensive debugging I have realized that it's a bug in Deno because the same code correctly works with Node. The problem lies in the fact that the file stream which is sent prematurely closes (which does not happen in Node). The file does get transferred in it's entirety, but the file stream which is used to read from the file gets closed before that and I think that makes Fastify think that the transfer finished.
For now I'm switching back to Node, but here's a minimal reproducible example which showcases the bug in case someone is interested in fixing it.
How to reproduce
import*asfsfrom"node:fs"importFastifyfrom'fastify'import{Readable,Transform,Writable}from'node:stream';// helperasyncfunctionsleep(ms){returnnewPromise((resolve)=>{setTimeout(resolve,ms);});}constfastifyServer=Fastify();fastifyServer.post("/get-file-stream",async(req,reply)=>{console.time();console.info(newDate(),"Request received!");// print "ping" every second while sending dataconstpingTaskId=setInterval(()=>{console.log(newDate(),"ping");},1000);// file can be created via 'head -c 15MB /dev/urandom > ./random-15mb.txt'constfileStream=fs.createReadStream('./random-15mb.txt');fileStream.on('end',()=>{console.info(newDate(),"💧 Stream finished");})fileStream.on('close',()=>{console.info(newDate(),"💧 Stream CLOSED");console.timeEnd();console.log();// newline to visually separate stuff})reply.status(200).send(fileStream);awaitreply;// wait until the file is fully sentconsole.info(newDate(),"📩 Reply awaited");clearInterval(pingTaskId);// stop printing "ping"});awaitfastifyServer.listen({port: 42105,host: "0.0.0.0"});console.log("Server listening...");
One can curl the server to trigger the bug:
curl -X POST http://0.0.0.0:42105/get-file-stream > /dev/null
NOTE: the file transfer needs to be longer so that one can actually notice that the post-transfer action is triggered before the transfer is finished, so either a slow network is required over which the file is sent or (if querying the server locally) the file needs to be quite large.
Notice the 4 pings while the file is transferred and how the transfer takes 4.9 sec.
What do you see instead?
Running with Deno produces:
$ deno run -A test2.jsNot implemented: Server.setTimeout()Server listening...2024-12-29T01:36:04.073Z Request received!2024-12-29T01:36:04.131Z 💧 Stream finished2024-12-29T01:36:04.132Z 📩 Reply awaited2024-12-29T01:36:04.134Z 💧 Stream CLOSEDdefault: 60.6ms
Notice that there's no pings and how the transfer allegedly took only 60 millisec (which is obviously wrong), the transfer was still ongoing for 4 more sec after the message got printed.
Additional information
Platform
This is just for Linux, on MacOS the bug doesn't occur and follows the behavior of Node.
Sending a stream (as in /dev/random) and aborting causes the server to never close the stream
Try adding the below endpoint to the server, curling it and then Ctrl-C curl. Node closes the stream normally, while it remains open forever in Deno and we never await the reply.
fastifyServer.post("/get-infinite-file-stream",async(req,reply)=>{console.time();console.info(newDate(),"Request received!");// print "ping" every second while sending dataconstpingTaskId=setInterval(()=>{console.log(newDate(),"ping");},1000);constfileStream=fs.createReadStream("/dev/random");fileStream.on('end',()=>{console.info(newDate(),"💧 Stream finished");});fileStream.on('close',()=>{console.info(newDate(),"💧 Stream CLOSED");console.timeEnd();console.log();// newline to visually separate stuff});reply.status(200).send(fileStream);awaitreply;console.info(newDate(),"📩 Reply awaited");clearInterval(pingTaskId);// stop printing "ping"});
Sending a generated stream works normally (as in Node)
fastifyServer.post("/get-generator-stream",async(req,reply)=>{console.time();console.info(newDate(),"Request received!");// print "ping" every second while sending dataconstpingTaskId=setInterval(()=>{console.log(newDate(),"ping");},1000);asyncfunction*generate(){awaitsleep(1000);yield'1000\n';awaitsleep(1000);yield'2000\n'awaitsleep(3000);yield'5000\n'awaitsleep(1000);yield'END\n'}constfileStream=newReadable.from(generate());fileStream.on('end',()=>{console.info(newDate(),"💧 Stream finished");})fileStream.on('close',()=>{console.info(newDate(),"💧 Stream CLOSED");console.timeEnd();console.log();// newline to visually separate stuff});reply.status(200).send(fileStream);awaitreply;console.log(newDate(),`📩 Reply awaited`);clearInterval(pingTaskId);// stop printing "ping"});
Version:
deno 2.1.4 (stable, release, x86_64-unknown-linux-gnu)
v8 13.0.245.12-rusty
typescript 5.6.2
Description
I was trying to send a file with a Fastify server and upon completion of the file's transfer have some post-transfer action execute, but realized that the post-transfer action was executing too quickly. The file transfer was taking at least 10 seconds, but the post-transfer action was executed almost immediately after starting the transfer...
Upon extensive debugging I have realized that it's a bug in Deno because the same code correctly works with Node. The problem lies in the fact that the file stream which is sent prematurely closes (which does not happen in Node). The file does get transferred in it's entirety, but the file stream which is used to read from the file gets closed before that and I think that makes Fastify think that the transfer finished.
For now I'm switching back to Node, but here's a minimal reproducible example which showcases the bug in case someone is interested in fixing it.
How to reproduce
One can
curl
the server to trigger the bug:curl -X POST http://0.0.0.0:42105/get-file-stream > /dev/null
NOTE: the file transfer needs to be longer so that one can actually notice that the post-transfer action is triggered before the transfer is finished, so either a slow network is required over which the file is sent or (if querying the server locally) the file needs to be quite large.
What is the expected behavior?
Running with Node produces:
Notice the 4 pings while the file is transferred and how the transfer takes 4.9 sec.
What do you see instead?
Running with Deno produces:
Notice that there's no pings and how the transfer allegedly took only 60 millisec (which is obviously wrong), the transfer was still ongoing for 4 more sec after the message got printed.
Additional information
Platform
This is just for Linux, on MacOS the bug doesn't occur and follows the behavior of Node.
Sending a stream (as in /dev/random) and aborting causes the server to never close the stream
Try adding the below endpoint to the server,
curl
ing it and then Ctrl-C curl. Node closes the stream normally, while it remains open forever in Deno and we never await the reply.Sending a generated stream works normally (as in Node)
The text was updated successfully, but these errors were encountered: