Skip to content

Commit 6b90f8d

Browse files
authored
Introduce sessionConnectionTimeout and updateRoutingTableTimeout (#966)
`sessionConnnectionTimeout` is the maximum amount of time the session will wait when trying to establish a usable read/write connection to the remote host. This encompasses *everything* that needs to happen for this, including, if necessary, updating the routing table<sup>1</sup>, fetching a connection from the pool, and, if necessary performing a BOLT and Authentication handshake with the reader/writer. `updateRoutingTableTimeout` is the maximum amount of time the driver will attempt to fetch a new routing table. This encompasses *everything* that needs to happen for this, including fetching connections from the pool, performing handshakes, and requesting and receiving a fresh routing table. <sup>1</sup>Updating the routing table in turn might also require fetching a connection for the pool and performing the handshakes, plus potentially trying other routers.
1 parent d417920 commit 6b90f8d

File tree

16 files changed

+874
-111
lines changed

16 files changed

+874
-111
lines changed

packages/bolt-connection/src/connection-provider/connection-provider-direct.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
ConnectionErrorHandler
2525
} from '../connection'
2626
import { internal, error } from 'neo4j-driver-core'
27+
import { controller } from '../lang'
2728

2829
const {
2930
constants: { BOLT_PROTOCOL_V3, BOLT_PROTOCOL_V4_0, BOLT_PROTOCOL_V4_4 }
@@ -49,12 +50,17 @@ export default class DirectConnectionProvider extends PooledConnectionProvider {
4950
this._handleAuthorizationExpired(error, address, database)
5051
})
5152

52-
return this._connectionPool
53-
.acquire(this._address)
54-
.then(
55-
connection =>
56-
new DelegateConnection(connection, databaseSpecificErrorHandler)
57-
)
53+
const acquireConnectionJob = {
54+
run: () => this._connectionPool
55+
.acquire(this._address)
56+
.then(
57+
connection =>
58+
new DelegateConnection(connection, databaseSpecificErrorHandler)
59+
),
60+
onTimeout: connection => connection._release()
61+
}
62+
63+
return controller.runWithTimeout(this._sessionConnectionTimeoutConfig, acquireConnectionJob)
5864
}
5965

6066
_handleAuthorizationExpired (error, address, database) {

packages/bolt-connection/src/connection-provider/connection-provider-pooled.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import { createChannelConnection, ConnectionErrorHandler } from '../connection'
2121
import Pool, { PoolConfig } from '../pool'
22-
import { error, ConnectionProvider, ServerInfo } from 'neo4j-driver-core'
22+
import { error, newError, ConnectionProvider, ServerInfo } from 'neo4j-driver-core'
2323

2424
const { SERVICE_UNAVAILABLE } = error
2525
export default class PooledConnectionProvider extends ConnectionProvider {
@@ -58,6 +58,12 @@ export default class PooledConnectionProvider extends ConnectionProvider {
5858
log: this._log
5959
})
6060
this._openConnections = {}
61+
this._sessionConnectionTimeoutConfig = {
62+
timeout: this._config.sessionConnectionTimeout,
63+
reason: () => newError(
64+
`Session acquisition timed out in ${this._config.sessionConnectionTimeout} ms.`
65+
)
66+
}
6167
}
6268

6369
_createConnectionErrorHandler () {

packages/bolt-connection/src/connection-provider/connection-provider-routing.js

Lines changed: 109 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { HostNameResolver } from '../channel'
2323
import SingleConnectionProvider from './connection-provider-single'
2424
import PooledConnectionProvider from './connection-provider-pooled'
2525
import { LeastConnectedLoadBalancingStrategy } from '../load-balancing'
26+
import { controller } from '../lang'
2627
import {
2728
createChannelConnection,
2829
ConnectionErrorHandler,
@@ -75,6 +76,13 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
7576
)
7677
})
7778

79+
this._updateRoutingTableTimeoutConfig = {
80+
timeout: this._config.updateRoutingTableTimeout,
81+
reason: () => newError(
82+
`Routing table update timed out in ${this._config.updateRoutingTableTimeout} ms.`
83+
)
84+
}
85+
7886
this._routingContext = { ...routingContext, address: address.toString() }
7987
this._seedRouter = address
8088
this._rediscovery = new Rediscovery(this._routingContext)
@@ -143,53 +151,66 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
143151
this._handleAuthorizationExpired(error, address, context.database)
144152
)
145153

146-
const routingTable = await this._freshRoutingTable({
147-
accessMode,
148-
database: context.database,
149-
bookmarks: bookmarks,
150-
impersonatedUser,
151-
onDatabaseNameResolved: (databaseName) => {
152-
context.database = context.database || databaseName
153-
if (onDatabaseNameResolved) {
154-
onDatabaseNameResolved(databaseName)
154+
const refreshRoutingTableJob = {
155+
run: async (_, cancelationToken) => {
156+
const routingTable = await this._freshRoutingTable({
157+
accessMode,
158+
database: context.database,
159+
bookmarks: bookmarks,
160+
impersonatedUser,
161+
onDatabaseNameResolved: (databaseName) => {
162+
context.database = context.database || databaseName
163+
if (onDatabaseNameResolved) {
164+
onDatabaseNameResolved(databaseName)
165+
}
166+
},
167+
cancelationToken
168+
})
169+
170+
// select a target server based on specified access mode
171+
if (accessMode === READ) {
172+
address = this._loadBalancingStrategy.selectReader(routingTable.readers)
173+
name = 'read'
174+
} else if (accessMode === WRITE) {
175+
address = this._loadBalancingStrategy.selectWriter(routingTable.writers)
176+
name = 'write'
177+
} else {
178+
throw newError('Illegal mode ' + accessMode)
155179
}
156-
}
157-
})
158180

159-
// select a target server based on specified access mode
160-
if (accessMode === READ) {
161-
address = this._loadBalancingStrategy.selectReader(routingTable.readers)
162-
name = 'read'
163-
} else if (accessMode === WRITE) {
164-
address = this._loadBalancingStrategy.selectWriter(routingTable.writers)
165-
name = 'write'
166-
} else {
167-
throw newError('Illegal mode ' + accessMode)
168-
}
169-
170-
// we couldn't select a target server
171-
if (!address) {
172-
throw newError(
173-
`Failed to obtain connection towards ${name} server. Known routing table is: ${routingTable}`,
174-
SESSION_EXPIRED
175-
)
181+
// we couldn't select a target server
182+
if (!address) {
183+
throw newError(
184+
`Failed to obtain connection towards ${name} server. Known routing table is: ${routingTable}`,
185+
SESSION_EXPIRED
186+
)
187+
}
188+
return { routingTable, address }
189+
}
176190
}
177191

178-
try {
179-
const connection = await this._acquireConnectionToServer(
180-
address,
181-
name,
182-
routingTable
183-
)
192+
const acquireConnectionJob = {
193+
run: async ({ routingTable, address }) => {
194+
try {
195+
const connection = await this._acquireConnectionToServer(
196+
address,
197+
name,
198+
routingTable
199+
)
184200

185-
return new DelegateConnection(connection, databaseSpecificErrorHandler)
186-
} catch (error) {
187-
const transformed = databaseSpecificErrorHandler.handleAndTransformError(
188-
error,
189-
address
190-
)
191-
throw transformed
201+
return new DelegateConnection(connection, databaseSpecificErrorHandler)
202+
} catch (error) {
203+
const transformed = databaseSpecificErrorHandler.handleAndTransformError(
204+
error,
205+
address
206+
)
207+
throw transformed
208+
}
209+
},
210+
onTimeout: connection => connection._release()
192211
}
212+
213+
return controller.runWithTimeout(this._sessionConnectionTimeoutConfig, refreshRoutingTableJob, acquireConnectionJob)
193214
}
194215

195216
async _hasProtocolVersion (versionPredicate) {
@@ -301,22 +322,28 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
301322
return this._connectionPool.acquire(address)
302323
}
303324

304-
_freshRoutingTable ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved } = {}) {
305-
const currentRoutingTable = this._routingTableRegistry.get(
306-
database,
307-
() => new RoutingTable({ database })
308-
)
325+
_freshRoutingTable ({ accessMode, database, bookmarks, impersonatedUser, onDatabaseNameResolved, cancelationToken = new controller.CancelationToken(() => false) } = {}) {
326+
const refreshRoutingTableJob = {
327+
run: (_, refreshCancelationToken) => {
328+
const combinedCancelationToken = refreshCancelationToken.combine(cancelationToken)
329+
const currentRoutingTable = this._routingTableRegistry.get(
330+
database,
331+
() => new RoutingTable({ database })
332+
)
309333

310-
if (!currentRoutingTable.isStaleFor(accessMode)) {
311-
return currentRoutingTable
334+
if (!currentRoutingTable.isStaleFor(accessMode)) {
335+
return currentRoutingTable
336+
}
337+
this._log.info(
338+
`Routing table is stale for database: "${database}" and access mode: "${accessMode}": ${currentRoutingTable}`
339+
)
340+
return this._refreshRoutingTable(currentRoutingTable, bookmarks, impersonatedUser, onDatabaseNameResolved, combinedCancelationToken)
341+
}
312342
}
313-
this._log.info(
314-
`Routing table is stale for database: "${database}" and access mode: "${accessMode}": ${currentRoutingTable}`
315-
)
316-
return this._refreshRoutingTable(currentRoutingTable, bookmarks, impersonatedUser, onDatabaseNameResolved)
343+
return controller.runWithTimeout(this._updateRoutingTableTimeoutConfig, refreshRoutingTableJob)
317344
}
318345

319-
_refreshRoutingTable (currentRoutingTable, bookmarks, impersonatedUser, onDatabaseNameResolved) {
346+
_refreshRoutingTable (currentRoutingTable, bookmarks, impersonatedUser, onDatabaseNameResolved, cancelationToken) {
320347
const knownRouters = currentRoutingTable.routers
321348

322349
if (this._useSeedRouter) {
@@ -325,15 +352,17 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
325352
currentRoutingTable,
326353
bookmarks,
327354
impersonatedUser,
328-
onDatabaseNameResolved
355+
onDatabaseNameResolved,
356+
cancelationToken
329357
)
330358
}
331359
return this._fetchRoutingTableFromKnownRoutersFallbackToSeedRouter(
332360
knownRouters,
333361
currentRoutingTable,
334362
bookmarks,
335363
impersonatedUser,
336-
onDatabaseNameResolved
364+
onDatabaseNameResolved,
365+
cancelationToken
337366
)
338367
}
339368

@@ -342,7 +371,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
342371
currentRoutingTable,
343372
bookmarks,
344373
impersonatedUser,
345-
onDatabaseNameResolved
374+
onDatabaseNameResolved,
375+
cancelationToken
346376
) {
347377
// we start with seed router, no routers were probed before
348378
const seenRouters = []
@@ -351,7 +381,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
351381
this._seedRouter,
352382
currentRoutingTable,
353383
bookmarks,
354-
impersonatedUser
384+
impersonatedUser,
385+
cancelationToken
355386
)
356387

357388
if (newRoutingTable) {
@@ -362,7 +393,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
362393
knownRouters,
363394
currentRoutingTable,
364395
bookmarks,
365-
impersonatedUser
396+
impersonatedUser,
397+
cancelationToken
366398
)
367399
newRoutingTable = newRoutingTable2
368400
error = error2 || error
@@ -381,13 +413,15 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
381413
currentRoutingTable,
382414
bookmarks,
383415
impersonatedUser,
384-
onDatabaseNameResolved
416+
onDatabaseNameResolved,
417+
cancelationToken
385418
) {
386419
let [newRoutingTable, error] = await this._fetchRoutingTableUsingKnownRouters(
387420
knownRouters,
388421
currentRoutingTable,
389422
bookmarks,
390-
impersonatedUser
423+
impersonatedUser,
424+
cancelationToken
391425
)
392426

393427
if (!newRoutingTable) {
@@ -397,7 +431,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
397431
this._seedRouter,
398432
currentRoutingTable,
399433
bookmarks,
400-
impersonatedUser
434+
impersonatedUser,
435+
cancelationToken
401436
)
402437
}
403438

@@ -413,13 +448,15 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
413448
knownRouters,
414449
currentRoutingTable,
415450
bookmarks,
416-
impersonatedUser
451+
impersonatedUser,
452+
cancelationToken
417453
) {
418454
const [newRoutingTable, error] = await this._fetchRoutingTable(
419455
knownRouters,
420456
currentRoutingTable,
421457
bookmarks,
422-
impersonatedUser
458+
impersonatedUser,
459+
cancelationToken
423460
)
424461

425462
if (newRoutingTable) {
@@ -444,16 +481,19 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
444481
seedRouter,
445482
routingTable,
446483
bookmarks,
447-
impersonatedUser
484+
impersonatedUser,
485+
cancelationToken
448486
) {
449487
const resolvedAddresses = await this._resolveSeedRouter(seedRouter)
450488

489+
cancelationToken.throwIfCancellationRequested()
490+
451491
// filter out all addresses that we've already tried
452492
const newAddresses = resolvedAddresses.filter(
453493
address => seenRouters.indexOf(address) < 0
454494
)
455495

456-
return await this._fetchRoutingTable(newAddresses, routingTable, bookmarks, impersonatedUser)
496+
return await this._fetchRoutingTable(newAddresses, routingTable, bookmarks, impersonatedUser, cancelationToken)
457497
}
458498

459499
async _resolveSeedRouter (seedRouter) {
@@ -465,7 +505,7 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
465505
return [].concat.apply([], dnsResolvedAddresses)
466506
}
467507

468-
async _fetchRoutingTable (routerAddresses, routingTable, bookmarks, impersonatedUser) {
508+
async _fetchRoutingTable (routerAddresses, routingTable, bookmarks, impersonatedUser, cancelationToken) {
469509
return routerAddresses.reduce(
470510
async (refreshedTablePromise, currentRouter, currentIndex) => {
471511
const [newRoutingTable] = await refreshedTablePromise
@@ -499,11 +539,13 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
499539
impersonatedUser
500540
), null]
501541
} catch (error) {
542+
cancelationToken.throwIfCancellationRequested()
502543
return this._handleRediscoveryError(error, currentRouter)
503544
} finally {
504-
session.close()
545+
await session.close()
505546
}
506547
} else {
548+
cancelationToken.throwIfCancellationRequested()
507549
// unable to acquire connection and create session towards the current router
508550
// return null to signal that the next router should be tried
509551
return [null, error]

0 commit comments

Comments
 (0)