Skip to content

Commit 45137ff

Browse files
committed
Tests and set warning for miss-configuration
1 parent 41fc61f commit 45137ff

File tree

9 files changed

+132
-46
lines changed

9 files changed

+132
-46
lines changed

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

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ const {
2525

2626
const { SERVICE_UNAVAILABLE } = error
2727

28-
const DEFAULT_CONNECTION_TIMEOUT_MILLIS = 30000 // 30 seconds by default
29-
3028
const ALLOWED_VALUES_ENCRYPTED = [
3129
null,
3230
undefined,
@@ -58,7 +56,7 @@ export default class ChannelConfig {
5856
this.trustedCertificates = extractTrustedCertificates(driverConfig)
5957
this.knownHostsPath = extractKnownHostsPath(driverConfig)
6058
this.connectionErrorCode = connectionErrorCode || SERVICE_UNAVAILABLE
61-
this.connectionTimeout = extractConnectionTimeout(driverConfig)
59+
this.connectionTimeout = driverConfig.connectionTimeout
6260
}
6361
}
6462

@@ -90,19 +88,3 @@ function extractKnownHostsPath (driverConfig) {
9088
return driverConfig.knownHosts || null
9189
}
9290

93-
function extractConnectionTimeout (driverConfig) {
94-
const configuredTimeout = parseInt(driverConfig.connectionTimeout, 10)
95-
if (configuredTimeout === 0) {
96-
// timeout explicitly configured to 0
97-
return null
98-
} else if (configuredTimeout && configuredTimeout < 0) {
99-
// timeout explicitly configured to a negative value
100-
return null
101-
} else if (!configuredTimeout) {
102-
// timeout not configured, use default value
103-
return DEFAULT_CONNECTION_TIMEOUT_MILLIS
104-
} else {
105-
// timeout configured, use the provided value
106-
return configuredTimeout
107-
}
108-
}

packages/bolt-connection/src/pool/pool.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ class Pool {
7474
* @return {Object} resource that is ready to use.
7575
*/
7676
acquire (address) {
77-
7877
const key = address.asKey()
7978

8079
// We're out of resources and will try to acquire later on when an existing resource is released.
@@ -83,7 +82,6 @@ class Pool {
8382
if (!requests) {
8483
allRequests[key] = []
8584
}
86-
8785
return new Promise((resolve, reject) => {
8886
let request
8987

@@ -335,6 +333,13 @@ class Pool {
335333
// request is still pending and can be resolved with the newly acquired resource
336334
pendingRequest.resolve(resource) // resolve the pending request with the acquired resource
337335
}
336+
} else {
337+
// failed to acquire a valid resource from the pool
338+
// return the pending request back to the pool
339+
if (!this._acquireRequests[key]) {
340+
this._acquireRequests[key] = []
341+
}
342+
this._acquireRequests[key].unshift(pendingRequest)
338343
}
339344
})
340345
} else {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ describe('NodeChannel', () => {
110110
})
111111
})
112112

113-
function createMockedChannel (connected, config = {}) {
113+
function createMockedChannel (connected, config = { connectionTimeout: 30000 }) {
114114
let endCallback = null
115115
const address = ServerAddress.fromUrl('bolt://localhost:9999')
116116
const channelConfig = new ChannelConfig(address, config, SERVICE_UNAVAILABLE)

packages/bolt-connection/test/pool/pool.test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,40 @@ describe('#unit Pool', () => {
877877
expect(resource1.observer).toBeFalsy()
878878
expect(resource2.observer).toBeFalsy()
879879
})
880+
881+
it('should thrown aquisition timeout exception if resource takes longer to be created', async () => {
882+
const address = ServerAddress.fromUrl('bolt://localhost:7687')
883+
const acquisitionTimeout = 1000
884+
let counter = 0
885+
886+
const pool = new Pool({
887+
create: (server, release) =>
888+
new Promise(resolve => setTimeout(
889+
() => resolve(new Resource(server, counter++, release))
890+
, acquisitionTimeout + 10)),
891+
destroy: res => Promise.resolve(),
892+
validate: resourceValidOnlyOnceValidationFunction,
893+
config: new PoolConfig(1, acquisitionTimeout)
894+
})
895+
896+
try {
897+
await pool.acquire(address)
898+
fail('should have thrown')
899+
} catch (e) {
900+
expect(e).toEqual(
901+
newError(
902+
`Connection acquisition timed out in ${acquisitionTimeout} ms. `
903+
+ 'Pool status: Active conn count = 0, Idle conn count = 0.'
904+
)
905+
)
906+
907+
const numberOfIdleResourceAfterResourceGetCreated = await new Promise(resolve =>
908+
setTimeout(() => resolve(idleResources(pool, address)), 11))
909+
910+
expect(numberOfIdleResourceAfterResourceGetCreated).toEqual(1)
911+
expect(counter).toEqual(1)
912+
}
913+
})
880914
})
881915

882916
function expectNoPendingAcquisitionRequests (pool) {
@@ -895,6 +929,13 @@ function expectNoIdleResources (pool, address) {
895929
}
896930
}
897931

932+
function idleResources (pool, address) {
933+
if (pool.has(address)) {
934+
return pool._pools[address.asKey()].length
935+
}
936+
return undefined
937+
}
938+
898939
function expectNumberOfAcquisitionRequests (pool, address, expectedNumber) {
899940
expect(pool._acquireRequests[address.asKey()].length).toEqual(expectedNumber)
900941
}

packages/core/src/driver.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
ACCESS_MODE_READ,
2727
ACCESS_MODE_WRITE,
2828
FETCH_ALL,
29+
DEFAULT_CONNECTION_TIMEOUT_MILLIS,
2930
DEFAULT_POOL_ACQUISITION_TIMEOUT,
3031
DEFAULT_POOL_MAX_SIZE
3132
} from './internal/constants'
@@ -131,12 +132,15 @@ class Driver {
131132
createSession: CreateSession = args => new Session(args)
132133
) {
133134
sanitizeConfig(config)
134-
validateConfig(config)
135+
136+
const log = Logger.create(config)
137+
138+
validateConfig(config, log)
135139

136140
this._id = idGenerator++
137141
this._meta = meta
138142
this._config = config
139-
this._log = Logger.create(config)
143+
this._log = log;
140144
this._createConnectionProvider = createConnectonProvider
141145
this._createSession = createSession
142146

@@ -366,13 +370,22 @@ class Driver {
366370
* @private
367371
* @returns {Object} the given config.
368372
*/
369-
function validateConfig(config: any): any {
373+
function validateConfig(config: any, log: Logger): any {
370374
const resolver = config.resolver
371375
if (resolver && typeof resolver !== 'function') {
372376
throw new TypeError(
373377
`Configured resolver should be a function. Got: ${resolver}`
374378
)
375379
}
380+
381+
if (config.connectionAcquisitionTimeout < config.connectionTimeout) {
382+
log.warn(
383+
'Configuration for "connectionAcquisitionTimeout" should be greater than ' +
384+
'or equal to "connectionTimeout". Otherwise, the connection acquisition ' +
385+
'timeout will take precedence for over the connection timeout in scenarios ' +
386+
'where a new connection is created while it is acquired'
387+
)
388+
}
376389
return config
377390
}
378391

@@ -396,6 +409,7 @@ function sanitizeConfig(config: any) {
396409
config.fetchSize,
397410
DEFAULT_FETCH_SIZE
398411
)
412+
config.connectionTimeout = extractConnectionTimeout(config)
399413
}
400414

401415
/**
@@ -431,6 +445,26 @@ function validateFetchSizeValue(
431445
}
432446
}
433447

448+
/**
449+
* @private
450+
*/
451+
function extractConnectionTimeout (config: any): number|null {
452+
const configuredTimeout = parseInt(config.connectionTimeout, 10)
453+
if (configuredTimeout === 0) {
454+
// timeout explicitly configured to 0
455+
return null
456+
} else if (configuredTimeout && configuredTimeout < 0) {
457+
// timeout explicitly configured to a negative value
458+
return null
459+
} else if (!configuredTimeout) {
460+
// timeout not configured, use default value
461+
return DEFAULT_CONNECTION_TIMEOUT_MILLIS
462+
} else {
463+
// timeout configured, use the provided value
464+
return configuredTimeout
465+
}
466+
}
467+
434468
/**
435469
* @private
436470
* @returns {ConfiguredCustomResolver} new custom resolver that wraps the passed-in resolver function.

packages/core/src/internal/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
const FETCH_ALL = -1
2121
const DEFAULT_POOL_ACQUISITION_TIMEOUT = 60 * 1000 // 60 seconds
2222
const DEFAULT_POOL_MAX_SIZE = 100
23+
const DEFAULT_CONNECTION_TIMEOUT_MILLIS = 30000 // 30 seconds by default
2324

2425
const ACCESS_MODE_READ: 'READ' = 'READ'
2526
const ACCESS_MODE_WRITE: 'WRITE' = 'WRITE'
@@ -37,6 +38,7 @@ export {
3738
FETCH_ALL,
3839
ACCESS_MODE_READ,
3940
ACCESS_MODE_WRITE,
41+
DEFAULT_CONNECTION_TIMEOUT_MILLIS,
4042
DEFAULT_POOL_ACQUISITION_TIMEOUT,
4143
DEFAULT_POOL_MAX_SIZE,
4244
BOLT_PROTOCOL_V1,

packages/core/test/driver.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Driver from '../src/driver'
2121
import { Bookmarks } from '../src/internal/bookmarks'
2222
import { Logger } from '../src/internal/logger'
2323
import { ConfiguredCustomResolver } from '../src/internal/resolver'
24+
import { LogLevel } from '../src/types'
2425

2526
describe('Driver', () => {
2627
let driver: Driver | null
@@ -155,6 +156,44 @@ describe('Driver', () => {
155156
expect(driver.isEncrypted()).toEqual(expectedValue)
156157
})
157158

159+
it.each([
160+
[{ connectionTimeout: 30000, connectionAcquisitionTimeout: 60000 }, true],
161+
[{ connectionTimeout: null, connectionAcquisitionTimeout: 60000 }, true],
162+
[{ connectionTimeout: 30000, connectionAcquisitionTimeout: null }, true],
163+
[{ connectionTimeout: null, connectionAcquisitionTimeout: null }, true],
164+
[{ connectionAcquisitionTimeout: 60000 }, true],
165+
[{ connectionTimeout: 30000 }, true],
166+
[{}, true],
167+
[{ connectionTimeout: 30000, connectionAcquisitionTimeout: 20000 }, false],
168+
[{ connectionAcquisitionTimeout: 20000 }, false],
169+
[{ connectionTimeout: 70000 }, false],
170+
// No connection timeouts should be considered valid, since it means
171+
// the user doesn't case about the connection timeout at all.
172+
[{ connectionTimeout: 0, connectionAcquisitionTimeout: 2000 }, true],
173+
[{ connectionTimeout: -1, connectionAcquisitionTimeout: 2000 }, true],
174+
])('should emit warning if `connectionAcquisitionTimeout` and `connectionTimeout` are conflicting. [%o} ', async (config, valid) => {
175+
const logging = {
176+
level: 'warn' as LogLevel,
177+
logger: jest.fn()
178+
}
179+
180+
const driver = new Driver(META_INFO, { ...config, logging }, mockCreateConnectonProvider(new ConnectionProvider()), createSession)
181+
182+
if (valid) {
183+
expect(logging.logger).not.toHaveBeenCalled()
184+
} else {
185+
expect(logging.logger).toHaveBeenCalledWith(
186+
'warn',
187+
'Configuration for "connectionAcquisitionTimeout" should be greater than ' +
188+
'or equal to "connectionTimeout". Otherwise, the connection acquisition ' +
189+
'timeout will take precedence for over the connection timeout in scenarios ' +
190+
'where a new connection is created while it is acquired'
191+
)
192+
}
193+
194+
await driver.close()
195+
})
196+
158197
function mockCreateConnectonProvider(connectionProvider: ConnectionProvider) {
159198
return (
160199
id: number,
@@ -172,6 +211,7 @@ describe('Driver', () => {
172211
fetchSize: 1000,
173212
maxConnectionLifetime: 3600000,
174213
maxConnectionPoolSize: 100,
214+
connectionTimeout: 30000,
175215
},
176216
connectionProvider,
177217
database: '',

packages/neo4j-driver/test/internal/channel-config.test.js

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -111,28 +111,10 @@ describe('#unit ChannelConfig', () => {
111111
expect(config.connectionErrorCode).toEqual(SERVICE_UNAVAILABLE)
112112
})
113113

114-
it('should have connection timeout by default', () => {
114+
it('should have connection timeout being used as it is', () => {
115115
const config = new ChannelConfig(null, {}, '')
116116

117-
expect(config.connectionTimeout).toEqual(30000)
118-
})
119-
120-
it('should respect configured connection timeout', () => {
121-
const config = new ChannelConfig(null, { connectionTimeout: 424242 }, '')
122-
123-
expect(config.connectionTimeout).toEqual(424242)
124-
})
125-
126-
it('should respect disabled connection timeout with value zero', () => {
127-
const config = new ChannelConfig(null, { connectionTimeout: 0 }, '')
128-
129-
expect(config.connectionTimeout).toBeNull()
130-
})
131-
132-
it('should respect disabled connection timeout with negative value', () => {
133-
const config = new ChannelConfig(null, { connectionTimeout: -42 }, '')
134-
135-
expect(config.connectionTimeout).toBeNull()
117+
expect(config.connectionTimeout).toEqual(undefined)
136118
})
137119

138120
it('should validate value of "encrypted" property', () => {

packages/testkit-backend/src/request-handlers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ export function GetFeatures (_context, _params, wire) {
383383
'Feature:Bolt:4.4',
384384
'Feature:API:Result.List',
385385
'Feature:API:Result.Peek',
386-
'Feature:Configuruation:ConnectionAcquisitionTimeout',
386+
'Feature:Configuration:ConnectionAcquisitionTimeout',
387387
'Optimization:EagerTransactionBegin',
388388
'Optimization:ImplicitDefaultArguments',
389389
'Optimization:PullPipelining',

0 commit comments

Comments
 (0)