December 24th, 2020

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

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.

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.

5 comments

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

  • switchdesktopwithfade@hotmail.com

    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...

    Read more
  • Richard Yu

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

    • 紅樓鍮

      Also Callback should be static

  • 紅樓鍮

    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?

  • 紅樓鍮

    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)?