Утечка в пакетах

Версия Minecraft
1.12.2
API
Forge
7,099
324
1,510
Сервак в продакшене через какое-то время заполняет всю выделенную память и начинает жутко фризить из-за частых вызовов gc.
Сделал дамп кучи, посмотрел его в EclipseMemoryAnalizer, там один игрок занимает 10гб
EntityPlayerMP -> connection: NetHandlerPlayServer -> netManager: NetworkManager -> outboundPacketsQueue: Queue<InboundHandlerTuplePacketListener>
Вот эта коллекция с пакетами для отправки игроку - занимает 10гб, они там как-то копятся и не удаляются.
Такое бывает в разное время у разных игроков.
Посмотрел ванильную форжу, не нашел возможной причины застревания пакетов.
Юзается ядро catserver, версия git-CatServer-1.12.2-222f2a5f.
В катсервере патчи посмотрел, вроде там нет существенных отличий этой части логики.
В качестве быстрой заплатки сделал мод, который раз в 10 мин смотрит всех игроков и очищает эту коллекцию у тех, у кого больше 1000 элементов
Java:
@Mod(modid = "packet_leak_fix", acceptableRemoteVersions = "*")
@Mod.EventBusSubscriber
public class Main {

    private static int tick = 0;

    @SubscribeEvent
    public static void serverTick(TickEvent.ServerTickEvent event) {
        if (event.phase == TickEvent.Phase.END) {
            tick++;
            if (tick > 20 * 60 * 10) {
                tick = 0;
                for (EntityPlayerMP player : FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList().getPlayers()) {
                    Queue<NetworkManager.InboundHandlerTuplePacketListener> outboundPacketsQueue = player.connection.netManager.outboundPacketsQueue;
                    if (outboundPacketsQueue.size() > 1000) {
                        outboundPacketsQueue.clear();
                    }
                }
            }
        }
    }
}
Прошла пара дней и ситуация не изменилась.
Что посоветуете с этим делать?
 
  • Wow
Реакции: jopi
1,038
57
229
приветик, давай на выходных помучаем. Напишу в дискорде..
 
1,074
72
372
Я подозреваю что какой-то мод обильно отправляет пакеты игрокам. Уже не раз встречались "гении", которые отправляли ненужные пакеты всем игрокам на сервере.
Суть какая: на приём и отправку у сервера есть лимит в 1000 пакетов/тик что может приводить к захламлению очереди когда новые элементы добавляются гораздо быстрее. Надо дамп памяти снимать и смотреть чем именно забита очередь.
 
7,099
324
1,510
Там встречаются рандомные пакеты, которые вроде никак не связаны, в том числе много ванильных пакетов обновления тайлов из разных модов
Похоже, что это ванильный баг или баг форжа или баг ядра или какой-то мод влезает в эту систему пакетов
 
1,074
72
372
в том числе много ванильных пакетов обновления тайлов из разных модов
И этим авторы тоже грешат, отправляя обновление блока на каждый чих. В EMT попадался механизм, который ничего не делая, отправлял обновление блока каждый тик. FPS на клиенте садился хорошо.
 
7,099
324
1,510
Немного отладили
Корневую причину не нашли, но уточнили симптомы:
У рандомных игроков в какой-то момент NetworkManager#channel()!=null и NetworkManager#channel().isOpen()==false
При этом такое состояние остается надолго и пакеты копятся в очереди
При этом те игроки, похоже, не испытывают трудностей в игре, втф
Следующая заплатка работает хорошо:
Java:
@Hook
public static void sendPacket(NetworkManager networkManager, Packet<?> packetIn) {
    freePacketsQueue(networkManager);
}

@Hook
public static void flushOutboundQueue(NetworkManager networkManager) {
    freePacketsQueue(networkManager);
}

private static void freePacketsQueue(NetworkManager networkManager) {
    if (networkManager.channel() != null && !networkManager.channel().isOpen())
        if (networkManager.outboundPacketsQueue.size() > 100)
            while (!networkManager.outboundPacketsQueue.isEmpty()) {
                NetworkManager.InboundHandlerTuplePacketListener e = networkManager.outboundPacketsQueue.poll();
                if (e.packet instanceof SPacketCustomPayload) {
                    PacketBuffer data = ((SPacketCustomPayload) e.packet).getBufferData();
                    if (data != null)
                        data.release();
                }
            }
}
 
Сверху