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 函数中,它将关键字对象转换为字节数组,这现在似乎不同了。(如果我用字符串而不是关键字作为局部参数,那么生成的 uuid 在 Clojure 升级前后是一致的。)
生成的 uuid 用于下游系统,处理这种变化将非常困难。我能以完全相同的方式生成之前的 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),或者依靠将字符串序列化后再转换为字节,而不是通过对象流进行二进制序列化等。

也许我们可以通过将clojure.lang.Keyword的serialversionUID设置为在1.10中的值来“修复”此特定情况(因为这些对象可能是二进制兼容的),但即使这样,我也建议使用更稳定的各种字符串来作为clj-uuid本地名称部分。

感谢Alex,很有道理。我将在clj-uuid上提出一个问题,看看是否有进一步的方法。
可能通过创建类似的类和符号类(相同的非瞬态字段)并在这两个类中设置serialVersionUID为1.10版本,来解决您特定的错误问题。我相信您随后可以稳定地复制已序列化的字节。您可以覆盖clj-uuid的协议以使用这些类作为关键字序列化。您将永远陷入这个修补漏洞中,但这可能至少为您提供一个迁移途径。或者,如果您有少量关键字命名空间,甚至可以硬编码关键字到字节的映射。
感谢Alex,我会试试。出于好奇,为什么如果Symbol类在1.11.0版本中没有被更改,还需要创建它呢?
关键字类有一个字段保存了一个符号,因此序列化将包括这两个类。
明白了,谢谢。
大家好。是的,我确实希望我为更广泛的对象(尤其是关键字)提供一个稳定的字节数组转换的特殊情况。

为了更好地理解“伪关键字/符号”类想法,这些是否可以以相同的方式序列化而不共享相同的(冲突的)类名?即使我们以相同的方式序列化(.writeObject),我对于ObjectOutputStream的读取似乎有些失望——它序列化“对象的类、类签名以及所有非transient字段的值”。我想我们是否也需要一个特殊的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的serialVersionUID恢复到1.10.3版本。以防这有助于决定延后升级路径。
关于uuid的延后升级路径,有什么看法权衡最佳处理方法?强制使用不必要的(但稳定的)序列化作为可选方案?或作为默认选项?
我认为没有好的标准做法来处理这个问题,除了在库中添加兼容性说明。你可以利用版本条件语句,但这些选项都相当粗糙,且会影响性能。
Clojure 1.11.1-rc1已发布,请进行测试并反馈结果!
非常感谢,看起来很好!
...