Skip to content

Commit 79aa880

Browse files
committed
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 79aa880

File tree

2 files changed

+225
-38
lines changed

2 files changed

+225
-38
lines changed

src/internal/connection-provider-routing.js

Lines changed: 125 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,40 @@ 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+
applyWhenDontExists: () => {
213+
throw newError(
214+
'Could not forget address for a non existing routing table'
215+
)
216+
}
217+
})
216218

217219
// We're firing and forgetting this operation explicitly and listening for any
218220
// errors to avoid unhandled promise rejection
219221
this._connectionPool.purge(address).catch(() => {})
220222
}
221223

222224
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-
}
225+
this._routingTableRegistry.apply(database, {
226+
applyWhenExists: routingTable => routingTable.forgetWriter(address),
227+
applyWhenDontExists: () => {
228+
throw newError(
229+
'Could not forget writer address for a non existing routing table'
230+
)
231+
}
232+
})
230233
}
231234

232235
_acquireConnectionToServer (address, serverName, routingTable) {
233236
return this._connectionPool.acquire(address)
234237
}
235238

236239
_freshRoutingTable ({ accessMode, database, bookmark } = {}) {
237-
const currentRoutingTable =
238-
this._routingTables[database] || new RoutingTable({ database })
240+
const currentRoutingTable = this._routingTableRegistry.get(
241+
database,
242+
() => new RoutingTable({ database })
243+
)
239244

240245
if (!currentRoutingTable.isStaleFor(accessMode)) {
241246
return currentRoutingTable
@@ -481,16 +486,11 @@ export default class RoutingConnectionProvider extends PooledConnectionProvider
481486
async _updateRoutingTable (newRoutingTable) {
482487
// close old connections to servers not present in the new routing table
483488
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
489+
this._routingTableRegistry.removeExpired()
490+
this._routingTableRegistry.register(
491+
newRoutingTable.database,
492+
newRoutingTable
493+
)
494494
this._log.info(`Updated routing table ${newRoutingTable}`)
495495
}
496496

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

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)