请在2024年Clojure状态调查!中分享您的看法。

欢迎!请参阅关于页面以获取有关如何使用此功能的更多信息。

+52
Clojure
已关闭

Android ART在字节码上运行编译时验证,并在锁定宏的任何使用上失败。错误看起来像CLJ-1829中看到的那样(在这种情况下,clojure.core.server/stop-server调用锁定宏)

10-16 14:49:26.801 2008-2008/? E/AndroidRuntime: java.lang.VerifyError: Rejecting class clojure.core.server$stop_server because it failed compile-time verification (declaration of 'clojure.core.server$stop_server' appears in /data/app/com.clojure_on_android-1/base.apk)

原因:根据Android问题讨论(https://code.google.com/p/android/issues/detail?id=80823),这可能是由于JVM规范中对“结构化锁定”条款的更严格执行(https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.11.10)。

看起来,monitor-enter、monitor-exit与 locking 中的 try/finally 块的组合创建了ART标识为未平衡 monitorenter/monitorexit 字节码的路径。特别是,monitorenter和monitorexit自身可以抛出(在null锁定对象上)。Java字节码对这些情况进行了一些复杂的异常表处理以覆盖这些情况,这在afaict中无法在不修改Clojure编译器的情况下完成。

方法:一种可能的方法是让锁定在Java的synchronized块上下文中调用传入的body。这避免了通过交给Java处理来处理复杂的字节码问题,但可能对性能产生影响尚不清楚。

补丁: clj-1472-3.patch

参阅:将字节码与java synchronized块进行比较可以发现许多差异
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

审查: Alex Miller - 我认为这是一个可行的解决方案,可以修复问题,并且由于它的使用频率不高,我不会太担心这将成为性能问题。我认为另一种处理方法是使 locking 成为具有编译器支持的特形式,但我不确定这是否值得做,因此我将由Rich决定。

以“fixed”(已修复)的备注关闭

41 条回答

0
 
最佳答案

此问题的修复已随1.10.2-alpha1版发布。

0

评论者:adamclements

在使用一段时间后,我发现将此(代码)移出try块会导致nREPL崩溃。

查看字节码,clojure.tools.nrepl.middleware.session/session-out中的锁定monitorenter和几处其他地方最终进入了完全不同的方法定义,我们现在得到了JVM的IllegalMonitorStateException以及ART验证错误。

0

评论者:jafingerhut

Adam,我无法评论您的补丁是否有兴趣,但确实如此,如果作者没有签署贡献者协议,任何补丁都不能提交到Clojure,现在您可以在https://clojure.org/contributing上在线签署

0

评论者:adamclements

上传了新的补丁(并签署了贡献者协议)。此补丁通过了JVM和ART字节码验证。围绕监视器退出的额外try/catch是可选的(无论有无均通过验证),但在Java版本中重新尝试监视器退出将通过无限期重试并在适当的时间显示死锁,而不是捕获监视器退出中的错误,在未来某个不确定的时间点可能会失败,实际上没有显示出真正的问题。

这看起来不是很好,但没有更细粒度的对生成的字节码的控制,这是我能够做的最好的。

0

评论者:adamclements

刚刚用Lollipop测试,这个补丁可能不再足够。

联系ART团队,看看他们是否能提供更多信息,并验证是否可以在AOSP的当前master分支上工作。

0

评论者:adamclements

在AOSP项目中提交了bug报告,希望能在这方面为解决问题指明方向。

https://code.google.com/p/android/issues/detail?id=80823

0

评论者:adamclements

我已上传了锁定宏的替代实现(0001-CLJ-1472-Locking-macro-without-explicit-monitor-ente.patch),它稍微作弊了一点点——synchronized块实际上是使用Java实现的,因此可以保证兼容性。这是以增加一点额外间接性为代价的,而命名/位置可能更好。

但这确实修复了bug,且在所有版本的android、android + art和jvm上都能工作。这种做法是否可以接受?

0

评论者:hiredman

我还没有看到任何证据表明Clojure生成的字节码以某种方式违反了JVM规范,所以我怀疑clojure需要JVM运行,而Android不提供JVM,只是如果你不偏离正轨,有一种看起来像JVM的东西。

0

评论者:hiredman

考虑到《大Java虚拟机规范》中关于结构化锁语言的内容

0

评论者:adamclements

是的,第一个补丁确实是错误的,我留下它来提供一些对话的背景,但最好还是为了清晰起见移除它。

对于希望避开反编译和观察字节码的对话跟踪者,这里有Java同步块与Clojure locking之间的差异的gist,链接:https://gist.github.com/AdamClements/2ae6c4919964b71eb470

我发现难以确定从规范中偏离的地方,虽然我能看到与Java版本之间的差异,但如果有什么的话,Clojure版本看起来比Java版本更接近规范中描述的内容!

如果有人比我懂得更多的专业知识,可以参与AOSP的bug https://code.google.com/p/android/issues/detail?id=80823,或许我们可以将其作为一个Android bug来解决问题,这个问题过于侧重于Java实现而不是JVM规范,或者他们可能会发现Clojure实现中的某些问题。我已经上传了原始的Clojure行为,并请他们解释更多为什么它失败了。

0

评论者:adamclements

ART团队关于他们认为我们违反的内容的回应是

"结构化锁定"部分包含以下内容

"(link: ...)实现允许但不要求强制执行以下两个规则,这两个规则保证了结构化锁定。(link: ...)"
ART当前在验证时间强制执行这两个规则,包括

"在方法调用期间,T在M上执行的监视器退出数不得超过T在M上执行的监视器进入数。"

(包含原始英文内容,此处无法提供翻译)
(包含原始英文内容,此处无法提供翻译)
(包含原始英文内容,此处无法提供翻译)

0

评论者:adamclements

例如,如果指令 https://gist.github.com/AdamClements/2ae6c4919964b71eb470#file-test_locks-class-L24 或下一行的monitor-enter本身失败,它会不会最终进入finally子句并尝试释放锁,即使它从未被捕获?

我认为这违反了链接的JVM规范中的结构化锁定规则。

0

评论者:hiredman

0

评论者:adamclements

是的,我不应该将它与修补版本混淆。现在 gist 和当前上传的版本都使用纯 clojure 的锁定宏。

我认为问题来自异常表和覆盖的指令。如果第 24 行可以抛出异常,那么在运行时你会遇到 monitor-exit,但从未遇到 monitor-enter。

0

评论者:gshayban

根据 Marcus Lagergren 的说法,JRockit 和 Hotspot 都考虑了锁释放位于不同的方法中……也许 Android 有不同的(错误的)解释呢?

...