Полное название / сводка:
OverlappingFileLockException
при использовании базы данных H2 в автономном приложении Clojure / JVM (встроенный сервер httpkit), работающем в контейнере Docker со службой приложений Azure в Linux и Persisted SharedХранилище
Intro
Примечание: Первоначально я подал это как проблему в базу данных H2 github repo , но я публикую ее здесь после сбора более подробной информациив надежде, что я смогу получить больше советов о том, как действовать.
Я устранял проблемы, связанные с ненадлежащим OverlappingFileLockException
выбросом драйвера JDBC H2 при запуске нашего приложения со службой приложений Azure в Linux.
Мы используем H2 в режиме «внедренного файла» с DB_CLOSE_DELAY=0
(по умолчанию: новая транзакция должна открывать совершенно новое соединение БД, потому что старое уже закрыто) и MULTI_THREADED=1
;В настоящее время мы проводим тестирование с последней стабильной версией 1.4.199
.
Мы получили отчеты от двух независимых пользователей, работающих с очень похожей установкой Azure. Мы не получали такого отчета от других пользователей, работающих с этим приложением.
Подробности Env
- Версия встроенной базы данных H2: 1.4.199
- Служба приложений Azureс числом экземпляров, ограниченным 1, и WEBSITES_ENABLE_APP_SERVICE_STORAGE = true
- Образ докера: ubuntu: 18.10
java -version
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment (build 11.0.1+13-Ubuntu-3ubuntu3.18.10.1)
OpenJDK 64-Bit Server VM (build 11.0.1+13-Ubuntu-3ubuntu3.18.10.1, mixed mode, sharing)
У меня нет доступа ни к хост-компьютеру, ни к общему ресурсу Azure Files, только к SSH-доступу к работающему контейнеру Docker.
Stacktrace
2019-10-10T06:43:08.432104251Z 2019-10-10 06:43:08.426 07058ebde3f2 pool-23-thread-2 ERROR [<some-backgroun-thread-reading-from-DB>:158] - Failed to do xxx.
2019-10-10T06:43:08.432142451Z [37mjava.lang.Thread.run[m [32m Thread.java: 834[m
2019-10-10T06:43:08.432149151Z [37mjava.util.concurrent.ThreadPoolExecutor$Worker.run[m [32m ThreadPoolExecutor.java: 628[m
2019-10-10T06:43:08.432153651Z [37mjava.util.concurrent.ThreadPoolExecutor.runWorker[m [32m ThreadPoolExecutor.java: 1128[m
2019-10-10T06:43:08.432158251Z [37mjava.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run[m [32mScheduledThreadPoolExecutor.java: 305[m
2019-10-10T06:43:08.432162751Z [37mjava.util.concurrent.FutureTask.runAndReset[m [32m FutureTask.java: 305[m
2019-10-10T06:43:08.432167151Z [37mjava.util.concurrent.Executors$RunnableAdapter.call[m [32m Executors.java: 515[m
... some app logic ...
2019-10-10T06:43:08.432210251Z [33mclojure.java.jdbc/[1;33mdb-query-with-resultset[m [32m jdbc.clj: 848[m
2019-10-10T06:43:08.432215551Z [33mclojure.java.jdbc/[1;33mget-connection[m [32m jdbc.clj: 272[m
2019-10-10T06:43:08.432226751Z [37mjava.sql.DriverManager.getConnection[m [32m DriverManager.java: 189[m
2019-10-10T06:43:08.432230951Z [37mjava.sql.DriverManager.getConnection[m [32m DriverManager.java: 677[m
2019-10-10T06:43:08.432234651Z [37morg.h2.Driver.connect[m [32m Driver.java: 69[m
2019-10-10T06:43:08.432238451Z [37morg.h2.jdbc.JdbcConnection.<init>[m [32m JdbcConnection.java: 148[m
2019-10-10T06:43:08.432242451Z [37morg.h2.jdbc.JdbcConnection.<init>[m [32m JdbcConnection.java: 169[m
2019-10-10T06:43:08.432246151Z [37morg.h2.engine.SessionRemote.connectEmbeddedOrServer[m [32m SessionRemote.java: 336[m
2019-10-10T06:43:08.432249951Z [37morg.h2.engine.Engine.createSession[m [32m Engine.java: 31[m
2019-10-10T06:43:08.432253851Z [37morg.h2.engine.Engine.createSession[m [32m Engine.java: 161[m
2019-10-10T06:43:08.432257751Z [37morg.h2.engine.Engine.createSessionAndValidate[m [32m Engine.java: 178[m
2019-10-10T06:43:08.432282951Z [37morg.h2.engine.Engine.openSession[m [32m Engine.java: 201[m
2019-10-10T06:43:08.432286851Z [37morg.h2.engine.Engine.openSession[m [32m Engine.java: 69[m
2019-10-10T06:43:08.432290851Z [37morg.h2.engine.Database.<init>[m [32m Database.java: 313[m
2019-10-10T06:43:08.432295251Z [37morg.h2.engine.Database.openDatabase[m [32m Database.java: 319[m
2019-10-10T06:43:08.432299351Z [37morg.h2.engine.Database.open[m [32m Database.java: 769[m
2019-10-10T06:43:08.432303351Z [37morg.h2.engine.Database.getPageStore[m [32m Database.java: 2739[m
2019-10-10T06:43:08.432307151Z [37morg.h2.mvstore.db.MVTableEngine.init[m [32m MVTableEngine.java: 95[m
2019-10-10T06:43:08.432311151Z [37morg.h2.mvstore.db.MVTableEngine$Store.open[m [32m MVTableEngine.java: 162[m
2019-10-10T06:43:08.432315051Z [37morg.h2.mvstore.MVStore$Builder.open[m [32m MVStore.java: 3343[m
2019-10-10T06:43:08.432321351Z [37morg.h2.mvstore.MVStore.<init>[m [32m MVStore.java: 390[m
2019-10-10T06:43:08.432325351Z [37morg.h2.mvstore.FileStore.open[m [32m FileStore.java: 163[m
2019-10-10T06:43:08.432329451Z [37mjava.nio.channels.FileChannel.tryLock[m [32m FileChannel.java: 1165[m
2019-10-10T06:43:08.432333251Z [37morg.h2.store.fs.FileNio.tryLock[m [32m FilePathNio.java: 121[m
2019-10-10T06:43:08.432337151Z [37msun.nio.ch.FileChannelImpl.tryLock[m [32m FileChannelImpl.java: 1154[m
2019-10-10T06:43:08.432340951Z [37msun.nio.ch.FileLockTable.add[m [32m FileLockTable.java: 123[m
2019-10-10T06:43:08.432344851Z [37msun.nio.ch.FileLockTable.checkList[m [32m FileLockTable.java: 229[m
2019-10-10T06:43:08.432348851Z [1;31mjava.nio.channels.OverlappingFileLockException[m: [3m[m
2019-10-10T06:43:08.432352651Z [1;31mjava.lang.IllegalStateException[m: [3mThe file is locked: nio:/configuration/myapp.mv.db [1.4.199/7][m
2019-10-10T06:43:08.432356951Z [1;31morg.h2.jdbc.JdbcSQLNonTransientConnectionException[m: [3mDatabase may be already in use: null. Possible solutions: close all other connection(s); use the server mode [90020-199][m
2019-10-10T06:43:08.432361151Z [1mSQLState[m: "90020"
2019-10-10T06:43:08.432364951Z [1merrorCode[m: 90020
2019-10-10T06:43:08.432368651Z [1moriginalMessage[m: "Database may be already in use: null. Possible solutions: close all other connection(s); use the server mode"
2019-10-10T06:43:08.432372751Z
Когда возникает такая ситуация, я даже не могу сделать копию файла db - я полагаю, это потому, что блокировка файлов работает в SMB / Windows:
root@07058ebde3f2:/configuration# cp my-db.mv.db tmp.bak
cp: error reading 'my-db.mv.db': Permission denied
По документации тИсключение выдается, когда JVM уже удерживает блокировку. Кажется, по какой-то причине эта блокировка не снимается экземпляром org.h2.engine.Database
, что должно быть сделано в Database # closeImpl -> ... -> org.h2.mvstore.FileStore #закройте , если правильно понимаете исходный код. Это, в свою очередь, удаляет блокировку, сохраненную в FileLockTable.lockMap
(которая была получена ранее путем вызова FileChannel#tryLock
) [1].
lockMap
является статическим (JVM-широким) полем, хранящим блокировку внутри WeakReference
. Это соответствует моим наблюдениям о том, что блокировка действительно снимается, когда выполняется цикл Full GC
(например, через jcmd
), который заставляет JVM собирать мусор для любых ожидающих WeakReference-s => приложение снова начинает работать должным образом, пока проблема не возникнет снова.
Связь с циклами GC
Как я уже упоминал ранее, проблема, кажется, исчезает, когда я запускаю Full GC. Я взял и проанализировал дамп кучи примерно в то время, когда приложение было сломано, и в FileLockTable.lockMap
действительно есть запись, показывающая ссылку на файл БД:
Iподозревать (хотя и не совсем уверенным), что GC (не обязательно Full GC) также является причиной, по которой приложение иногда может "лечить себя";например, на основе журналов, он, казалось, работал правильно в следующие периоды времени (9.10.2019):
- 00: 01 - 01: 16
- 01: 16 - 03:08
- 03: 33: 48 - 04: 25: 08
- 04: 31: 18 - 04: 49: 09
- 04: 50: 30 - 04:54:59
- 04: 55: 58 - 06: 02: 58
Между этими периодами в журналах были частые OverlappingLockException
с.
Больше примечаний потестирование и различные эксперименты, которые я провел
Изначально мы использовали версию H2 1.4.193
, но я безуспешно пытался переключиться на 1.4.199
(показанная трассировка стека соответствует этой версии).
Я попробовал настройку AUTO_SERVER=TRUE
, но ошибка все еще присутствовала (хотя казалось, что приложение могло работать должным образом чаще, чем раньше - возможно, из-за GC, см. Ниже).
Я также пытался сохранить БД в локальном файле в файловой системе контейнера-докера, и это сработало, но это не то, что нам нужно, потому что нам нужно сохранять данные при перезапуске контейнеров (поэтому используется общее хранилище).
Наконец, я попытался воспроизвести это локально, настроив новый общий файловый ресурс на автономном сервере Windows, работающем в Azure, смонтировав его на моей Mac OS и затем передав его как «bind mount» в контейнер Docker, но не сделалполучить сообщение об ошибке в течение 10 часов (в конце концов оно не удалось из-за неисправного монтирования, и демон Docker перестал отвечать полностью и его пришлось уничтожить).
Любые советы о том, как выполнить дальнейшее устранение неполадок, или идеи о том, что может быть не так, высоко ценятся,Например, было бы замечательно, если бы я мог как-то отследить, когда / где была создана блокирующая файл с ошибками, и отследить ее до объекта (если он присутствует в дампе кучи), который его создал.
Спасибо!
[1]: Под капотом tryLock
(в Linux) используется системный вызов fcntl
- возможно, это одна из причин сохранения существующих блокировок в FileLockTable.lockMap
тот факт, что Linux автоматически снимает блокировки файлов, когда системный вызов close
вызывается для любого из файловых дескрипторов, используемых для получения блокировки (http://0pointer.de/blog/projects/locking.html).