[misc] rewrite: free functions

This commit is contained in:
Jakob Ackermann 2020-11-09 17:15:36 +00:00
parent da8bef821b
commit 4aebc1b0b6
2 changed files with 118 additions and 119 deletions

View file

@ -6,7 +6,6 @@
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let RedisSharelatex
const _ = require('underscore')
const os = require('os')
const crypto = require('crypto')
@ -17,130 +16,125 @@ const PID = process.pid
const RND = crypto.randomBytes(4).toString('hex')
let COUNT = 0
module.exports = RedisSharelatex = {
createClient(opts) {
let client, standardOpts
if (opts == null) {
opts = { port: 6379, host: 'localhost' }
function createClient(opts) {
let client, standardOpts
if (opts == null) {
opts = { port: 6379, host: 'localhost' }
}
if (opts.retry_max_delay == null) {
opts.retry_max_delay = 5000 // ms
}
if (opts.cluster != null) {
const Redis = require('ioredis')
standardOpts = _.clone(opts)
delete standardOpts.cluster
delete standardOpts.key_schema
client = new Redis.Cluster(opts.cluster, standardOpts)
client.healthCheck = clusterHealthCheckBuilder(client)
_monkeyPatchIoredisExec(client)
} else {
standardOpts = _.clone(opts)
const ioredis = require('ioredis')
client = new ioredis(standardOpts)
_monkeyPatchIoredisExec(client)
client.healthCheck = singleInstanceHealthCheckBuilder(client)
}
return client
}
const HEARTBEAT_TIMEOUT = 2000
function singleInstanceHealthCheckBuilder(client) {
const healthCheck = (callback) => _checkClient(client, callback)
return healthCheck
}
function clusterHealthCheckBuilder(client) {
return singleInstanceHealthCheckBuilder(client)
}
function _checkClient(client, callback) {
callback = _.once(callback)
// check the redis connection by storing and retrieving a unique key/value pair
const uniqueToken = `host=${HOST}:pid=${PID}:random=${RND}:time=${Date.now()}:count=${COUNT++}`
const timer = setTimeout(function () {
const error = new Error(
`redis client health check timed out ${__guard__(
client != null ? client.options : undefined,
(x) => x.host
)}`
)
console.error(
{
err: error,
key: client.options != null ? client.options.key : undefined, // only present for cluster
clientOptions: client.options,
uniqueToken,
},
'client timed out'
)
return callback(error)
}, HEARTBEAT_TIMEOUT)
const healthCheckKey = `_redis-wrapper:healthCheckKey:{${uniqueToken}}`
const healthCheckValue = `_redis-wrapper:healthCheckValue:{${uniqueToken}}`
// set the unique key/value pair
let multi = client.multi()
multi.set(healthCheckKey, healthCheckValue, 'EX', 60)
return multi.exec(function (err, reply) {
if (err != null) {
clearTimeout(timer)
return callback(err)
}
if (opts.retry_max_delay == null) {
opts.retry_max_delay = 5000 // ms
}
if (opts.cluster != null) {
const Redis = require('ioredis')
standardOpts = _.clone(opts)
delete standardOpts.cluster
delete standardOpts.key_schema
client = new Redis.Cluster(opts.cluster, standardOpts)
client.healthCheck = RedisSharelatex.clusterHealthCheckBuilder(client)
RedisSharelatex._monkeyPatchIoredisExec(client)
} else {
standardOpts = _.clone(opts)
const ioredis = require('ioredis')
client = new ioredis(standardOpts)
RedisSharelatex._monkeyPatchIoredisExec(client)
client.healthCheck = RedisSharelatex.singleInstanceHealthCheckBuilder(
client
)
}
return client
},
HEARTBEAT_TIMEOUT: 2000,
singleInstanceHealthCheckBuilder(client) {
const healthCheck = (callback) =>
RedisSharelatex._checkClient(client, callback)
return healthCheck
},
clusterHealthCheckBuilder(client) {
return RedisSharelatex.singleInstanceHealthCheckBuilder(client)
},
_checkClient(client, callback) {
callback = _.once(callback)
// check the redis connection by storing and retrieving a unique key/value pair
const uniqueToken = `host=${HOST}:pid=${PID}:random=${RND}:time=${Date.now()}:count=${COUNT++}`
const timer = setTimeout(function () {
const error = new Error(
`redis client health check timed out ${__guard__(
client != null ? client.options : undefined,
(x) => x.host
)}`
)
console.error(
{
err: error,
key: client.options != null ? client.options.key : undefined, // only present for cluster
clientOptions: client.options,
uniqueToken,
},
'client timed out'
)
return callback(error)
}, RedisSharelatex.HEARTBEAT_TIMEOUT)
const healthCheckKey = `_redis-wrapper:healthCheckKey:{${uniqueToken}}`
const healthCheckValue = `_redis-wrapper:healthCheckValue:{${uniqueToken}}`
// set the unique key/value pair
let multi = client.multi()
multi.set(healthCheckKey, healthCheckValue, 'EX', 60)
// check that we can retrieve the unique key/value pair
multi = client.multi()
multi.get(healthCheckKey)
multi.del(healthCheckKey)
return multi.exec(function (err, reply) {
clearTimeout(timer)
if (err != null) {
clearTimeout(timer)
return callback(err)
}
// check that we can retrieve the unique key/value pair
multi = client.multi()
multi.get(healthCheckKey)
multi.del(healthCheckKey)
return multi.exec(function (err, reply) {
clearTimeout(timer)
if (err != null) {
return callback(err)
}
if (
(reply != null ? reply[0] : undefined) !== healthCheckValue ||
(reply != null ? reply[1] : undefined) !== 1
) {
return callback(new Error('bad response from redis health check'))
}
return callback()
})
})
},
_monkeyPatchIoredisExec(client) {
const _multi = client.multi
return (client.multi = function (...args) {
const multi = _multi.call(client, ...Array.from(args))
const _exec = multi.exec
multi.exec = function (callback) {
if (callback == null) {
callback = function () {}
}
return _exec.call(multi, function (error, result) {
// ioredis exec returns an results like:
// [ [null, 42], [null, "foo"] ]
// where the first entries in each 2-tuple are
// presumably errors for each individual command,
// and the second entry is the result. We need to transform
// this into the same result as the old redis driver:
// [ 42, "foo" ]
const filtered_result = []
for (const entry of Array.from(result || [])) {
if (entry[0] != null) {
return callback(entry[0])
} else {
filtered_result.push(entry[1])
}
}
return callback(error, filtered_result)
})
if (
(reply != null ? reply[0] : undefined) !== healthCheckValue ||
(reply != null ? reply[1] : undefined) !== 1
) {
return callback(new Error('bad response from redis health check'))
}
return multi
return callback()
})
},
})
}
function _monkeyPatchIoredisExec(client) {
const _multi = client.multi
return (client.multi = function (...args) {
const multi = _multi.call(client, ...Array.from(args))
const _exec = multi.exec
multi.exec = function (callback) {
if (callback == null) {
callback = function () {}
}
return _exec.call(multi, function (error, result) {
// ioredis exec returns an results like:
// [ [null, 42], [null, "foo"] ]
// where the first entries in each 2-tuple are
// presumably errors for each individual command,
// and the second entry is the result. We need to transform
// this into the same result as the old redis driver:
// [ 42, "foo" ]
const filtered_result = []
for (const entry of Array.from(result || [])) {
if (entry[0] != null) {
return callback(entry[0])
} else {
filtered_result.push(entry[1])
}
}
return callback(error, filtered_result)
})
}
return multi
})
}
function __guard__(value, transform) {
@ -148,3 +142,7 @@ function __guard__(value, transform) {
? transform(value)
: undefined
}
module.exports = {
createClient,
}

View file

@ -126,7 +126,8 @@ describe('index', function () {
this.results = []
this.multiOrig = { exec: sinon.stub().yields(null, this.results) }
this.client = { multi: sinon.stub().returns(this.multiOrig) }
this.redis._monkeyPatchIoredisExec(this.client)
this.ioredisConstructor.returns(this.client)
this.redis.createClient(this.client)
return (this.multi = this.client.multi())
})