2024 Clojure 状况调查! 中分享您的想法。

欢迎!有关如何使用此服务的更多信息,请查看 关于 页面。

+2
Clojure
已关闭

嗨,我在升级到 Clojure 1.11.0 时遇到了以下问题。在我们的应用程序中,我们使用 danlentz/clj-uuid 库创建了某些 V5(基于名称,SHA1 哈希)uuid,在升级后产生的 v5 uuid 不同。一个例子如下:

(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 fn 中,它将 keyword 对象转换为字节序列,现在似乎不同了。(如果我用字符串而不是关键字作为局部参数,则产生的 uuid 在 Clojure 升级前后是一致的。)
产生的 uuids 用在下游系统中,它们处理这种变化会很困难。我是否可以实现与之前完全相同的 uuid?

关闭的注释为:1.11.1 中修复

2 个答案

0

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

是的,同样的Java版本(openjdk版本 "17.0.2")
0

编辑了

在此记录从别处重提的对话 - 看起来,将关键词用作名称的局部部分会导致clj-uuid使用Java序列化将对象序列化为字节数据。

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

我们没有保证Clojure对象的二进制可序列化性在不同版本之间,因此此处期望这些对象是相同的这一假设是不正确的。如果需要,可以在clj-uuid中通过多种方式解决这个问题 - 通过为UUIDNameBytes协议中的关键词特别提供自定义序列化(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序列化。您可以永远使用这个漏洞,但这可能为您至少提供了一个迁移的途径。或者,如果您的关键字命名空间数量较少,您甚至可以将关键字到字节的映射硬编码。
非常感谢亚历克斯,我会试一下。出于好奇,为什么需要在1.11.0中没有改变的Symbol类中创建一个类呢?
关键字类有一个字段用来存储符号,所以序列化将包括这两个类。
明白了,谢谢。
by
大家好。是的,我确实希望为更广泛的对象(特别是关键字)提供稳定的字节数组转换的特殊情况。

关于“伪关键字/符号”类想法,它们是否可以以相同的方式序列化,而无需共享相同的(冲突的)类名?即使我们在 .writeObject 中序列化相同的内容,我认为我对 ObjectOutputStream 的阅读令人失望 — 它序列化“对象的类、类签名以及所有非瞬态字段的值”。我们可能也需要一个专门的 ObjectOutputStream,我想?

我一直在研究各种命名空间关键字的字节数组序列化。这可能有可能重建,但至少第一眼看起来,它不如直接插入关键字名称和命名空间的字节那么清晰。
by
是的,我认为它需要相同的类名。在类加载器中加载不同的类(即使是动态类加载器)是可能的,但这成了相当复杂的解决方案。
by
我找到了一种方法,但必须复制 clojure.lang.Keyword 类(正如你所说的,它确实需要具有完全相同的类名)并添加一个 serialVersionUID(其值等于之前版本类自动生成的内容)。

我开始尝试是否有不同的类名并覆盖它在序列化逻辑中作为“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版本现在可用 - 请进行测试并报告结果!
非常感谢,看起来不错!
...