Skip to content

Commit 6b91c51

Browse files
committed
Close open sessions when HTTP driver is closed
This will rollback all open transactions and cancel running queries.
1 parent 3703292 commit 6b91c51

File tree

5 files changed

+190
-16
lines changed

5 files changed

+190
-16
lines changed

src/v1/internal/http/http-driver.js

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,20 @@
1919

2020
import Driver from '../../driver';
2121
import HttpSession from './http-session';
22+
import HttpSessionTracker from './http-session-tracker';
2223

2324
export default class HttpDriver extends Driver {
2425

2526
constructor(url, userAgent, token, config) {
2627
super(url, userAgent, token, config);
27-
this._sessionIdGenerator = 0;
28-
this._openSessions = {};
28+
this._sessionTracker = new HttpSessionTracker();
2929
}
3030

3131
session() {
32-
const id = this._sessionIdGenerator;
33-
this._sessionIdGenerator++;
34-
const session = new HttpSession(this._url, this._token, this._config);
35-
this._openSessions[id] = session;
36-
return session;
32+
return new HttpSession(this._url, this._token, this._config, this._sessionTracker);
3733
}
3834

3935
close() {
40-
Object.keys(this._openSessions).forEach(id => {
41-
const session = this._openSessions[id];
42-
if (session) {
43-
session.close();
44-
}
45-
delete this._openSessions[id];
46-
});
36+
return this._sessionTracker.close();
4737
}
4838
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Copyright (c) 2002-2018 "Neo Technology,"
3+
* Network Engine for Objects in Lund AB [http://neotechnology.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
export default class HttpSessionTracker {
21+
22+
constructor() {
23+
this._openSessions = new Set();
24+
}
25+
26+
/**
27+
* Record given session as open.
28+
* @param {HttpSession} session the newly open session.
29+
*/
30+
sessionOpened(session) {
31+
this._openSessions.add(session);
32+
}
33+
34+
/**
35+
* Record given session as close.
36+
* @param {HttpSession} session the just closed session.
37+
*/
38+
sessionClosed(session) {
39+
this._openSessions.delete(session);
40+
}
41+
42+
/**
43+
* Close this tracker and all open sessions.
44+
*/
45+
close() {
46+
const sessions = Array.from(this._openSessions);
47+
this._openSessions.clear();
48+
return Promise.all(sessions.map(session => closeSession(session)));
49+
}
50+
}
51+
52+
/**
53+
* Close given session and get a promise back.
54+
* @param {HttpSession} session the session to close.
55+
* @return {Promise<void>} promise resolved when session is closed.
56+
*/
57+
function closeSession(session) {
58+
return new Promise(resolve => {
59+
session.close(() => {
60+
resolve();
61+
});
62+
});
63+
}

src/v1/internal/http/http-session.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ import Result from '../../result';
2727

2828
export default class HttpSession extends Session {
2929

30-
constructor(url, authToken, config) {
30+
constructor(url, authToken, config, sessionTracker) {
3131
super(WRITE, null, null, config);
3232
this._ongoingTransactionIds = [];
3333
this._serverInfoSupplier = createServerInfoSupplier(url);
3434
this._requestRunner = new HttpRequestRunner(url, authToken);
35+
this._sessionTracker = sessionTracker;
36+
this._sessionTracker.sessionOpened(this);
3537
}
3638

3739
run(statement, parameters = {}) {
@@ -79,7 +81,11 @@ export default class HttpSession extends Session {
7981
rollbackTransactionSilently(transactionId, this._requestRunner)
8082
);
8183

82-
Promise.all(rollbackAllOngoingTransactions).then(() => callback());
84+
Promise.all(rollbackAllOngoingTransactions)
85+
.then(() => {
86+
this._sessionTracker.sessionClosed(this);
87+
callback();
88+
});
8389
}
8490
}
8591

test/internal/http/http-driver.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,28 @@ describe('http driver', () => {
463463
done);
464464
});
465465

466+
it('should close all open sessions when closed', done => {
467+
if (testUtils.isServer()) {
468+
done();
469+
return;
470+
}
471+
472+
const session1 = withFakeClose(httpDriver.session());
473+
const session2 = withFakeClose(httpDriver.session());
474+
const session3 = withFakeClose(httpDriver.session());
475+
476+
expect(session1.closed).toBeFalsy();
477+
expect(session2.closed).toBeFalsy();
478+
expect(session3.closed).toBeFalsy();
479+
480+
httpDriver.close().then(() => {
481+
expect(session1.closed).toBeTruthy();
482+
expect(session2.closed).toBeTruthy();
483+
expect(session3.closed).toBeTruthy();
484+
done();
485+
});
486+
});
487+
466488
function testReceiveSingleValueWithHttpDriver(query, expectedValue, done) {
467489
runQueryAndGetResults(query, {}, httpDriver).then(results => {
468490
const receivedValue = results[0][0];
@@ -558,4 +580,14 @@ describe('http driver', () => {
558580
return serverVersion.compareTo(VERSION_3_4_0) >= 0;
559581
}
560582

583+
function withFakeClose(httpSession) {
584+
httpSession.closed = false;
585+
const originalClose = httpSession.close.bind(httpSession);
586+
httpSession.close = callback => {
587+
httpSession.closed = true;
588+
originalClose(callback);
589+
};
590+
return httpSession;
591+
}
592+
561593
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* Copyright (c) 2002-2018 "Neo Technology,"
3+
* Network Engine for Objects in Lund AB [http://neotechnology.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
import sharedNeo4j from '../../internal/shared-neo4j';
20+
import HttpSession from '../../../src/v1/internal/http/http-session';
21+
import urlUtil from '../../../src/v1/internal/url-util';
22+
import HttpSessionTracker from '../../../src/v1/internal/http/http-session-tracker';
23+
24+
describe('http session tracker', () => {
25+
26+
it('should close open sessions', done => {
27+
const tracker = new HttpSessionTracker();
28+
29+
const session1 = new FakeHttpSession(tracker);
30+
const session2 = new FakeHttpSession(tracker);
31+
const session3 = new FakeHttpSession(tracker);
32+
33+
tracker.sessionOpened(session1);
34+
tracker.sessionOpened(session2);
35+
tracker.sessionOpened(session3);
36+
37+
tracker.close().then(() => {
38+
expect(session1.timesClosed).toEqual(1);
39+
expect(session2.timesClosed).toEqual(1);
40+
expect(session3.timesClosed).toEqual(1);
41+
done();
42+
});
43+
});
44+
45+
it('should not close closed sessions', done => {
46+
const tracker = new HttpSessionTracker();
47+
48+
const session1 = new FakeHttpSession(tracker);
49+
const session2 = new FakeHttpSession(tracker);
50+
const session3 = new FakeHttpSession(tracker);
51+
const session4 = new FakeHttpSession(tracker);
52+
53+
tracker.sessionOpened(session1);
54+
tracker.sessionOpened(session2);
55+
tracker.sessionOpened(session3);
56+
tracker.sessionOpened(session4);
57+
58+
tracker.sessionClosed(session2);
59+
tracker.sessionClosed(session4);
60+
61+
tracker.close().then(() => {
62+
expect(session1.timesClosed).toEqual(1);
63+
expect(session2.timesClosed).toEqual(0);
64+
expect(session3.timesClosed).toEqual(1);
65+
expect(session4.timesClosed).toEqual(0);
66+
done();
67+
});
68+
});
69+
70+
});
71+
72+
class FakeHttpSession extends HttpSession {
73+
74+
constructor(sessionTracker) {
75+
super(urlUtil.parseDatabaseUrl('http://localhost:7474'), sharedNeo4j.authToken, {}, sessionTracker);
76+
this.timesClosed = 0;
77+
}
78+
79+
close(callback) {
80+
this.timesClosed++;
81+
callback();
82+
}
83+
}

0 commit comments

Comments
 (0)