2024 年 Clojure 状态调查中分享您的想法!

欢迎!请参阅关于页面以了解此工作的更多信息。

0
Clojure
通过这个测试,您可以看到我们无法序列化协议中的方法(即time-from-tweet),因为这会导致一个java.io.NotSerializableException:clojure.lang.MethodImplCache异常
 at java.io.ObjectOutputStream.writeObject0 (ObjectOutputStream.java:1183)
    java.io.ObjectOutputStream.defaultWriteFields (ObjectOutputStream.java:1547)
    java.io.ObjectOutputStream.writeSerialData (ObjectOutputStream.java:1508)
    java.io.ObjectOutputStream.writeOrdinaryObject (ObjectOutputStream.java:1431)
    java.io.ObjectOutputStream.writeObject0 (ObjectOutputStream.java:1177)
    java.io.ObjectOutputStream.writeObject (ObjectOutputStream.java:347)
    sparkling.protocol_test$serialize.invoke (protocol_test.clj:11)


这是实际的测试

(ns sparkling.protocol-test
  (:require [clojure.test :refer :all])
  (:import [java.io ObjectInputStream ByteArrayInputStream ObjectOutputStream ByteArrayOutputStream]))


(defn- serialize
  "序列化单个对象,返回一个字节数组。"
  [v]
  (with-open [bout (ByteArrayOutputStream.)
              oos (ObjectOutputStream. bout)]
    (.writeObject oos v)
    (.flush oos)
    (.toByteArray bout)))

(defn- deserialize
  "从给定的字节数组中反序列化并返回一个单个对象。"
  [bytes]
  (with-open [ois (-> bytes ByteArrayInputStream. ObjectInputStream.)]
    (.readObject ois)))


(defprotocol timestamped
  (time-from-tweet [item]))

(defrecord tweet [username tweet timestamp]
  timestamped
  (time-from-tweet [_]
    timestamp
    ))

(deftest sequable-serialization
  (testing "函数的序列化"
    (let [item identity]
      (is item (-> item serialize deserialize))))

  (testing "协议方法的序列化"
    (let [item time-from-tweet]
      (is item (-> item serialize deserialize)))))

6 答案

0

评论者:chrisbetz

顺便说一句,对于多方法而言,也是如此,这里的异常是 java.io.NotSerializableException: clojure.lang.MultiFn

0

评论者:alexmiller

我认为我们不应该期望函数以这种方式可序列化。协议和多方法实质上基于扩展它们的实现具有运行时状态。这些函数序列化意味着什么?你会使用当时已加载的实现进行序列化吗?或者不加载任何实现?这两者对我来说都似乎有问题。常规函数是闭包,可以捕获它们环境的状态。我认为更好的答案是AOT或者为常规函数引入类似serializable-fn库的解决方案。

0

评论者:chrisbetz

你好,

感谢评论。首先,有些背景信息:我正在开发Sparkling,这是一个Apache Spark的Clojure API。为了在集群中分发代码,它依赖于AOT编译的函数,因此,你不能简单地对任何函数进行序列化,它需要进行AOT编译。序列化提供了对当前绑定的支持等,一切如预期运行。所以,AFunction之所以可序列化,有一个原因,AFn/IFn的其他实现也是如此,一切运行良好。

关于协议和多方法的状态——我认为它与函数的状态(哪个函数定义,变量可能被多次绑定等)以及绑定中提供的闭包的概念上是相同的。我没有理由作为协议的用户认为协议中的方法与函数不同。事实上,(ifn? protocol-method)也返回true。

serializable-fn最初并不是为了网络传输序列化而设计的,它与序列化函数绑定中的函数集合以及由Spark在相同环境中反复创建同一函数而导致PermGen污染存在问题。

我认为我现在可以,因为我可以将协议方法封装在函数中,但我还相信这是一个错误。

顺祝商祺

Chris

0

评论者:chrisbetz

实际上,这是(链接:https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/AFunction.java 文本:clojure.lang.AFunction)中的代码片段引起的痛苦

`
public abstract class AFunction extends AFn implements IObj, Comparator, Fn, Serializable {

public volatile MethodImplCache __methodImplCache;
`

AFunction 是可序列化的,但 MethodImplCache 不是。我不确定仅将其标记为 transient 是否足够,因为我没有检查初始化发生在哪里。

0
发件人

评论者:chrisbetz

我的邮件评论在 SMTP-Nirvana 中丢失了:有一个简单的解决方案。将协议方法包装在一个函数中,这将解决问题,但会使得你的代码更加糟糕;)

0
发件人
参考:https://clojure.atlassian.net/browse/CLJ-1701 (记录人:alex+import)
...