Skip to content

Cannot properly close/cleanup character device ReadStream (process hangs) #15439

Closed
@lfk

Description

@lfk

Issue observed on two distinct systems:

  • System 1. Linux laptop

    • Version: 8.4.0
    • Platform: Arch Linux 4.11.9 (x86_64)
  • System 2. Beaglebone Black

    • Version: 6.10.3
    • Platform: Linux 4.9.41 (armv71)

I'm using fs.ReadStream to continuously read from a character device, e.g. /dev/input/event0. Receiving events works great, but on attempting to shut down the process does not exit as expected. Googling has turned up similar-sounding issues all the way back to node-0.x, but I have been unable to find a solution.

Code to reproduce (behaves the same on both aforementioned systems):

const fs = require('fs')
const stream = fs.createReadStream('/dev/input/event0')

stream
  .on('end', () => { console.log('End') })
  .on('open', fd => { console.log(`Opened (fd=${fd})`) })
  .on('close', () => { console.log('Closed') })
  .on('error', err => { console.log(`Error: ${err}`) })
  .on('data', data => { console.log(data) })

setTimeout(() => {
  console.log('Attempting to close stream')

  stream.on('close', () => {
    console.log(`Stream fd is now ${stream.fd}`)
    process.exit(0) // Hangs
  })

  stream.close()
}, 1000)

Output:

Opened (fd=9)
Attempting to close stream
Closed
Stream fd is now null
(Hung here, ^C or kill process to exit)

A few interesting (?) observations:

Explicitly obtaining the file descriptor we get identical results, unless if the .on('data', ...) listener is not attached, in which case we can exit as expected. Removing the listener in the shutdown process does not work, however, nor do we get this behavior if the fd is opened by fs.createReadStream (i.e. simply commenting out the .on('data', ...) from the first sample. Adjusted sample:

const fs = require('fs')

const fd = fs.openSync('/dev/input/event0', 'r')
const stream = fs.createReadStream('ignored', { fd: fd })

stream
  .on('end', () => { console.log('End') })
  //.on('open', fd => { console.log(`Opened (fd=${fd})`) }) // No event, as expected
  .on('close', () => { console.log('Closed') })
  .on('error', err => { console.log(`Error: ${err}`) })
  //.on('data', data => { console.log(data) }) // Exit works if commented out

setTimeout(() => {
  console.log('Attempting to close stream')

  //stream.removeAllListeners('data') // No result

  stream.on('close', () => {
    console.log(`Stream fd is now ${stream.fd}`)
    process.exit(0)
  })

  stream.close()
  //fs.closeSync(fd) // Does not trigger the `close` event
}, 1000)

This is probably not related to the problem, but I get the same result if I "trigger" an end-event by doing the following in my shutdown routine as such:

// `.push(null)` and `.read(0)` together trigger an `end` event
stream.push(null)
stream.read(0)

Is this a bug, or am I simply missing something? Cheers!

Metadata

Metadata

Assignees

No one assigned

    Labels

    docIssues and PRs related to the documentations.fsIssues and PRs related to the fs subsystem / file system.good first issueIssues that are suitable for first-time contributors.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions