While working on the logging mechanism in the RunAsSys example of the JEDI, I encountered a really strange problem on XP systems. The application worked fine on Vista system. However on Windows XP, it just stopped working with no response at all. This wasn’t the usual dead lock situation you know in multi threaded application, because there were only one thread. So what happened? I could luckily debug the program and found out that it hang on a log sequence. Going deeper, I found out that on XP a call to EnterCriticalSection hang indefinitely when it was called right after two subsequent calls to LeaveCriticalSection. Of course it was wrong. I should always leave the critical section only once for every entering. How could that happen? Just accident. I didn’t realize that the Delphi function “exit” called within try/finally, jumps into the finally block first and then exits the function.

procedure XY;
var CS : TCriticalSection;
try
  CS.Enter;
  if SomeThingBadHappensHere then
  begin
    CS.Leave;
    exit;
  end;
finally
  CS.Leave;
end;

Thus the next call to Enter will hang on XP Systems. It is in contrast to Vista where it works. So why did this happen? Maybe the internal lock may tell us more. The following output comes from this little test application:

begin
  CS := TCriticalSectionDeb.Create;
  DebugOut;
  CS.Enter;
  DebugOut;
  CS.Enter;
  DebugOut;
  CS.Enter;
  DebugOut;

  DebugOut(‘leave’);
  CS.Leave;
  DebugOut;

  CS.Leave;
  DebugOut;
  readln;
end;

The result shows the following output. The values in the left column are from Windows XP, where the ones on the right side come from Windows Vista.

Version XP Vista
LockCount: -1 -1
RecursionCount: 0 0
LockCount: 0 -2
RecursionCount: 1 1
LockCount: 1 -2
RecursionCount: 2 2
LockCount: 2 -2
RecursionCount: 3 3
leave
LockCount: 2 -2
RecursionCount: 3 3
LockCount: 1 -2
RecursionCount: 2 2
LockCount: 0 -2
RecursionCount: 1 1
LockCount: -1 -1
RecursionCount: 0 0

You see a significant difference between XP and Vista. Windows XP counts the (internal) Lockcount upwards. It is in contrast to Windows Vista, where the LockCount counts downwards and stops at -2. But why does a call to EnterCriticalSection lock up the app in XP and not in Vista?

The initial state at the beginning and at the (proper) end is

LockCount: -1 -1
RecursionCount: 0 0

In this case, we can safely call EnterCriticalSection without waiting. There is no lock on the section. Let us see and compare what happens when we call Leave one more time.

LockCount: -2 -1
RecursionCount: -1 -1

Windows XP decreases the RecursionCount and additionally the LockCount value. In Vista only the RecursionCount is decreased. That is the difference because EnterCriticalSection blocks only if it finds a LockCount different of -1 (the initial state). The Windows Vista API seems to be aware that the same thread calls EnterCriticalSection several times and thus just increases the recursion value.

That is the reason why we have to be careful even if we use critical sections in only one thread. The MSDN help about LeaveCriticalSection states this problem the following way:

If a thread calls LeaveCriticalSection when it does not have ownership of the specified critical section object, an error occurs that may cause another thread using EnterCriticalSection to wait indefinitely.

It should be instead of “another thread” say “any thread”.

How did I get the lock count information from the VCL TCriticalSection? The next code shows this simple task:

type
  TCriticalSectionDeb = class(TCriticalSection)
  public
    property Section: TRTLCriticalSection read FSection;
  end;

var CS : TCriticalSectionDeb;

procedure DebugOut(Head : String = );
begin
  Writeln;
  if Length(Head) > 0 then
    WRiteln(Head);
  Writeln(‘LockCount: ‘, CS.Section.LockCount);
  Writeln(‘RecursionCount: ‘, CS.Section.RecursionCount);
  Writeln(‘EntryCount: ‘, CS.Section.DebugInfo.EntryCount);
end;

begin
  CS := TCriticalSectionDeb.Create;

  DebugOut;
  CS.Enter;
  DebugOut;
  …