Delphi: отладка зависания критической секции путем сообщения стека вызовов запущенных потоков о блокировке "сбой" - PullRequest
8 голосов
/ 15 сентября 2010

Я ищу способ отладки редкого зависания / тупика критической секции Delphi 7 (TCriticalSection). В этом случае, если поток ожидает критическую секцию более, чем, скажем, 10 секунд, я хотел бы создать отчет с трассировкой стека как потока, в настоящий момент блокирующего критическую секцию, так и потока, который не смог заблокировать критическую секцию после ожидания 10 секунд. Тогда это нормально, если возникает исключение или приложение закрывается.

Я бы предпочел продолжать использовать критические секции, а не использовать другие примитивы синхронизации, если это возможно, но может переключаться при необходимости (например, для получения функции тайм-аута).

Если инструмент / метод работает во время выполнения за пределами IDE, это является бонусом, поскольку его трудно воспроизвести по требованию. В редком случае я могу продублировать тупик внутри IDE, если я попытаюсь сделать паузу, чтобы начать отладку, IDE просто сидит там, ничего не делая, и никогда не достигает состояния, когда я могу просматривать потоки или стеки вызовов. Хотя я могу сбросить запущенную программу.

Обновление: в этом случае я имею дело только с одним критическим разделом и двумя потоками, так что, скорее всего, это не проблема порядка блокировки. Я полагаю, что существует неправильная вложенная попытка ввести блокировку в двух разных потоках, что приводит к взаимоблокировке.

Ответы [ 5 ]

8 голосов
/ 15 сентября 2010

Вы должны создать и использовать свой собственный класс объекта блокировки. Это может быть реализовано с использованием критических секций или мьютексов, в зависимости от того, хотите ли вы отладить это или нет.

Создание собственного класса имеет дополнительное преимущество: вы можете реализовать иерархию блокировок и вызвать исключение при его нарушении. Временные блокировки возникают, когда блокировки не всегда принимаются в одном и том же порядке. Назначение уровня блокировки для каждого замка позволяет проверить, что блокировки взяты в правильном порядке. Вы можете сохранить текущий уровень блокировки в потоке, и разрешить использование только тех блокировок, которые имеют более низкий уровень блокировки, в противном случае вы вызовете исключение. Это позволит отследить все нарушения, даже если не возникнет тупиковая ситуация, поэтому это должно значительно ускорить отладку.

Что касается получения трассировки стека потоков, здесь много вопросов о переполнении стека, связанных с этим.

Обновление

Вы пишете:

В этом случае я имею дело только с одним критическим разделом и двумя потоками, так что, скорее всего, это не проблема порядка блокировки. Я полагаю, что существует неверная вложенная попытка ввести блокировку в двух разных потоках, что приводит к тупику.

Это не может быть целой историей. В Windows нет способа взаимоблокировки с двумя потоками и одним критическим разделом, поскольку критические разделы могут быть рекурсивно получены там потоком. Должен быть задействован другой механизм блокировки, например, вызов SendMessage().

Но если вы действительно имеете дело только с двумя потоками, то один из них должен быть основным / VCL / GUI-потоком. В этом случае вы сможете использовать функцию MadExcept «Проверка заморозки основного потока» . Он попытается отправить сообщение в основной поток и завершится неудачно по истечении настраиваемого времени без обработки сообщения. Если ваш основной поток блокирует критическую секцию, а другой поток блокирует вызов обработки сообщения, то MadExcept сможет перехватить это и дать вам трассировку стека для обоих потоков.

3 голосов
/ 15 сентября 2010

Это не прямой ответ на ваш вопрос, но что-то, с чем я недавно столкнулся, на какое-то время поставило меня (и пару коллег) в тупик.

Это было прерывистое зависание потока, связанное с критическимраздел и, как только мы узнали причину, это было очень очевидно и подарило всем нам момент «д'о».Тем не менее, потребовалась серьезная охота, чтобы найти (добавив все больше и больше журналов трассировки, чтобы точно определить ошибочное утверждение), и именно поэтому я подумал, что упомяну это.

Это также было в критической секции enter.Другой поток действительно получил этот критический раздел.По-видимому, причиной этого была не заблокированная блокировка, поскольку в ней был задействован только один критический раздел, поэтому проблем с получением блокировок в другом порядке не могло быть.Поток, содержащий критическую секцию, должен был просто продолжить, а затем снять блокировку, позволяя другому потоку получить ее.

В итоге оказалось, что поток, удерживающий блокировку, в конечном итоге получает доступ к ItemIndex объекта (IIRC) комбобокс, довольно безобидный, казалось бы.К сожалению, получение этого ItemIndex зависит от обработки сообщений.И поток, ожидающий блокировки, был основным потоком приложения ... (на всякий случай, если кто-то задается вопросом: основной поток выполняет всю обработку сообщений ...)

Мы могли бы подумать об этом намного раньше, если быс самого начала было немного более очевидно, что vcl был вовлечен.Однако это началось с кода, не относящегося к пользовательскому интерфейсу, и участие vcl стало очевидным только после добавления инструментария (трассировка входа-выхода) вдоль дерева вызовов и обратно через все инициируемые события и их обработчики до кода пользовательского интерфейса.

Просто надеюсь, что эта история поможет кому-то, кто столкнулся с загадочным зависанием.

2 голосов
/ 16 сентября 2010

Используйте Mutex вместо критического раздела. Существует небольшая разница между мьютексами и критическими секциями - критические секции более эффективны, а мьютексы более гибки. Вы можете легко переключаться между мьютексами и критическими секциями, используя, например, мьютексы в отладочной версии.

для критического раздела мы используем:

var
  FLock: TRTLCriticalSection;

  InitializeCriticalSection(FLock);  // create lock
  DeleteCriticalSection(FLock);      // free lock
  EnterCriticalSection(FLock);       // acquire lock
  LeaveCriticalSection(FLock);       // release lock

то же самое с мьютексом:

var FLock: THandle;

  FLock:= CreateMutex(nil, False, nil);  // create lock
  CloseHandle(FLock);                    // free lock
  WaitForSingleObject(FLock, Timeout);   // acquire lock
  ReleaseMutex(FLock);                   // release lock

Вы можете использовать тайм-ауты (в миллисекундах; 10000 в течение 10 секунд) с мьютексами, реализовав функцию захвата захвата следующим образом:

function AcquireLock(Lock: THandle; TimeOut: LongWord): Boolean;
begin
  Result:= WaitForSingleObject(Lock, Timeout) = WAIT_OBJECT_0;
end;
1 голос
/ 23 ноября 2011

Вы также можете использовать Критические разделы с TryEnterCriticalSection API вместо EnterCriticalSection.

Если вы используете TryEnterCriticalSection, и получение блокировки не удается, API возвращает False, и вы можете справиться с ошибкой любым удобным для вас способом, а не просто заблокировать поток.

Что-то вроде

while not TryEnterCriticalSection(fLock) and (additional_checks) do
begin
  deal_with_failure();
  sleep(500); // wait 500 ms
end;

Обратите внимание, что Delphi TCriticalSection использует EnterCriticalSection, поэтому, если вы не настроите этот класс, вам придется создать свой собственный класс или вам придется заниматься инициализацией / деинициализацией критического раздела.

0 голосов
/ 15 сентября 2010

Если вы хотите иметь возможность ждать чего-то с тайм-аутом, вы можете попробовать заменить критический раздел сигналом TEvent.Вы можете сказать, что нужно подождать событие, указать время ожидания и проверить код результата.Если сигнал был установлен, то вы можете продолжить.Если нет, то по истечении этого времени вы создадите исключение.

По крайней мере, так я бы поступил в D2010.Я не уверен, что в Delphi 7 есть TEvent, но, вероятно, он есть.

...