Skip to content

Commit 3bc16a0

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

File tree

8 files changed

+123
-26
lines changed

8 files changed

+123
-26
lines changed

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

+1-19
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

+7-2
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

+1-1
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

+41
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

+37-3
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

+2
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

+33
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,37 @@ describe('Driver', () => {
155156
expect(driver.isEncrypted()).toEqual(expectedValue)
156157
})
157158

159+
it.each([
160+
[{ connectionTimeout: 30000, connectionAcquisitionTimeout: 60000 }, true],
161+
[{ connectionAcquisitionTimeout: 60000 }, true],
162+
[{ connectionTimeout: 30000 }, true],
163+
[{}, true],
164+
[{ connectionTimeout: 30000, connectionAcquisitionTimeout: 20000 }, false],
165+
[{ connectionAcquisitionTimeout: 20000 }, false],
166+
[{ connectionTimeout: 70000 }, false]
167+
])('should emit warning if `connectionAcquisitionTimeout` and `connectionTimeout` are conflicting', async (config, valid) => {
168+
const logging = {
169+
level: 'warn' as LogLevel,
170+
logger: jest.fn()
171+
}
172+
173+
const driver = new Driver(META_INFO, { ...config, logging }, mockCreateConnectonProvider(new ConnectionProvider()), createSession)
174+
175+
if (valid) {
176+
expect(logging.logger).not.toHaveBeenCalled()
177+
} else {
178+
expect(logging.logger).toHaveBeenCalledWith(
179+
'warn',
180+
'Configuration for "connectionAcquisitionTimeout" should be greater than ' +
181+
'or equal to "connectionTimeout". Otherwise, the connection acquisition ' +
182+
'timeout will take precedence for over the connection timeout in scenarios ' +
183+
'where a new connection is created while it is acquired'
184+
)
185+
}
186+
187+
await driver.close()
188+
})
189+
158190
function mockCreateConnectonProvider(connectionProvider: ConnectionProvider) {
159191
return (
160192
id: number,
@@ -172,6 +204,7 @@ describe('Driver', () => {
172204
fetchSize: 1000,
173205
maxConnectionLifetime: 3600000,
174206
maxConnectionPoolSize: 100,
207+
connectionTimeout: 30000,
175208
},
176209
connectionProvider,
177210
database: '',

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

+1-1
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)