mirror of
https://github.com/penpot/penpot.git
synced 2025-07-29 10:37:29 +02:00
✨ Improve msgbus subscription handling.
This commit is contained in:
parent
8fd37dbad5
commit
7e1ee087d3
4 changed files with 194 additions and 124 deletions
|
@ -62,9 +62,14 @@
|
|||
snd-conn (.connect ^RedisClient rclient ^RedisCodec codec)
|
||||
rcv-conn (.connectPubSub ^RedisClient rclient ^RedisCodec codec)
|
||||
|
||||
pub-buff (a/chan (a/dropping-buffer buffer-size))
|
||||
rcv-buff (a/chan (a/dropping-buffer buffer-size))
|
||||
sub-buff (a/chan 1)
|
||||
;; Channel used for receive publications from the application.
|
||||
pub-chan (a/chan (a/dropping-buffer buffer-size))
|
||||
|
||||
;; Channel used for receive data from redis
|
||||
rcv-chan (a/chan (a/dropping-buffer buffer-size))
|
||||
|
||||
;; Channel used for receive subscription requests.
|
||||
sub-chan (a/chan)
|
||||
cch (a/chan 1)]
|
||||
|
||||
(.setTimeout ^StatefulRedisConnection snd-conn ^Duration (dt/duration {:seconds 10}))
|
||||
|
@ -73,10 +78,10 @@
|
|||
(log/debugf "initializing msgbus (uri: '%s')" (str uri))
|
||||
|
||||
;; Start the sending (publishing) loop
|
||||
(impl-publish-loop snd-conn pub-buff cch)
|
||||
(impl-publish-loop snd-conn pub-chan cch)
|
||||
|
||||
;; Start the receiving (subscribing) loop
|
||||
(impl-subscribe-loop rcv-conn rcv-buff sub-buff cch)
|
||||
(impl-subscribe-loop rcv-conn rcv-chan sub-chan cch)
|
||||
|
||||
(with-meta
|
||||
(fn run
|
||||
|
@ -84,14 +89,14 @@
|
|||
([command params]
|
||||
(a/go
|
||||
(case command
|
||||
:pub (a/>! pub-buff params)
|
||||
:sub (a/>! sub-buff params)))))
|
||||
:pub (a/>! pub-chan params)
|
||||
:sub (a/>! sub-chan params)))))
|
||||
|
||||
{::snd-conn snd-conn
|
||||
::rcv-conn rcv-conn
|
||||
::cch cch
|
||||
::pub-buff pub-buff
|
||||
::rcv-buff rcv-buff})))
|
||||
::pub-chan pub-chan
|
||||
::rcv-chan rcv-chan})))
|
||||
|
||||
(defmethod ig/halt-key! ::msgbus
|
||||
[_ f]
|
||||
|
@ -99,14 +104,14 @@
|
|||
(.close ^StatefulRedisConnection (::snd-conn mdata))
|
||||
(.close ^StatefulRedisPubSubConnection (::rcv-conn mdata))
|
||||
(a/close! (::cch mdata))
|
||||
(a/close! (::pub-buff mdata))
|
||||
(a/close! (::rcv-buff mdata))))
|
||||
(a/close! (::pub-chan mdata))
|
||||
(a/close! (::rcv-chan mdata))))
|
||||
|
||||
(defn- impl-publish-loop
|
||||
[conn pub-buff cch]
|
||||
[conn pub-chan cch]
|
||||
(let [rac (.async ^StatefulRedisConnection conn)]
|
||||
(a/go-loop []
|
||||
(let [[val _] (a/alts! [cch pub-buff] :priority true)]
|
||||
(let [[val _] (a/alts! [cch pub-chan] :priority true)]
|
||||
(when (some? val)
|
||||
(let [result (a/<! (impl-redis-pub rac val))]
|
||||
(when (ex/exception? result)
|
||||
|
@ -114,7 +119,7 @@
|
|||
(recur))))))
|
||||
|
||||
(defn- impl-subscribe-loop
|
||||
[conn rcv-buff sub-buff cch]
|
||||
[conn rcv-chan sub-chan cch]
|
||||
;; Add a unique listener to connection
|
||||
(.addListener conn (reify RedisPubSubListener
|
||||
(message [it pattern topic message])
|
||||
|
@ -122,48 +127,96 @@
|
|||
;; There are no back pressure, so we use a slidding
|
||||
;; buffer for cases when the pubsub broker sends
|
||||
;; more messages that we can process.
|
||||
(a/>!! rcv-buff {:topic topic :message (blob/decode message)}))
|
||||
(let [val {:topic topic :message (blob/decode message)}]
|
||||
(when-not (a/offer! rcv-chan val)
|
||||
(log/warn "dropping message on subscription loop"))))
|
||||
(psubscribed [it pattern count])
|
||||
(punsubscribed [it pattern count])
|
||||
(subscribed [it topic count])
|
||||
(unsubscribed [it topic count])))
|
||||
|
||||
(a/go-loop [chans {}]
|
||||
(let [[val port] (a/alts! [sub-buff cch rcv-buff] :priority true)]
|
||||
(cond
|
||||
;; Stop condition; just do nothing
|
||||
(= port cch)
|
||||
nil
|
||||
(let [chans (agent {} :error-handler #(log/error % "unexpected error on agent"))
|
||||
|
||||
;; If we receive a message on sub-buff this means that a new
|
||||
;; subscription is requested by the notifications module.
|
||||
(= port sub-buff)
|
||||
(let [topic (:topic val)
|
||||
output (:chan val)
|
||||
chans (update chans topic (fnil conj #{}) output)]
|
||||
(when (= 1 (count (get chans topic)))
|
||||
(let [result (a/<! (impl-redis-sub conn topic))]
|
||||
(when (ex/exception? result)
|
||||
(log/errorf result "unexpected exception on subscribing to '%s'" topic))))
|
||||
(recur chans))
|
||||
subscribe-to-single-topic
|
||||
(fn [nsubs topic chan]
|
||||
(let [nsubs (if (nil? nsubs) #{chan} (conj nsubs chan))]
|
||||
(when (= 1 (count nsubs))
|
||||
(let [result (a/<!! (impl-redis-sub conn topic))]
|
||||
(log/tracef "opening subscription to %s" topic)
|
||||
(when (ex/exception? result)
|
||||
(log/errorf result "unexpected exception on subscribing to '%s'" topic))))
|
||||
nsubs))
|
||||
|
||||
;; This means we receive data from redis and we need to
|
||||
;; forward it to the underlying subscriptions.
|
||||
(= port rcv-buff)
|
||||
(let [topic (:topic val)
|
||||
pending (loop [chans (seq (get chans topic))
|
||||
pending #{}]
|
||||
(if-let [ch (first chans)]
|
||||
(if (a/>! ch (:message val))
|
||||
(recur (rest chans) pending)
|
||||
(recur (rest chans) (conj pending ch)))
|
||||
pending))
|
||||
chans (update chans topic #(reduce disj % pending))]
|
||||
(when (empty? (get chans topic))
|
||||
(let [result (a/<! (impl-redis-unsub conn topic))]
|
||||
(when (ex/exception? result)
|
||||
(log/errorf result "unexpected exception on unsubscribing from '%s'" topic))))
|
||||
(recur chans))))))
|
||||
subscribe-to-topics
|
||||
(fn [state topics chan]
|
||||
(let [topics (into #{} (map str) topics)
|
||||
state (update state :chans assoc chan topics)]
|
||||
(reduce (fn [state topic]
|
||||
(update-in state [:topics topic] subscribe-to-single-topic topic chan))
|
||||
state
|
||||
topics)))
|
||||
|
||||
unsubscribe-from-single-topic
|
||||
(fn [nsubs topic chan]
|
||||
;; (log/tracef "unsubscribe-from-single-topic %s | %s | %s" nsubs topic chan)
|
||||
(let [nsubs (disj nsubs chan)]
|
||||
(when (empty? nsubs)
|
||||
(let [result (a/<!! (impl-redis-unsub conn topic))]
|
||||
(log/tracef "closing subscription to %s" topic)
|
||||
(when (ex/exception? result)
|
||||
(log/errorf result "unexpected exception on unsubscribing from '%s'" topic))))
|
||||
nsubs))
|
||||
|
||||
unsubscribe-channels
|
||||
(fn [state pending]
|
||||
;; (log/tracef "unsubscribe-channels %s" (pr-str pending))
|
||||
(reduce (fn [state ch]
|
||||
(let [topics (get-in state [:chans ch])
|
||||
state (update state :chans dissoc ch)]
|
||||
;; (log/tracef "unsubscribe-channels topics=%s" topics)
|
||||
(reduce (fn [state topic]
|
||||
(update-in state [:topics topic] unsubscribe-from-single-topic topic ch))
|
||||
state
|
||||
topics)))
|
||||
state
|
||||
pending))]
|
||||
|
||||
;; Asynchronous subscription loop; terminates when sub-chan is
|
||||
;; closed.
|
||||
(a/go-loop []
|
||||
(when-let [{:keys [topics chan]} (a/<! sub-chan)]
|
||||
(send-off chans subscribe-to-topics topics chan)
|
||||
(recur)))
|
||||
|
||||
(a/go-loop []
|
||||
(let [[val port] (a/alts! [cch rcv-chan])]
|
||||
(cond
|
||||
;; Stop condition; close all underlying subscriptions and
|
||||
;; exit. The close operation is performed asynchronously.
|
||||
(= port cch)
|
||||
(send-off chans (fn [state]
|
||||
(log/tracef "close")
|
||||
(->> (vals state)
|
||||
(mapcat identity)
|
||||
(filter some?)
|
||||
(run! a/close!))))
|
||||
|
||||
;; This means we receive data from redis and we need to
|
||||
;; forward it to the underlying subscriptions.
|
||||
(= port rcv-chan)
|
||||
(let [topic (:topic val) ; topic is already string
|
||||
pending (loop [chans (seq (get-in @chans [:topics topic]))
|
||||
pending #{}]
|
||||
(if-let [ch (first chans)]
|
||||
(if (a/>! ch (:message val))
|
||||
(recur (rest chans) pending)
|
||||
(recur (rest chans) (conj pending ch)))
|
||||
pending))]
|
||||
;; (log/tracef "received message => pending: %s" (pr-str pending))
|
||||
(some->> (seq pending)
|
||||
(send-off chans unsubscribe-channels))
|
||||
|
||||
(recur)))))))
|
||||
|
||||
(defn- impl-redis-pub
|
||||
[rac {:keys [topic message]}]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue