From ba6477c0976aa545699b3f9bcfbe355ce527bd41 Mon Sep 17 00:00:00 2001 From: Peter Taoussanis Date: Tue, 5 Dec 2023 10:51:13 +0100 Subject: [PATCH] [nop] Refactor type-checking, remove vestigial utils ns --- src/taoensso/nippy/impl.clj | 79 +++++++++---------- src/taoensso/nippy/utils.clj | 144 ----------------------------------- 2 files changed, 38 insertions(+), 185 deletions(-) delete mode 100644 src/taoensso/nippy/utils.clj diff --git a/src/taoensso/nippy/impl.clj b/src/taoensso/nippy/impl.clj index 2f6e08f..25d388e 100644 --- a/src/taoensso/nippy/impl.clj +++ b/src/taoensso/nippy/impl.clj @@ -6,54 +6,51 @@ ;;;; Fallback type tests -(defn- memoize-type-test - "Unfortunately the only ~reliable way we can tell if something's - really serializable/readable is to actually try a full roundtrip." - [test-fn] - (let [cache_ (enc/latom {})] ; { } +(defn cache-by-type [f] + (let [cache_ (enc/latom {})] ; { } (fn [x] - (let [t (type x) - gensym? (re-find #"__\d+" (str t)) - cacheable? (not gensym?) ; Hack, but no obviously better solutions - test (fn [] (try (test-fn x) (catch Exception _ false)))] + (let [t (if (fn? x) ::fn (type x))] + (if-let [result_ (get (cache_) t)] + @result_ + (if-let [uncacheable-type? (re-find #"\d" (str t))] + (do (f x)) + @(cache_ t #(or % (delay (f x)))))))))) - (if cacheable? - @(cache_ t #(if % % (delay (test)))) - (do (test))))))) +(def seems-readable? + (cache-by-type + (fn [x] + (try + (enc/read-edn (enc/pr-edn x)) + true + (catch Throwable _ false))))) -(def seems-readable? (memoize-type-test (fn [x] (-> x enc/pr-edn enc/read-edn) true))) (def seems-serializable? - (let [mtt - (memoize-type-test - (fn [x] - (let [class-name (.getName (class x)) - c (Class/forName class-name) ; Try 1st (fail fast) - bas (java.io.ByteArrayOutputStream.) - _ (.writeObject (java.io.ObjectOutputStream. bas) x) - ba (.toByteArray bas)] - - #_ - (cast c - (.readObject ; Unsafe + usu. unnecessary to check - (ObjectInputStream. - (ByteArrayInputStream. ba)))) - - true)))] - + (cache-by-type (fn [x] - (if (instance? java.io.Serializable x) - (if (fn? x) - false ; Reports as true but is unreliable - (mtt x)) - false)))) + (enc/cond + (fn? x) false ; Falsely reports as Serializable + + (instance? java.io.Serializable x) + (try + (let [c (Class/forName (.getName (class x))) ; Try 1st (fail fast) + bas (java.io.ByteArrayOutputStream.) + _ (.writeObject (java.io.ObjectOutputStream. bas) x) + ba (.toByteArray bas)] + #_ + (cast c + (.readObject ; Unsafe + usu. unnecessary to check + (ObjectInputStream. (ByteArrayInputStream. ba)))) + true) + (catch Throwable _ false)) + + :else false)))) (comment - (enc/qb 1e4 ; [2.52 2.53 521.34 0.63] - (seems-readable? "Hello world") ; Cacheable - (seems-serializable? "Hello world") ; Cacheable - (seems-readable? (fn [])) ; Uncacheable - (seems-serializable? (fn [])) ; Uncacheable - )) + (enc/qb 1e6 ; [60.83 61.16 59.86 57.37] + (seems-readable? "Hello world") + (seems-serializable? "Hello world") + (seems-readable? (fn [])) + (seems-serializable? (fn [])))) ;;;; Java Serializable diff --git a/src/taoensso/nippy/utils.clj b/src/taoensso/nippy/utils.clj deleted file mode 100644 index 4f03608..0000000 --- a/src/taoensso/nippy/utils.clj +++ /dev/null @@ -1,144 +0,0 @@ -(ns ^:no-doc taoensso.nippy.utils - "Private, implementation detail." - (:require [clojure.string :as str] - [taoensso.encore :as enc]) - (:import [java.io ByteArrayInputStream ByteArrayOutputStream Serializable - ObjectOutputStream ObjectInputStream])) - -;;;; Fallback type tests -;; Unfortunately the only ~reliable way we can tell if something's -;; really serializable/readable is to actually try a full roundtrip. - -(defn- memoize-type-test [test-fn] - (let [cache_ (enc/latom {})] ; { } - (fn [x] - (let [t (type x) - gensym? (re-find #"__\d+" (str t)) - cacheable? (not gensym?) ; Hack, but no obviously better solutions - test (fn [] (try (test-fn x) (catch Exception _ false)))] - - (if cacheable? - @(cache_ t #(if % % (delay (test)))) - (do (test))))))) - -(def readable? (memoize-type-test (fn [x] (-> x enc/pr-edn enc/read-edn) true))) -(def serializable? - (let [mtt - (memoize-type-test - (fn [x] - (let [class-name (.getName (class x)) - c (Class/forName class-name) ; Try 1st (fail fast) - bas (ByteArrayOutputStream.) - _ (.writeObject (ObjectOutputStream. bas) x) - ba (.toByteArray bas)] - - #_ - (cast c - (.readObject ; Unsafe + usu. unnecessary to check - (ObjectInputStream. - (ByteArrayInputStream. ba)))) - - true)))] - - (fn [x] - (if (instance? Serializable x) - (if (fn? x) - false ; Reports as true but is unreliable - (mtt x)) - false)))) - -(comment - (enc/qb 1e4 - (readable? "Hello world") ; Cacheable - (serializable? "Hello world") ; Cacheable - (readable? (fn [])) ; Uncacheable - (serializable? (fn [])) ; Uncacheable - )) ; [2.52 2.53 521.34 0.63] - -;;;; - -(defn- is-coll? - "Checks for explicit `IPersistentCollection` types with Nippy support. - Tedious but preferable since a `freezable?` false positive would be much - worse than a false negative." - [x] - (let [is? #(when (instance? % x) %)] - (or - (is? clojure.lang.APersistentVector) - (is? clojure.lang.APersistentMap) - (is? clojure.lang.APersistentSet) - (is? clojure.lang.PersistentList) - (is? clojure.lang.PersistentList$EmptyList) ; (type '()) - (is? clojure.lang.PersistentQueue) - (is? clojure.lang.PersistentTreeSet) - (is? clojure.lang.PersistentTreeMap) - (is? clojure.lang.PersistentVector$ChunkedSeq) - - (is? clojure.lang.IRecord) ; TODO Possible to avoid the interface check? - (is? clojure.lang.LazySeq) - - ;; Too non-specific: could result in false positives (which would be a - ;; serious problem here): - ;; (is? clojure.lang.ISeq) - - ))) - -(comment (is-coll? (clojure.lang.PersistentVector$ChunkedSeq. [1 2 3] 0 0))) - -(defmacro ^:private is? [x c] `(when (instance? ~c ~x) ~c)) - -(defn freezable? - "Alpha - subject to change. - Returns truthy iff Nippy *appears* to support freezing the given argument. - - `:allow-clojure-reader?` and `:allow-java-serializable?` options may be - used to enable the relevant roundtrip fallback test(s). These tests are - only **moderately reliable** since they're cached by arg type and don't - test for pre/post serialization value equality (there's no good general - way of doing so)." - - ;; TODO Not happy with this approach in general, could do with a refactor. - ;; Maybe return true/false/nil (nil => maybe)? - - ([x] (freezable? x nil)) - ([x {:keys [allow-clojure-reader? allow-java-serializable?]}] - (if (is-coll? x) - (when (enc/revery? freezable? x) (type x)) - (or - (is? x clojure.lang.Keyword) - (is? x java.lang.String) - (is? x java.lang.Long) - (is? x java.lang.Double) - (nil? x) - - (is? x clojure.lang.BigInt) - (is? x clojure.lang.Ratio) - - (is? x java.lang.Boolean) - (is? x java.lang.Integer) - (is? x java.lang.Short) - (is? x java.lang.Byte) - (is? x java.lang.Character) - (is? x java.math.BigInteger) - (is? x java.math.BigDecimal) - (is? x #=(java.lang.Class/forName "[B")) - - (is? x clojure.lang.Symbol) - - (is? x java.util.Date) - (is? x java.util.UUID) - (is? x java.util.regex.Pattern) - - (when (and allow-clojure-reader? (readable? x)) :clojure-reader) - (when (and allow-java-serializable? (serializable? x)) :java-serializable))))) - -(comment - (enc/qb 10000 (freezable? "hello")) ; 0.79 - (freezable? [:a :b]) - (freezable? [:a (fn [x] (* x x))]) - (freezable? (.getBytes "foo")) - (freezable? (java.util.Date.) {:allow-clojure-reader? true}) - (freezable? (Exception. "_") {:allow-clojure-reader? true}) - (freezable? (Exception. "_") {:allow-java-serializable? true}) - (freezable? (atom {}) {:allow-clojure-reader? true - :allow-java-serializable? true}))