-
-
Notifications
You must be signed in to change notification settings - Fork 34.3k
Description
- Version: all supported versions
- Platform: all UNIX variants
- Subsystem: stream, child_process
I was working on a potential solution for the data truncation in child standard streams upon process exit problem, and came across this scenario:
const cp = require('child_process')
const KB = 1024
const MB = KB * KB
if (process.argv[2] === 'child') {
const data = Buffer.alloc(KB).fill('x').toString()
for (var i = 0; i < MB; i++)
console.log(data)
// process.exit(0)
} else {
const child = cp.spawn(process.execPath, [__filename, 'child'])
var count = 0
child.stdout.on('data', c => count += c.length)
child.stdout.on('end', () => console.log(count))
child.stderr.on('data', (d) => console.log(d.toString()))
}- create a 1KB buffer in a spawned child.
- write it to the parent 1MB times.
- parent should get
1074790400bytes (1GB +1MB new line chars that console.log adds)
actual data obtained in UNIX is much less, and is arbitrary in each run. Windows works fine.
The behavior is well understood in the presence of process.exit but in the absence of it, the process is supposed to drain all the data while there are active requests in the event loop. That is not happening here.
Tracing back, I landed at d85d120 which introduced vectored writes in pipe streams. With this in place, when the channel experiences congestion, it throws UV_ENOBUFS which is passed to the calling code unhandled:
Error: write ENOBUFS
at exports._errnoException (util.js:1026:11)
at Socket._writeGeneric (net.js:715:26)
at Socket._writev (net.js:729:8)
at doWrite (_stream_writable.js:324:12)
at clearBuffer (_stream_writable.js:413:5)
at onwrite (_stream_writable.js:365:7)
at WriteWrap.afterWrite [as oncomplete] (net.js:816:12)
Further, commit 1d6b729 inadvertently caused this error from not being visible.
Subsequently there are lot of changes in stream_base.cc and stream_wrap.cc around DoTryWrite and Writev which makes it difficult to pin-point any particular point where this can be rectified.
Looking at Writev implementation, it does not handle when the data size is more than INT_MAX. In my opinion this should be fixed, as vectored write is an internal mechanism that should hide itself from the JS API. Also, looks like it does not leverage libuv's write-when-you-can capability (uv__write)?
/cc @nodejs/streams @nodejs/child_process
Happy to do any further assistance / testing.