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

Refactor to scope the lifecycle of SQLite storage #1060

Merged
merged 9 commits into from
Jan 6, 2025

Conversation

sirosen
Copy link
Member

@sirosen sirosen commented Dec 6, 2024

The storage adapter is here rewritten entirely away from a "pure functional style" in which attributes and values live in global scopeinto a more object-oriented style. The reason for this refactor is that we want to better control the lifecycle of the storage and ensure that it is closed cleanly when the program exits; furthermore, we want to simulate that same open/close behavior in the testsuite.

The overall tack taken here is

  • wrap SQLiteAdapter in a new container type, CLITokenstorage
  • give CLITokenstorage as many passthrough methods as are necessary to make this transition easy as a rewrite for various usage sites
  • take steps to rewrite usages which do not have a handle on a LoginManager, and use the LoginManager as a way of getting access to the token storage
  • start work on testsutie fixes

This approach has proven more successful than alternatives which were attempted. At time of writing, mypy is passing on the result, some interactive testing seems to work, and tests are in need of numerous adjustments, but not so severe as some other failed rewrites:

50 failed, 977 passed, 13 errors

Therefore, there should be at most 63 remediation steps, after which this should be a functioning option for us.


Opened as a draft PR to share my work as I progress, and collect early feedback.

The storage adapter is here rewritten entirely away from a "pure
functional style" in which attributes and values live in global scope
into a more object-oriented style. The reason for this refactor is
that we want to better control the lifecycle of the storage and ensure
that it is closed cleanly when the program exits; furthermore, we want
to simulate that same open/close behavior in the testsuite.

The overall tack taken here is
- wrap SQLiteAdapter in a new container type, CLITokenstorage
- give CLITokenstorage as many passthrough methods as are necessary to
  make this transition easy as a rewrite for various usage sites
- take steps to rewrite usages which do not have a handle on a
  LoginManager, and use the LoginManager as a way of getting access to
  the token storage
- start work on testsutie fixes

This approach has proven more successful than alternatives which were
attempted. At time of writing, `mypy` is passing on the result, some
interactive testing seems to work, and tests are in need of numerous
adjustments, but not so severe as some other failed rewrites:

  50 failed, 977 passed, 13 errors

Therefore, there should be at most 63 remediation steps, after which
this should be a functioning option for us.
This resolves test failures due to the scope contract version data
being absent.
The LoginManager now self-registers itself with the click context to
call `call_on_close` when instantiated via the command decorator.

In order to handle the effects of this, a few changes are needed:

- CLITokenstorage.close no longer tries to clear
  `internal_native_client` -- with the attribute patched in many
  cases, this causes failures and does not provide a clear benefit

- Uses of the LoginManager.requires_login decorator in tests now
  leverage the test_click_context in order to ensure that the context
  lookup will not fail

- Reorder the closing semantics for `test_token_storage` used by the
  testsuite -- `close()` is mocked to do nothing, and then the true
  closing method is called when the fixture is finalized. As a result,
  we can inspect the storage after a command has run, but without
  changing the real runtime logic which cleans up this resource.
@sirosen sirosen added the no-news-is-good-news This change does not require a news file label Dec 6, 2024
@sirosen sirosen marked this pull request as ready for review December 6, 2024 23:37
@sirosen
Copy link
Member Author

sirosen commented Dec 6, 2024

I've finished getting the refactor in working order. Most notably, in the final change here I finally achieved one of the goals here, which is to have click.Context.call_on_close close the sqlite connection. This is setup as part of LoginManager.requires_login.

I don't have new tests which ensure that we use the new behavior yet. I'm still thinking a little about whether or not to add new tests and what they should contain. Given that all of the existing tests are passing, I think we can call this ready for review while I think about that.

Copy link
Contributor

@derek-globus derek-globus left a comment

Choose a reason for hiding this comment

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

Commenting early

src/globus_cli/login_manager/tokenstore.py Outdated Show resolved Hide resolved
src/globus_cli/login_manager/tokenstore.py Outdated Show resolved Hide resolved
src/globus_cli/login_manager/tokenstore.py Outdated Show resolved Hide resolved
Comment on lines 89 to 91
return globus_sdk.ConfidentialAppAuthClient(
client_id, client_secret, app_name="Globus CLI"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it worth thinking about caching this value?

I realize we want to do the is_client_login check, but could still throw it in an instance variable and return it if it's already been instantiated.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, simply wrapping this in cached_property poses no issues, as far as I can tell (one mock needed a tweak, that was all). I'm including it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to evict the cache if someone calls delete_templated_client?

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 don't think it's necessary (although it would be more conceptually clean). It can only happen during logout when a user uses the (hidden) --delete-client flag, and the client is never used after that in that process.

Would you like me to add that? I believe it's just a matter of doing a del self.cli_confidential_client at the end of the delete.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we're going to cache it, then yeah we should evict on delete.

It's true that in the current usage mode, a cache eviction will also terminate the process (command ends after logging out). But that's not a part of this object's contract and could easily result in a future bug if we forget that subtlety.

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 added the del, nothing broke in existing tests (I didn't expect it would, but hey! good to confirm!), and I've "locked it in" with a couple of new tests.

src/globus_cli/login_manager/tokenstore.py Outdated Show resolved Hide resolved
Overall, this refactor is fairly minor. No major changes in
functionality. Some naming and "level of abstraction" issues have been
smoothed out, and it's overall less code for the same effect.

- Rename `tokenstore.py` to `storage.py`
- Rename `CLITokenstorage` to `CLIStorage`
- Remove "passthrough" methods for the underlying storage adapter --
  just expose and use it directly
- Make CLITokenstorage.adapter non-nullable
- Rename `internal_auth_client` and `internal_native_client` for
  clarity
- Use a cached property for `cli_confidential_client` (was
  `internal_auth_client`)
- Use eager imports where perf impact is negligible
Copy link
Contributor

@derek-globus derek-globus left a comment

Choose a reason for hiding this comment

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

This thread is my only outstanding sticking point.

The cached ConfidentialAppAuthClient gets cleared now as part of
deletion. New unit tests confirm the before-and-after state of
affairs.
@sirosen sirosen merged commit d2a8289 into globus:main Jan 6, 2025
6 checks passed
@sirosen sirosen deleted the close-sqlite-connections-on-exit branch January 6, 2025 21:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no-news-is-good-news This change does not require a news file
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants