Skip to content

regression: data truncation with stream.writev in IPC channel #24992

@gireeshpunathil

Description

@gireeshpunathil
  • 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 1074790400 bytes (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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    child_processIssues and PRs related to the child_process subsystem.help wantedIssues that need assistance from volunteers or PRs that need help to proceed.streamIssues and PRs related to the stream subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions