Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing a simple registry watcher #365

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

keith-horton
Copy link
Member

Implementing a simple registry watcher, which maintains the same contract as the unique_threadpool objects it uses for the callbacks - it guarantees all callbacks have completed and no more will be invoked when the d'tor has completed

…ract as the unique_threadpool objects it uses for the callbacks - it guarantees all callbacks have completed and no more will be invoked when the d'tor has completed
slim_registry_watcher_t() WI_NOEXCEPT = default;

// Exception-based constructors
slim_registry_watcher_t(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, ::wistd::function<void(::wil::RegistryChangeKind)>&& callback)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it would be safer if there were a series of constructors that took various kinds of smart pointers (weak and strong). The registry watcher can then keep the callback alive (if given a strong pointer) or allow it to safely destruct (if given a weak pointer).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm at a loss to see what benefit differing behaviors provides based off the type of callback one provides (well, what type of container holds their callback).

It seems some folks really wanted to decouple the object enabling the callbacks from their callback code that runs. That doesn't make sense to me - if I want to unregister, I don't want more callbacks. But I'm only 1 person, so cool - if some folks want to employ weak-ref models with their callback designs, and they don't want to worry about properly synchronizing when objects are destroyed -- then they have an option.

But I think that should be a strong interface contract at the class level, not implicit behavior on what type of callback container they provide.

Hope this makes sense (?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I expressed my suggestion well. What I'm trying to suggest is that slim_registry_watcher is a tuple containing everything you already have AND some sort of lifetime management for the thing that is being called back. That could be a strong reference, a weak reference, or for non-class-based code it can be left off.

Providing a strong or weak pointer should completely close the race with object destruction. A strong reference prevents destruction. A weak reference allows the callback to know that it is already destructed so it can NOOP.

inline void delete_registry_watcher_state(_In_opt_ registry_watcher_state* watcherStorage) { watcherStorage->Release(); }

typedef resource_policy<registry_watcher_state*, decltype(&details::delete_registry_watcher_state),
details::delete_registry_watcher_state, details::pointer_access_none> registry_watcher_state_resource_policy;
}
/// @endcond

template <typename err_policy = ::wil::err_exception_policy>
class slim_registry_watcher_t
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one feature that is missing from the registry watcher is access to the HKEY that it holds.

if you could add that feature it would be nice.

{
public:
// HRESULT or void error handling
typedef typename err_policy::result result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, but here using result = err_policy::result; is the more modern syntax.

return unique_registry_watcher_failfast(rootKey, subKey, isRecursive, wistd::move(callback));
return ::wil::unique_registry_watcher_failfast(rootKey, subKey, isRecursive, wistd::move(callback));
}
inline ::wil::unique_slim_registry_watcher_failfast make_slim_registry_watcher_failfast(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, add a blank line

return unique_registry_watcher_failfast(wistd::move(keyToWatch), isRecursive, wistd::move(callback));
return ::wil::unique_registry_watcher_failfast(wistd::move(keyToWatch), isRecursive, wistd::move(callback));
}
inline ::wil::unique_slim_registry_watcher_failfast make_slim_registry_watcher_failfast(unique_hkey&& keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, add blank line

SECTION("unique_slim_registry_watcher_nothrow with recurssive changes")
{
wil::unique_hkey hkey;
REQUIRE_SUCCEEDED(wil::reg::create_unique_key_nothrow(HKEY_CURRENT_USER, testSubkey, hkey, wil::reg::key_access::readwrite));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using the throwing version to simplify the test and avoid treating this setup code as the test subject. that is avoid using REQUIRE_XXX on things other than the test subject (your slim reg watcher)

{
callbackTracking.ResetEvent();
REQUIRE_SUCCEEDED(wil::reg::set_value_nothrow(hkey.get(), L"test", count));
REQUIRE(callbackTracking.wait(500));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test should be fast, avoid Sleep. use witest::RunUntilTrue to probe for the desired state and finish as soon as it is achieved.

{
callbackEntered.SetEvent();
// now wait 5 seconds - ensuring we are in the d'tor on the main thread
Sleep(5000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

witest::RunUntilTrue

::wil::unique_threadpool_wait m_threadPoolWait;
bool m_isRecursive;

static void __stdcall callback(PTP_CALLBACK_INSTANCE, void* context, TP_WAIT*, TP_WAIT_RESULT) WI_NOEXCEPT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to enable testing the races, you can add a template param used in a constexpr expression that lets you control a delay value. this is a NOP in retail, but your test version has a call to Sleep().

This is how I tested the races with COM apartment variables. see this example

@@ -5195,3 +5195,127 @@ TEST_CASE("BasicRegistryTests::key_heap_string_nothrow_iterator", "[registry]]")
REQUIRE(count == 4);
}
}
TEST_CASE("BasicRegistryTests::slim_registry_watcher_t", "[registry]]")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a tricky case that I struggled with when creating the registry watcher was the ability to call reset in the callback. Maybe that is the source of the bug. here is the test for that.

but make sure you can tolerate that case too.

inline void delete_registry_watcher_state(_In_opt_ registry_watcher_state* watcherStorage) { watcherStorage->Release(); }

typedef resource_policy<registry_watcher_state*, decltype(&details::delete_registry_watcher_state),
details::delete_registry_watcher_state, details::pointer_access_none> registry_watcher_state_resource_policy;
}
/// @endcond

template <typename err_policy = ::wil::err_exception_policy>
class slim_registry_watcher_t
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

write a comment that explains the relationship between this new imlp and the existing.

I assume we want all uses of registry_watcher to be replaced with this one. If so maybe safe_registry_watcher is a better name. or registry_watcher2 to make it clear it is a superset.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants