mirror of
https://github.com/penpot/penpot.git
synced 2026-11-10 20:10:37 +00:00
* ✨ Cache the transit reader when decoding strings On the cljs side, we create a new transit reader on every call to decode-str. This turns out to have a fair bit overhead in both transit-cljs and transit-js, since it has to merge the handlers, call clj->js, allocate a new ReadCache, etc. By reusing it the measured decode time goes from ~58us -> ~41us, so about 1.4x faster. This particularly helps for some operations like whenever a component is edited and every object needs to be decoded, see https://github.com/penpot/penpot/issues/9344, although they could probably be made more efficient in other ways. The reader usually clears its read cache after a successful read, but whenever it throws it hangs around. So in this case we need to reset the cache ourselves, otherwise the next read will be from a clobbered cache. The tests were written by Claude Sonnet 5.5, and should cover the clobbering behaviour above. Signed-off-by: Luke Lau <luke@igalia.com> * Keep one transit reader per type and rebuild it on add-handlers! --------- Signed-off-by: Luke Lau <luke@igalia.com> Co-authored-by: Andrey Antukh <niwi@niwi.nz>
46 lines
1.9 KiB
Clojure
46 lines
1.9 KiB
Clojure
;; 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 SUBSIDIARY SL
|
|
|
|
(ns common-tests.transit-test
|
|
(:require
|
|
[app.common.transit :as tr]
|
|
[clojure.test :as t]))
|
|
|
|
;; A handler that always fails, to make a read throw after the reader has
|
|
;; already written to its read cache.
|
|
#?(:cljs
|
|
(tr/add-handlers!
|
|
{:id "test/throws"
|
|
:rfn (fn [_] (throw (js/Error. "invalid representation")))}))
|
|
|
|
(t/deftest decode-str-round-trip
|
|
(let [data {:a 1 :b [:c "d"] :e #{1 2}}]
|
|
(t/is (= data (-> data tr/encode-str tr/decode-str)))
|
|
(t/is (= data (-> data (tr/encode-str {:type :json-verbose})
|
|
(tr/decode-str {:type :json-verbose}))))))
|
|
|
|
;; `decode-str` reuses the readers across calls, but `add-handlers!` rebuilds
|
|
;; them, so a handler registered after the first read still applies.
|
|
#?(:cljs
|
|
(t/deftest decode-str-uses-handlers-added-after-the-first-read
|
|
(t/is (= [:x] (tr/decode-str "[\"~:x\"]")))
|
|
(tr/add-handlers! {:id "test/late" :rfn (fn [v] (str "late:" v))})
|
|
(t/is (= "late:x" (tr/decode-str "[\"~#test/late\",\"x\"]")))))
|
|
|
|
;; In ClojureScript `decode-str` reuses the same reader across calls, and a
|
|
;; read that throws leaves that reader's read cache dirty. The next message
|
|
;; would then resolve its cache codes ("^0", "^1") to entries of the failed
|
|
;; one if the cache is not cleared.
|
|
#?(:cljs
|
|
(t/deftest decode-str-after-a-failed-read
|
|
(let [good "[\"~:aaaa\",\"~:bbbb\",\"^0\",\"^1\"]"
|
|
bad "[\"~:cccc\",[\"~#test/throws\",1]]"
|
|
expected [:aaaa :bbbb :aaaa :bbbb]]
|
|
(t/is (= expected (tr/decode-str good)))
|
|
(t/is (thrown? js/Error (tr/decode-str bad)))
|
|
(t/is (= expected (tr/decode-str good)))
|
|
(t/is (= expected (tr/decode-str good))))))
|