Skip to content

Commit 9b3211a

Browse files
authored
routing: Fix default database name in the routing connection provider
A RoutingTableRegistry was extracted from the Connection Provider to help re-use some behaviours.
1 parent 26b8b97 commit 9b3211a

File tree

2 files changed

+215
-38
lines changed

2 files changed

+215
-38
lines changed

src/internal/connection-provider-routing.js

Lines changed: 115 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
6363
})
6464

6565
this._seedRouter = address
66-
this._routingTables = {}
6766
this._rediscovery = new Rediscovery(routingContext, address.toString())
6867
this._loadBalancingStrategy = new LeastConnectedLoadBalancingStrategy(
6968
this._connectionPool
@@ -72,9 +71,11 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
7271
this._dnsResolver = new HostNameResolver()
7372
this._log = log
7473
this._useSeedRouter = true
75-
this._routingTablePurgeDelay = routingTablePurgeDelay
76-
? int(routingTablePurgeDelay)
77-
: DEFAULT_ROUTING_TABLE_PURGE_DELAY
74+
this._routingTableRegistry = new RoutingTableRegistry(
75+
routingTablePurgeDelay
76+
? int(routingTablePurgeDelay)
77+
: DEFAULT_ROUTING_TABLE_PURGE_DELAY
78+
)
7879
}
7980

8081
_createConnectionErrorHandler () {
@@ -87,15 +88,15 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
8788
this._log.warn(
8889
`Routing driver ${this._id} will forget ${address} for database '${database}' because of an error ${error.code} '${error.message}'`
8990
)
90-
this.forget(address, database || '')
91+
this.forget(address, database || DEFAULT_DB_NAME)
9192
return error
9293
}
9394

9495
_handleWriteFailure (error, address, database) {
9596
this._log.warn(
9697
`Routing driver ${this._id} will forget writer ${address} for database '${database}' because of an error ${error.code} '${error.message}'`
9798
)
98-
this.forgetWriter(address, database || '')
99+
this.forgetWriter(address, database || DEFAULT_DB_NAME)
99100
return newError(
100101
'No longer possible to write to server at ' + address,
101102
SESSION_EXPIRED
@@ -206,36 +207,30 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
206207
}
207208

208209
forget (address, database) {
209-
if (database || database === '') {
210-
this._routingTables[database].forget(address)
211-
} else {
212-
Object.values(this._routingTables).forEach(routingTable =>
213-
routingTable.forget(address)
214-
)
215-
}
210+
this._routingTableRegistry.apply(database, {
211+
applyWhenExists: routingTable => routingTable.forget(address)
212+
})
216213

217214
// We're firing and forgetting this operation explicitly and listening for any
218215
// errors to avoid unhandled promise rejection
219216
this._connectionPool.purge(address).catch(() => {})
220217
}
221218

222219
forgetWriter (address, database) {
223-
if (database || database === '') {
224-
this._routingTables[database].forgetWriter(address)
225-
} else {
226-
Object.values(this._routingTables).forEach(routingTable =>
227-
routingTable.forgetWriter(address)
228-
)
229-
}
220+
this._routingTableRegistry.apply(database, {
221+
applyWhenExists: routingTable => routingTable.forgetWriter(address)
222+
})
230223
}
231224

232225
_acquireConnectionToServer (address, serverName, routingTable) {
233226
return this._connectionPool.acquire(address)
234227
}
235228

236229
_freshRoutingTable ({ accessMode, database, bookmark } = {}) {
237-
const currentRoutingTable =
238-
this._routingTables[database] || new RoutingTable({ database })
230+
const currentRoutingTable = this._routingTableRegistry.get(
231+
database,
232+
() => new RoutingTable({ database })
233+
)
239234

240235
if (!currentRoutingTable.isStaleFor(accessMode)) {
241236
return currentRoutingTable
@@ -481,16 +476,11 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
481476
async _updateRoutingTable (newRoutingTable) {
482477
// close old connections to servers not present in the new routing table
483478
await this._connectionPool.keepAll(newRoutingTable.allServers())
484-
485-
// filter out expired to purge (expired for a pre-configured amount of time) routing table entries
486-
Object.values(this._routingTables).forEach(value => {
487-
if (value.isExpiredFor(this._routingTablePurgeDelay)) {
488-
delete this._routingTables[value.database]
489-
}
490-
})
491-
492-
// make this driver instance aware of the new table
493-
this._routingTables[newRoutingTable.database] = newRoutingTable
479+
this._routingTableRegistry.removeExpired()
480+
this._routingTableRegistry.register(
481+
newRoutingTable.database,
482+
newRoutingTable
483+
)
494484
this._log.info(`Updated routing table ${newRoutingTable}`)
495485
}
496486

@@ -501,3 +491,96 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
501491
}
502492
}
503493
}
494+
495+
/**
496+
* Responsible for keeping track of the existing routing tables
497+
*/
498+
class RoutingTableRegistry {
499+
/**
500+
* Constructor
501+
* @param {int} routingTablePurgeDelay The routing table purge delay
502+
*/
503+
constructor (routingTablePurgeDelay) {
504+
this._tables = new Map()
505+
this._routingTablePurgeDelay = routingTablePurgeDelay
506+
}
507+
508+
/**
509+
* Put a routing table in the registry
510+
*
511+
* @param {string} database The database name
512+
* @param {RoutingTable} table The routing table
513+
* @returns {RoutingTableRegistry} this
514+
*/
515+
register (database, table) {
516+
this._tables.set(database, table)
517+
return this
518+
}
519+
520+
/**
521+
* Apply function in the routing table for an specific database. If the database name is not defined, the function will
522+
* be applied for each element
523+
*
524+
* @param {string} database The database name
525+
* @param {object} callbacks The actions
526+
* @param {function (RoutingTable)} callbacks.applyWhenExists Call when the db exists or when the database property is not informed
527+
* @param {function ()} callbacks.applyWhenDontExists Call when the database doesn't have the routing table registred
528+
* @returns {RoutingTableRegistry} this
529+
*/
530+
apply (database, { applyWhenExists, applyWhenDontExists = () => {} } = {}) {
531+
if (this._tables.has(database)) {
532+
applyWhenExists(this._tables.get(database))
533+
} else if (typeof database === 'string' || database === null) {
534+
applyWhenDontExists()
535+
} else {
536+
this._forEach(applyWhenExists)
537+
}
538+
return this
539+
}
540+
541+
/**
542+
* Retrieves a routing table from a given database name
543+
* @param {string} database The database name
544+
* @param {function()|RoutingTable} defaultSupplier The routing table supplier, if it's not a function or not exists, it will return itself as default value
545+
* @returns {RoutingTable} The routing table for the respective database
546+
*/
547+
get (database, defaultSupplier) {
548+
if (this._tables.has(database)) {
549+
return this._tables.get(database)
550+
}
551+
return typeof defaultSupplier === 'function'
552+
? defaultSupplier()
553+
: defaultSupplier
554+
}
555+
556+
/**
557+
* Remove the routing table which is already expired
558+
* @returns {RoutingTableRegistry} this
559+
*/
560+
removeExpired () {
561+
return this._removeIf(value =>
562+
value.isExpiredFor(this._routingTablePurgeDelay)
563+
)
564+
}
565+
566+
_forEach (apply) {
567+
for (const [, value] of this._tables) {
568+
apply(value)
569+
}
570+
return this
571+
}
572+
573+
_remove (key) {
574+
this._tables.delete(key)
575+
return this
576+
}
577+
578+
_removeIf (predicate) {
579+
for (const [key, value] of this._tables) {
580+
if (predicate(value)) {
581+
this._remove(key)
582+
}
583+
}
584+
return this
585+
}
586+
}

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

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,6 +1677,99 @@ describe('#unit RoutingConnectionProvider', () => {
16771677
)
16781678
})
16791679

1680+
it('should forget write server from the default database routing table on availability error', async () => {
1681+
const pool = newPool()
1682+
const connectionProvider = newRoutingConnectionProvider(
1683+
[
1684+
newRoutingTable(
1685+
'databaseA',
1686+
[server1, server2, server3],
1687+
[server1, server2],
1688+
[server3]
1689+
),
1690+
newRoutingTable(
1691+
null,
1692+
[serverA, serverB, serverC],
1693+
[serverA, serverB],
1694+
[serverA, serverC]
1695+
)
1696+
],
1697+
pool
1698+
)
1699+
1700+
const conn1 = await connectionProvider.acquireConnection({
1701+
accessMode: WRITE,
1702+
database: null
1703+
})
1704+
1705+
// when
1706+
conn1._errorHandler.handleAndTransformError(
1707+
newError('connection error', SERVICE_UNAVAILABLE),
1708+
conn1.address
1709+
)
1710+
1711+
expectRoutingTable(
1712+
connectionProvider,
1713+
'databaseA',
1714+
[server1, server2, server3],
1715+
[server1, server2],
1716+
[server3]
1717+
)
1718+
expectRoutingTable(
1719+
connectionProvider,
1720+
null,
1721+
[serverA, serverB, serverC],
1722+
[serverB],
1723+
[serverC]
1724+
)
1725+
})
1726+
1727+
it('should forget write server from the default database routing table on availability error when db not informed', async () => {
1728+
const pool = newPool()
1729+
const connectionProvider = newRoutingConnectionProvider(
1730+
[
1731+
newRoutingTable(
1732+
'databaseA',
1733+
[server1, server2, server3],
1734+
[server1, server2],
1735+
[server3]
1736+
),
1737+
newRoutingTable(
1738+
null,
1739+
[serverA, serverB, serverC],
1740+
[serverA, serverB],
1741+
[serverA, serverC]
1742+
)
1743+
],
1744+
pool
1745+
)
1746+
1747+
const conn1 = await connectionProvider.acquireConnection({
1748+
accessMode: WRITE
1749+
})
1750+
1751+
// when
1752+
conn1._errorHandler.handleAndTransformError(
1753+
newError('connection error', SERVICE_UNAVAILABLE),
1754+
conn1.address
1755+
)
1756+
1757+
expectRoutingTable(
1758+
connectionProvider,
1759+
'databaseA',
1760+
[server1, server2, server3],
1761+
[server1, server2],
1762+
[server3]
1763+
)
1764+
expectRoutingTable(
1765+
connectionProvider,
1766+
null,
1767+
[serverA, serverB, serverC],
1768+
[serverB],
1769+
[serverC]
1770+
)
1771+
})
1772+
16801773
it('should forget write server from correct routing table on write error', async () => {
16811774
const pool = newPool()
16821775
const connectionProvider = newRoutingConnectionProvider(
@@ -1847,7 +1940,7 @@ function newRoutingConnectionProviderWithSeedRouter (
18471940
})
18481941
connectionProvider._connectionPool = pool
18491942
routingTables.forEach(r => {
1850-
connectionProvider._routingTables[r.database] = r
1943+
connectionProvider._routingTableRegistry.register(r.database, r)
18511944
})
18521945
connectionProvider._rediscovery = new FakeRediscovery(routerToRoutingTable)
18531946
connectionProvider._hostNameResolver = new FakeDnsResolver(seedRouterResolved)
@@ -1903,14 +1996,15 @@ function expectRoutingTable (
19031996
readers,
19041997
writers
19051998
) {
1906-
expect(connectionProvider._routingTables[database].database).toEqual(database)
1907-
expect(connectionProvider._routingTables[database].routers).toEqual(routers)
1908-
expect(connectionProvider._routingTables[database].readers).toEqual(readers)
1909-
expect(connectionProvider._routingTables[database].writers).toEqual(writers)
1999+
const routingTable = connectionProvider._routingTableRegistry.get(database)
2000+
expect(routingTable.database).toEqual(database)
2001+
expect(routingTable.routers).toEqual(routers)
2002+
expect(routingTable.readers).toEqual(readers)
2003+
expect(routingTable.writers).toEqual(writers)
19102004
}
19112005

19122006
function expectNoRoutingTable (connectionProvider, database) {
1913-
expect(connectionProvider._routingTables[database]).toBeFalsy()
2007+
expect(connectionProvider._routingTableRegistry.get(database)).toBeFalsy()
19142008
}
19152009

19162010
function expectPoolToContain (pool, addresses) {

0 commit comments

Comments
 (0)