请在2024 Clojure 状态调查!分享您的想法。

欢迎!请参阅关于页面,了解更多关于这个平台如何运作的信息。

+2
Clojure
已关闭

嘿,我在升级到 Clojure 1.11.0 时遇到了以下问题。在我们的应用程序中,我们使用 danlentz/clj-uuid 库创建了 V5(基于名称,SHA1 哈希)uuids,但在升级后,生成的 v5 uuids 有所不同。例如

(def ^:const +namespace+ #uuid "50d94d91-a1cf-422d-9586-4ddacf6df176")

(clj-uuid/v5 +namespace+ :some-keyword) 

;; Clojure 1.10.3
=> #uuid "d30e9c3c-ced2-534e-a6b8-ecf784fb0785"

;; Clojure 1.11.0
=> #uuid "a16f6719-952a-55b9-b71b-b15dd263665b"

经过一些试验和错误,我发现差异似乎是由于局部参数(即::some-keyword)引起的,因为在 clj-uuid/v5 函数中,它将关键字对象转换为字节数组,现在看起来似乎不同。(如果我用字符串而不是关键字作为局部参数,那么生成的 uuid 在 Clojure 升级前后是一致的。)
生成的 uuids 会在下游系统中使用,而且要让他们处理这种变化相当困难。我能否实现与前完全相同的 previous uuids?

在注释中关闭: 1.11.1 中修复

2 答案

0

你好。谢谢你的报告。为了澄清 —— 测试之间使用的 Java 版本是一样的,对吗?

是的,Java版本(openjdk版本“17.0.2”)相同。
0

编辑

在此记录下从其他地方重提的对话 - 使用key词作为名称的局部部分似乎会导致clj-uuid使用Java序列化将对象序列化为字节。

Clojure 1.11在Keyword(以改进arity异常报告)中做了一些向后兼容的增量更改,因此关键词的二进制序列化在1.10和1.11之间发生了变化。

我们不保证Clojure对象在不同版本之间的二进制可序列化性,因此这里预期这些将是相同的期望是不正确的。在clj-uuid中,可以通过几种方式处理此特性 - 通过在UUIDNameBytes协议中为Keyword提供自定义序列化(https://github.com/danlentz/clj-uuid/blob/master/src/clj_uuid.clj#L557),或者通过依赖pr将字符串转换为字节,而不是将二进制序列化到对象流中等。

我们有可能通过将clojure.lang.Keyword的serialversionUID设置为1.10中的值来解决这个具体案例(因为这些对象可能是二进制兼容的),我们将进一步考虑这一点。但即便如此,我仍然建议为clj-uuid本地名称部分使用更稳定的东西(例如字符串)。

谢谢Alex,说得有道理。我将在clj-uuid上提出一个问题,看看在那里是否有前进的道路。
可能通过创建类似Keyword类和类似Symbol类的Java类(相同的非瞬态字段)并在这两个类中设置serialVersionUID为1.10的serialvers来修复您特定的错误。我认为您之后可以(稳定地)复制序列化的字节。然后可以在clj-uuid中重写协议以使用这些伪类进行Keyword序列化。您将永远无法摆脱这个黑客行为,但这可能会为您至少提供一条迁移路径。或者,如果您有少量关键字命名空间,甚至可以硬编码关键字到字节的映射。
谢谢Alex,我将试试看。顺便问一下,如果在1.11.0中没有改变,为什么不创建Symbol类呢?
Keyword类有一个包含Symbol字段,所以序列化将包括这两个类。
明白了,谢谢
大家好。是的,我确实希望为更广泛的对象,特别是关键字,专门编写稳定字节数组转换。

为了更好地理解“伪关键字/符号”类的想法,这些是否可以以相同的方式序列化而不共享相同的(冲突的)类名?即使我们在ObjectOutputStream中(见https://docs.oracle.com/javase/7/docs/api/java/io/ObjectOutputStream.html的文档)以相同的方式序列化(.writeObject),但我的看法似乎很失望——它序列化“对象类、类签名以及所有非瞬态字段的值”。我相信我们需要专门的ObjectOutputStream?

我一直在研究各种命名空间关键字的字节数组序列化。这可能是有可能的,但至少从第一眼看来,它并不像直接丢入关键字名称和命名空间的字节数那样清晰。
是的,我认为它需要相同的类名。在类加载器中(甚至在动态类加载器中)加载替代类是可能的,但这将成为一个相当复杂的解决方案。
我已经找出了一套方法,但不得不复制clojure.lang.Keyword类(正如你所提到的,这确实需要与该类相同的名称),并且添加了一个serialVersionUID(其值与1.10.0版本中自动生成的类值相等)。

我开始尝试让它有一个不同的类名,然后在序列化逻辑中将其重写为“clojure.lang.Keyword”,这样定制的类就只用于uuid生成/不会产生冲突——但运气不太好。例如,这个https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/io/ObjectStreamClass.java#L708就是ObjectStreamClass中写入名称的位置
……另外,ObjectOutputStream上还有一个.writeObjectOverride方法……但重新实现默认的writeObject并不很有吸引力。
顺便说一下,我认为我们将进行1.11.1版本的更新,并将Keyword和ArraySeq的serialVersionUIDs回退到1.10.3版本。以防这有助于决定延迟升级的路径。
关于uuid的延迟升级路径,大家有什么意见吗?强制一个不兼容但稳定的序列化为可选的?默认设置为这种?
我认为没有好的标准方法来处理这个问题,除了在库中添加兼容性注释。你可以使用版本条件,但是所有这些选项都相当糟糕,会影响性能。
Clojure 1.11.1-rc1现在可用 - 请测试并反馈!
非常感谢,看起来不错!
...