diff --git a/project.clj b/project.clj index fd5ecdd..1bcde1a 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject views "0.4.3" +(defproject views "0.4.4" :description "You underestimate the power of the SQL side" :url "https://github.com/diligenceengine/views" diff --git a/src/views/db/deltas.clj b/src/views/db/deltas.clj index 19e2021..7faef8c 100644 --- a/src/views/db/deltas.clj +++ b/src/views/db/deltas.clj @@ -1,4 +1,5 @@ (ns views.db.deltas + (:import (java.sql SQLException)) (:require [clojure.string :refer [split]] [clojure.java.jdbc :as j] @@ -8,7 +9,7 @@ [views.db.load :as vdbl] [views.db.checks :as vc] [views.db.honeysql :as vh] - [views.db.util :refer [safe-map log-exception]])) + [views.db.util :refer [safe-map log-exception serialization-error?]])) ;; ;; Terminology and data structures used throughout this code @@ -218,9 +219,10 @@ (try (let [refresh-set (get (vdbl/initial-view db view-sig templates view) view-sig)] (update-in d [view-sig] (update-deltas-with-refresh-set refresh-set))) - (catch Exception e ;; ignore any failed view deltas - (log-exception e) - d))) + ;; allow serialization errors + (catch SQLException e (if (serialization-error? e) (throw e) d)) + ;; ignore any failed view deltas + (catch Exception e (log-exception e) d))) deltas refresh-only-views)) diff --git a/src/views/db/util.clj b/src/views/db/util.clj index ddd1a6d..53c6752 100644 --- a/src/views/db/util.clj +++ b/src/views/db/util.clj @@ -9,7 +9,7 @@ ;; java.sql.SQLException: ERROR: could not serialize access due to concurrent update ;; (defn get-nested-exceptions* - [exceptions e] + [exceptions ^SQLException e] (if-let [next-e (.getNextException e)] (recur (conj exceptions next-e) next-e) exceptions)) @@ -19,6 +19,11 @@ [e] (get-nested-exceptions* [e] e)) +(defn serialization-error? + "True if e is a serialization error." + [^SQLException e] + (boolean (some #(= (.getSQLState ^SQLException %) "40001") (get-nested-exceptions e)))) + ;; TODO: update to avoid stack overflow. (defn retry-on-transaction-failure "Retry a function whenever we receive a transaction failure." @@ -31,7 +36,7 @@ (debug "Exception message: " (.getMessage e)) ;; (debug "stack trace message: " (.printStackTrace e)) - (if (some #(= (.getSQLState %) "40001") (get-nested-exceptions e)) + (if (serialization-error? e) (retry-on-transaction-failure transaction-fn) ;; try it again (throw e))))) ;; otherwise rethrow @@ -42,7 +47,7 @@ (retry-on-transaction-failure tfn#))) (defn log-exception - [e] + [^Exception e] (error "views internal" (str "e: " e @@ -50,6 +55,11 @@ " trace: " (with-out-str (print-stack-trace e))))) (defn safe-map - "A non-lazy map that skips any results that throw exeptions." + "A non-lazy map that skips any results that throw exeptions other than SQL + serialization errors." [f items] - (reduce #(try (conj %1 (f %2)) (catch Exception e %1)) [] items)) + (reduce #(try (conj %1 (f %2)) + (catch SQLException e (if (serialization-error? e) (throw e) %1)) + (catch Exception e %1)) + [] + items))