Make registries thread-safe, and add pre/post hooks for operations.
Review Request #13802 — Created April 27, 2024 and submitted
We've had some long-standing problems where, on occasion, two threads
will attempt to populate or manipulate a registry at the same time and
end up with bad results. The common scenario is that two threads would
initiate population of the registry, one would win, and the other would
see 0 items in the registry.Registries were not built to be thread-safe, but they should have been.
Locks around mutation operations can do a lot to help with that, amd
this change introduces those.Where this got more complicated is how we handled state management and
registry customization.Registries were previously in one of two states: Populated, or
unpopulated. When population started, but before any default items were
registered, the registry was put into the Populated state, which would
make any other threads think that it was ready to pull items out of the
registry.What we needed was a third state: Populating. This is set when
population begins, but before/during default item registration. To
represent this, we now have aRegistryState
enum, which represents a
PENDING
(initial) state,POPULATING
, andREADY
(populated).The old
populating
property now returnsTrue
if this state is
anything butPENDING
, helping cover existing behavior. This is
deprecated in favor ofstate
.The other part is registry customization. We had registries that would
subclasspopulate()
,register()
, andunregister()
, adding
specialized pre/post behavior. However, this wasn't compatible with how
locks and state are now managed. The solution here is to have new hook
methods that subclasses can override to manage state at the right time
and with thread-safety guarantees.Existing registries will continue to work, but just won't have the full
degree of thread safety until they move to the hooks.The same reentrant lock is shared for all mutation operations, which
helps ensure that one thread won't be populating while another is
resetting or registering. It's still possible that one thread may expect
an item to be there that won't exist due to another thread's operations,
but now at least there won't be mixed state, and it's still always up to
the caller to handle any exceptions anyway.Along with this, thread safety has been added to
lazy_import_registry()
. Previously, if two threads called this
simultaneously, multiple registries could be created, though only one
would be assigned to the resulting variable. Now there's a lock in
place, ensuring only one is ever created. This was likely never an issue
in practice, but it's worth ensuring we don't have side effects from
redundant registry creation.
Djblets and Review Board unit tests pass.
Ran each of these Djblets Registry threading tests without the locks.
It reproduced the issues we've seen sporadically in production.This will need real-world testing in production.
Summary | ID |
---|---|
39bec43294eabd7e7c4b7be10c435cf4eb688d1b |
Description | From | Last Updated |
---|---|---|
This sentence doesn't make sense. I think you meant to say "usage and"? Or take out "usage an" altogether. |
maubin | |
Should add a "Version Added". |
maubin | |
Should add a "Version Added". |
maubin | |
I don't think we need to includes this "Raises" section in the base class? |
maubin | |
I think this can be taken out now? Per my comment in the previous review |
maubin |
- Change Summary:
-
- Fixed a typo in
RegistryState
. - Updated
OrderedRegistry
hook docs to be more clear on expectations and uses.
- Fixed a typo in
- Commits:
-
Summary ID 05ab068c2a8156b98da60421541f3277c28d1d80 58988bf2855fddaaf373142bc6e1778134a621db