Учитывая следующий вопрос SO: Опасно ли использовать синхронизацию в не- Приложение VCL?

Мне было интересно, как влияет на TThread's Synchronize, CheckSynchronize и WaitFor их использование внутри DLL веб-приложения Apache.

Я начал исследовать это, когда понял, что WaitFor блокируется / зависает. Внутри WaitFor он проверяет if CurrentThread.ThreadID = MainThreadID, а затем повторяет проверку результата MsgWaitForMultipleObjects и в зависимости от результата выполняет CheckSynchronize или PeekMessage, а когда получает { {X6}} он наконец выйдет из цикла. Если MainThreadID не совпадает с CurrentThreadID, то WaitFor выполнит одиночный WaitForSingleObject.

Итак, я создал следующую тестовую DLL веб-модуля Apache, чтобы узнать, что такое MainThreadID в течение времени существования веб-запроса.

type
  TTheThread = class(TThread)
  private
  protected
    procedure Execute; override;
  public
  end;

  TWebModule1 = class(TWebModule)
    procedure WebModule1DefaultHandlerAction(Sender: TObject;
      Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
    procedure WebModuleCreate(Sender: TObject);
    procedure WebModuleDestroy(Sender: TObject);
  private
  public
  end;

procedure DoLog(AMsg : String);

var
  WebModuleClass: TComponentClass = TWebModule1;
  TestThread : TTheThread;
  Logger : TLogger;

implementation

{%CLASSGROUP 'System.Classes.TPersistent'}

{$R *.dfm}

procedure DoLog(AMsg : String);
begin
  AMsg := AMsg + #13#10 + 'MainThreadID: ' + InttoStr(MainThreadID) +
                 #13#10 + 'CurrThreadID: ' + InttoStr(GetCurrentThreadId);

  Logger.Log(ltNormal,AMsg);
end;

procedure TTheThread.Execute;
begin
  While not Terminated do
    begin
      Sleep(5000);
      DoLog('Thread Execute - Inside While - Terminated: ' + BoolToStr(Terminated,True));
    end;

  DoLog('Thread Execute - End - Terminated: ' + BoolToStr(Terminated,True));
end;


procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  Response.Content :=
    '<html>' +
    '<head><title>Web Server Application</title></head>' +
    '<body>Web Server Application</body>' +
    '</html>';

  DoLog('WebModule1DefaultHandlerAction');
end;

procedure TWebModule1.WebModuleCreate(Sender: TObject);
begin
  Logger := TLogger.Create(nil);
  {set some Logger properties}

  DoLog('WebModuleCreate - Before Thread Create');

  TestThread := TTheThread.Create(True);
  TestThread.Start;

  DoLog('WebModuleCreate - Created Thread');
end;

procedure TWebModule1.WebModuleDestroy(Sender: TObject);
begin
  DoLog('WebModuleDestroy Before Terminate');

  TestThread.Terminate;

  DoLog('WebModuleDestroy Before WaitFor');

  TestThread.WaitFor;

  DoLog('WebModuleDestroy Before Free');

  TestThread.Free;

  DoLog('WebModuleDestroy End');

  Logger.Free;
end;

end.

Итак, я запускаю Apache, перехожу на веб-страницу в браузере, отсчитываю около 12 секунд и останавливаю Apache. Я даю ему некоторое время, но служба httpd уничтожает только несколько потоков, но затем остается неизменной и никогда не закрывается.

Вот журнал:

2021-03-03 10:23:42 081  WebModuleCreate - Before Thread Create
2021-03-03 10:23:42 081    ^ MainThreadID: 2432
2021-03-03 10:23:42 081    ^ CurrThreadID: 6328
2021-03-03 10:23:42 081  WebModuleCreate - Created Thread
2021-03-03 10:23:42 081    ^ MainThreadID: 2432
2021-03-03 10:23:42 081    ^ CurrThreadID: 6328
2021-03-03 10:23:42 081  WebModule1DefaultHandlerAction
2021-03-03 10:23:42 081    ^ MainThreadID: 2432
2021-03-03 10:23:42 081    ^ CurrThreadID: 6328
2021-03-03 10:23:47 093  Thread Execute - Inside While - Terminated: False
2021-03-03 10:23:47 093    ^ MainThreadID: 2432
2021-03-03 10:23:47 093    ^ CurrThreadID: 2220
2021-03-03 10:23:52 100  Thread Execute - Inside While - Terminated: False
2021-03-03 10:23:52 100    ^ MainThreadID: 2432
2021-03-03 10:23:52 100    ^ CurrThreadID: 2220
2021-03-03 10:23:57 101  Thread Execute - Inside While - Terminated: False
2021-03-03 10:23:57 101    ^ MainThreadID: 2432
2021-03-03 10:23:57 101    ^ CurrThreadID: 2220
2021-03-03 10:23:58 313  WebModuleDestroy Before Terminate
2021-03-03 10:23:58 313    ^ MainThreadID: 2432
2021-03-03 10:23:58 313    ^ CurrThreadID: 2432
2021-03-03 10:23:58 313  WebModuleDestroy Before WaitFor
2021-03-03 10:23:58 313    ^ MainThreadID: 2432
2021-03-03 10:23:58 313    ^ CurrThreadID: 2432
2021-03-03 10:24:02 108  Thread Execute - Inside While - Terminated: True
2021-03-03 10:24:02 108    ^ MainThreadID: 2432
2021-03-03 10:24:02 108    ^ CurrThreadID: 2220
2021-03-03 10:24:02 108  Thread Execute - End - Terminated: True
2021-03-03 10:24:02 108    ^ MainThreadID: 2432
2021-03-03 10:24:02 108    ^ CurrThreadID: 2220

Как видите, WaitFor никогда не заканчивается. Интересно, что OnWebModuleCreate выполняется в другом потоке, чем предполагаемый MainThreadID, но OnWebModuleDestroy выполняется в том же MainThreadID и как таковой CurrentThreadID = MainThreadID, когда WaitFor называется.

Так что я подозреваю, что мне никогда не придется использовать Synchronize и WaitFor в этом случае. Это правильно, или в этом есть нечто большее, чем я вижу?

Что мне использовать для синхронизации, если, по моему предположению, так называемый основной поток может вообще не вызывать CheckSynchronize, и могу ли я просто сделать один WaitForSingleObject вместо WaitFor? Я подозреваю, что синхронизация, в смысле основного потока, может здесь вообще никогда не потребоваться.

Обновить

После долгого чтения я наткнулся на информацию о DLLMain/DLLProc и о том, что он вызывается всякий раз, когда процесс «присоединяется / отсоединяется», а поток «присоединяется / отсоединяется».

Также есть сообщение в блоге Рэймонда Чена, в котором обсуждаются взаимоблокировки потоков в DLLMain.

Таким образом, кажется, что поскольку DLLMain вызывается, когда DLL выгружается, а также когда поток выходит, я не могу выполнить поток WaitFor в коде выгрузки DLL (OnWebModuleDestroy), поскольку это, очевидно, вызвать тупик, так как один будет ждать другого. Буду признателен, если кто-нибудь может посоветовать это мнение.

0
Blurry Sterk 3 Мар 2021 в 11:54

1 ответ

Лучший ответ

Резюмируем:

DLLMain/DLLProc библиотеки DLL вызывается всякий раз, когда процесс "прикреплен / отсоединен", а поток "присоединен / отсоединен"

Также есть сообщение в блоге Раймонда Чена, в котором [обсуждает тупиковые ситуации] [4] в DLLMain.

Поскольку DLLMain вызывается при выгрузке DLL, а также при выходе из потока, я не могу выполнить поток WaitFor в коде выгрузки DLL (OnWebModuleDestroy), так как это, очевидно, вызовет взаимоблокировку, поскольку один будет ждать другого.

После комментария Р. Хука я начал искать, вызывает ли Apache какую-то процедуру очистки, прежде чем он фактически выгружает библиотеку модуля и наткнулся на ее apr_pool_cleanup и сопутствующую процедуру apr_pool_cleanup_register, которая регистрирует перехватчик в уборка. К счастью, это уже было обработано внутри модуля Web.ApacheApp и его класса TApacheApplication, который имеет OnTerminate "событие", которое вызывается apr_pool_cleanup.

Назначение кода очистки "событию" OnTerminate позволяет правильно избавиться от ваших потоков, не опасаясь тупиковых ситуаций в DLLMain:

TApacheApplication(Application).OnTerminate := OnApplicationTerminate;
0
Blurry Sterk 8 Мар 2021 в 08:43