MEGA Search
20.3 Million


Sign Up
From: Markus Springweiler  
Subject: WaitForSingleObject on a thread hangs in an ISAPI
NewsGroup: borland.public.delphi.internet.isapi-webbroker
Date Posted: 19-Mar-2003 at 11:57:9 PST
Hello,


I created a simple test thread class which outputs several debug strings. I
put it into a unit which creates and frees one thread so that including
simple "uses" is all you have to do to test it. The source is at the end of
this mail.


If I use this unit in a newly created project (VCL) then it does its job
without problems: No work queued by TWorkThread.AddWork gets ever lost.

If I close the application the Event log looks like following:

[...]
ODS: Terminating STT
ODS: inherited terminate
ODS: SetEvent (without work)
ODS: Event-WaitFor returned: wrSignaled
ODS: processing: nothing to do
ODS: WaitForSingleObject STT
ODS: execution stopped: 1072
Thread Exit: Thread ID: 1072.
ODS: Freeing STT; WfSO-Result: 0
ODS: destroying tWorkThread
ODS: tWorkThread Destroyed
[...]

All as I expected -- and as it should be (I think so).


Now I create a new ISAPI project and also include my test unit: The work is
queued and processed without problems, too.

But when I stop the webserver it runs into problems and the Event log looks
like:

[...]
ODS: Terminating STT
ODS: inherited terminate
ODS: SetEvent (without work)
ODS: Event-WaitFor returned: wrSignaled
ODS: processing: nothing to do
ODS: WaitForSingleObject STT
ODS: execution stopped: 2628
ODS: Freeing STT; WfSO-Result: 258
ODS: destroying tWorkThread
ODS: tWorkThread Destroyed
Module Unload: OHTTPD20.dll.
Module Unload: WSOCK32.dll.
Module Unload: Project1.dll.
Module Unload: OLEAUT32.dll.
Module Unload: WSHTCPIP.dll.
Module Unload: ole32.dll.
Thread Exit: Thread ID: 2628.
[...]

The big difference is that the thread doesn't exit after execution of
TWorkThread.Execute -- and so the call to WaitForSingleObject wouldn't
return if there wasn't specified a timeout (Wait_Timeout=258).

I traced a little and it seems that the call to the windows kernel function
"ExitThread" doesn't return, and so the thread is terminated at the very end
of programm execution -- more likely it is killed.


I'm using D7 with OmniHTTPd and surely IsapiThreadPool is NOT included in
the project.


Here comes the unit:

unit SimpleTestThread;

interface

uses
  Windows, Classes, SyncObjs;

type
  tWorkThread = class(tThread)
  private
    FSL : tStringList;
    FEv : tEvent;
    FCS : tCriticalSection;

  protected
    procedure Execute; override;

  public
    constructor Create;
    destructor Destroy; override;

    procedure AddWork(const Work: string);
    procedure Terminate;

  end;

var
  STT : tWorkThread;

implementation

uses
  SysUtils, Math;

{ tWorkThread }

procedure tWorkThread.AddWork(const Work: string);
begin
     FCS.Enter;
     try
       FSL.Add(Work);
       OutputDebugString(pChar(Work + ' added'));
     finally
       FCS.Leave;
     end;
     FEv.SetEvent;
end;

constructor tWorkThread.Create;
begin
     inherited Create(false);

     FSL := tStringList.Create;
     FEv := TEvent.Create(nil, true, false, '');
     FCS := tCriticalSection.Create;

     OutputDebugString('tWorkThread Created');
end;

destructor tWorkThread.Destroy;
begin
     OutputDebugString('destroying tWorkThread');
     FCS.Free;
     FEv.Free;
     FSL.Free;

     inherited;
     OutputDebugString('tWorkThread Destroyed');
end;

procedure tWorkThread.Execute;
const
     cWaitResult : array [tWaitResult] of string
                 = ('wrSignaled', 'wrTimeout', 'wrAbandoned', 'wrError');
  var
     Finished, moretodo : boolean;
     WR : TWaitResult;
     s : string;
begin
     OutputDebugString('executing...');

     finished := false;
     repeat
       OutputDebugString('waiting for event...');
       WR := FEv.WaitFor(15 * 1000);
       OutputDebugString(pChar('Event-WaitFor returned: ' +
cWaitResult[WR]));

       case WR
       of wrSignaled :
            begin
              repeat
                FCS.Enter;
                try
                  if FSL.Count>0
                  then begin
                         s := FSL[0];
                         FSL.Delete(0);
                       end
                  else s := 'nothing to do';

                  moretodo := FSL.Count > 0;

                  if not moretodo
                  then FEv.ResetEvent;
                finally
                  FCS.Leave;
                end;

                OutputDebugString(pChar('processing: ' + s));
                Sleep(randomrange(200,800));

              until Terminated or not moretodo;
            end;

          wrTimeout :
            begin
              OutputDebugString('TimeOut sleep');
              Sleep(2000);
              FCS.Enter;
              try
                OutputDebugString('workthread 15 sek timeout');
              finally
                FCS.Leave;
              end;
            end;

          wrAbandoned :
            begin
              finished := true;
              OutputDebugString('event abandoned');
            end;

          wrError :
            begin
              finished := true;
              OutputDebugString('event errored');
            end;

       end;

     until Finished or Terminated;

     OutputDebugString(pChar('execution stopped: ' + IntToStr(ThreadID)));
end;

procedure tWorkThread.Terminate;
begin
     OutPutDebugString('inherited terminate');
     inherited;

     OutPutDebugString('SetEvent (without work)');
     FEv.SetEvent;
end;

initialization
  STT := tWorkThread.Create;

finalization
  OutPutDebugString('Terminating STT');
  STT.Terminate;
  OutPutDebugString('WaitForSingleObject STT');
  OutPutDebugString( pChar('Freeing STT; WfSO-Result: '
    + IntToStr(WaitForSingleObject(STT.Handle, 10 * 1000)) ) );
  STT.Free;

end.



Maybe someone has an idea what is going wrong -- maybe I'm doing something
totally wrong; I hope someone can tell me.


Ciao Ciao,
/\/\arkus.
--
AIM Springy1976 * ICQ 30618236 * IRC Springy{76} * MSN springy@gmx.de

Bildersammlung ausmisten? --> http://www.doublepics.de


From: Markus Springweiler  
Subject: Re: WaitForSingleObject on a thread hangs in an ISAPI
NewsGroup: borland.public.delphi.internet.isapi-webbroker
Date Posted: 20-Mar-2003 at 14:7:35 PST
Hi Dmitri,

> I have noticed that if I select "Flush Server" then everything works fine,
> but if I select "Shutdown Server" then I have the same problem with WfSO
as
> you have.

I always only tried "Shutdown Server". Since works with flushing the server
before shutting it down, I can live with that during developing time -- IIS
seems doing it always alright.

> I believe this is a bug in the OmniHTTPd server.

Seems so.


Thank you for helping and trying :)


Ciao Ciao,
/\/\arkus.
--
AIM Springy1976 * ICQ 30618236 * IRC Springy{76} * MSN springy@gmx.de

Bildersammlung ausmisten? --> http://www.doublepics.net


From: Dmitri Oulitski  
Subject: Re: WaitForSingleObject on a thread hangs in an ISAPI
NewsGroup: borland.public.delphi.internet.isapi-webbroker
Date Posted: 20-Mar-2003 at 12:45:40 PST
Hi Markus,

I have noticed that if I select "Flush Server" then everything works fine,
but if I select "Shutdown Server" then I have the same problem with WfSO as
you have.

I believe this is a bug in the OmniHTTPd server.

Regards,

Dmitri Oulitski




From: Markus Springweiler  
Subject: Re: WaitForSingleObject on a thread hangs in an ISAPI
NewsGroup: borland.public.delphi.internet.isapi-webbroker
Date Posted: 20-Mar-2003 at 12:45:2 PST
Hi Dmitri,

> I just installed OmniHTTPd server and checked your example once again.
> It definitely works when I use TerminateExtension procedure
> (i.e. it returns "Freeing STT; WfSO-Result: 0")


I can't still get it to work in a minimalistic ISAPI DLL. I'm using D7 with
Omni 2.10.

Maybe you can send me your simple project, but there is not much I could do
wrong with so less code.

I moved the code from the finalization part into:

function TerminateExtension(dwFlags: DWORD): BOOL;
begin
     OutPutDebugString('Terminating STT');
     STT.Terminate;
     OutPutDebugString('WaitForSingleObject STT');
     OutPutDebugString( pChar('Freeing STT; WfSO-Result: ' +
IntToStr(WaitForSingleObject(STT.Handle, 10 * 1000)) ) );
     STT.Free;

     OutputDebugString('Calling old TerminateExtension');
     result := IsapiApp.TerminateExtension(dwFlags);
     OutputDebugString('TerminateExtension finished');
end;


and the Event log looks like:

[...]
ODS: Terminating STT
ODS: inherited terminate
ODS: SetEvent (without work)
ODS: WaitForSingleObject STT
ODS: Event-WaitFor returned: wrSignaled
ODS: processing: nothing to do
ODS: execution stopped: 3096
ODS: Freeing STT; WfSO-Result: 258
ODS: destroying tWorkThread
ODS: tWorkThread Destroyed
ODS: Calling old TerminateExtension
ODS: TerminateExtension finished
Module Unload: OHTTPD20.dll.
Module Unload: WSOCK32.dll.
Module Unload: Project1.dll.
Module Unload: OLEAUT32.dll.
Module Unload: WSHTCPIP.dll.
Module Unload: ole32.dll.
Thread Exit: Thread ID: 3096.


Ciao Ciao,
/\/\arkus.
--
AIM Springy1976 * ICQ 30618236 * IRC Springy{76} * MSN springy@gmx.de

Bildersammlung ausmisten? --> http://www.doublepics.net


From: Dmitri Oulitski  
Subject: Re: WaitForSingleObject on a thread hangs in an ISAPI
NewsGroup: borland.public.delphi.internet.isapi-webbroker
Date Posted: 19-Mar-2003 at 22:36:4 PST
I just installed OmniHTTPd server and checked your example once again.

It definitely works when I use TerminateExtension procedure
(i.e. it returns "Freeing STT; WfSO-Result: 0")

And it definitely does not work when I don't use TerminateExtension
procedure
and use finalization section
(i.e. it returns "Freeing STT; WfSO-Result: 258")

Perhaps there is something else.

Regards,

Dmitri Oulitski



From: Markus Springweiler  
Subject: Re: WaitForSingleObject on a thread hangs in an ISAPI
NewsGroup: borland.public.delphi.internet.isapi-webbroker
Date Posted: 19-Mar-2003 at 21:48:18 PST
Hi, me again:

> > function TerminateExtension(dwFlags: DWORD): BOOL; stdcall;

I'm frustrated: It doesn't change anything. My TerminateExtension function
gets called but the WaitForSingleObject hangs anyway.

I tested terminating (and waiting for thread exit) from a webmodule action:
Works like a charm.

Surprisingly under IIS there are no troubles, with or without my own
TerminateExtension implementation.


Since TerminateExtension seems to be the earliest point where notification
about termination occurs, I have no idea where else to terminate my own
created threads.


Ciao Ciao,
/\/\arkus.
--
AIM Springy1976 * ICQ 30618236 * IRC Springy{76} * MSN springy@gmx.de

Bildersammlung ausmisten? --> http://www.doublepics.de


From: Markus Springweiler  
Subject: Re: WaitForSingleObject on a thread hangs in an ISAPI
NewsGroup: borland.public.delphi.internet.isapi-webbroker
Date Posted: 19-Mar-2003 at 17:32:8 PST
Hi Dmitri,


> function TerminateExtension(dwFlags: DWORD): BOOL; stdcall;
> begin
[...]
>   Integer(Result) := 1;
> end;

According to how IsapiThreadPool is implemented it should be safer calling
the latest TerminateExtensions procedure known :

  result := ISAPIApp.TerminateExtension(dwFlags);

or if IsapiThreadPool is used (which will be in my case since the production
server is IIS);

  result := IsapiThreadPool.TerminateExtension(dwFlags);

> initialization
>   // by the way I think it is better to move this code into
> GetExtensionVersion procedure

This was also for testing purposes -- the real world app creates the thread
in a webmodule requesthandler on first demand -- and also multiple instances
are used for different conditions (and stored in a tList).

> Make sure that you declared SimpleTestThread after ISAPIApp in the project
> file,
> so compiler will pickup TerminateExtension from SimpleTestThread
> not from ISAPIApp.

I examined nearly every unit involved in the webbroker framework and decided
to do it like IsapiThreadPool does it -- just one minute before your message
appeared in OE :)

> I hope it will help

It did -- so I'm affirmed that this way may be the right one.


Ciao Ciao,
/\/\arkus.
--
AIM Springy1976 * ICQ 30618236 * IRC Springy{76} * MSN springy@gmx.de

Bildersammlung ausmisten? --> http://www.doublepics.de


From: Dmitri Oulitski  
Subject: Re: WaitForSingleObject on a thread hangs in an ISAPI
NewsGroup: borland.public.delphi.internet.isapi-webbroker
Date Posted: 19-Mar-2003 at 15:29:53 PST
You may try to write your own implementation, for example

unit SimpleTestThread;

interface

// .......... you code here

// declare function in interface part of unit
function TerminateExtension(dwFlags: DWORD): BOOL; stdcall;

implementation

// .......... you code here

function TerminateExtension(dwFlags: DWORD): BOOL; stdcall;
begin
  try
    OutPutDebugString('TerminateExtension CALLED');

    OutPutDebugString('Terminating STT');
    STT.Terminate;
    OutPutDebugString('WaitForSingleObject STT');
    OutPutDebugString( pChar('Freeing STT; WfSO-Result: '
      + IntToStr(WaitForSingleObject(STT.Handle, 10 * 1000)) ) );
    STT.Free;
  except
  end;
  Integer(Result) := 1;
end;

initialization
  // by the way I think it is better to move this code into
GetExtensionVersion procedure
  STT := tWorkThread.Create;

// i moved finalization code into TerminateExtension

end.

Make sure that you declared SimpleTestThread after ISAPIApp in the project
file,
so compiler will pickup TerminateExtension from SimpleTestThread
not from ISAPIApp.





> Not in D6 and later: TThread.Create contains this line:
>
> FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self),
CREATE_SUSPENDED,
> ThreadID);
>

Yes, you are right.


I hope it will help.

Regards,

Dmitri Oulitski





From: Markus Springweiler  
Subject: Re: WaitForSingleObject on a thread hangs in an ISAPI
NewsGroup: borland.public.delphi.internet.isapi-webbroker
Date Posted: 19-Mar-2003 at 13:0:55 PST
Hi,

> You need to terminate thread early, for example in the TerminateExtension
> procedure.

Do you have a clue how this can be done this way? I found neither an event
which I could respond to nor a virtual method which could be overidden.

> By the way this code is unsafe
>
> constructor tWorkThread.Create;
> begin
>       inherited Create(false);
>
> // thread may try to use FSL, Fev, FCS before you create them!!!
> // you need to create them before inherited Create(False)

Not in D6 and later: TThread.Create contains this line:

FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED,
ThreadID);

Therefore threads are always created in a suspended state and if it should
not be suspended the thread is resumed in TThread.AfterConstruction, which
is called "...after the last constructor has executed" (D7 Help).


Ciao Ciao,
/\/\arkus.
--
AIM Springy1976 * ICQ 30618236 * IRC Springy{76} * MSN springy@gmx.de

Bildersammlung ausmisten? --> http://www.doublepics.de


From: Dmitri Oulitski  
Subject: Re: WaitForSingleObject on a thread hangs in an ISAPI
NewsGroup: borland.public.delphi.internet.isapi-webbroker
Date Posted: 19-Mar-2003 at 11:21:46 PST
According to MSDN you probably have deadlock in the finalization section.
You are trying to synchronise threads in STT.Free and this is a reason why
you
have deadlock. You should NOT synchronise threads during dll unloading.

You need to terminate thread early, for example in the TerminateExtension
procedure.

By the way this code is unsafe

constructor tWorkThread.Create;
begin
      inherited Create(false);

// thread may try to use FSL, Fev, FCS before you create them!!!
// you need to create them before inherited Create(False)

      FSL := tStringList.Create;
      FEv := TEvent.Create(nil, true, false, '');
      FCS := tCriticalSection.Create;

      OutputDebugString('tWorkThread Created');
end;

Regards,

Dmitri Oulitski