taoensso / nippy

The fastest serialization library for Clojure
https://www.taoensso.com/nippy
Eclipse Public License 1.0
1.04k stars 60 forks source link

freezing and thawing Booleans in object-arrays #89

Closed aiba closed 7 years ago

aiba commented 7 years ago

Perhaps this is not the best thing to be doing in the first place, but I recently tracked a weird bug in our system down to freezing/thawing an object-array, and the thawed booleans behaving differently from the frozen ones.

(defn booly-str [x]
  (if x "truthy" "falsy"))

(let [a (object-array 1)]
    (aset a 0 false)
    (println (booly-str (aget a 0)))
    (let [b (-> a nippy/freeze nippy/thaw)]
      (println (booly-str (aget b 0)))))

This prints falsy followed by truthy. The thawed object-array value behaves differently from the frozen one.

Is this desirable behavior? If so, is there a recommended way to thaw object-arrays such that booleans behave as you'd expect?

ptaoussanis commented 7 years ago

Hi Aaron,

Sorry for the long delay getting back to you on this. Thanks for the clear minimal example - that was helpful.

So this is actually an interesting issue, and reduces to the following bit of unintuitive interop:

(if (java.lang.Boolean. false) :truthy :falsey) ; => :truthy

It gets weirder:

(type false) ; => java.lang.Boolean
(type (java.lang.Boolean. false)) ; => java.lang.Boolean
(if false :truthy :falsey) ; => :falsey
(if (java.lang.Boolean. false) :truthy :falsey) ; => :truthy

So what's going on? Here's a good explanation: http://stackoverflow.com/a/18682738

Obvious next question: if (Boolean. false) is such a sharp edge, why does Nippy use it? Actually, it doesn't.

(seq (nippy/freeze (object-array 1)))
=> 
(78 80 89 0 ; Nippy header
46 ; Nippy type id (identifies how your object array is being stored by Nippy)
 19 91 76 106 97 118 97 46 108 97 110 103 46 79 98 106 101 99 116 59 -84 -19 0 5 117 114 0 19 91 76 106 97 118 97 46 108 97 110 103 46 79 98 106 101 99 116 59 -112 -50 88 -97 16 115 41 108 2 0 0 120 112 0 0 0 1 112)

What's a 46 type? You can see here.

I.e. Nippy is freezing your object array using Java's own Serializable facility.

I.e. you're freezing an interop type (object-array), so getting an interop serialization that in this case happens not to play well with Clojure for [good but complicated] reasons.

Some options include:

So despite the sharp edge, this is unfortunately not something that I/Nippy can automatically take care of for you in a sensible/general way. When dealing with interop, you'll sometimes bang into little things like this that'll necessarily expose some of the messier lower-level details that Clojure normally shields you from.

Hope that helps / makes sense?

aiba commented 7 years ago

Wow, thank you for the detailed and thoughtful response! Yes, that all makes sense.

In our situation, using an object-array at runtime makes the most sense. By your suggestion, we are now calling vec before nippy/freeze, and then object-array after nippy/thaw. This solves it.

Thank you again!

(This issue can be closed as far as I'm concerned.)

ptaoussanis commented 7 years ago

No problem, cheers! :-)