0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-24 15:39:50 -05:00

Add generic (blocking) retry macro

And use it on audit handling
This commit is contained in:
Andrey Antukh 2022-12-13 11:17:47 +01:00
parent 7a9172560d
commit c820c49fc5
3 changed files with 61 additions and 19 deletions

View file

@ -21,6 +21,7 @@
[app.main :as-alias main] [app.main :as-alias main]
[app.metrics :as mtx] [app.metrics :as mtx]
[app.tokens :as tokens] [app.tokens :as tokens]
[app.util.retry :as rtry]
[app.util.time :as dt] [app.util.time :as dt]
[app.worker :as wrk] [app.worker :as wrk]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
@ -143,22 +144,29 @@
(defn- persist-event! (defn- persist-event!
[pool event] [pool event]
(us/verify! ::event event) (us/verify! ::event event)
(let [now (dt/now) (let [params {:id (uuid/next)
params {:id (uuid/next)
:name (:name event) :name (:name event)
:type (:type event) :type (:type event)
:profile-id (:profile-id event) :profile-id (:profile-id event)
:created-at now
:tracked-at now
:ip-addr (:ip-addr event) :ip-addr (:ip-addr event)
:props (:props event)}] :props (:props event)}]
(when (contains? cf/flags :audit-log) (when (contains? cf/flags :audit-log)
(db/insert! pool :audit-log
(-> params ;; NOTE: this operation may cause primary key conflicts on inserts
(update :props db/tjson) ;; because of the timestamp precission (two concurrent requests), in
(update :ip-addr db/inet) ;; this case we just retry the operation.
(assoc :source "backend")))) (rtry/with-retry {::rtry/when rtry/conflict-exception?
::rtry/max-retries 6
::rtry/label "persist-audit-log-event"}
(let [now (dt/now)]
(db/insert! pool :audit-log
(-> params
(update :props db/tjson)
(update :ip-addr db/inet)
(assoc :created-at now)
(assoc :tracked-at now)
(assoc :source "backend"))))))
(when (and (contains? cf/flags :webhooks) (when (and (contains? cf/flags :webhooks)
(::webhooks/event? event)) (::webhooks/event? event))

View file

@ -5,23 +5,23 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.rpc.retry (ns app.rpc.retry
"A fault tolerance helpers. Allow retry some operations that we know "A fault tolerance RPC middleware. Allow retry some operations that we
we can retry." know we can retry."
(:require (:require
[app.common.logging :as l] [app.common.logging :as l]
[app.util.retry :refer [conflict-exception?]]
[app.util.services :as sv] [app.util.services :as sv]
[promesa.core :as p])) [promesa.core :as p]))
(defn conflict-db-insert? (defn conflict-db-insert?
"Check if exception matches a insertion conflict on postgresql." "Check if exception matches a insertion conflict on postgresql."
[e] [e]
(and (instance? org.postgresql.util.PSQLException e) (conflict-exception? e))
(= "23505" (.getSQLState e))))
(def always-false (constantly false))
(defn wrap-retry (defn wrap-retry
[_ f {:keys [::matches ::sv/name] [_ f {:keys [::matches ::sv/name] :or {matches always-false} :as mdata}]
:or {matches (constantly false)}
:as mdata}]
(when (::enabled mdata) (when (::enabled mdata)
(l/debug :hint "wrapping retry" :name name)) (l/debug :hint "wrapping retry" :name name))
@ -29,8 +29,8 @@
(if-let [max-retries (::max-retries mdata)] (if-let [max-retries (::max-retries mdata)]
(fn [cfg params] (fn [cfg params]
(letfn [(run [retry] (letfn [(run [retry]
(-> (f cfg params) (->> (f cfg params)
(p/catch (partial handle-error retry)))) (p/merr (partial handle-error retry))))
(handle-error [retry cause] (handle-error [retry cause]
(if (matches cause) (if (matches cause)
@ -40,6 +40,6 @@
(run current-retry) (run current-retry)
(throw cause))) (throw cause)))
(throw cause)))] (throw cause)))]
(run 0))) (run 1)))
f)) f))

View file

@ -0,0 +1,34 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.util.retry
"A fault tolerance helpers. Allow retry some operations that we know
we can retry."
(:require
[app.common.logging :as l])
(:import
org.postgresql.util.PSQLException))
(defn conflict-exception?
"Check if exception matches a insertion conflict on postgresql."
[e]
(and (instance? PSQLException e)
(= "23505" (.getSQLState ^PSQLException e))))
(defmacro with-retry
[{:keys [::when ::max-retries ::label] :or {max-retries 3}} & body]
`(loop [tnum# 1]
(let [result# (try
~@body
(catch Throwable cause#
(if (and (~when cause#) (<= tnum# ~max-retries))
::retry
(throw cause#))))]
(if (= ::retry result#)
(do
(l/warn :hint "retrying operation" :label ~label)
(recur (inc tnum#)))
result#))))