По-перше, я усвідомлюю, що це не ідеальне питання стилю Q&A з абсолютною відповіддю, але я не можу придумати жодних формулювань, щоб зробити це краще. Я не думаю, що для цього немає абсолютного рішення, і це одна з причин, чому я публікую його тут, а не Stack Overflow.
Протягом останнього місяця я переписував досить старий фрагмент коду сервера (mmorpg), щоб бути більш сучасним та легшим для розширення / мод. Я почав з мережевої частини та впровадив сторонні бібліотеки (libevent), щоб обробляти речі для мене. З урахуванням всіх рефакторингу та змін коду я десь ввів корупцію пам'яті, і я намагаюся з'ясувати, де це відбувається.
Я не можу, здається, надійно відтворити його на моєму середовищі розробки / тестування, навіть коли впроваджую примітивні боти для імітації деякого навантаження, я більше не отримую збоїв (я виправив проблему, яка викликала деякі проблеми).
Я намагався поки що:
Valgrinding чорт з цього - Ніяких недійсних записів, поки річ не зламається (що може зайняти 1+ день у виробництві .. або всього лише годину), що нас справді бентежить, напевно, в якийсь момент він отримає доступ до недійсної пам'яті і не перезапише речі шанс? (Чи є спосіб "поширити" діапазон адрес?)
Засоби аналізу коду, а саме покриття та cppcheck. Поки вони вказували на деякі .. гнучкість та крайні випадки в кодексі, нічого серйозного не було.
Записуючи процес, поки він не збоїть з gdb (через unodb), а потім пропрацюю назад. Це / звучить / начебто воно повинно бути виконаним, але я або закінчую збої gdb за допомогою функції автоматичного завершення, або опиняюсь у якійсь внутрішній структурі, де я втрачаюсь, оскільки занадто багато можливих гілок (одна корупція спричиняє іншу і так на). Я думаю, було б добре, якби я міг побачити, що вказівник спочатку належить / де він був виділений, що усуне більшість питань розгалуження. Я не можу запускати valgrind з unodb, і я нормальний запис gdb неприпустимо повільний (якщо це навіть працює в поєднанні з valgrind).
Перегляд коду! Сам (ретельно) і маючи друзів переглядаю мій код, хоча я сумніваюся, що це було досить ретельно. Я думав над тим, щоб, можливо, найняти розробника, щоб зробити якийсь перегляд коду / налагодження зі мною, але я не можу дозволити собі вкласти занадто багато грошей, і я не знаю, де шукати когось, хто був би готовий працювати за мало- без грошей, якщо він не знайде проблему або когось не кваліфікує.
Я також повинен зазначити: я зазвичай отримую послідовний зворотній зв'язок. Є кілька місць, де трапляються збої, в основному пов’язані з тим, що клас сокетів якось пошкоджується. Будь це недійсний вказівник, який вказує на те, що не є сокетом, або сам клас сокетів стає перезаписаним (частково?) Безглуздим. Хоча я підозрюю, що там найбільше виходить з ладу, оскільки це одна з найбільш використовуваних частин, тому це перша пошкоджена пам'ять, яка звикає.
В цілому це питання займало мене майже 2 місяці (і вимикається, більше хобі-проекту), і це насправді засмучує мене до того, коли я стаю бурхливою IRL і думаю про те, щоб просто відмовитись. Я просто не можу думати, що ще я повинен зробити, щоб знайти проблему.
Чи є корисні прийоми, які я пропустив? Як ти з цим справляється? (Це може бути не так часто, оскільки інформації про це не так багато. Або я просто сліпий?)
Редагувати:
Деякі технічні характеристики, якщо це має значення:
Використання c ++ (11) через gcc 4.7 (версія надається debian wheezy)
База даних коду становить близько 150 000 рядків
Редагувати у відповідь на повідомлення david.pfx: (вибачте за повільну відповідь)
Чи ведете ви ретельний облік аварій, щоб шукати шаблони?
Так, у мене все ще лежать звалища останніх аварій
Чи справді кілька місць подібні? Яким чином?
Добре, що в останній версії (вони, здається, змінюються щоразу, коли я додаю / видалюю код або змінюю пов'язані структури), він завжди потрапляє у метод таймера елементів. В основному предмет має певний час, після якого він закінчується, і він надсилає оновлену інформацію клієнту. Недійсний покажчик сокета був би в (все ще діє, наскільки я можу сказати) клас гравців, в основному пов'язаний з цим. Я також відчуваю безліч збоїв у фазі очищення, після нормального відключення, коли це руйнує всі статичні класи, які не були явно знищені ( __run_exit_handlers
у backtrace). В основному це std::map
один клас, здогадуючись, що це лише перше, що з'являється.
Як виглядають корумповані дані? Нулі? Ascii? Шаблони?
Я ще не знайшов жодної структури, мені здається дещо випадковою. Це важко сказати, оскільки я не знаю, звідки почалася корупція.
Це пов’язано з купою?
Це повністю пов'язано з купою (я включив захист стека gcc, і це нічого не застало).
Чи корупція трапляється після
free()
?
Вам доведеться трохи детальніше розібратися над цим. Ви маєте на увазі наявність покажчиків уже вільних об'єктів, що лежать навколо? Я встановлюю кожне посилання на null, коли об’єкт знищується, тому, якщо я десь щось не пропустив, ні. Це повинно з'явитися у вальгринд, хоча цього не було.
Чи є щось відмінне в мережевому трафіку (розмір буфера, цикл відновлення)?
Мережевий трафік складається з необроблених даних. Отже, масиви char, (u) intX_t або упаковані (для видалення padding) структури для складніших речей, кожен пакет має заголовок, що складається з ідентифікатора та самого розміру пакета, який перевіряється на очікуваний розмір. Вони становлять близько 10-60 байт, найбільший (внутрішній пакет "завантаження", запускається один раз при запуску), розміром якого є декілька Мб.
Багато і багато тверджень про виробництво. Збій рано і передбачувано до того, як шкода пошириться.
У мене колись стався збій, пов’язаний з std::map
корупцією, кожен суб'єкт господарювання має карту його "перегляду", кожна організація, яка може його бачити, і навпаки - в цьому. Я додав 200-байтний буфер спереду і після, заповнив його 0x33 і перевірив перед кожним доступом. Корупція просто магічно зникла, я, мабуть, пересунув щось, що зробило її корумпованою чимось іншим.
Стратегічний журнал, щоб ви точно знали, що відбувалося напередодні. Додайте до журналу, коли ви наближаєтесь до відповіді.
Це працює .. в розширеному сенсі.
Ви можете у відчаї зберегти стан та автозавантажити? Я можу придумати кілька програм виробничого програмного забезпечення, які це роблять.
Я дещо так роблю. Програмне забезпечення складається з основного "кеш-процесу" та деяких інших робочих, які отримують доступ до кешу, щоб отримати та зберегти речі. Тож за аварію я не втрачаю особливого прогресу, він все ще відключає всіх користувачів і так далі, це точно не рішення.
Паралельність: нарізка на нитку, умови гонки тощо
Існує тема mysql для запитів "асинхронізації", але це все не зачіпається, і лише ділиться інформацією класу бази даних за допомогою функцій із усім блокуванням.
Переривання
Існує таймер переривання, щоб запобігти його запису, який просто перериває, якщо він не завершив цикл протягом 30 секунд, але цей код повинен бути безпечним:
if (!tics) {
abort();
} else
tics = 0;
тики, volatile int tics = 0;
які збільшуються щоразу, коли цикл завершується. Старий код теж.
події / зворотні виклики / винятки: непредсказуемо пошкоджує стан або стек
Використовується безліч зворотних дзвінків (асинхронний введення / виведення мережі, таймери), але вони нічого поганого не повинні робити.
Незвичайні дані: незвичні вхідні дані / терміни / стан
У мене було кілька крайніх випадків, пов’язаних із цим. Відключення сокета, поки пакети ще обробляються, призводило до доступу до nullptr і подібного, але це було легко помітити досі, оскільки кожна посилання очищається відразу після того, як повідомити самому класу, що це зроблено. (Сам руйнування обробляється циклом, що видаляє всі знищені об'єкти кожного циклу)
Залежність від асинхронного зовнішнього процесу.
Хочете допрацювати? Це дещо так, згаданий вище процес кешування. Єдине, що я міг собі уявити вгорі голови, - це не закінчити досить швидко та використовувати дані сміття, але це не так, оскільки для цього також використовується мережа. Та ж модель пакету.
/analyze
) та Apple Malloc та Scribble. Ви також повинні використовувати якомога більше компіляторів, використовуючи якомога більше стандартів, оскільки попередження компілятора є діагностикою, і вони з часом стають кращими. Срібної кулі немає, і один розмір підходить не всім. Чим більше інструментів та компіляторів ви використовуєте, тим повніше охоплення, оскільки кожен інструмент має свої сильні та слабкі сторони.