Skip to content

Commit f5c0748

Browse files
committed
Send GOODBYE message before closing connections in Bolt V3
To make the database know that connection is being terminated intentionally. No such message is sent in Bolt V1 and V2.
1 parent d1a8bf3 commit f5c0748

File tree

9 files changed

+112
-2
lines changed

9 files changed

+112
-2
lines changed

src/v1/internal/bolt-protocol-v1.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ export default class BoltProtocol {
7272
this._connection.write(message, observer, true);
7373
}
7474

75+
prepareToClose(observer) {
76+
// no need to notify the database in this protocol version
77+
}
78+
7579
/**
7680
* Begin an explicit transaction.
7781
* @param {Bookmark} bookmark the bookmark.

src/v1/internal/bolt-protocol-v3.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ export default class BoltProtocol extends BoltProtocolV2 {
4747
this._connection.write(message, observer, true);
4848
}
4949

50+
prepareToClose(observer) {
51+
const message = RequestMessage.goodbye();
52+
this._connection.write(message, observer, true);
53+
}
54+
5055
beginTransaction(bookmark, txConfig, observer) {
5156
prepareToHandleSingleResponse(observer);
5257
const message = RequestMessage.begin(bookmark, txConfig);

src/v1/internal/connection.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,11 +382,23 @@ export default class Connection {
382382
* Call close on the channel.
383383
* @param {function} cb - Function to call on close.
384384
*/
385-
close(cb) {
385+
close(cb = (() => null)) {
386386
if (this._log.isDebugEnabled()) {
387387
this._log.debug(`${this} closing`);
388388
}
389-
this._ch.close(cb);
389+
390+
if (this._protocol) {
391+
// protocol has been initialized
392+
// use it to notify the database about the upcoming close of the connection
393+
this._protocol.prepareToClose(NO_OP_OBSERVER);
394+
}
395+
396+
this._ch.close(() => {
397+
if (this._log.isDebugEnabled()) {
398+
this._log.debug(`${this} closed`);
399+
}
400+
cb();
401+
});
390402
}
391403

392404
toString() {

src/v1/internal/request-message.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const DISCARD_ALL = 0x2F; // 0010 1111 // DISCARD_ALL - unused
2626
const PULL_ALL = 0x3F; // 0011 1111 // PULL_ALL
2727

2828
const HELLO = 0x01; // 0000 0001 // HELLO <metadata>
29+
const GOODBYE = 0x02; // 0000 0010 // GOODBYE
2930
const BEGIN = 0x11; // 0001 0001 // BEGIN <metadata>
3031
const COMMIT = 0x12; // 0001 0010 // COMMIT
3132
const ROLLBACK = 0x13; // 0001 0011 // ROLLBACK
@@ -125,6 +126,14 @@ export default class RequestMessage {
125126
return new RequestMessage(RUN, [statement, parameters, metadata],
126127
() => `RUN ${statement} ${JSON.stringify(parameters)} ${JSON.stringify(metadata)}`);
127128
}
129+
130+
/**
131+
* Get a GOODBYE message.
132+
* @return {RequestMessage} the GOODBYE message.
133+
*/
134+
static goodbye() {
135+
return GOODBYE_MESSAGE;
136+
}
128137
}
129138

130139
/**
@@ -152,3 +161,4 @@ const PULL_ALL_MESSAGE = new RequestMessage(PULL_ALL, [], () => 'PULL_ALL');
152161
const RESET_MESSAGE = new RequestMessage(RESET, [], () => 'RESET');
153162
const COMMIT_MESSAGE = new RequestMessage(COMMIT, [], () => 'COMMIT');
154163
const ROLLBACK_MESSAGE = new RequestMessage(ROLLBACK, [], () => 'ROLLBACK');
164+
const GOODBYE_MESSAGE = new RequestMessage(GOODBYE, [], () => 'GOODBYE');

test/internal/connection.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import ConnectionErrorHandler from '../../src/v1/internal/connection-error-handl
3232
import testUtils from '../internal/test-utils';
3333
import Bookmark from '../../src/v1/internal/bookmark';
3434
import TxConfig from '../../src/v1/internal/tx-config';
35+
import boltStub from '../internal/bolt-stub';
3536

3637
const ILLEGAL_MESSAGE = {signature: 42, fields: []};
3738
const SUCCESS_MESSAGE = {signature: 0x70, fields: [{}]};
@@ -346,6 +347,29 @@ describe('Connection', () => {
346347
connection._handleFatalError(newError('Hello', SERVICE_UNAVAILABLE));
347348
});
348349

350+
it('should send hello and goodbye messages', done => {
351+
if (!boltStub.supported) {
352+
done();
353+
return;
354+
}
355+
356+
const server = boltStub.start('./test/resources/boltstub/hello_goodbye.script', 9001);
357+
358+
boltStub.run(() => {
359+
connection = createConnection('bolt://127.0.0.1:9001', {encrypted: false});
360+
connection.connect('single-connection/1.2.3', basicAuthToken())
361+
.then(() => {
362+
connection.close(() => {
363+
server.exit(code => {
364+
expect(code).toEqual(0);
365+
done();
366+
});
367+
});
368+
})
369+
.catch(error => done.fail(error));
370+
});
371+
});
372+
349373
function packedHandshakeMessage() {
350374
const result = alloc(4);
351375
result.putInt32(0, 1);

test/internal/request-message.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,11 @@ describe('RequestMessage', () => {
115115
expect(message.toString()).toEqual(`RUN ${statement} ${JSON.stringify(parameters)} ${JSON.stringify(expectedMetadata)}`);
116116
});
117117

118+
it('should create GOODBYE message', () => {
119+
const message = RequestMessage.goodbye();
120+
121+
expect(message.signature).toEqual(0x02);
122+
expect(message.fields).toEqual([]);
123+
expect(message.toString()).toEqual('GOODBYE');
124+
});
118125
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
!: BOLT 3
2+
3+
C: HELLO {"credentials": "password", "scheme": "basic", "user_agent": "single-connection/1.2.3", "principal": "neo4j"}
4+
S: SUCCESS {"server": "Neo4j/9.9.9"}
5+
C: GOODBYE
6+
S: SUCCESS {}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
!: BOLT 3
2+
!: AUTO RESET
3+
4+
C: HELLO {"credentials": "password", "scheme": "basic", "user_agent": "neo4j-javascript/0.0.0-dev", "principal": "neo4j"}
5+
S: SUCCESS {"server": "Neo4j/9.9.9"}
6+
C: RUN "MATCH (n) RETURN n.name" {} {}
7+
PULL_ALL
8+
S: SUCCESS {"fields": ["n.name"]}
9+
RECORD ["Foo"]
10+
RECORD ["Bar"]
11+
SUCCESS {}
12+
C: GOODBYE
13+
S: SUCCESS {}

test/v1/direct.driver.boltkit.test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,33 @@ describe('direct driver with stub server', () => {
333333
});
334334
});
335335

336+
it('should send goodbye message when closed', done => {
337+
if (!boltStub.supported) {
338+
done();
339+
return;
340+
}
341+
342+
const server = boltStub.start('./test/resources/boltstub/hello_run_goodbye.script', 9001);
343+
344+
boltStub.run(() => {
345+
const driver = boltStub.newDriver('bolt://127.0.0.1:9001');
346+
const session = driver.session();
347+
348+
session.run('MATCH (n) RETURN n.name')
349+
.then(result => {
350+
const names = result.records.map(record => record.get('n.name'));
351+
expect(names).toEqual(['Foo', 'Bar']);
352+
353+
session.close(() => {
354+
driver.close();
355+
server.exit(code => {
356+
expect(code).toEqual(0);
357+
done();
358+
});
359+
});
360+
})
361+
.catch(error => done.fail(error));
362+
});
363+
});
364+
336365
});

0 commit comments

Comments
 (0)