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

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

+52
Clojure
已关闭

ART在Android中对字节码进行编译时验证,并对任何锁定宏的使用失败。错误看起来像CLJ-1829中看到的(在那个例子中,clojure.core.server/stop-server调用了锁定宏)

10-16 14:49:26.801 2008-2008/? E/AndroidRuntime: java.lang.VerifyError: 拒绝类 clojure.core.server$stop_server 因为它在编译时验证失败('clojure.core.server$stop_server'的声明出现在/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和try/finally块的组合创建了ART将其标记为未平衡monitorenter/monitorexit字节码的路径。特别是,monitorenter和monitorexit本身可以抛出异常(在null锁定对象上)。Java字节码对这些情况进行了一些棘手的异常表处理,这(afaict)在不修改Clojure编译器的情况下是不可能的。

方法:一种可能的办法是让锁定在Java的synchronized块上下文中调用传入的主体。这通过将其交给Java避免了棘手字节码的问题,但性能影响尚不清楚。

补丁: clj-1472-3.patch

另见:将字节码与Java synchronized块的对比显示了多个差异
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

审核人:Alex Miller - 我认为这是一个可行的解决方案,可以修复该问题,并且由于使用频率不高,我认为这不是一个真正关注性能问题。我认为另一种处理方法是使locking成为一个特殊形式,并具有编译器支持,但我不确定这是否值得做,所以我会把它留给Rich来决定。

已关闭,备注为:已修复

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字节码验证,围绕monitor exit的额外try/catch是可选的(有或没有它验证都通过),但在java版本中,monitor-exit会无限重试,并在正确的时间显示死锁,如果没有捕获monitor-exit的错误,未来可能会有未确定的monitor-enter失败,无法显示实际错误。

这看起来不是很美观,但在对生成字节码没有更精细控制的情况下,这是我最好的解决方案。

0

评论者:adamclements

刚刚用Lollipop测试了一下,这个补丁可能已经不再足够。

正在与ART团队联系,看看他们是否能提供更多线索,并验证它是否能在AOSP当前主分支上工作。

0
by

评论者:adamclements

在AOSP项目上提交了错误报告,希望他们能对是否是我们的问题提供一些见解,如果是的话,我们应该如何修复它。

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

0
by

评论者:adamclements

我已上传了锁定宏的替代实现(0001-CLJ-1472-Locking-macro-without-explicit-monitor-ente.patch),它稍微有点投机取巧——同步块实际上是在Java中实现的,从而保证了兼容性。这是以增加一点额外间接性和名称/位置可能更好的代价。

但这确实修复了错误,且能在所有版本的Android、Android + Art和JVM上运行。这种做法可以接受吗?

0
by

评论者:hiredman

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

0
by

评论者:hiredman

根据https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.11.10中的结构化锁定术语,(locking nil)可能生成违反结构化锁定的运行时行为的字节码。关于这个问题的第一个补丁可能使编译器在不同的方法中发出monitorenter/exit指令,这显然违反了结构化锁定。

0

评论者:adamclements

是的,第一个补丁肯定是不对的,我留着它是为了提供一些对话的上下文,但为了清晰起见,最好还是删除它。

对于关注此讨论但不想反汇编和观察字节码的人来说,这里有一个包含Javaynchronized块与Clojure锁定差异的摘要
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

虽然我可以看到Java版本的差异,但我很难找到与规范偏离的地方,但Clojure版本看起来比Java版本更接近规范描述的内容!

如果有人比我更了解这个话题,并且能参与AOSP错误
https://code.google.com/p/android/issues/detail?id=80823

,那么我们也许可以将这作为一个too focused在Java实现在我而不是JVM规范上的Android错误来解决,或者他们也许会发现Clojure实现中有错误。我已经上传了原始的Clojure行为并要求他们解释为什么它失败的原因。

0
by

评论者:adamclements

ART团队关于我们认为我们违反了什么的回应是

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

“(链接: ...)实现允许但不强制执行以下两个规则,以确保结构化锁定。(链接: ...)”
ART目前在验证时强制执行这两个规则,包括

“在方法调用期间,由T在M上执行的程度和由T在M上执行的程度不得超过方法调用中执行的监视器退出数。”

“在方法调用期间,由T在M上执行的程度和由T在M上执行的程度不得超过方法调用中执行的监视器退出数。”
如果指令
https://gist.github.com/AdamClements/2ae6c4919964b71eb470#file-test_locks-class-L24

或下一行的monitor-enter本身失败,它 couldn't it could end up in the finally clause and attempt to release the lock even though it has never been captured?
我认为这违反了你在链接的JVM规范中提到的结构化锁定规则。

0
by

评论者:adamclements

例如,如果
https://gist.github.com/AdamClements/2ae6c4919964b71eb470#file-test_locks-class-L24

或下一行的monitor-enter本身失败,它 couldn't it could end up in the finally clause and attempt to release the lock even though it has never been captured?

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

0
by

评论者:hiredman

0

评论者:adamclements

0

评论者:gshayban

据Marcus Lagergren说,JRockit和Hotspot都考虑到了锁释放在不同的方法中...也许Android有不同(错误的)理解?

...