2014-09-26 09:46:23 -04:00
|
|
|
_ = require("underscore")
|
2019-09-19 10:05:32 -04:00
|
|
|
os = require('os')
|
|
|
|
crypto = require('crypto')
|
|
|
|
|
|
|
|
# generate unique values for health check
|
|
|
|
HOST = os.hostname()
|
|
|
|
PID = process.pid
|
|
|
|
RND = crypto.randomBytes(4).toString('hex')
|
|
|
|
COUNT = 0
|
2014-09-26 09:46:23 -04:00
|
|
|
|
2014-11-19 06:01:02 -05:00
|
|
|
module.exports = RedisSharelatex =
|
|
|
|
createClient: (opts = {port: 6379, host: "localhost"})->
|
2014-11-19 06:02:27 -05:00
|
|
|
if !opts.retry_max_delay?
|
|
|
|
opts.retry_max_delay = 5000 # ms
|
|
|
|
|
2020-11-09 07:37:33 -05:00
|
|
|
if opts.cluster?
|
2017-04-12 09:53:34 -04:00
|
|
|
Redis = require("ioredis")
|
2017-06-16 10:28:55 -04:00
|
|
|
standardOpts = _.clone(opts)
|
|
|
|
delete standardOpts.cluster
|
2017-06-19 10:46:21 -04:00
|
|
|
delete standardOpts.key_schema
|
2017-06-16 10:28:55 -04:00
|
|
|
client = new Redis.Cluster(opts.cluster, standardOpts)
|
2017-04-12 09:53:34 -04:00
|
|
|
client.healthCheck = RedisSharelatex.clusterHealthCheckBuilder(client)
|
|
|
|
RedisSharelatex._monkeyPatchIoredisExec(client)
|
2014-09-25 12:33:27 -04:00
|
|
|
else
|
2014-09-26 09:46:23 -04:00
|
|
|
standardOpts = _.clone(opts)
|
2019-05-03 06:33:24 -04:00
|
|
|
ioredis = require("ioredis")
|
|
|
|
client = new ioredis(standardOpts)
|
|
|
|
RedisSharelatex._monkeyPatchIoredisExec(client)
|
2017-04-12 09:53:34 -04:00
|
|
|
client.healthCheck = RedisSharelatex.singleInstanceHealthCheckBuilder(client)
|
2014-11-19 18:18:56 -05:00
|
|
|
return client
|
2017-04-12 09:53:34 -04:00
|
|
|
|
|
|
|
HEARTBEAT_TIMEOUT: 2000
|
|
|
|
singleInstanceHealthCheckBuilder: (client) ->
|
|
|
|
healthCheck = (callback) ->
|
|
|
|
RedisSharelatex._checkClient(client, callback)
|
|
|
|
return healthCheck
|
|
|
|
|
|
|
|
clusterHealthCheckBuilder: (client) ->
|
2019-09-23 06:41:53 -04:00
|
|
|
return RedisSharelatex.singleInstanceHealthCheckBuilder(client)
|
2017-04-12 09:53:34 -04:00
|
|
|
|
|
|
|
_checkClient: (client, callback) ->
|
|
|
|
callback = _.once(callback)
|
2019-09-19 10:05:32 -04:00
|
|
|
# check the redis connection by storing and retrieving a unique key/value pair
|
|
|
|
uniqueToken = "host=#{HOST}:pid=#{PID}:random=#{RND}:time=#{Date.now()}:count=#{COUNT++}"
|
2017-04-12 09:53:34 -04:00
|
|
|
timer = setTimeout () ->
|
2019-09-19 10:05:32 -04:00
|
|
|
error = new Error("redis client health check timed out #{client?.options?.host}")
|
2017-04-12 09:53:34 -04:00
|
|
|
console.error {
|
|
|
|
err: error,
|
|
|
|
key: client.options?.key # only present for cluster
|
2019-05-30 05:13:05 -04:00
|
|
|
clientOptions: client.options
|
2019-09-19 10:05:32 -04:00
|
|
|
uniqueToken: uniqueToken
|
2017-04-12 09:53:34 -04:00
|
|
|
}, "client timed out"
|
|
|
|
callback(error)
|
|
|
|
, RedisSharelatex.HEARTBEAT_TIMEOUT
|
2019-09-19 10:05:32 -04:00
|
|
|
healthCheckKey = "_redis-wrapper:healthCheckKey:{#{uniqueToken}}"
|
|
|
|
healthCheckValue = "_redis-wrapper:healthCheckValue:{#{uniqueToken}}"
|
|
|
|
# set the unique key/value pair
|
|
|
|
multi = client.multi()
|
|
|
|
multi.set healthCheckKey, healthCheckValue, "EX", 60
|
|
|
|
multi.exec (err, reply) ->
|
|
|
|
if err?
|
|
|
|
clearTimeout timer
|
|
|
|
return callback(err)
|
|
|
|
# check that we can retrieve the unique key/value pair
|
|
|
|
multi = client.multi()
|
|
|
|
multi.get healthCheckKey
|
|
|
|
multi.del healthCheckKey
|
|
|
|
multi.exec (err, reply) ->
|
|
|
|
clearTimeout timer
|
|
|
|
return callback(err) if err?
|
2019-09-19 10:41:43 -04:00
|
|
|
return callback(new Error("bad response from redis health check")) if reply?[0] isnt healthCheckValue or reply?[1] isnt 1
|
2019-09-19 10:05:32 -04:00
|
|
|
return callback()
|
|
|
|
|
2017-04-12 09:53:34 -04:00
|
|
|
_monkeyPatchIoredisExec: (client) ->
|
|
|
|
_multi = client.multi
|
|
|
|
client.multi = (args...) ->
|
|
|
|
multi = _multi.call(client, args...)
|
|
|
|
_exec = multi.exec
|
2017-05-05 08:36:39 -04:00
|
|
|
multi.exec = (callback = () ->) ->
|
|
|
|
_exec.call multi, (error, result) ->
|
2017-04-12 09:53:34 -04:00
|
|
|
# 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" ]
|
|
|
|
filtered_result = []
|
|
|
|
for entry in result or []
|
|
|
|
if entry[0]?
|
|
|
|
return callback(entry[0])
|
|
|
|
else
|
|
|
|
filtered_result.push entry[1]
|
|
|
|
callback error, filtered_result
|
|
|
|
return multi
|
|
|
|
|
2014-11-19 18:18:56 -05:00
|
|
|
|
2017-04-12 09:53:34 -04:00
|
|
|
|
2014-11-19 18:18:56 -05:00
|
|
|
|