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

Make YubiKeyDeviceListener resettable #89

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

jamiehankins
Copy link
Collaborator

@jamiehankins jamiehankins commented May 15, 2024

Currently, YubiKeyDeviceListener only exposes a singleton instance with no way for a consumer to reset it once it is started. The constructor is private, so the consumer has no option to use a private instance.

This adds the ability for the consumer to reset the lazy-initialized singleton so that the listen thread doesn't stick around.

Also, even though this class implemented IDisposable, most things that need to be in the Dispose method weren't there, including a way to stop the thread.

The listen thread is now replaced by an asynchronous task that doesn't consume a system thread while it's not working.

Description

Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.

Fixes # YESDK-1313

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)

How has this been tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

Test configuration:

  • Firmware version: 5.4.3
  • Yubikey model:

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have run dotnet format to format my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Currently, YubiKeyDeviceListener only exposes a singleton instance with no
way for a consumer to reset it once it is started. The constructor is private, so
the consumer has no option to use a private instance.

This adds the ability for the consumer to reset the lazy-initialized singleton so that
the listen thread doesn't stick around.

Also, even though this class implemented IDisposable, most things that need to be
in the Dispose method weren't there, including a way to stop the thread.

The listen thread is now replaced by an asynchronous task that doesn't consume
a system thread while it's not working.
@jamiehankins jamiehankins self-assigned this May 15, 2024
Copy link

Test Results: Windows

    2 files      2 suites   2s ⏱️
3 615 tests 3 615 ✅ 0 💤 0 ❌
3 617 runs  3 617 ✅ 0 💤 0 ❌

Results for commit ce5ddc1.

Copy link

Test Results: Ubuntu

    2 files      2 suites   5s ⏱️
3 607 tests 3 607 ✅ 0 💤 0 ❌
3 609 runs  3 609 ✅ 0 💤 0 ❌

Results for commit ce5ddc1.

Copy link

Test Results: MacOS

    2 files      2 suites   1s ⏱️
3 607 tests 3 607 ✅ 0 💤 0 ❌
3 609 runs  3 609 ✅ 0 💤 0 ❌

Results for commit ce5ddc1.

Copy link

Code Coverage

Package Line Rate Branch Rate Complexity Health
Yubico.Core 42% 32% 4245
Yubico.YubiKey 51% 47% 18438
Summary 49% (31665 / 64296) 45% (8014 / 17985) 22683

Minimum allowed line rate is 40%

Copy link
Collaborator

@DennisDyallo DennisDyallo left a comment

Choose a reason for hiding this comment

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

It looks good to me. Because of limited experience with this SDK, I don't feel confident in testing this PR myself. Perhaps @GregDomzalski will have some time to take a look ?

Copy link
Contributor

@GregDomzalski GregDomzalski left a comment

Choose a reason for hiding this comment

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

I think we need to do the same thing for the HidDeviceListener and SmartCardDeviceListener classes. Otherwise we're not really stopping anything or freeing up threads/resources.

private static readonly Lazy<YubiKeyDeviceListener> _lazyInstance =
new Lazy<YubiKeyDeviceListener>(() => new YubiKeyDeviceListener());
/// <summary>
/// Disposes and closes the singleton instance of <see cref="YubiKeyDeviceListener"/>.
Copy link
Contributor

Choose a reason for hiding this comment

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

Summary: Instructs the SDK to stop listening for YubiKey events, effectively causing the SDK to go "dormant". Accessing Instance or calling YubiKeyDevice.FindAll() will restart the listener.

Remarks: Enumerating YubiKeys is actually done via a cache. As such, this cache must be maintained and kept up-to-date. This is done by starting several listening threads that run in the background. These listen for the relevant OS device arrival and removal events. Normally, these background listeners will run starting with the first enumeration call to the SDK and remain active until the process shuts down (i.e. a lazy singleton). But there are cases where you may not want the overhead of these listeners running all the time. While they do their best to not eat CPU, they can sometimes generate log noise, exceptions, etc. This method allows you to stop these background listeners and reclaim as much resources as possible from the SDK. This will not invalidate any existing IYubiKeyDevice instances, however you will not receive any additional events regarding that device. Any subsequent calls to YubiKeyDevice.FindAll, YubiKeyDevice.FindByTransport, or YubiKeyDeviceListener.Instance will restart the listening threads.

Suggested documentation updates describing the impact to the caller rather than what's it's doing under the covers.

// Signal the listen thread that it's time to end.
_tokenSource.Cancel();

// Shut down the listener handlers.
Copy link
Contributor

Choose a reason for hiding this comment

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

So really, we should be implementing this same pattern in the individual device type listeners. Because stopping this thread is not actually stopping the underlying devices from raising the PnP events. That needs to happen to for this story to really be complete.

Update();
try
{
await _semaphore.WaitAsync(CancellationToken).ConfigureAwait(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

We dropped a log message here. Knowing what started the update process is very useful to know.

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

Successfully merging this pull request may close these issues.

None yet

3 participants