Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

db.attachment.getAsStream method does not propagate axios errors to the caller #236

Open
gyszalai opened this issue Nov 9, 2020 · 8 comments

Comments

@gyszalai
Copy link

gyszalai commented Nov 9, 2020

Expected Behavior

If I call db.attachment.getAsStream() method and an axios error is thrown the caller should be notified.

Current Behavior

If I call e.g. db.attachment.getAsStream() with a non-existing document id, axios throws an error (404)
The PassThrough instance that db.attachment.getAsStream() returns won't emit any events, so the caller
will never receive the error, an unhandledPromiseRejection warning is raised instead.

Possible Solution

In case of an underlying axios error, the returned PassThrough instance should emit an error event.

I inspected the corresponding code, nano.js line 343:

axios(req).then((response) => { response.data.pipe(outStream) })

There should be a catch branch.

Steps to Reproduce (for bugs)

const couchUrl = 'http://admin:admin@localhost:5984'
const nano = require('nano')(couchUrl)
const dbName = 'mydb'
const db = nano.use(dbName)
async function run () {
  console.log('start')
  await nano.db.destroy(dbName)
  console.log('db destroyed')
  await nano.db.create(dbName)
  console.log('db created')

  const docId = 'abc1234'
  const doc = { _id: docId, something: 'a' }
  const data = 'some attachment'
  const { rev } = await db.insert(doc)
  const attName = 'attName'
  console.log('inserting attachment')
  const response = await db.attachment.insert(docId, attName, Buffer.from(data, 'utf-8'), 'text/plain', { rev })
  console.log('attachment inserted', response)

  const attStream = await db.attachment.getAsStream(docId, attName)
  console.log('attachment stream returned')
  await new Promise((resolve, reject) => {
    attStream.on('data', () => console.log('data received:', data))
    attStream.on('end', resolve)
    attStream.on('error', reject)
  })
  console.log('attachment read OK')
  try {
    console.log('try reading attachment of a non-existing doc...')
    const attStream2 = await db.attachment.getAsStream('fake_doc_id', attName)
    console.log('attachment stream returned')
    await new Promise((resolve, reject) => {
      attStream2.on('data', () => console.log('data received: ', data))
      attStream2.on('end', resolve)
      attStream2.on('error', reject)
    })
  } catch (e) {
    console.log('error', e.message)
  }
}

run()

Context

I am trying to create an internal API for reading and writing documents and their attachments.

Your Environment

  • Version used: 9.0.1
  • Browser Name and version: -
  • Operating System and version (desktop or mobile): macOS 10.15.7
  • Link to your project:
@gyszalai
Copy link
Author

@glynnbird any thoughts on this issue?

byronmurg pushed a commit to byronmurg/couchdb-nano that referenced this issue Dec 27, 2020
Fixes issue apache#236.

When calling an *AsStream method a client needs to handle errors
raised by couch. These errors can include retrieving an attachment
that doesn't exist or querying a deleted view.
byronmurg pushed a commit to byronmurg/couchdb-nano that referenced this issue Dec 27, 2020
Fixes issue apache#236.

When calling an *AsStream method a client needs to handle errors
raised by couch. These errors can include retrieving an attachment
that doesn't exist or querying a deleted view.
@byronmurg
Copy link
Contributor

I too have run into this issue. It's quite poor for the error to go unhandled and stop the running process. I've created a pull request to resolve, or at least start some discussion on the problem.

glynnbird pushed a commit that referenced this issue Jan 8, 2021
* Propagate axios errors to stream client

Fixes issue #236.

When calling an *AsStream method a client needs to handle errors
raised by couch. These errors can include retrieving an attachment
that doesn't exist or querying a deleted view.

* Removing extra blank line

* Correcting README stream function names

The examples of several stream functions we not named *AsStream.

Co-authored-by: Byron Murgatroyd <[email protected]>
@gyszalai
Copy link
Author

@glynnbird Isn't this issue fixed yet? #246 fixed it and is already merged.

@gyszalai
Copy link
Author

It seems that error propagation is OK now, but after catching the error the VM doesn't exit. So there must be some unsettled promise.

@gyszalai
Copy link
Author

@glynnbird @byronmurg Just checked the example code with why-is-node-running and found that the followin things prevent the VM from exiting:

# ZLIB
/Users/gyszalai/work/github/nano9_bug/node_modules/axios/lib/adapters/http.js:213 - stream = stream.pipe(zlib.createUnzip());

# TTYWRAP
/Users/gyszalai/work/github/nano9_bug/node_modules/axios/lib/adapters/http.js:213 - stream = stream.pipe(zlib.createUnzip());

# TCPWRAP
/Users/gyszalai/work/github/nano9_bug/node_modules/axios/lib/adapters/http.js:195       - var req = transport.request(options, function handleResponse(res) {
/Users/gyszalai/work/github/nano9_bug/node_modules/axios/lib/adapters/http.js:46        - return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) {
/Users/gyszalai/work/github/nano9_bug/node_modules/axios/lib/core/dispatchRequest.js:52 - return adapter(config).then(function onAdapterResolution(response) {

Hope that helps.

@glynnbird
Copy link
Contributor

Thanks. I'm struggling to get the bottom of this one:

  • Nano returns a stream.PassThrough object because it wants to be able to return a stream to the user synchronously
  • we then run Axios to do the HTTP request to CouchDB. On success the axios stream is piped to the PassThrough stream and all is well. On failure (such as 404), nothing gets piped by an 'error' event is emitted.

I've tried all combinations of close, end, destroy on failure of the axios request but I can't persuade Node.js to kill these objects.

Here's the code in question if anyone wants to suggest some other approach: https://github.com/apache/couchdb-nano/blob/main/lib/nano.js#L382-L393

@gyszalai
Copy link
Author

I just dug down and found that if you don't set maxSockets: 50 in the defaultHttpAgent options, the VM exits gracefully after receiving and logging the error from axios.

@gyszalai
Copy link
Author

It seems that even keepAlive has to be disabled in the defaultHttpAgent options

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants