Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the OwningThread member of CRITICAL_SECTION of type HANDLE, when it is denoting the thread ID?

I'm trying to add some debug checking for a CRITICAL_SECTION unlocking code, and I tried the following:

...
  if (m_pCritSect) {
    ASSERT(m_pCritSect->OwningThread == GetCurrentThreadId());
    LeaveCriticalSection(m_pCritSect);
  }
}

From debugging CRITICAL_SECTIONS (With VS 2005, mostly on WindowsXP) I "know" that the value of OwningThread (member of the RTL_CRITICAL_SECTION structure defined in winnt.h) is the value of th ID of the thread holding the lock.

However thread IDs are represented by DWORD (typedef for unsigned long) values while this variable has type HANDLE (typedef for void*) requiring a reinterpret_cast for the use of the HandleToULong Macro from basetsd.h for the above code to work.

Even the MSDN docs state:

When the first thread calls the EnterCriticalSection routine, (...) OwningThread becomes the thread ID of the caller.

So why on earth is this defined as a HANDLE?


Edit Note: I found a statement where a poster suggests that the HANDLE / DWORD-Id mismatch is some known misfeature of some Windows internals. So maybe this is the case here too:

GetCurrentThreadId returns a DWORD, which I send up to the kernel in a message. PsLookupThreadByThreadId takes the thread Id in a HANDLE, ... ...

This is a known Windows API bug ("known" in that I talked to the relevant filter manager DEV about this, as it shows up in Filter Manager as well because of the I/O Manager API issue.) As long as you don't have more than a half billion or so threads and processes (they use a single shared handle table) you'll be fine. Maybe by the time that's a real issue, we'll be running something different. [RE: ThreadId to HANDLE for 64 bit?, 08 Aug 08 14:21, Tony Mason]

like image 901
Martin Ba Avatar asked Oct 01 '12 14:10

Martin Ba


2 Answers

Any identifier in the SDK whose name starts with RTL or Rtl is code or declarations that are part of the runtime layer, the glue that marries the well-documented Winapi to the undocumented native operating system api. The winapi is cast in stone, the native operating system changes heavily with each Windows release. Inevitably, the glue changes as well.

The winapi is the documented layer, the native operating system is undocumented. The runtime layer was undocumented as well, but over time parts of it were revealed. Either because it back-fills a missing feature in the winapi. Or, in this case, because is genuinely useful to troubleshoot problems. One core problem with doing so however is that once a declaration is revealed, Microsoft can never change it again. Because doing so will break existing programs, a great burden to its customers.

So surely, the ThreadOwner field once truly held the handle to the thread in a previous Windows version. Note how the LockSemaphore is misleading too, it is actually an auto-reset event. Too late to fix it, the cat is out of the bag.

like image 86
Hans Passant Avatar answered Nov 07 '22 04:11

Hans Passant


I believe that the main reason is that it is an implementation detail. I wouldn't be surprised if at one time in history it really was a handle or something like that.

Additionally, I would strongly suggest not using the internal members in production code, and I am not alone. If you look closely, synchronization APIs use CRITICAL_SECTION which you won't find documented as a structure in MSDN, and not RTL_CRITICAL_SECTION (which is typedef'ed to CRITICAL_SECTION)

The value which is stored in OwningThread member is taken from CLIENT_ID part of Thread Information Block. In CLIENT_ID it as modeled as PVOID which is likely why it is modeled in the same way within CRITICAL_SECTION:

typedef struct _CLIENT_ID
{
   PVOID UniqueProcess;
   PVOID UniqueThread;
} CLIENT_ID, *PCLIENT_ID;
like image 31
Zdeslav Vojkovic Avatar answered Nov 07 '22 05:11

Zdeslav Vojkovic