Skip to content

Commit 98f3c45

Browse files
committed
Purge old routing table entries on inactivity
1 parent dca6185 commit 98f3c45

File tree

4 files changed

+147
-17
lines changed

4 files changed

+147
-17
lines changed

src/internal/connection-provider-routing.js

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@ import ConnectionErrorHandler from './connection-error-handler'
3131
import DelegateConnection from './connection-delegate'
3232
import LeastConnectedLoadBalancingStrategy from './least-connected-load-balancing-strategy'
3333
import Bookmark from './bookmark'
34+
import { int } from '../integer'
35+
import { ConsoleReporter } from 'jasmine'
3436

3537
const UNAUTHORIZED_ERROR_CODE = 'Neo.ClientError.Security.Unauthorized'
3638
const DATABASE_NOT_FOUND_ERROR_CODE =
3739
'Neo.ClientError.Database.DatabaseNotFound'
3840
const SYSTEM_DB_NAME = 'system'
3941
const DEFAULT_DB_NAME = ''
42+
const DEFAULT_ROUTING_TABLE_PURGE_DELAY = int(30000)
4043

4144
export default class RoutingConnectionProvider extends PooledConnectionProvider {
4245
constructor ({
@@ -47,7 +50,8 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
4750
config,
4851
log,
4952
userAgent,
50-
authToken
53+
authToken,
54+
routingTablePurgeDelay
5155
}) {
5256
super({ id, config, log, userAgent, authToken })
5357

@@ -61,6 +65,9 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
6165
this._dnsResolver = new HostNameResolver()
6266
this._log = log
6367
this._useSeedRouter = true
68+
this._routingTablePurgeDelay = routingTablePurgeDelay
69+
? int(routingTablePurgeDelay)
70+
: DEFAULT_ROUTING_TABLE_PURGE_DELAY
6471
}
6572

6673
_createConnectionErrorHandler () {
@@ -71,23 +78,15 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
7178

7279
_handleUnavailability (error, address, database) {
7380
this._log.warn(
74-
`Routing driver ${
75-
this._id
76-
} will forget ${address} for database '${database}' because of an error ${
77-
error.code
78-
} '${error.message}'`
81+
`Routing driver ${this._id} will forget ${address} for database '${database}' because of an error ${error.code} '${error.message}'`
7982
)
8083
this.forget(address, database || '')
8184
return error
8285
}
8386

8487
_handleWriteFailure (error, address, database) {
8588
this._log.warn(
86-
`Routing driver ${
87-
this._id
88-
} will forget writer ${address} for database '${database}' because of an error ${
89-
error.code
90-
} '${error.message}'`
89+
`Routing driver ${this._id} will forget writer ${address} for database '${database}' because of an error ${error.code} '${error.message}'`
9190
)
9291
this.forgetWriter(address, database || '')
9392
return newError(
@@ -427,6 +426,13 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
427426
// close old connections to servers not present in the new routing table
428427
await this._connectionPool.keepAll(newRoutingTable.allServers())
429428

429+
// filter out expired to purge (expired for a pre-configured amount of time) routing table entries
430+
Object.values(this._routingTables).forEach(value => {
431+
if (value.isExpiredFor(this._routingTablePurgeDelay)) {
432+
delete this._routingTables[value.database]
433+
}
434+
})
435+
430436
// make this driver instance aware of the new table
431437
this._routingTables[newRoutingTable.database] = newRoutingTable
432438
this._log.info(`Updated routing table ${newRoutingTable}`)

src/internal/routing-table.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const MIN_ROUTERS = 1
2424
export default class RoutingTable {
2525
constructor ({ database, routers, readers, writers, expirationTime } = {}) {
2626
this.database = database
27+
this.databaseName = database || 'default database'
2728
this.routers = routers || []
2829
this.readers = readers || []
2930
this.writers = writers || []
@@ -61,14 +62,24 @@ export default class RoutingTable {
6162
)
6263
}
6364

65+
/**
66+
* Check if this routing table is expired for specified amount of duration
67+
*
68+
* @param {Integer} duration amount of duration in milliseconds to check for expiration
69+
* @returns {boolean}
70+
*/
71+
isExpiredFor (duration) {
72+
return this.expirationTime.add(duration).lessThan(Date.now())
73+
}
74+
6475
allServers () {
6576
return [...this.routers, ...this.readers, ...this.writers]
6677
}
6778

6879
toString () {
6980
return (
70-
`RoutingTable[` +
71-
`database=${this.database}, ` +
81+
'RoutingTable[' +
82+
`database=${this.databaseName}, ` +
7283
`expirationTime=${this.expirationTime}, ` +
7384
`currentTime=${Date.now()}, ` +
7485
`routers=[${this.routers}], ` +

test/internal/connection-provider-routing.test.js

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,6 +1723,92 @@ describe('#unit RoutingConnectionProvider', () => {
17231723
[serverC]
17241724
)
17251725
})
1726+
1727+
it('should purge expired routing tables after specified duration on update', async () => {
1728+
var originalDateNow = Date.now
1729+
Date.now = () => 50000
1730+
try {
1731+
const routingTableToLoad = newRoutingTable(
1732+
'databaseC',
1733+
[server1, server2, server3],
1734+
[server2, server3],
1735+
[server1]
1736+
)
1737+
const connectionProvider = newRoutingConnectionProviderWithSeedRouter(
1738+
server1,
1739+
[server1],
1740+
[
1741+
newRoutingTable(
1742+
'databaseA',
1743+
[server1, server2, server3],
1744+
[server1, server2],
1745+
[server3],
1746+
int(Date.now() + 12000)
1747+
),
1748+
newRoutingTable(
1749+
'databaseB',
1750+
[server1, server2, server3],
1751+
[server1, server3],
1752+
[server2],
1753+
int(Date.now() + 2000)
1754+
)
1755+
],
1756+
{
1757+
databaseC: {
1758+
'server1:7687': routingTableToLoad
1759+
}
1760+
},
1761+
null,
1762+
4000
1763+
)
1764+
1765+
expectRoutingTable(
1766+
connectionProvider,
1767+
'databaseA',
1768+
[server1, server2, server3],
1769+
[server1, server2],
1770+
[server3]
1771+
)
1772+
expectRoutingTable(
1773+
connectionProvider,
1774+
'databaseB',
1775+
[server1, server2, server3],
1776+
[server1, server3],
1777+
[server2]
1778+
)
1779+
1780+
// make routing table for databaseA to report true for isExpiredFor(4000)
1781+
// call.
1782+
Date.now = () => 58000
1783+
1784+
// force a routing table update for databaseC
1785+
const conn1 = await connectionProvider.acquireConnection({
1786+
accessMode: WRITE,
1787+
database: 'databaseC'
1788+
})
1789+
expect(conn1).not.toBeNull()
1790+
expect(conn1.address).toBe(server1)
1791+
1792+
// Then
1793+
expectRoutingTable(
1794+
connectionProvider,
1795+
'databaseA',
1796+
[server1, server2, server3],
1797+
[server1, server2],
1798+
[server3]
1799+
)
1800+
expectRoutingTable(
1801+
connectionProvider,
1802+
'databaseC',
1803+
[server1, server2, server3],
1804+
[server2, server3],
1805+
[server1]
1806+
)
1807+
expectNoRoutingTable(connectionProvider, 'databaseB')
1808+
} finally {
1809+
Date.now = originalDateNow
1810+
}
1811+
})
17261812
})
17271813
})
17281814

@@ -1746,7 +1832,8 @@ function newRoutingConnectionProviderWithSeedRouter (
17461832
seedRouterResolved,
17471833
routingTables,
17481834
routerToRoutingTable = { '': {} },
1749-
connectionPool = null
1835+
connectionPool = null,
1836+
routingTablePurgeDelay = null
17501837
) {
17511838
const pool = connectionPool || newPool()
17521839
const connectionProvider = new RoutingConnectionProvider({
@@ -1755,7 +1842,8 @@ function newRoutingConnectionProviderWithSeedRouter (
17551842
routingContext: {},
17561843
hostNameResolver: new SimpleHostNameResolver(),
17571844
config: {},
1758-
log: Logger.noOp()
1845+
log: Logger.noOp(),
1846+
routingTablePurgeDelay: routingTablePurgeDelay
17591847
})
17601848
connectionProvider._connectionPool = pool
17611849
routingTables.forEach(r => {
@@ -1821,6 +1909,10 @@ function expectRoutingTable (
18211909
expect(connectionProvider._routingTables[database].writers).toEqual(writers)
18221910
}
18231911

1912+
function expectNoRoutingTable (connectionProvider, database) {
1913+
expect(connectionProvider._routingTables[database]).toBeFalsy()
1914+
}
1915+
18241916
function expectPoolToContain (pool, addresses) {
18251917
addresses.forEach(address => {
18261918
expect(pool.has(address)).toBeTruthy()

test/internal/routing-table.test.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,29 @@ describe('#unit RoutingTable', () => {
201201
}
202202
})
203203

204-
function expired () {
205-
return Date.now() - 3600 // expired an hour ago
204+
it('should report correct value when expired for is tested', () => {
205+
const originalDateNow = Date.now
206+
try {
207+
Date.now = () => 50000
208+
const table = createTable(
209+
[server1, server2, server3],
210+
[server2, server1, server5],
211+
[server5, server1],
212+
expired(7200)
213+
)
214+
215+
expect(table.isStaleFor(READ)).toBeTruthy()
216+
expect(table.isStaleFor(WRITE)).toBeTruthy()
217+
218+
expect(table.isExpiredFor(3600)).toBeTruthy()
219+
expect(table.isExpiredFor(10800)).toBeFalsy()
220+
} finally {
221+
Date.now = originalDateNow
222+
}
223+
})
224+
225+
function expired (expiredFor) {
226+
return Date.now() - (expiredFor || 3600) // expired an hour ago
206227
}
207228

208229
function notExpired () {

0 commit comments

Comments
 (0)