-
Notifications
You must be signed in to change notification settings - Fork 511
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
Multi-tenancy stale wallet clean up #1692
Multi-tenancy stale wallet clean up #1692
Conversation
Signed-off-by: Timo Glastra <timo@animo.id> Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
remove usage of self._instances in AskarProfileMultitenantManager add profile_id to AskarProfile init (to clearly differentiate between AskarProfiles where default profile is used and alternate profiles are used) base update_wallet only updates records; managers where profiles are held open are responsible for updating currently open profiles Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
I'm still in the process of validating the cache behavior so I've opened this as a draft PR but I would appreciate feedback on what's here so far. Due to resourcing constraints, it's not likely that I'll be able to implement a time based cache (I haven't evaluated what that would look like, though, so if it ends up being super easy, maybe lol). I would be particularly interested to hear feedback from @TimoGlastra and @andrewwhitehead since the two of you are the original participants on #928 but more than happy to get feedback from anyone. |
@PaulWen - this would seem a really good PR to test with the Load Generator. Do you have an easy way to run tests on a PR branch? |
Odd how submitting a PR suddenly brings clarity lol; as implemented, if more than 100 (or whatever the capacity is defined as) is actively in use, this can result in rapid opening and closure of wallets and |
This AcaPy repo is already integrated as a git submodule into the Load Generator repo to build AcaPy images with a debugger. Therefore, it should not be difficult to checkout any branch and build a docker image in a similar manner. @dbluhm Could you describe to me what a suitable test flow would look like? Feel free to drop me a PM in Discord. |
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Codecov Report
@@ Coverage Diff @@
## main #1692 +/- ##
==========================================
- Coverage 95.23% 95.21% -0.03%
==========================================
Files 525 526 +1
Lines 33056 33143 +87
==========================================
+ Hits 31482 31558 +76
- Misses 1574 1585 +11 |
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
I've sorted out most of the issues with this PR. I was having a heck of a time identifying where references to profiles were being held open but I've found the culprits (reponders held references to profiles/contexts and then profiles held references to responders...) and my rudimentary tests behave as expected. I'll do my bit to cover new lines better for the sake of code coverage but I'm open to ideas for how or even if we should have tests exercising this functionality inside of ACA-Py. Should have this ready for review soon. |
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
And make tests follow pytest convention Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
aries_cloudagent/askar/profile.py
Outdated
def _finalize(opened: Optional[AskarOpenStore]): | ||
if opened: | ||
LOGGER.debug("Profile finalizer called; closing wallet") | ||
asyncio.get_event_loop().create_task(opened.close()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's nice but not necessary to close the store this way, as the library has its own finalizers. With multiple profile instances potentially referencing the same Store it would probably not be correct to close the store when a single profile is closed, either.
aries_cloudagent/core/profile.py
Outdated
@@ -115,6 +116,18 @@ def inject_or( | |||
async def close(self): | |||
"""Close the profile instance.""" | |||
|
|||
def finalizer(self) -> Optional[finalize]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the finalizer can just be set up in the profile's __init__
method when required. We don't have to keep a reference to it, and requiring the caller to initialize it seems error prone. (It could make testing a little harder.)
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Not realizing that Askar already had finalizers and considering @andrewwhitehead's comments regarding requiring the caller to initialize the finalizer being potentially error prone, I've simplified the finalizer additions. Essentially, finalizers for Indy SDK wallet profiles are always created. I don't think there's a scenario where it would be appropriate for the wallet to remain opened after the profile has gone out of scope so I think this is a valid simplification. In other words, all profiles managed by the profile cache are now assumed to clean up their own resources after falling out of scope and it is up to the profile implementation to ensure this is the case. |
aries_cloudagent/indy/sdk/profile.py
Outdated
""" | ||
|
||
def _finalize(opened: Optional[IndyOpenWallet]): | ||
if opened: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think opened can be None because it receives the original IndyOpenWallet instance. Maybe if opened.handle
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This came as a recommendation from pyright which indicates that IndySdkProfile.opened is IndyOpenWallet | None
. I think it derived this from the self.opened = None
line in the close
method.
Following that same vein, I think it was possible before for the finalizer to be created after close
had been called. Now that it's only intended to be called in __init__
, I don't think this is a problem any more. I'll fix that.
@@ -15,13 +16,23 @@ class AskarProfileMultitenantManager(BaseMultitenantManager): | |||
|
|||
DEFAULT_MULTIENANT_WALLET_NAME = "multitenant_sub_wallet" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could fix this variable name
aries_cloudagent/multitenant/base.py
Outdated
@abstractproperty | ||
def open_profiles(self) -> Iterable[Profile]: | ||
"""Return iterator over open profiles.""" | ||
... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ellipsis?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was to satisfy pyright more than anything else. Oddly, it seems fine if I do @property
and @abstractmethod
but I have to add the ellipsis if I use @abstractproperty
. I'll remove this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently it's about the same as writing 'pass', I just haven't seen it much before.
Added a couple small comments but it looks great otherwise, I'd like to get this merged. |
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Signed-off-by: Daniel Bluhm <dbluhm@pm.me>
Digging into the unit test failure; it's not behaving quite the same way when I run |
This PR is intended to supersede #928. Due to significant changes since the original PR was opened, I cherry-picked a commit but had to modify it. History has not been well preserved, unfortunately. Credit to @TimoGlastra for the original work on the
ProfileCache
especially and for assistance in finding the solution implemented here.This PR adds a LRU cache for profiles opened by
MultitenantManager
(corresponding to wallet typesindy
andaskar
, but notaskar-profile
).weakref.finalize
is used to ensure the profiles are closed when they fall out of scope. An ordered dictionary of profiles holds a (strong) reference to the profile until it is evicted. If that profile happens to still be in use at the time it is evicted, it will not be closed until other (strong) references to it expire, i.e. after processing of a message or admin request has finished. This exact scenario is unlikely but possible.In addition to this profile caching mechanism, I also updated a few aspects of the
BaseMultitenantManager
and its subclasses to make them more consistent with conventions used in ACA-Py.Also, after being very confused by handling of askar profiles (not to be confused with
AskarProfile
s) within theAskarProfileMultitenantManager
andAskarProfile
, I did some light updates to (I hope) improve clarity.A brief listing of known limitations:
As implemented, the capacity of the LRU cache is statically defined as 100.Update:
I've taken a pass at improving the multitenancy-config argument. It will still accept json (read: this shouldn't be a breaking change) but it will now also accept
key=value
pairs. I'm still a bit iffy on whether these shouldn't just be their own separate arguments though...