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

Raymond Chen

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.

3 comments

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

  • 紅樓鍮 0

    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 0

      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?

      • 紅樓鍮 0

        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.

Feedback usabilla icon