Repository navigation
fs.readSync throws EOF Error instead of returning 0 (Windows, if STDIN is a pipe) #35997
Description
Activity
The stack trace of the error points at the following code:
Lines 590 to 592 in c1da528
const result = binding.read(fd, buffer, offset, length, position, undefined, ctx); handleErrorFromBinding(ctx); I basically expect the windows
bindingreturns different codes forSocketthan forReadStreamand in turn causes the difference in behavior.You will get much better results with:
/// script.js const fs = require('fs'); console.error('Node: ' + process.execPath); console.error('Version: ' + process.version); console.error('Platform: ' + process.platform); console.error('process.stdin: ' + process.stdin.constructor.name); const buffer = Buffer.alloc(4_096); let read; do { read = fs.readFileSync('/dev/stdin', buffer, 0, buffer.length, null); if (read != 0) { console.log('STDIN: ' + buffer.slice(0, read).toString('utf-8')); } } while (read != 0); console.log('EOF was (normally) reached.');
Don't fiddle with the 0, 1 and 2 file descriptors. They are open in non-blocking mode and libuv doesn't expect this when you are passing a file descriptor.
Hey @mmomtchev,
read = fs.readFileSync('/dev/stdin', buffer, 0, buffer.length, null);
This is interesting and all... But
/dev/stdinis not a thing on Windows, and I cannot require WSL... 🤷🏻♂️
Or doesfshave some magic tricks to make it look like this is a file that exists?No, you are right, I can't think of any easy way to synchronously read from stdin on Windows
There are npm modules or you could go the async way
There is an awful solution here http://corpus.hubwiz.com/2/node.js/3430939.html that will simulate blocking access, repeating the call onEAGAINand breaking the loop onEOFBut the real answer is that Node is built for async I/O
We already have the ugly code to loop on
EAGAIN...
Unfortunately, the use-case I have does not lend itself toasyncexchange here... As there are ordering guarantees I must maintain.The real thing I'm concerned with here, is how
fs.readSyncthrows an EOF error... When it is documented to never do this and instead return0.It says that the descriptor you are passing must be opened in blocking mode.
It says that the descriptor you are passing must be opened in blocking mode.
Where though?
I'm looking at: https://nodejs.org/api/fs.html#fs_fs_readsync_fd_buffer_offset_length_position, and the only place on this page where a requirement for
fdto be in blocking (or non-blocking for that matters) mode is oncreateReadStreamandcreateWriteStream, none of which I am using...
In any case, in light of your confirming that we are doing things likely to trip
libuvin weird and awkward ways, I've been implementing a more comprehensive (dirty) workaround in aws/jsii#2238 that also catches theEOFerror; which would buy us time to figure out if/how we can proceed without needing to do synchronous I/O on ourSTD{IN,OUT,ERR}...@RomainMuller in fact, the stdin is not opened in non-blocking mode on Windows, that is the case only on Linux and OSX, so maybe there is a solution to your problem
On Windows libuv does not properly handle pipes when operating in file mode. I wonder if it is supposed to, because normally, there is a separate pipe mode, but it is a trivial change. In any case, this issue should be taken to https://github.com/libuv/libuv
It happens because the writing process exits and closes the anonymous pipe.
Feel free to open it there, referencing this issue.This is everything that is needed to make it work:
--- a/deps/uv/src/win/fs.c +++ b/deps/uv/src/win/fs.c @@ -918,7 +918,7 @@ void fs__read(uv_fs_t* req) { SET_REQ_RESULT(req, bytes); } else { error = GetLastError(); - if (error == ERROR_HANDLE_EOF) { + if (error == ERROR_HANDLE_EOF || error == ERROR_BROKEN_PIPE) { SET_REQ_RESULT(req, bytes); } else { SET_REQ_WIN32_ERROR(req, error);But then again, you will end up with Windows-specific JS code, which is not the Node way
@ronag is this supposed to work?
Sorry, sync api's is not my area of expertise.
It is not specific to synchronous mode. This is getting stranger by the minute.
On Linux this leavesstdinin non-blocking mode:const fs = require('fs'); console.error('process.stdin: ' + process.stdin.constructor.name); (async () => { let read; read = fs.createReadStream(null, { fd: 0 }) for await (const r of read) console.log(r) console.log('EOF was (normally) reached.') })();
but this leaves it in blocking mode:
const fs = require('fs'); (async () => { let read; read = fs.createReadStream(null, { fd: 0 }) for await (const r of read) console.log(r) console.log('EOF was (normally) reached.') })();
The difference is accessing
process.stdin.constructor.name. Adding a simpleconsole.errordoesn't change anything.Easiest way to check if a file descriptor is in blocking or non-blocking mode is
cat /proc/<pid>/fdinfo/<fd>- look atflags, the fourth digit from the right to the left will be 4 for non-blocking mode@RomainMuller, please disregard what I said, it seems that Node has a problem with handling consistently
stdinacross different situations and different platforms13 remaining items
@joyeecheung, the current solution when it comes to FDs 0, 1 and 2 is absolutely horrible - ie they tend to transparently switch from blocking to non-blocking, requiring the user to reimplement the async read loop in his code by eventually microsleeping... However me too (and I didn't take @mcollina word for granted, I spent some time reading/testing) I came to the conclusion that there is simply no other solution. I still think that Node should not incite people to write horrible code, so this should not be official 😄 , but jokes apart, people are using these FDs, and most of the time they don't even realize something very weird is going on.
They only viable long-term solution I see is that libuv supports reading from regular files in non-blocking mode - something that would be totally useless except to give Node (and the user code) an uniform abstraction layer.
When it comes to the lazy-loading - at this point it doesn't change anything to not do it - the user code will still have to expect these FDs to be in either mode.
I will only try pushing a PR to libuv for the
EOFproblem on Windows - now that we all agree that reading from a pipe in file mode should be supported.If the confusing part is that the FD switches from blocking to non-blocking once you access
process.stdinetc. (due to lazy loading), I guess we could just do something early and make sure that the mode is consistently non-blocking for users (which is probably the old behavior).Yes, it is exactly what made that issue so confusing for me. Changing this however this will have a detrimental performance effect on process creation for everyone and it won't really accomplish anything - the user will still need to support both blocking and non-blocking FDs
Changing it to non-blocking is often likely going to be problematic for any native C libraries that nodejs interacts with, as well as any programs that are execv from nodejs. Thus, I don't recommend doing that. It's also potentially quite problematic in the (very infrequent) case where some other external library calls
dup2to replace the stdio handles with something else.Me too, as an old-school UNIX guy, I was initially appalled by the design decision to have a non-blocking stdio - it is something that is known to break things. But how do you implement pipes in libuv? They are currently built around
net.Socketandnet.Socketis always in non-blocking mode. And at this point it is not even about blocking or non-blocking - it is about it being sometimes blocking and sometimes non-blocking - which is the major problem. But having a consistent stdio requires that libuv fully supports both files, pipes and ttys in one of those modes - which is not the case. You are libuv, would you consider a PR that adds a completely useless support for blocking I/O on pipes/sockets/ttys (through threads) or non-blocking supports for files (that actually blocks and also requires threads)? And the only reason for doing it would be to have a consistent stdio in Node across all platforms? I am not sure it is worth the effort.
By the way, while researching this, I stumbled upon an effort by DJ Bernstein to standardize a new POSIX file API - that will have separateread_nonblocking/write_nonblockingcalls instead of a flag attached to the descriptor. There was even an attempt to implement it in Linux (readv2/writev2calls) but the PR has been frozen for years.the following patch appears to work (tested with piped curl)
fs.readSync = function(origReadSync) { return function newReadSync() { try { return origReadSync.apply(this, arguments); } catch (e) { if (e.code === 'EOF') return 0; else throw e; } }; }(fs.readSync);
Reacted by Momtchil Momtchev@bughit it is a remarkably ugly solution, so I think it will be very appropriate to include it in the read loop with the microsleep 😄
@RomainMuller there is a work around for your issue, this is libuv, so the final fix won't happen before Node 16- addedfsIssues and PRs related to file-system APIs and the fs module.Issues and PRs related to file-system APIs and the fs module.
on Dec 16, 2020 - added a commit that references this issue
on May 14, 2021 Hi @RomainMuller, I wanted to look into this issue, but was not able to reproduce (tried 18.18.2, 20.10.0 and 22.0.0-pre). Do you think this can be closed now?
Since this is no longer reproducible, and there has been no answer since the last comment, I'll close this. If you still experience the same issue, please reopen this issue or open a new one.
- added a commit that references this issue
on Jul 10, 2026
fsWhat steps will reproduce the bug?
Given the following node script:
Will behave differently based on how it is invoked:
How often does it reproduce? Is there a required condition?
This issue happens 100% of the time when
process.stdinis aSocketinstance.What is the expected behavior?
The expected and documented behavior is that
fs.readSyncreturns0when EOF is reached.What do you see instead?
Instead, an
Error: EOFis thrown, which is unexpected.Additional information
This problem was surfaced as users are starting to see failures on Windows at aws/aws-cdk#11314.
We have additionally seen issues where in similar conditions (
process.stdinis a socket/pipe), attempting to performfs.readSyncon file descriptor0(aka STDIN), occasionally results in unexpectedEAGAINerrors being thrown, which might be related. That was highlighted in aws/aws-cdk#5187 and we are currently working with a pretty ugly workaround that would be nice if removed (but that other issue is a lot more difficult to steadily reproduce, as it does not always trigger, and may be caused by side-effects of another operation).