2019-07-18 06:25:10 -04:00
|
|
|
logger = require 'logger-sharelatex'
|
|
|
|
metrics = require "metrics-sharelatex"
|
2019-07-18 07:40:14 -04:00
|
|
|
settings = require "settings-sharelatex"
|
2019-07-18 06:25:10 -04:00
|
|
|
|
2019-07-24 09:17:19 -04:00
|
|
|
ClientMap = new Map() # for each redis client, store a Map of subscribed channels (channelname -> subscribe promise)
|
2019-07-18 06:25:10 -04:00
|
|
|
|
|
|
|
# Manage redis pubsub subscriptions for individual projects and docs, ensuring
|
|
|
|
# that we never subscribe to a channel multiple times. The socket.io side is
|
|
|
|
# handled by RoomManager.
|
|
|
|
|
|
|
|
module.exports = ChannelManager =
|
2019-07-22 06:23:33 -04:00
|
|
|
getClientMapEntry: (rclient) ->
|
2019-07-24 09:17:19 -04:00
|
|
|
# return the per-client channel map if it exists, otherwise create and
|
|
|
|
# return an empty map for the client.
|
2019-07-23 12:02:09 -04:00
|
|
|
ClientMap.get(rclient) || ClientMap.set(rclient, new Map()).get(rclient)
|
2019-07-18 06:25:10 -04:00
|
|
|
|
|
|
|
subscribe: (rclient, baseChannel, id) ->
|
2019-07-24 09:17:19 -04:00
|
|
|
clientChannelMap = @getClientMapEntry(rclient)
|
2019-07-18 06:25:10 -04:00
|
|
|
channel = "#{baseChannel}:#{id}"
|
2020-05-15 05:34:07 -04:00
|
|
|
actualSubscribe = () ->
|
|
|
|
# subscribe is happening in the foreground and it should reject
|
|
|
|
p = rclient.subscribe(channel)
|
|
|
|
p.finally () ->
|
|
|
|
if clientChannelMap.get(channel) is subscribePromise
|
|
|
|
clientChannelMap.delete(channel)
|
|
|
|
.then () ->
|
|
|
|
logger.log {channel}, "subscribed to channel"
|
|
|
|
metrics.inc "subscribe.#{baseChannel}"
|
|
|
|
.catch (err) ->
|
|
|
|
logger.error {channel, err}, "failed to subscribe to channel"
|
|
|
|
metrics.inc "subscribe.failed.#{baseChannel}"
|
|
|
|
return p
|
|
|
|
|
|
|
|
pendingActions = clientChannelMap.get(channel) || Promise.resolve()
|
|
|
|
subscribePromise = pendingActions.then(actualSubscribe, actualSubscribe)
|
|
|
|
clientChannelMap.set(channel, subscribePromise)
|
|
|
|
logger.log {channel}, "planned to subscribe to channel"
|
|
|
|
return subscribePromise
|
2019-07-18 06:25:10 -04:00
|
|
|
|
|
|
|
unsubscribe: (rclient, baseChannel, id) ->
|
2019-07-24 09:17:19 -04:00
|
|
|
clientChannelMap = @getClientMapEntry(rclient)
|
2019-07-18 06:25:10 -04:00
|
|
|
channel = "#{baseChannel}:#{id}"
|
2020-05-15 05:34:07 -04:00
|
|
|
actualUnsubscribe = () ->
|
|
|
|
# unsubscribe is happening in the background, it should not reject
|
|
|
|
p = rclient.unsubscribe(channel)
|
|
|
|
.finally () ->
|
|
|
|
if clientChannelMap.get(channel) is unsubscribePromise
|
|
|
|
clientChannelMap.delete(channel)
|
|
|
|
.then () ->
|
|
|
|
logger.log {channel}, "unsubscribed from channel"
|
|
|
|
metrics.inc "unsubscribe.#{baseChannel}"
|
|
|
|
.catch (err) ->
|
|
|
|
logger.error {channel, err}, "unsubscribed from channel"
|
|
|
|
metrics.inc "unsubscribe.failed.#{baseChannel}"
|
|
|
|
return p
|
|
|
|
|
|
|
|
pendingActions = clientChannelMap.get(channel) || Promise.resolve()
|
|
|
|
unsubscribePromise = pendingActions.then(actualUnsubscribe, actualUnsubscribe)
|
|
|
|
clientChannelMap.set(channel, unsubscribePromise)
|
|
|
|
logger.log {channel}, "planned to unsubscribe from channel"
|
|
|
|
return unsubscribePromise
|
2019-07-18 06:25:10 -04:00
|
|
|
|
|
|
|
publish: (rclient, baseChannel, id, data) ->
|
2020-03-30 05:31:44 -04:00
|
|
|
metrics.summary "redis.publish.#{baseChannel}", data.length
|
2019-07-18 07:40:14 -04:00
|
|
|
if id is 'all' or !settings.publishOnIndividualChannels
|
2019-07-18 06:25:10 -04:00
|
|
|
channel = baseChannel
|
|
|
|
else
|
|
|
|
channel = "#{baseChannel}:#{id}"
|
|
|
|
# we publish on a different client to the subscribe, so we can't
|
|
|
|
# check for the channel existing here
|
2019-07-22 07:25:41 -04:00
|
|
|
rclient.publish channel, data
|