You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					381 lines
				
				8.0 KiB
			
		
		
			
		
	
	
					381 lines
				
				8.0 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								'use strict'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const fs = require('fs')
							 | 
						||
| 
								 | 
							
								const EventEmitter = require('events')
							 | 
						||
| 
								 | 
							
								const flatstr = require('flatstr')
							 | 
						||
| 
								 | 
							
								const inherits = require('util').inherits
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const BUSY_WRITE_TIMEOUT = 100
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const sleep = require('atomic-sleep')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// 16 MB - magic number
							 | 
						||
| 
								 | 
							
								// This constant ensures that SonicBoom only needs
							 | 
						||
| 
								 | 
							
								// 32 MB of free memory to run. In case of having 1GB+
							 | 
						||
| 
								 | 
							
								// of data to write, this prevents an out of memory
							 | 
						||
| 
								 | 
							
								// condition.
							 | 
						||
| 
								 | 
							
								const MAX_WRITE = 16 * 1024 * 1024
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function openFile (file, sonic) {
							 | 
						||
| 
								 | 
							
								  sonic._opening = true
							 | 
						||
| 
								 | 
							
								  sonic._writing = true
							 | 
						||
| 
								 | 
							
								  sonic._asyncDrainScheduled = false
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // NOTE: 'error' and 'ready' events emitted below only relevant when sonic.sync===false
							 | 
						||
| 
								 | 
							
								  // for sync mode, there is no way to add a listener that will receive these
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function fileOpened (err, fd) {
							 | 
						||
| 
								 | 
							
								    if (err) {
							 | 
						||
| 
								 | 
							
								      sonic._reopening = false
							 | 
						||
| 
								 | 
							
								      sonic._writing = false
							 | 
						||
| 
								 | 
							
								      sonic._opening = false
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (sonic.sync) {
							 | 
						||
| 
								 | 
							
								        process.nextTick(() => {
							 | 
						||
| 
								 | 
							
								          if (sonic.listenerCount('error') > 0) {
							 | 
						||
| 
								 | 
							
								            sonic.emit('error', err)
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        })
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        sonic.emit('error', err)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    sonic.fd = fd
							 | 
						||
| 
								 | 
							
								    sonic.file = file
							 | 
						||
| 
								 | 
							
								    sonic._reopening = false
							 | 
						||
| 
								 | 
							
								    sonic._opening = false
							 | 
						||
| 
								 | 
							
								    sonic._writing = false
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (sonic.sync) {
							 | 
						||
| 
								 | 
							
								      process.nextTick(() => sonic.emit('ready'))
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      sonic.emit('ready')
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (sonic._reopening) {
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // start
							 | 
						||
| 
								 | 
							
								    const len = sonic._buf.length
							 | 
						||
| 
								 | 
							
								    if (len > 0 && len > sonic.minLength && !sonic.destroyed) {
							 | 
						||
| 
								 | 
							
								      actualWrite(sonic)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (sonic.sync) {
							 | 
						||
| 
								 | 
							
								    try {
							 | 
						||
| 
								 | 
							
								      const fd = fs.openSync(file, 'a')
							 | 
						||
| 
								 | 
							
								      fileOpened(null, fd)
							 | 
						||
| 
								 | 
							
								    } catch (err) {
							 | 
						||
| 
								 | 
							
								      fileOpened(err)
							 | 
						||
| 
								 | 
							
								      throw err
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    fs.open(file, 'a', fileOpened)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function SonicBoom (opts) {
							 | 
						||
| 
								 | 
							
								  if (!(this instanceof SonicBoom)) {
							 | 
						||
| 
								 | 
							
								    return new SonicBoom(opts)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let { fd, dest, minLength, sync } = opts || {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  fd = fd || dest
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this._buf = ''
							 | 
						||
| 
								 | 
							
								  this.fd = -1
							 | 
						||
| 
								 | 
							
								  this._writing = false
							 | 
						||
| 
								 | 
							
								  this._writingBuf = ''
							 | 
						||
| 
								 | 
							
								  this._ending = false
							 | 
						||
| 
								 | 
							
								  this._reopening = false
							 | 
						||
| 
								 | 
							
								  this._asyncDrainScheduled = false
							 | 
						||
| 
								 | 
							
								  this.file = null
							 | 
						||
| 
								 | 
							
								  this.destroyed = false
							 | 
						||
| 
								 | 
							
								  this.sync = sync || false
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this.minLength = minLength || 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (typeof fd === 'number') {
							 | 
						||
| 
								 | 
							
								    this.fd = fd
							 | 
						||
| 
								 | 
							
								    process.nextTick(() => this.emit('ready'))
							 | 
						||
| 
								 | 
							
								  } else if (typeof fd === 'string') {
							 | 
						||
| 
								 | 
							
								    openFile(fd, this)
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    throw new Error('SonicBoom supports only file descriptors and files')
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this.release = (err, n) => {
							 | 
						||
| 
								 | 
							
								    if (err) {
							 | 
						||
| 
								 | 
							
								      if (err.code === 'EAGAIN') {
							 | 
						||
| 
								 | 
							
								        if (this.sync) {
							 | 
						||
| 
								 | 
							
								          // This error code should not happen in sync mode, because it is
							 | 
						||
| 
								 | 
							
								          // not using the underlining operating system asynchronous functions.
							 | 
						||
| 
								 | 
							
								          // However it happens, and so we handle it.
							 | 
						||
| 
								 | 
							
								          // Ref: https://github.com/pinojs/pino/issues/783
							 | 
						||
| 
								 | 
							
								          try {
							 | 
						||
| 
								 | 
							
								            sleep(BUSY_WRITE_TIMEOUT)
							 | 
						||
| 
								 | 
							
								            this.release(undefined, 0)
							 | 
						||
| 
								 | 
							
								          } catch (err) {
							 | 
						||
| 
								 | 
							
								            this.release(err)
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          // Let's give the destination some time to process the chunk.
							 | 
						||
| 
								 | 
							
								          setTimeout(() => {
							 | 
						||
| 
								 | 
							
								            fs.write(this.fd, this._writingBuf, 'utf8', this.release)
							 | 
						||
| 
								 | 
							
								          }, BUSY_WRITE_TIMEOUT)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        // The error maybe recoverable later, so just put data back to this._buf
							 | 
						||
| 
								 | 
							
								        this._buf = this._writingBuf + this._buf
							 | 
						||
| 
								 | 
							
								        this._writingBuf = ''
							 | 
						||
| 
								 | 
							
								        this._writing = false
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        this.emit('error', err)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (this._writingBuf.length !== n) {
							 | 
						||
| 
								 | 
							
								      this._writingBuf = this._writingBuf.slice(n)
							 | 
						||
| 
								 | 
							
								      if (this.sync) {
							 | 
						||
| 
								 | 
							
								        try {
							 | 
						||
| 
								 | 
							
								          do {
							 | 
						||
| 
								 | 
							
								            n = fs.writeSync(this.fd, this._writingBuf, 'utf8')
							 | 
						||
| 
								 | 
							
								            this._writingBuf = this._writingBuf.slice(n)
							 | 
						||
| 
								 | 
							
								          } while (this._writingBuf.length !== 0)
							 | 
						||
| 
								 | 
							
								        } catch (err) {
							 | 
						||
| 
								 | 
							
								          this.release(err)
							 | 
						||
| 
								 | 
							
								          return
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        fs.write(this.fd, this._writingBuf, 'utf8', this.release)
							 | 
						||
| 
								 | 
							
								        return
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this._writingBuf = ''
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (this.destroyed) {
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const len = this._buf.length
							 | 
						||
| 
								 | 
							
								    if (this._reopening) {
							 | 
						||
| 
								 | 
							
								      this._writing = false
							 | 
						||
| 
								 | 
							
								      this._reopening = false
							 | 
						||
| 
								 | 
							
								      this.reopen()
							 | 
						||
| 
								 | 
							
								    } else if (len > 0 && len > this.minLength) {
							 | 
						||
| 
								 | 
							
								      actualWrite(this)
							 | 
						||
| 
								 | 
							
								    } else if (this._ending) {
							 | 
						||
| 
								 | 
							
								      if (len > 0) {
							 | 
						||
| 
								 | 
							
								        actualWrite(this)
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        this._writing = false
							 | 
						||
| 
								 | 
							
								        actualClose(this)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      this._writing = false
							 | 
						||
| 
								 | 
							
								      if (this.sync) {
							 | 
						||
| 
								 | 
							
								        if (!this._asyncDrainScheduled) {
							 | 
						||
| 
								 | 
							
								          this._asyncDrainScheduled = true
							 | 
						||
| 
								 | 
							
								          process.nextTick(emitDrain, this)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        this.emit('drain')
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this.on('newListener', function (name) {
							 | 
						||
| 
								 | 
							
								    if (name === 'drain') {
							 | 
						||
| 
								 | 
							
								      this._asyncDrainScheduled = false
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function emitDrain (sonic) {
							 | 
						||
| 
								 | 
							
								  const hasListeners = sonic.listenerCount('drain') > 0
							 | 
						||
| 
								 | 
							
								  if (!hasListeners) return
							 | 
						||
| 
								 | 
							
								  sonic._asyncDrainScheduled = false
							 | 
						||
| 
								 | 
							
								  sonic.emit('drain')
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								inherits(SonicBoom, EventEmitter)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SonicBoom.prototype.write = function (data) {
							 | 
						||
| 
								 | 
							
								  if (this.destroyed) {
							 | 
						||
| 
								 | 
							
								    throw new Error('SonicBoom destroyed')
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this._buf += data
							 | 
						||
| 
								 | 
							
								  const len = this._buf.length
							 | 
						||
| 
								 | 
							
								  if (!this._writing && len > this.minLength) {
							 | 
						||
| 
								 | 
							
								    actualWrite(this)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return len < 16384
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SonicBoom.prototype.flush = function () {
							 | 
						||
| 
								 | 
							
								  if (this.destroyed) {
							 | 
						||
| 
								 | 
							
								    throw new Error('SonicBoom destroyed')
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this._writing || this.minLength <= 0) {
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  actualWrite(this)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SonicBoom.prototype.reopen = function (file) {
							 | 
						||
| 
								 | 
							
								  if (this.destroyed) {
							 | 
						||
| 
								 | 
							
								    throw new Error('SonicBoom destroyed')
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this._opening) {
							 | 
						||
| 
								 | 
							
								    this.once('ready', () => {
							 | 
						||
| 
								 | 
							
								      this.reopen(file)
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this._ending) {
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (!this.file) {
							 | 
						||
| 
								 | 
							
								    throw new Error('Unable to reopen a file descriptor, you must pass a file to SonicBoom')
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this._reopening = true
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this._writing) {
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const fd = this.fd
							 | 
						||
| 
								 | 
							
								  this.once('ready', () => {
							 | 
						||
| 
								 | 
							
								    if (fd !== this.fd) {
							 | 
						||
| 
								 | 
							
								      fs.close(fd, (err) => {
							 | 
						||
| 
								 | 
							
								        if (err) {
							 | 
						||
| 
								 | 
							
								          return this.emit('error', err)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      })
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  openFile(file || this.file, this)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SonicBoom.prototype.end = function () {
							 | 
						||
| 
								 | 
							
								  if (this.destroyed) {
							 | 
						||
| 
								 | 
							
								    throw new Error('SonicBoom destroyed')
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this._opening) {
							 | 
						||
| 
								 | 
							
								    this.once('ready', () => {
							 | 
						||
| 
								 | 
							
								      this.end()
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this._ending) {
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this._ending = true
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (!this._writing && this._buf.length > 0 && this.fd >= 0) {
							 | 
						||
| 
								 | 
							
								    actualWrite(this)
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this._writing) {
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  actualClose(this)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SonicBoom.prototype.flushSync = function () {
							 | 
						||
| 
								 | 
							
								  if (this.destroyed) {
							 | 
						||
| 
								 | 
							
								    throw new Error('SonicBoom destroyed')
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this.fd < 0) {
							 | 
						||
| 
								 | 
							
								    throw new Error('sonic boom is not ready yet')
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while (this._buf.length > 0) {
							 | 
						||
| 
								 | 
							
								    try {
							 | 
						||
| 
								 | 
							
								      fs.writeSync(this.fd, this._buf, 'utf8')
							 | 
						||
| 
								 | 
							
								      this._buf = ''
							 | 
						||
| 
								 | 
							
								    } catch (err) {
							 | 
						||
| 
								 | 
							
								      if (err.code !== 'EAGAIN') {
							 | 
						||
| 
								 | 
							
								        throw err
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      sleep(BUSY_WRITE_TIMEOUT)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								SonicBoom.prototype.destroy = function () {
							 | 
						||
| 
								 | 
							
								  if (this.destroyed) {
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  actualClose(this)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function actualWrite (sonic) {
							 | 
						||
| 
								 | 
							
								  sonic._writing = true
							 | 
						||
| 
								 | 
							
								  let buf = sonic._buf
							 | 
						||
| 
								 | 
							
								  const release = sonic.release
							 | 
						||
| 
								 | 
							
								  if (buf.length > MAX_WRITE) {
							 | 
						||
| 
								 | 
							
								    buf = buf.slice(0, MAX_WRITE)
							 | 
						||
| 
								 | 
							
								    sonic._buf = sonic._buf.slice(MAX_WRITE)
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    sonic._buf = ''
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  flatstr(buf)
							 | 
						||
| 
								 | 
							
								  sonic._writingBuf = buf
							 | 
						||
| 
								 | 
							
								  if (sonic.sync) {
							 | 
						||
| 
								 | 
							
								    try {
							 | 
						||
| 
								 | 
							
								      const written = fs.writeSync(sonic.fd, buf, 'utf8')
							 | 
						||
| 
								 | 
							
								      release(null, written)
							 | 
						||
| 
								 | 
							
								    } catch (err) {
							 | 
						||
| 
								 | 
							
								      release(err)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    fs.write(sonic.fd, buf, 'utf8', release)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function actualClose (sonic) {
							 | 
						||
| 
								 | 
							
								  if (sonic.fd === -1) {
							 | 
						||
| 
								 | 
							
								    sonic.once('ready', actualClose.bind(null, sonic))
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  // TODO write a test to check if we are not leaking fds
							 | 
						||
| 
								 | 
							
								  fs.close(sonic.fd, (err) => {
							 | 
						||
| 
								 | 
							
								    if (err) {
							 | 
						||
| 
								 | 
							
								      sonic.emit('error', err)
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (sonic._ending && !sonic._writing) {
							 | 
						||
| 
								 | 
							
								      sonic.emit('finish')
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    sonic.emit('close')
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								  sonic.destroyed = true
							 | 
						||
| 
								 | 
							
								  sonic._buf = ''
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = SonicBoom
							 |