Skip to content

Instantly share code, notes, and snippets.

@isaacs
Created September 28, 2019 23:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save isaacs/48776f89d03fa4577485b6b6c530f2f5 to your computer and use it in GitHub Desktop.
Save isaacs/48776f89d03fa4577485b6b6c530f2f5 to your computer and use it in GitHub Desktop.
const {executionAsyncId, AsyncResource} = require('async_hooks')
const _handle = Symbol('_handle')
class ConnectionWrap extends AsyncResource {
constructor (connection) {
super('connection.' + connection.id)
this[_handle] = connection
// Polyfill for Node.js 8 and before
/* istanbul ignore next */
if (!this.runInAsyncScope)
this.runInAsyncScope = (fn, thisArg, ...args) => {
this.emitBefore()
try {
return Reflect.apply(fn, thisArg, args)
} finally {
this.emitAfter()
}
}
}
runQuery (query, cb) {
this.runInAsyncScope(() => this[_handle].runQuery(query, cb))
}
close (cb) {
this.runInAsyncScope(() => this[_handle].pool.close(this[_handle], cb))
}
}
class Connection {
constructor (id, pool) {
this.id = id
this.consumers = 0
this.pool = pool
}
close (cb) {
console.error('%j: closing in eid %j', this.id, executionAsyncId())
setImmediate(cb)
}
runQuery (query, cb) {
console.error('%j: run %j in eid %j', this.id, query, executionAsyncId())
setImmediate(cb)
}
}
class ConnectionPool {
constructor () {
this.maxConnections = 5
this.pool = new Map()
}
close (connection, cb) {
connection.consumers --
if (connection.consumers === 0 && this.pool.size < this.maxConnections) {
this.pool.delete(connection.id)
connection.close(cb)
} else
setImmediate(cb)
}
createConnection (id, cb) {
const conn = this.pool.get(id) || new Connection(id, this)
this.pool.set(id, conn)
conn.consumers ++
const wrap = new ConnectionWrap(conn)
new ConnectionWrap(conn).runInAsyncScope(() => cb(wrap))
}
}
// consuming code
const pool = new ConnectionPool()
const fooConns = []
const foo = () => {
setTimeout(() => {
console.error('EID=%j', executionAsyncId())
pool.createConnection('foo', conn => {
fooConns.push(conn)
console.error('created foo conn EID=%j', executionAsyncId())
conn.runQuery('q=1', () => {
console.error('ran foo conn query EID=%j', executionAsyncId())
conn.close(() => {
console.error('closed foo conn EID=%j', executionAsyncId())
})
})
})
})
}
const barConns = []
const bar = () => {
setTimeout(() => {
console.error('EID=%j', executionAsyncId())
pool.createConnection('bar', conn => {
barConns.push(conn)
console.error('created bar conn EID=%j', executionAsyncId())
conn.runQuery('q=1', () => {
console.error('ran bar conn query EID=%j', executionAsyncId())
conn.close(() => {
console.error('closed bar conn EID=%j', executionAsyncId())
})
})
})
})
}
foo()
foo()
bar()
bar()
process.on('exit', () => {
const assert = require('assert')
assert(fooConns[0] !== fooConns[1])
assert(fooConns[0][_handle] === fooConns[1][_handle])
assert(barConns[0] !== barConns[1])
assert(barConns[0][_handle] === barConns[1][_handle])
console.error('continuations kept separate, actual connections shared')
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment