restify / restify/node-restify

throwing error in route handler after request aborted leaks inflight requests

Open
#1,768 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
10.7k
Forks
975
Avg merge
1d 6h
Merged PRs (30d)
5

Description

  • Used appropriate template for the issue type
  • Searched both open and closed issues for duplicates of this issue
  • Title adequately and concisely reflects the feature or the bug

Bug Report

Restify Version

Latest master.

Node.js Version

10.15.1.

Expected behaviour

When throwing an error in a route handler and not listening on the 'uncaughtException' event, the 'after' event is emitted.

This is similar to the issue described at https://github.com/restify/node-restify/issues/1765, except for the fact that in this case the application's code is not setting an event listener for the 'uncaughtException' event.

Actual behaviour

When throwing an error in a route handler and not listening on the 'uncaughtException' event, the 'after' event is not emitted.

Repro case

The following test added to test/server.test.js fails:

diff --git a/test/server.test.js b/test/server.test.js
index ba19cbb..31e580c 100644
--- a/test/server.test.js
+++ b/test/server.test.js
@@ -2051,6 +2051,40 @@ test(
     }
 );
 
+// eslint-disable-next-line max-len
+test("should emit 'after' on uncaughtException after response closed without custom uncaughtException listener", function(t) {
+    var ERR_MSG = 'foo';
+    var gotAfter = false;
+    var gotReqCallback = false;
+
+    SERVER.on('after', function(req, res, route, err) {
+        gotAfter = true;
+        t.ok(err);
+        t.equal(req.connectionState(), 'close');
+        t.equal(res.statusCode, 444);
+        t.equal(err.name, 'Error');
+        t.equal(err.message, ERR_MSG);
+        if (gotReqCallback) {
+            t.end();
+        }
+    });
+
+    SERVER.get('/foobar', function(req, res, next) {
+        res.on('close', function onResClose() {
+            throw new Error(ERR_MSG);
+        });
+    });
+
+    FAST_CLIENT.get('/foobar', function(err, _, res) {
+        gotReqCallback = true;
+        t.ok(err);
+        t.equal(err.name, 'RequestTimeoutError');
+        if (gotAfter) {
+            t.end();
+        }
+    });
+});
+
 test('should increment/decrement inflight request count', function(t) {
     SERVER.get('/foo', function(req, res, next) {
         t.equal(SERVER.inflightRequests(), 1);

Cause

When the request/response actual lifecycle is complete before an exception is thrown and before the next callback of a route handler is called, it means that the route handlers are not marked as finished when Restify attempts to mark the request/response lifecycle as finished.

When an exception is eventually thrown, and as a result the next() callback of the route handler is never called, Restify's default behavior is to send a response, but it still doesn't mark route handlers as finished.

Thus, the res._handlersFinished property is always false, and no 'after' event is emitted. This also causes the number of inflight requests to not be decremented.

Are you willing and able to fix this?

Yes.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the failing repro in test/server.test.js and run the test suite around the server lifecycle tests. Trace how res._handlersFinished is updated when the response closes and an error is thrown before next is called. Done means the 'after' event is emitted and inflightRequests() is decremented for this case.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.