December 22nd, 2020

How can I emulate the REG_NOTIFY_THREAD_AGNOSTIC flag on systems that don’t support it? part 2

We continue our exercise of emulating the REG_NOTIFY_THREAD_AGNOSTIC flag, this time for the case where we don’t have a readily-available persistent thread. In that case, we can use one from the thread pool, but it’ll be a bit trickier because we’ll have to wait for the thread pool work item to run in order to get the result.

struct RegNotifyChangeKeyValueAsyncArgs
{
  HKEY hkey;
  BOOL bWatchSubtree;
  DWORD dwNotifyFilter;
  HANDLE hEvent;
  LONG result;
  HANDLE hComplete;

  ~RegNotifyChangeKeyValueAsyncArgs()
  {
    if (hComplete) CloseHandle(hComplete);
  }
};

DWORD CALLBACK RegNotifyChangeKeyValueOnPersistentThread(
    void* param)
{
  auto args = reinterpret_cast<
               RegNotifyChangeKeyValueAsyncArgs*>(param);
  args->result = RegNotifyChangeKeyValue(
    args->hkey,
    args->bWatchSubtree,
    args->dwNotifyFilter,
    args->hEvent,
    TRUE);
  SetEvent(args->hComplete);
  return 0;
}

// See discussion before using this function.
LONG RegNotifyChangeKeyValueAsync(
  HKEY hkey,
  BOOL bWatchSubtree,
  DWORD dwNotifyFilter,
  HANDLE hEvent)
{
  RegNotifyChangeKeyValueAsyncArgs args = 
    { hkey, bWatchSubtree, dwNotifyFilter, hEvent,
      ERROR_INVALID_PARAMETER,
      CreateEvent(nullptr, TRUE, FALSE, nullptr) };
  if (!args.hComplete) {
    return static_cast<LONG>(GetLastError());
  }
  if (!QueueUserWorkItem(
          RegNotifyChangeKeyValueOnPersistentThread,
          &args,
          WT_EXECUTEINPERSISTENTTHREAD)) {
    return static_cast<LONG>(GetLastError());
  }
  WaitForSingleObject(args->hComplete, INFINITE);
  return args.result;
}

One of the tricks of this exercise is limiting the solution to the features that were available in Windows XP.

Since we need to do the registration from a thread that will remain running indefinitely, we ask the thread pool for help by calling QueueUserWorkItem with the WT_EXECUTE­IN­PERSISTENT­THREAD flag. The callback runs on a persistent thread, and it is there that we call Reg­Notify­Change­Key­Value and pass the result back. Meanwhile, the main thread waits for the work item to finish so it can propagate the results to the caller.

This design seems to work, but it has a trap: If the caller of Reg­Notify­Change­Key­Value­Async is itself on the thread pool, then you end up with the risk of starving the thread pool and preventing it from doing the thing that would relieve the starvation.

We’ll look at ways of fixing this next time.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

3 comments

Discussion is closed. Login to edit/delete existing comments.

  • 紅樓鍮

    Since this operation fundamentally (as viewed from the userland) has thread affinity, I’d probably just spin up my own thread to perform it.

    • Raymond ChenMicrosoft employee Author

      This can get expensive if you have 200 objects that all want to monitor registry keys. Are you going to create 200 threads and keep them alive for the lifetime of their respective objects?

      • 紅樓鍮

        I wanted to say I’d create one thread that sits in a loop handing requests from the objects; I’m just not comfortable using a thread pool thread to do this.