Skip to content

Commit 027c496

Browse files
committed
Merge pull request brianc#315 from brianc/handle-stream-end
better handling of client stream termination
2 parents 4b19869 + 07a049d commit 027c496

File tree

9 files changed

+142
-33
lines changed

9 files changed

+142
-33
lines changed

lib/client.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ Client.prototype.connect = function(callback) {
171171
}
172172
});
173173

174+
con.once('end', function() {
175+
if(self.activeQuery) {
176+
self.activeQuery.handleError(new Error('Stream unexpectedly ended during query execution'));
177+
self.activeQuery = null;
178+
}
179+
self.emit('end');
180+
});
181+
182+
174183
con.on('notice', function(msg) {
175184
self.emit('notice', msg);
176185
});

lib/connection.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ Connection.prototype.connect = function(port, host) {
4040
self.emit('error', error);
4141
});
4242

43+
this.stream.on('end', function() {
44+
self.emit('end');
45+
});
46+
4347
if(this.ssl) {
4448
this.stream.once('data', function(buffer) {
4549
self.setBuffer(buffer);

lib/native/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,15 @@ var clientBuilder = function(config) {
198198
}
199199
});
200200

201+
connection.on('_end', function() {
202+
process.nextTick(function() {
203+
if(connection._activeQuery) {
204+
connection._activeQuery.handleError(new Error("Connection was ended during query"));
205+
}
206+
connection.emit('end');
207+
});
208+
});
209+
201210
connection.on('_readyForQuery', function() {
202211
var q = this._activeQuery;
203212
//a named query finished being prepared

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"deprecate": "~0.1.0"
2323
},
2424
"devDependencies": {
25-
"jshint": "git://github.com/jshint/jshint.git"
25+
"jshint": "1.1.0"
2626
},
2727
"scripts": {
2828
"test": "make test-all connectionString=pg://postgres@localhost:5432/postgres",

src/binding.cc

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#include <stdlib.h>
77

88
#define LOG(msg) printf("%s\n",msg);
9-
#define TRACE(msg) //printf("%s\n", msg);
9+
#define TRACE(msg) //printf(%s\n, msg);
1010

1111

1212
#define THROW(msg) return ThrowException(Exception::Error(String::New(msg)));
@@ -434,12 +434,15 @@ class Connection : public ObjectWrap {
434434

435435
if(revents & UV_READABLE) {
436436
TRACE("revents & UV_READABLE");
437+
TRACE("about to consume input");
437438
if(PQconsumeInput(connection_) == 0) {
439+
TRACE("could not read, terminating");
438440
End();
439441
EmitLastError();
440442
//LOG("Something happened, consume input is 0");
441443
return;
442444
}
445+
TRACE("Consumed");
443446

444447
//declare handlescope as this method is entered via a libuv callback
445448
//and not part of the public v8 interface
@@ -450,8 +453,11 @@ class Connection : public ObjectWrap {
450453
if (!this->copyInMode_ && !this->copyOutMode_ && PQisBusy(connection_) == 0) {
451454
PGresult *result;
452455
bool didHandleResult = false;
456+
TRACE("PQgetResult");
453457
while ((result = PQgetResult(connection_))) {
458+
TRACE("HandleResult");
454459
didHandleResult = HandleResult(result);
460+
TRACE("PQClear");
455461
PQclear(result);
456462
if(!didHandleResult) {
457463
//this means that we are in copy in or copy out mode
@@ -469,6 +475,7 @@ class Connection : public ObjectWrap {
469475
}
470476

471477
PGnotify *notify;
478+
TRACE("PQnotifies");
472479
while ((notify = PQnotifies(connection_))) {
473480
Local<Object> result = Object::New();
474481
result->Set(channel_symbol, String::New(notify->relname));
@@ -515,6 +522,7 @@ class Connection : public ObjectWrap {
515522
}
516523
bool HandleResult(PGresult* result)
517524
{
525+
TRACE("PQresultStatus");
518526
ExecStatusType status = PQresultStatus(result);
519527
switch(status) {
520528
case PGRES_TUPLES_OK:
@@ -526,6 +534,7 @@ class Connection : public ObjectWrap {
526534
break;
527535
case PGRES_FATAL_ERROR:
528536
{
537+
TRACE("HandleErrorResult");
529538
HandleErrorResult(result);
530539
return true;
531540
}
@@ -610,8 +619,15 @@ class Connection : public ObjectWrap {
610619
{
611620
HandleScope scope;
612621
//instantiate the return object as an Error with the summary Postgres message
613-
Local<Object> msg = Local<Object>::Cast(Exception::Error(String::New(PQresultErrorField(result, PG_DIAG_MESSAGE_PRIMARY))));
614-
622+
TRACE("ReadResultField");
623+
const char* errorMessage = PQresultErrorField(result, PG_DIAG_MESSAGE_PRIMARY);
624+
if(!errorMessage) {
625+
//there is no error, it has already been consumed in the last
626+
//read-loop callback
627+
return;
628+
}
629+
Local<Object> msg = Local<Object>::Cast(Exception::Error(String::New(errorMessage)));
630+
TRACE("AttachErrorFields");
615631
//add the other information returned by Postgres to the error object
616632
AttachErrorField(result, msg, severity_symbol, PG_DIAG_SEVERITY);
617633
AttachErrorField(result, msg, code_symbol, PG_DIAG_SQLSTATE);
@@ -625,6 +641,7 @@ class Connection : public ObjectWrap {
625641
AttachErrorField(result, msg, line_symbol, PG_DIAG_SOURCE_LINE);
626642
AttachErrorField(result, msg, routine_symbol, PG_DIAG_SOURCE_FUNCTION);
627643
Handle<Value> m = msg;
644+
TRACE("EmitError");
628645
Emit("_error", &m);
629646
}
630647

@@ -638,9 +655,11 @@ class Connection : public ObjectWrap {
638655

639656
void End()
640657
{
658+
TRACE("stopping read & write");
641659
StopRead();
642660
StopWrite();
643661
DestroyConnection();
662+
Emit("_end");
644663
}
645664

646665
private:
@@ -719,7 +738,7 @@ class Connection : public ObjectWrap {
719738
void StopWrite()
720739
{
721740
TRACE("write STOP");
722-
if(ioInitialized_) {
741+
if(ioInitialized_ && writing_) {
723742
uv_poll_stop(&write_watcher_);
724743
writing_ = false;
725744
}
@@ -739,7 +758,7 @@ class Connection : public ObjectWrap {
739758
void StopRead()
740759
{
741760
TRACE("read STOP");
742-
if(ioInitialized_) {
761+
if(ioInitialized_ && reading_) {
743762
uv_poll_stop(&read_watcher_);
744763
reading_ = false;
745764
}

test/integration/client/error-handling-tests.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,16 @@ test('multiple connection errors (gh#31)', function() {
158158
var badConString = "tcp://aslkdfj:oi14081@"+helper.args.host+":"+helper.args.port+"/"+helper.args.database;
159159
return false;
160160
});
161+
});
161162

163+
test('query receives error on client shutdown', function() {
164+
var client = new Client(helper.config);
165+
client.connect(assert.calls(function() {
166+
client.query('SELECT pg_sleep(5)', assert.calls(function(err, res) {
167+
assert(err);
168+
}));
169+
client.end();
170+
assert.emits(client, 'end');
171+
}));
162172
});
163173

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
var helper = require(__dirname + '/test-helper');
2+
var util = require('util');
3+
4+
test('error during query execution', function() {
5+
var client = new Client(helper.args);
6+
client.connect(assert.success(function() {
7+
var sleepQuery = 'select pg_sleep(5)';
8+
client.query(sleepQuery, assert.calls(function(err, result) {
9+
assert(err);
10+
client.end();
11+
assert.emits(client, 'end');
12+
}));
13+
var client2 = new Client(helper.args);
14+
client2.connect(assert.success(function() {
15+
var killIdleQuery = "SELECT procpid, (SELECT pg_terminate_backend(procpid)) AS killed FROM pg_stat_activity WHERE current_query = $1";
16+
client2.query(killIdleQuery, [sleepQuery], assert.calls(function(err, res) {
17+
assert.ifError(err);
18+
assert.equal(res.rowCount, 1);
19+
client2.end();
20+
assert.emits(client2, 'end');
21+
}));
22+
}));
23+
}));
24+
});

test/native/error-tests.js

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,30 @@ test('query with non-text as first parameter throws error', function() {
55
var client = new Client(helper.config);
66
client.connect();
77
assert.emits(client, 'connect', function() {
8-
assert.throws(function() {
9-
client.query({text:{fail: true}});
10-
})
118
client.end();
12-
})
13-
})
9+
assert.emits(client, 'end', function() {
10+
assert.throws(function() {
11+
client.query({text:{fail: true}});
12+
});
13+
});
14+
});
15+
});
1416

1517
test('parameterized query with non-text as first parameter throws error', function() {
1618
var client = new Client(helper.config);
1719
client.connect();
1820
assert.emits(client, 'connect', function() {
19-
assert.throws(function() {
20-
client.query({
21-
text: {fail: true},
22-
values: [1, 2]
23-
})
24-
})
2521
client.end();
26-
})
27-
})
22+
assert.emits(client, 'end', function() {
23+
assert.throws(function() {
24+
client.query({
25+
text: {fail: true},
26+
values: [1, 2]
27+
})
28+
});
29+
});
30+
});
31+
});
2832

2933
var connect = function(callback) {
3034
var client = new Client(helper.config);
@@ -37,24 +41,28 @@ var connect = function(callback) {
3741
test('parameterized query with non-array for second value', function() {
3842
test('inline', function() {
3943
connect(function(client) {
40-
assert.throws(function() {
41-
client.query("SELECT *", "LKSDJF")
42-
})
4344
client.end();
44-
})
45-
})
45+
assert.emits(client, 'end', function() {
46+
assert.throws(function() {
47+
client.query("SELECT *", "LKSDJF")
48+
});
49+
});
50+
});
51+
});
4652

4753
test('config', function() {
4854
connect(function(client) {
49-
assert.throws(function() {
50-
client.query({
51-
text: "SELECT *",
52-
values: "ALSDKFJ"
53-
})
54-
})
5555
client.end();
56-
})
57-
})
58-
})
56+
assert.emits(client, 'end', function() {
57+
assert.throws(function() {
58+
client.query({
59+
text: "SELECT *",
60+
values: "ALSDKFJ"
61+
});
62+
});
63+
});
64+
});
65+
});
66+
});
5967

6068

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
var helper = require(__dirname + '/test-helper');
2+
var Connection = require(__dirname + '/../../../lib/connection');
3+
var Client = require(__dirname + '/../../../lib/client');
4+
5+
test('emits end when not in query', function() {
6+
var stream = new (require('events').EventEmitter)();
7+
stream.write = function() {
8+
//NOOP
9+
}
10+
var client = new Client({connection: new Connection({stream: stream})});
11+
client.connect(assert.calls(function() {
12+
client.query('SELECT NOW()', assert.calls(function(err, result) {
13+
assert(err);
14+
}));
15+
}));
16+
assert.emits(client, 'end');
17+
client.connection.emit('connect');
18+
process.nextTick(function() {
19+
client.connection.emit('readyForQuery');
20+
assert.equal(client.queryQueue.length, 0);
21+
assert(client.activeQuery, 'client should have issued query');
22+
process.nextTick(function() {
23+
stream.emit('end');
24+
});
25+
});
26+
});

0 commit comments

Comments
 (0)