java.net.PlainSocketImpl.finalize() timed out after 10 seconds

java.util.concurrent.TimeoutException: java.net.PlainSocketImpl.finalize() timed out after 10 seconds
at libcore.io.Posix.close(Native Method)
at libcore.io.BlockGuardOs.close( BlockGuardOs.java:101)
at libcore.io.IoBridge.closeAndSignalBlockedThreads( IoBridge.java:207)
at java.net.PlainSocketImpl.close( PlainSocketImpl.java:148)
at java.net.PlainSocketImpl.finalize( PlainSocketImpl.java:206)
at java.lang.Daemons$FinalizerDaemon.doFinalize( Daemons.java:190)
at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:173)
at java.lang.Thread.run(Thread.java:818)

この例外、コールスタックからだとFinalizerDaemon.run() を動かしているスレッドから投げられたように見える。しかし https://android.googlesource.com/platform/libcore/+/master/libart/src/main/java/java/lang/Daemons.java を確認すると、実際にはこの例外はFinalizerDaemonからではなく java.lang.Daemons$FinalizerWatchdogDaemon#finalizerTimedOut()で生成されてThread.getDefaultUncaughtExceptionHandler().uncaughtException(...) に渡される。一見だとそう見えないのは、この例外のコールスタック情報はFinalizerDaemonのthread.getStackTrace()のもので上書きされているからだ。

FinalizerWatchdogDaemonはこの例外を uncaughtException()に渡した後は終了してしまう。

FinalizerDaemonはおそらくPosix.close(Native Method) でブロックしているが、これが一定時間内に復帰する保証はない。

アプリ側であらかじめソケットをクローズしておけばいいのかもしれないが、そのソケットというのはHttpUrlConnectionが内部に持つコネクションプールのものだ。このコネクションプールを明示的に制御する方法はよくわからない。

そもそも、10秒タイムアウトでアプリを落とすというのは、日本のモバイルネットワークだとやや短すぎるのではないかと思う。かといってFinalizerWatchdogDaemonのタイムアウト定数をアプリ側から変更できる仕組みは用意されていない。

対策としては、Thread.setDefaultUncaughtExceptionHandler() で設定したハンドラでこの種のエラーを検出したら何もせずにreturnすれば良いかもしれない。FinalizerDaemonがブロックし続けた場合はまた別のエラーがどこかで発生するだろう。