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

Raymond Chen

We continue our exercise of emulating the REG_NOTIFY_THREAD_AGNOSTIC flag by making the whole thing a coroutine, assuming you’re willing to take the anachronism to an extreme by using C++20 features in code intended to run on Windows XP.

auto RegNotifyChangeKeyValueAsync(
  HKEY hkey,
  BOOL bWatchSubtree,
  DWORD dwNotifyFilter,
  HANDLE hEvent)
{
  struct awaiter
  {
    HKEY m_hkey;
    BOOL m_bWatchSubtree;
    DWORD m_dwNotifyFilter;
    HANDLE m_hEvent;
    LONG m_result;
    std::experimental::coroutine_handle<> m_handle;

    bool await_ready() const noexcept { return false; }

    bool await_suspend(std::experimental::coroutine_handle<> handle)
    {
      m_handle = handle;
      if (!QueueUserWorkItem(
          Callback,
          this,
          WT_EXECUTEINPERSISTENTTHREAD)) {
        m_result = static_cast<LONG>(GetLastError());
        return false;
      }
      return true;
    }

    LONG await_ready() const noexcept { return m_result; }

    DWORD CALLBACK Callback(void* param)
    {
      auto self = reinterpret_cast<awaiter*>(param);
      self->m_result = RegNotifyChangeKeyValueArgs(
        self->m_hkey,
        self->m_bWatchSubtree,
        self->m_dwNotifyFilter,
        self->m_hEvent,
        TRUE);
      self->m_handle();
      return 0;
    }
  };

  return awaiter(hkey, bWatchSubtree, dwNotifyFilter, hEvent);
}

The catch here is that the coroutine continues on the persistent thread, and you’re not supposed to run long operations on the persistent thread, so the caller should probably resume_background to get onto a non-persistent thread pool thread.

We can’t do the work ourselves of resuming on a non-persistent thread pool thread, say, by doing another QueueUserWorkItem, because if the second call fails, we are stuck on the persistent thread. If we are willing to bump the minimum system requirements to Windows Vista, we could preallocate the work items and remove the possibility of getting stuck halfway through.

So let’s go all the way with this absurd exercise, next time.

5 comments

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

  • 紅樓鍮 0

    Maybe link resume_background to its Docs page (since it’s not part of standard C++ and the reader may not know what it is)?

  • 紅樓鍮 0

    We can’t… [do] another QueueUserWorkItem, because if [that] fails, we are stuck on the persistent thread.

    Couldn’t resume_background fail? And if QueueUserWorkItem fails, couldn’t we simply handle the error and leave?

  • Richard Yu 0

    There are two `await_ready` in the code. Is it a mistake? I guess the second one should be `await_resume`.

    • 紅樓鍮 0

      Also Callback should be static

  • switchdesktopwithfade@hotmail.com 0

    I couldn’t help but ask “how long does the persisted pool thread persist”?

    And the docs say:
    “The callback function is queued to a thread that never terminates. It does not guarantee that the same thread is used each time. This flag should be used only for short tasks or it could affect other timer operations…Note that currently no worker thread is truly persistent…”

    It says the thread doesn’t terminate but then there’s a wink that says to expect it to disappear anyway. The whole thing sounds like a gamble. I should probably ask around, a lot of laymen have become strangely familiar with Windows XP internals in the past few months…

    I’m curious why you aren’t calling RegisterWaitForSingleObject() for the actual wait because only RegNotifyChangeKeyValue() has to be called on the persistent thread.

Feedback usabilla icon