Долгосрочный процесс приостановлен
У меня есть консольное приложение .NET 2.0, работающее на Windows Server GoDaddy VPS в среде Visual Studio 2010 IDE в режиме отладки (F5).
Приложение периодически зависает (как будто сборщик мусора временно приостановил выполнение), однако в редких случаях он никогда не возобновляет выполнение!
Я уже несколько месяцев меняю диагонали, и у меня заканчиваются идеи.
- Приложение работает так быстро, как может (он использует 100% использование ЦП), но при нормальном приоритете. Он также многопоточен.
- Когда приложение зависает, я могу разморозить его, используя VS2010 IDE, путем приостановки/приостановки процесса (поскольку он работает в отладчике).
- Местоположение последнего выполнения, когда я приостанавливаю замороженный процесс, кажется несущественным.
- В то время как замораживание, использование ЦП по-прежнему на 100%.
- После размораживания он работает отлично до следующего замораживания.
- Сервер может запускать 70 дней между зависаниями, или это может сделать только 24 часа.
- Использование памяти остается относительно постоянным; нет никаких признаков утечки памяти.
У кого-нибудь есть подсказки для диагностики того, что именно происходит?
Ответы
Ответ 1
Он также многопоточен
Это ключевая часть проблемы. Вы описываете очень типичный способ, которым многопоточная программа может плохо себя вести. Он страдает от тупика, одной из типичных проблем с резьбой.
Его можно сузить немного дальше от информации, очевидно, что ваш процесс не полностью заморожен, так как он все еще потребляет 100% -ный процессор. У вас, вероятно, есть горячий цикл ожидания в вашем коде, цикл, который вращается по другому потоку, сигнализируя о событии. Это, вероятно, вызовет особенно неприятное множество тупиков, live-lock. Живые блокировки очень чувствительны к синхронизации, незначительные изменения в порядке выполнения кода могут привести к его блокировке. И снова вернитесь.
Live-locks чрезвычайно сложно отлаживать, поскольку попытка сделать это заставляет условие исчезнуть. Подобно прикреплению отладчика или разрыву кода, достаточно изменить время потока и вывести его из состояния. Или добавление операторов регистрации в ваш код, общую стратегию для отладки проблем с потоками. Что изменяет время из-за издержек записи, что, в свою очередь, может привести к тому, что live-lock полностью исчезнет.
Неприятный материал и невозможно получить помощь с такой проблемой с сайта вроде SO, поскольку он чрезвычайно зависит от кода. Для выяснения причины часто требуется тщательный анализ кода. И нередко резкое переписывание. Удачи вам.
Ответ 2
Есть ли у приложения "код восстановления/предотвращения блокировки"? То есть, блокировка с таймаутом, а затем попытка снова, возможно, после сна?
Проверяет ли приложение коды ошибок (возвращаемые значения или исключения) и повторно повторяет попытку в случае ошибки где-либо?
Обратите внимание, что такой цикл может также произойти через цикл событий, где ваш код находится только в каком-либо обработчике событий. Он не должен быть фактическим циклом в вашем собственном коде. Хотя это, вероятно, не так, если приложение заморожено, указав заблокированный цикл событий.
Если у вас есть что-то вроде выше, вы можете попытаться смягчить проблему, сделав тайм-ауты и сон случайным интервалом, а также добавив короткие случайные спальные периоды в случаях, когда ошибка может привести к смерти/оживлению. Если такой цикл чувствителен к производительности, добавьте счетчик и начните только сон со случайным, возможно, увеличением интервала после некоторого количества неудачных попыток. И убедитесь, что любой сон, который вы добавляете, не спящий, когда что-то заблокировано.
Если ситуация будет происходить чаще, вы также можете использовать это, чтобы разделить ваш код и определить, какие циклы (потому что использование 100% процессора означает, что некоторые очень занятые циклы вращаются) несут ответственность. Но из-за редкости вопроса, я понимаю, вы будете счастливы, если проблема просто исчезнет на практике;)
Ответ 3
Ну, три вещи здесь...
Прежде всего, начните использовать GC сервера .NET: http://msdn.microsoft.com/en-us/library/ms229357.aspx. Это, вероятно, будет держать ваше приложение незаблокированным.
Во-вторых, если вы можете сделать это на своей виртуальной машине: проверьте наличие обновлений. Это всегда кажется очевидным, но я видел многочисленные случаи, когда простое обновление Windows фиксирует странные проблемы.
В-третьих, я хотел бы остановиться на времени жизни объекта, что может быть одной из проблем здесь. Это довольно длинная история, что происходит, так что несите меня.
Время жизни объекта в основном строится - сбор мусора - завершение. Все три процесса выполняются в отдельном потоке. GC передает данные в поток завершения, который имеет очередь, которая вызывает "деструкторы".
Итак, что, если у вас есть финализатор, который делает что-то странное, скажите что-то вроде:
public class FinalizerObject
{
public FinalizerObject(int n)
{
Console.WriteLine("Constructed {0}", n);
this.n = n;
}
private int n;
~FinalizerObject()
{
while (true) { Console.WriteLine("Finalizing {0}...", n); System.Threading.Thread.Sleep(1000); }
}
}
Поскольку финализаторы запускаются в отдельном потоке, который обрабатывает очередь, наличие одного финализатора, делающего что-то глупое, является серьезной проблемой для вашего приложения. Вы можете увидеть это, используя вышеуказанный класс 2 раза:
static void Main(string[] args)
{
SomeMethod();
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
Console.WriteLine("All done.");
Console.ReadLine();
}
static void SomeMethod()
{
var obj2 = new FinalizerObject(1);
var obj3 = new FinalizerObject(2);
}
Обратите внимание на то, как вы закончите с небольшой утечкой памяти, и если вы удалите Thread.Sleep также с 100% -ным процессом процессора, даже если ваш основной поток все еще отвечает. Поскольку они разные потоки, отсюда на нем довольно легко заблокировать весь процесс - например, с помощью блокировки:
static void Main(string[] args)
{
SomeMethod();
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
Thread.Sleep(1000);
lock (lockObject)
{
Console.WriteLine("All done.");
}
Console.ReadLine();
}
static object lockObject = new Program();
static void SomeMethod()
{
var obj2 = new FinalizerObject(1, lockObject);
var obj3 = new FinalizerObject(2, lockObject);
}
[...]
~FinalizerObject()
{
lock (lockObject) { while (true) { Console.WriteLine("Finalizing {0}...", n); System.Threading.Thread.Sleep(1000); } }
}
Итак, я вижу, что вы думаете: "Вы серьезно?"; дело в том, что вы можете делать что-то подобное, даже не осознавая этого. Здесь "изображение" появляется на картинке:
IEnumerable из "yield" на самом деле IDisposable и как таковой реализует шаблон IDisposable. Объедините реализацию "yield" с блокировкой, забудьте вызвать IDisposable, перечислив ее "MoveNext" и т.д., И вы получите довольно неприятное поведение, которое отражает вышеизложенное. Тем более, что финализаторы вызывают из очереди финализации отдельным потоком (!). Объедините его с бесконечным циклом или потоком небезопасного кода, и вы получите довольно неприятное неожиданное поведение, которое будет срабатывать в исключительных случаях (когда память закончится, или когда вещи GC будут что-то делать).
Другими словами: я бы проверял ваши расходные материалы и финализаторы и очень критично относился к ним. Убедитесь, что "yield" имеет неявные финализаторы и убедитесь, что вы вызываете IDisposable из того же потока. Некоторые примеры того, о чем вы опасаетесь:
try
{
for (int i = 0; i < 10; ++i)
{
yield return "foo";
}
}
finally
{
// Called by IDisposable
}
и
lock (myLock) // 'lock' and 'using' also trigger IDisposable
{
yield return "foo";
}