Подтвердить что ты не робот

Долгосрочный процесс приостановлен

У меня есть консольное приложение .NET 2.0, работающее на Windows Server GoDaddy VPS в среде Visual Studio 2010 IDE в режиме отладки (F5).

Приложение периодически зависает (как будто сборщик мусора временно приостановил выполнение), однако в редких случаях он никогда не возобновляет выполнение!

Я уже несколько месяцев меняю диагонали, и у меня заканчиваются идеи.

  • Приложение работает так быстро, как может (он использует 100% использование ЦП), но при нормальном приоритете. Он также многопоточен.
  • Когда приложение зависает, я могу разморозить его, используя VS2010 IDE, путем приостановки/приостановки процесса (поскольку он работает в отладчике).
  • Местоположение последнего выполнения, когда я приостанавливаю замороженный процесс, кажется несущественным.
  • В то время как замораживание, использование ЦП по-прежнему на 100%.
  • После размораживания он работает отлично до следующего замораживания.
  • Сервер может запускать 70 дней между зависаниями, или это может сделать только 24 часа.
  • Использование памяти остается относительно постоянным; нет никаких признаков утечки памяти.

У кого-нибудь есть подсказки для диагностики того, что именно происходит?

4b9b3361

Ответ 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";
    }