Помогите Теряется соединение с бд

  • Автор темы Автор темы reZero
  • Дата начала Дата начала

reZero

Активный участник
Пользователь
Баллы
76
Всем привет
Есть такая проблема..

Имеется самописный плагин, внедряющий новую механику в игру. Каждый раз, когда игрок взаимодействует с данной механикой, плагин обращается к бд mysql

В последнее время я заметил, что сервер крашится и офается, а причиной краша является долгий ответ от бд, причем после старта сервера какое-то время (мб час, два или больше, не измерял) все нормально, без крашей. Сама бд стоит на соседней машине, и по сути задержки должны быть минимальны

Вот сам краш
Код:
[Paper Watchdog Thread/ERROR]: ------------------------------
[Paper Watchdog Thread/ERROR]: Current Thread: Server thread
[Paper Watchdog Thread/ERROR]:     PID: 17 | Suspended: false | Native: false | State: TIMED_WAITING
[Paper Watchdog Thread/ERROR]:     Stack:
[Paper Watchdog Thread/ERROR]:         [email protected]/jdk.internal.misc.Unsafe.park(Native Method)
[Paper Watchdog Thread/ERROR]:         [email protected]/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:234)
[Paper Watchdog Thread/ERROR]:         [email protected]/java.util.concurrent.SynchronousQueue$TransferQueue.awaitFulfill(SynchronousQueue.java:761)
[Paper Watchdog Thread/ERROR]:         [email protected]/java.util.concurrent.SynchronousQueue$TransferQueue.transfer(SynchronousQueue.java:689)
[Paper Watchdog Thread/ERROR]:         [email protected]/java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:937)
[Paper Watchdog Thread/ERROR]:         com.zaxxer.hikari.util.ConcurrentBag.borrow(ConcurrentBag.java:151)
[Paper Watchdog Thread/ERROR]:         com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:180)
[Paper Watchdog Thread/ERROR]:         com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:162)
[Paper Watchdog Thread/ERROR]:         com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:100)
[Paper Watchdog Thread/ERROR]:         ru.rezero.mec.database.Table.getConnection(Table.java:157)
[Paper Watchdog Thread/ERROR]:         ru.rezero.mec.database.Table.createStatement(Table.java:152)
[Paper Watchdog Thread/ERROR]:         ru.rezero.mec.database.Permissions.load(Permissions.java:133)
[Paper Watchdog Thread/ERROR]:         ru.rezero.mec.listeners.Events.onLoggedIn(Events.java:1068)
[Paper Watchdog Thread/ERROR]:         com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor989.execute(Unknown Source)
[Paper Watchdog Thread/ERROR]:         app//org.bukkit.plugin.EventExecutor.lambda$create$1(EventExecutor.java:69)
[Paper Watchdog Thread/ERROR]:         app//org.bukkit.plugin.EventExecutor$$Lambda$4895/0x0000000841192840.execute(Unknown Source)
[Paper Watchdog Thread/ERROR]:         app//co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:76)
[Paper Watchdog Thread/ERROR]:         app//org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70)
[Paper Watchdog Thread/ERROR]:         app//org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:624)
[Paper Watchdog Thread/ERROR]:         app//net.minecraft.server.v1_16_R3.PlayerList.postChunkLoadJoin(PlayerList.java:357)
[Paper Watchdog Thread/ERROR]:         app//net.minecraft.server.v1_16_R3.PlayerList.lambda$a$1(PlayerList.java:304)
[Paper Watchdog Thread/ERROR]:         app//net.minecraft.server.v1_16_R3.PlayerList$$Lambda$6994/0x0000000842285040.run(Unknown Source)
[Paper Watchdog Thread/ERROR]:         app//net.minecraft.server.v1_16_R3.PlayerConnection.tick(PlayerConnection.java:316)
[Paper Watchdog Thread/ERROR]:         app//net.minecraft.server.v1_16_R3.NetworkManager.a(NetworkManager.java:592)
[Paper Watchdog Thread/ERROR]:         app//net.minecraft.server.v1_16_R3.ServerConnection.c(ServerConnection.java:173)
[Paper Watchdog Thread/ERROR]:         app//net.minecraft.server.v1_16_R3.MinecraftServer.b(MinecraftServer.java:1582)
[Paper Watchdog Thread/ERROR]:         app//net.minecraft.server.v1_16_R3.DedicatedServer.b(DedicatedServer.java:439)
[Paper Watchdog Thread/ERROR]:         app//net.minecraft.server.v1_16_R3.MinecraftServer.a(MinecraftServer.java:1402)
[Paper Watchdog Thread/ERROR]:         app//net.minecraft.server.v1_16_R3.MinecraftServer.w(MinecraftServer.java:1134)
[Paper Watchdog Thread/ERROR]:         app//net.minecraft.server.v1_16_R3.MinecraftServer.lambda$a$0(MinecraftServer.java:292)
[Paper Watchdog Thread/ERROR]:         app//net.minecraft.server.v1_16_R3.MinecraftServer$$Lambda$3142/0x0000000840860c40.run(Unknown Source)

А вот ошибка после краша
Код:
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]: java.sql.SQLTransientConnectionException: HikariPool-3 - Connection is not available, request timed out after 30000ms.
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:696)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:197)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:162)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:100)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at ru.rezero.mec.database.Table.getConnection(Table.java:157)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at ru.rezero.mec.database.Table.createStatement(Table.java:152)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at ru.rezero.mec.database.Towns.load(Towns.java:193)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at ru.rezero.mec.groups.TownInfo.load(TownInfo.java:760)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at ru.rezero.mec.managers.InfoManager.getInfoByTown(InfoManager.java:88)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at ru.rezero.mec.managers.InfoManager.getInfo(InfoManager.java:40)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at ru.rezero.mec.groups.GroupInfo.getWar(GroupInfo.java:344)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at ru.rezero.mec.managers.NameTagManager$2$1.run(NameTagManager.java:145)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at org.bukkit.craftbukkit.v1_16_R3.scheduler.CraftTask.run(CraftTask.java:100)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at org.bukkit.craftbukkit.v1_16_R3.scheduler.CraftAsyncTask.run(CraftAsyncTask.java:58)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at com.destroystokyo.paper.ServerSchedulerReportingWrapper.run(ServerSchedulerReportingWrapper.java:22)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
[02:01:47] [Craft Scheduler Thread - 91 - Mechanic/WARN]:     at java.base/java.lang.Thread.run(Thread.java:829)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]: java.lang.NullPointerException
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at ru.rezero.mec.database.Table.createStatement(Table.java:152)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at ru.rezero.mec.database.Towns.load(Towns.java:193)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at ru.rezero.mec.groups.TownInfo.load(TownInfo.java:760)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at ru.rezero.mec.managers.InfoManager.getInfoByTown(InfoManager.java:88)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at ru.rezero.mec.managers.InfoManager.getInfo(InfoManager.java:40)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at ru.rezero.mec.groups.GroupInfo.getWar(GroupInfo.java:344)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at ru.rezero.mec.managers.NameTagManager$2$1.run(NameTagManager.java:145)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at org.bukkit.craftbukkit.v1_16_R3.scheduler.CraftTask.run(CraftTask.java:100)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at org.bukkit.craftbukkit.v1_16_R3.scheduler.CraftAsyncTask.run(CraftAsyncTask.java:58)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at com.destroystokyo.paper.ServerSchedulerReportingWrapper.run(ServerSchedulerReportingWrapper.java:22)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
[02:01:47] [Craft Scheduler Thread - 76 - Mechanic/WARN]:     at java.base/java.lang.Thread.run(Thread.java:829)
[02:01:47] [Craft Scheduler Thread - 44 - Mechanic/WARN]: [Mechanic] Plugin Mechanic v1.2.0 generated an exception while executing task 135
java.lang.NullPointerException: null
    at ru.rezero.mec.database.Table.createStatement(Table.java:152) ~[?:?]
    at ru.rezero.mec.database.Towns.getColumn(Towns.java:158) ~[?:?]
    at ru.rezero.mec.database.Towns.save(Towns.java:727) ~[?:?]
    at ru.rezero.mec.managers.SaveManager.execute(SaveManager.java:34) ~[?:?]
    at ru.rezero.mec.managers.SaveManager$1.run(SaveManager.java:23) ~[?:?]
    at org.bukkit.craftbukkit.v1_16_R3.scheduler.CraftTask.run(CraftTask.java:100) ~[patched_1.16.5.jar:git-Airplane-"9f0a29b"]
    at org.bukkit.craftbukkit.v1_16_R3.scheduler.CraftAsyncTask.run(CraftAsyncTask.java:58) ~[patched_1.16.5.jar:git-Airplane-"9f0a29b"]
    at com.destroystokyo.paper.ServerSchedulerReportingWrapper.run(ServerSchedulerReportingWrapper.java:22) ~[patched_1.16.5.jar:git-Airplane-"9f0a29b"]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
    at java.lang.Thread.run(Thread.java:829) [?:?]

Вот сам код метода createStatement, который нулит
Код:
public PreparedStatement createStatement(String sql) throws SQLException {
        return this.getConnection().prepareStatement(sql);
    }

public Connection getConnection() {
        try {
            return hikariDataSource.getConnection();
        }
        catch (SQLException ex) {
            ex.printStackTrace();
            return null;
        }
    }
HikariDataSource инициализируется посредством вызова метода initialize в onEnable главного класса, тут косяка тоже нет.

Ну и такой еще вопрос: может быть стоило бы сделать систему кеширования нужных для плагина данных, чтобы каждые несколько тиков не обращаться к бд (пускай даже в асинхроне)? Работать это будет так: при включении плагина из бд подтягивается вся нужная инфа и хранится в мапах, листах и т.д., а при выключении плагина из этих мап, листов и т.д. вся инфа сохраняется обратно в бд, тем самым обращение к бд будет только 2 раза. Может, причина крашей как раз в этом: много обращений к бд?
 
надо закрывать PreparedStatement-ы и Connection-ы
закрытие коннекта в соответствии с настройкой пула hikari не обязательно его закроет физически
в большинстве случаев он просто будет возвращаться в пул.
по стектрейсу видно ты висишь на получении коннекта из пула, потому что их все забрали и не вернули
ConcurrentBag.borrow
проще всего закрывать через try-with-resources, примеров в интернете масса
 
надо закрывать PreparedStatement-ы и Connection-ы
закрытие коннекта в соответствии с настройкой пула hikari не обязательно его закроет физически
в большинстве случаев он просто будет возвращаться в пул.
по стектрейсу видно ты висишь на получении коннекта из пула, потому что их все забрали и не вернули
ConcurrentBag.borrow
проще всего закрывать через try-with-resources, примеров в интернете масса
Все манипуляции с бд проходят через блок try-catch-finally и в последнем закрываются все конекшены и статменты.
Но теперь я не уверен, что они действительно у меня закрываются по какой-либо причине...
Как можно проверить, закрылись ли они после моего блока try-catch-finally?
 
у тебя createStatement использует внутри getConnection()
и наружу его не отдает. только PreparedStatement
ты уверен что закрываешь Connection а не что-то другое?
 
у тебя должно быть
try (Connection conn = getConenction();
PreparedStatement ps = conn.prepareStatement();
ResultSet rs = ps.execute()) {
rs....
}
 
у тебя createStatement использует внутри getConnection()
и наружу его не отдает. только PreparedStatement
ты уверен что закрываешь Connection а не что-то другое?
Проверил, connection действительно закрывается после выполнения try-catch-finally
Код
Код:
  public void close(final Object... os) {
        for (final Object o : os) {
            if (o instanceof Connection) {
                this.closeConnection((Connection)o);
            }
            else if(o instanceof PreparedStatement) {
                this.closeStatement((Statement) o);
            }
            else if (o instanceof Statement) {
                this.closeStatement((Statement)o);
            }
            else if (o instanceof ResultSet) {
                this.closeResultSet((ResultSet)o);
            }
        }
    }
    
    public void closeConnection(final Connection con) {
        if (con == null) {
            return;
        }
        try {
            if (!con.isClosed()) {
                con.close();
            }
        }
        catch (Exception ex) {}
    }
    
    public void closeStatement(Statement stmt) {
        if (stmt == null) {
            return;
        }
        try {
            if (!stmt.isClosed()) {
                stmt.close();
            }
        }
        catch (Exception ex) {}
        try {
            this.closeConnection(stmt.getConnection());
        }
        catch (Exception ex2) {}
    }
    
    public void closeResultSet(final ResultSet rs) {
        if (rs == null) {
            return;
        }
        try {
            if (!rs.isClosed()) {
                rs.close();
            }
        }
        catch (Exception ex) {}
        try {
            this.closeStatement(rs.getStatement());
        }
        catch (Exception ex2) {}
        try {
            this.closeConnection(rs.getStatement().getConnection());
        }
        catch (Exception ex3) {}
    }
    
    public PreparedStatement createStatement(final String sql) throws SQLException {
        return this.getConnection().prepareStatement(sql);
    }
    
    public Connection getConnection() {
        try {
            return Table.hikariDataSource.getConnection();
        }
        catch (SQLException ex) {
            ex.printStackTrace();
            return null;
        }
    }
Код:
public void loadOnEnable() {
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            stmt = this.createStatement("SELECT * FROM " + this.table);
            rs = stmt.executeQuery();
            while (rs.next()) {
                main.getCache().getLoadedInfo().putIfAbsent(UUID.fromString(rs.getString("UUID")), InfoType.valueOf(rs.getString("INFO")));
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally {
            this.close(stmt, rs);
        }
    }
Как видно из кода, он всегда в конце закрывает connection, preparedStatement и resultSet
 
отвратительный код. тем не менее смотрим
Код:
        try {
            this.closeStatement(rs.getStatement());
        }
        catch (Exception ex2) {}
        try {
            this.closeConnection(rs.getStatement().getConnection());
        }
сначала закрываем стейтмент, потом коннект который берем из стейтмента
смотрим жавадок
https://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#getConnection()
Throws:
SQLException - if a database access error occurs or this method is called on a closed Statement
т.е. вполне вероятно что нифига ты не закрыл
убери весь этот говнокод и сделай нормально)
 
отвратительный код. тем не менее смотрим
Код:
        try {
            this.closeStatement(rs.getStatement());
        }
        catch (Exception ex2) {}
        try {
            this.closeConnection(rs.getStatement().getConnection());
        }
сначала закрываем стейтмент, потом коннект который берем из стейтмента
смотрим жавадок
https://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#getConnection()
Throws:
SQLException - if a database access error occurs or this method is called on a closed Statement
т.е. вполне вероятно что нифига ты не закрыл
убери весь этот говнокод и сделай нормально)
Ты имеешь ввиду, сделать так?
Код:
public void loadOnEnable() {
        try(Connection conn = getConnection(); PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + this.table); ResultSet rs = stmt.executeQuery()) {
            while (rs.next()) {
                main.getCache().getLoadedInfo().putIfAbsent(UUID.fromString(rs.getString("UUID")), InfoType.valueOf(rs.getString("INFO")));
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
Как тогда в таком случае все эти дела правильно закрыть?
 
Ты имеешь ввиду, сделать так?
да
Как тогда в таком случае все эти дела правильно закрыть?
а никак. такая конструкция сама все закроет и должна прекрасно работать.
иногда только в один try не влезает из-за необходимости в PreparedStatement параметры какие-то передать помимо строки запроса, но их можно вкладывать друг в друга
 
да

а никак. такая конструкция сама все закроет и должна прекрасно работать.
иногда только в один try не влезает из-за необходимости в PreparedStatement параметры какие-то передать помимо строки запроса, но их можно вкладывать друг в друга
Конструкция выглядит вырвиглазно, если честно. Но для меня главное чтоб работало
Спасибо большое, попробую поставить на сервер и последить за ошибками. Если что, отпишусь
 
ну если после ; сделаешь переводы строк, то будет получше)
Кстати, хотел спросить еще кое-что
Я когда плагин запускаю, появляется запись в логе
Код:
:56:07] [Server thread/INFO]: [com.zaxxer.hikari.HikariDataSource] HikariPool-2 - Starting...
[10:56:07] [Server thread/INFO]: [com.zaxxer.hikari.HikariDataSource] HikariPool-2 - Start completed.
[
А если плагин рестартнуть, например, через plugman, он запускает hikari pool новый
Код:
:56:07] [Server thread/INFO]: [com.zaxxer.hikari.HikariDataSource] HikariPool-3 - Starting...
[10:56:07] [Server thread/INFO]: [com.zaxxer.hikari.HikariDataSource] HikariPool-3 - Start completed.
[
Хотя есть сообщение о том, что старый вроде как стопается, когда плагин выключается.
Так и должно быть, что каждый раз там новое число?
 
ну новое и новое. если закрываешь датасорс во время рестарта, а не просто каждый раз новый создаешь, то и пофигу
 
Все бы плагины нестартить во время работы сервера.
 
Удивлён, что никто до сих пор не написал о том, что вся работа с БД должна происходить в отдельном потоке (например, в асинхронном шедулере). Если бы работало в асинхроне - не крашило бы сервер даже при проблемах связи с БД. У основного потока и без того задач много, частенько в сотку проца долбится
 
Удивлён, что никто до сих пор не написал о том, что вся работа с БД должна происходить в отдельном потоке (например, в асинхронном шедулере). Если бы работало в асинхроне - не крашило бы сервер даже при проблемах связи с БД. У основного потока и без того задач много, частенько в сотку проца долбится
Ну тогда все магазины придется под это переписывать. Они ведь не умеют ждать, когда там ответ по балансу придет.
 
Удивлён, что никто до сих пор не написал о том, что вся работа с БД должна происходить в отдельном потоке (например, в асинхронном шедулере). Если бы работало в асинхроне - не крашило бы сервер даже при проблемах связи с БД. У основного потока и без того задач много, частенько в сотку проца долбится
щас бы выделять целый поток для ожидания ответа от бд
 
щас бы выделять целый поток для ожидания ответа от бд
Щас бы заниматься словоблудием вместо реальной помощи.

Ну, окей.
Речи про целый поток не было, была речь про отдельный. Отдельный от основного серверного. Никак иначе, внезапно, это работать не может - иначе это будет являться блокировкой основного потока.

И ещё одно открытие - асинхронные баккитовские шедулеры, которые я привёл в пример, не создают по потоку на каждую задачу.
Неожиданно, правда?

Умничать и я умею, вот только какой в этом смысл, если человеку нужно сообщить об элементарной вещи, вникать в принцип работы которой ему сейчас нет никакой нужды?

Хочешь поумничать - напиши статью. Расскажешь там и про динамически расширяемый пул потоков, и про одновременную обработку нескольких результатов операций в одном потоке.

Почитают те, кто реально хочет досконально разобраться. Пользы больше будет. Ну, и благодарность вместо испанского стыда
 
Последнее редактирование:
щас бы выделять целый поток для ожидания ответа от бд
вообще я слышал со времен DOS много времени прошло и отдельный поток в каком бы то ни было смысле не является чем-то странным)
в любом нормальном java приложении в принципе дохрена потоков. начиная с gc, заканчивая io и логикой, и это не является какой-то проблемой.
 
Назад
Верх