Skip to content

Commit faf6f6b

Browse files
authored
Don't send RESET message when the connection is not open (#807)
Send RESET when the connection is not optimal since it was time waiting for the message send times out. It could be really fast in the nodejs environment, but it could take some minutes in browser. Skip send RESET when the connection is not open avoid this issue.
1 parent 02dd421 commit faf6f6b

File tree

8 files changed

+250
-9
lines changed

8 files changed

+250
-9
lines changed

bolt-connection/src/connection/connection-channel.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,10 @@ export default class ChannelConnection extends Connection {
270270
*/
271271
_handleFatalError (error) {
272272
this._isBroken = true
273-
this._error = this.handleAndTransformError(this._protocol.currentFailure || error, this._address)
273+
this._error = this.handleAndTransformError(
274+
this._protocol.currentFailure || error,
275+
this._address
276+
)
274277

275278
if (this._log.isErrorEnabled()) {
276279
this._log.error(
@@ -320,6 +323,10 @@ export default class ChannelConnection extends Connection {
320323
}
321324

322325
_resetOnFailure () {
326+
if (!this.isOpen()) {
327+
return
328+
}
329+
323330
this._protocol.reset({
324331
onError: () => {
325332
this._protocol.resetFailure()

bolt-connection/src/pool/pool.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,22 @@ class Pool {
134134
* Destroy all idle resources in this pool.
135135
* @returns {Promise<void>} A promise that is resolved when the resources are purged
136136
*/
137-
close () {
137+
async close () {
138138
this._closed = true
139-
return Promise.all(Object.keys(this._pools).map(key => this._purgeKey(key)))
139+
/**
140+
* The lack of Promise consuming was making the driver do not close properly in the scenario
141+
* captured at result.test.js:it('should handle missing onCompleted'). The test was timing out
142+
* because while wainting for the driver close.
143+
*
144+
* Consuming the Promise.all or by calling then or by awaiting in the result inside this method solved
145+
* the issue somehow.
146+
*
147+
* PS: the return of this method was already awaited at PooledConnectionProvider.close, but the await bellow
148+
* seems to be need also.
149+
*/
150+
return await Promise.all(
151+
Object.keys(this._pools).map(key => this._purgeKey(key))
152+
)
140153
}
141154

142155
/**

bolt-connection/test/channel/browser/browser-channel.test.js

+32
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,38 @@ describe('WebSocketChannel', () => {
294294
}
295295
})
296296

297+
describe('.close()', () => {
298+
it('should set _open to false before resolve the promise', async () => {
299+
const fakeSetTimeout = setTimeoutMock.install()
300+
try {
301+
// do not execute setTimeout callbacks
302+
fakeSetTimeout.pause()
303+
const address = ServerAddress.fromUrl('bolt://localhost:8989')
304+
const driverConfig = { connectionTimeout: 4242 }
305+
const channelConfig = new ChannelConfig(
306+
address,
307+
driverConfig,
308+
SERVICE_UNAVAILABLE
309+
)
310+
webSocketChannel = new WebSocketChannel(
311+
channelConfig,
312+
undefined,
313+
createWebSocketFactory(WS_OPEN)
314+
)
315+
316+
expect(webSocketChannel._open).toBe(true)
317+
318+
const promise = webSocketChannel.close()
319+
320+
expect(webSocketChannel._open).toBe(false)
321+
322+
await promise
323+
} finally {
324+
fakeSetTimeout.uninstall()
325+
}
326+
})
327+
})
328+
297329
describe('.setupReceiveTimeout()', () => {
298330
beforeEach(() => {
299331
const address = ServerAddress.fromUrl('http://localhost:8989')

bolt-connection/test/channel/node/node-channel.test.js

+14
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@ describe('NodeChannel', () => {
4444
return expect(channel.close()).resolves.not.toThrow()
4545
})
4646

47+
describe('.close()', () => {
48+
it('should set _open to false before resolve the promise', async () => {
49+
const channel = createMockedChannel(true)
50+
51+
expect(channel._open).toBe(true)
52+
53+
const promise = channel.close()
54+
55+
expect(channel._open).toBe(false)
56+
57+
await promise
58+
})
59+
})
60+
4761
describe('.setupReceiveTimeout()', () => {
4862
it('should call socket.setTimeout(receiveTimeout)', () => {
4963
const receiveTimeout = 42

bolt-connection/test/connection/connection-channel.test.js

+63-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import ChannelConnection from '../../src/connection/connection-channel'
2121
import { int, internal } from 'neo4j-driver-core'
22-
import { add } from 'lodash'
2322

2423
const {
2524
serverAddress: { ServerAddress },
@@ -127,6 +126,69 @@ describe('ChannelConnection', () => {
127126
)
128127
})
129128

129+
describe('._resetOnFailure()', () => {
130+
describe('when connection isOpen', () => {
131+
it('should call protocol.reset() and then protocol.resetFailure() onComplete', () => {
132+
const channel = {
133+
_open: true
134+
}
135+
136+
const protocol = {
137+
reset: jest.fn(observer => observer.onComplete()),
138+
resetFailure: jest.fn()
139+
}
140+
const protocolSupplier = () => protocol
141+
const connection = spyOnConnectionChannel({ channel, protocolSupplier })
142+
143+
connection._resetOnFailure()
144+
145+
expect(protocol.reset).toHaveBeenCalled()
146+
expect(protocol.resetFailure).toHaveBeenCalled()
147+
})
148+
149+
it('should call protocol.reset() and then protocol.resetFailure() onError', () => {
150+
const channel = {
151+
_open: true
152+
}
153+
154+
const protocol = {
155+
reset: jest.fn(observer => observer.onError()),
156+
resetFailure: jest.fn()
157+
}
158+
const protocolSupplier = () => protocol
159+
const connection = spyOnConnectionChannel({ channel, protocolSupplier })
160+
161+
connection._resetOnFailure()
162+
163+
expect(protocol.reset).toHaveBeenCalled()
164+
expect(protocol.resetFailure).toHaveBeenCalled()
165+
})
166+
})
167+
168+
describe('when connection is not open', () => {
169+
it('should not call protocol.reset() and protocol.resetFailure()', () => {
170+
const channel = {
171+
_open: false
172+
}
173+
174+
const protocol = {
175+
reset: jest.fn(observer => {
176+
observer.onComplete()
177+
observer.onError()
178+
}),
179+
resetFailure: jest.fn()
180+
}
181+
const protocolSupplier = () => protocol
182+
const connection = spyOnConnectionChannel({ channel, protocolSupplier })
183+
184+
connection._resetOnFailure()
185+
186+
expect(protocol.reset).not.toHaveBeenCalled()
187+
expect(protocol.resetFailure).not.toHaveBeenCalled()
188+
})
189+
})
190+
})
191+
130192
function spyOnConnectionChannel ({
131193
channel,
132194
errorHandler,

core/src/internal/connection-holder.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,13 @@ class ConnectionHolder implements ConnectionHolderInterface {
179179
this._connectionPromise = this._connectionPromise
180180
.then((connection?: Connection) => {
181181
if (connection) {
182-
return connection
183-
.resetAndFlush()
184-
.catch(ignoreError)
185-
.then(() => connection._release())
182+
if (connection.isOpen()) {
183+
return connection
184+
.resetAndFlush()
185+
.catch(ignoreError)
186+
.then(() => connection._release())
187+
}
188+
return connection._release()
186189
} else {
187190
return Promise.resolve()
188191
}

test/internal/connection-holder.test.js

+110
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,116 @@ describe('#unit ConnectionHolder', () => {
292292

293293
expect(connectionHolder.database()).toBe('testdb')
294294
})
295+
296+
describe('.releaseConnection()', () => {
297+
describe('when the connection is initialized', () => {
298+
describe('and connection is open', () => {
299+
let connection
300+
301+
beforeEach(async () => {
302+
connection = new FakeConnection()
303+
const connectionProvider = newSingleConnectionProvider(connection)
304+
const connectionHolder = new ConnectionHolder({
305+
mode: READ,
306+
connectionProvider
307+
})
308+
309+
connectionHolder.initializeConnection()
310+
311+
await connectionHolder.releaseConnection()
312+
})
313+
314+
it('should call connection.resetAndFlush', () => {
315+
expect(connection.resetInvoked).toBe(1)
316+
})
317+
318+
it('should call connection._release()', () => {
319+
expect(connection.releaseInvoked).toBe(1)
320+
})
321+
})
322+
323+
describe('and connection is not open', () => {
324+
let connection
325+
326+
beforeEach(async () => {
327+
connection = new FakeConnection()
328+
connection._open = false
329+
const connectionProvider = newSingleConnectionProvider(connection)
330+
const connectionHolder = new ConnectionHolder({
331+
mode: READ,
332+
connectionProvider
333+
})
334+
335+
connectionHolder.initializeConnection()
336+
337+
await connectionHolder.releaseConnection()
338+
})
339+
340+
it('should not call connection.resetAndFlush', () => {
341+
expect(connection.resetInvoked).toBe(0)
342+
})
343+
344+
it('should call connection._release()', () => {
345+
expect(connection.releaseInvoked).toBe(1)
346+
})
347+
})
348+
})
349+
})
350+
351+
describe('.close()', () => {
352+
describe('when the connection is initialized', () => {
353+
describe('and connection is open', () => {
354+
let connection
355+
356+
beforeEach(async () => {
357+
connection = new FakeConnection()
358+
const connectionProvider = newSingleConnectionProvider(connection)
359+
const connectionHolder = new ConnectionHolder({
360+
mode: READ,
361+
connectionProvider
362+
})
363+
364+
connectionHolder.initializeConnection()
365+
366+
await connectionHolder.close()
367+
})
368+
369+
it('should call connection.resetAndFlush', () => {
370+
expect(connection.resetInvoked).toBe(1)
371+
})
372+
373+
it('should call connection._release()', () => {
374+
expect(connection.releaseInvoked).toBe(1)
375+
})
376+
})
377+
378+
describe('and connection is not open', () => {
379+
let connection
380+
381+
beforeEach(async () => {
382+
connection = new FakeConnection()
383+
connection._open = false
384+
const connectionProvider = newSingleConnectionProvider(connection)
385+
const connectionHolder = new ConnectionHolder({
386+
mode: READ,
387+
connectionProvider
388+
})
389+
390+
connectionHolder.initializeConnection()
391+
392+
await connectionHolder.close()
393+
})
394+
395+
it('should not call connection.resetAndFlush', () => {
396+
expect(connection.resetInvoked).toBe(0)
397+
})
398+
399+
it('should call connection._release()', () => {
400+
expect(connection.releaseInvoked).toBe(1)
401+
})
402+
})
403+
})
404+
})
295405
})
296406

297407
class RecordingConnectionProvider extends SingleConnectionProvider {

test/result.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ describe('#integration result stream', () => {
7373
done()
7474
},
7575
onError: error => {
76-
console.log(error)
76+
done.fail(error)
7777
}
7878
})
7979
})

0 commit comments

Comments
 (0)