Skip to content

feat: POC of an approach to adding async #78

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

jackmott
Copy link

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

Related issues

Describe the solution you've provided

A proof of concept for adding async to the client, if LD is happy with this approach I can complete it and add test coverage. The intent here it to be backwards compatible and follow .NET conventions by adding Async versions of the calls.

Describe alternatives you've considered

Provide a clear and concise description of any alternative solutions or features you've considered.

Additional context

Add any other context about the pull request here.

@jackmott jackmott changed the title POC of an approach to adding async Feat: POC of an approach to adding async Feb 27, 2025
@jackmott jackmott changed the title Feat: POC of an approach to adding async feat: POC of an approach to adding async Feb 27, 2025
@jackmott jackmott mentioned this pull request Feb 27, 2025
Copy link

@dahlbyk dahlbyk left a comment

Choose a reason for hiding this comment

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

Would love to see this!

Copy link

@dahlbyk dahlbyk left a comment

Choose a reason for hiding this comment

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

See last comment. 😢

Comment on lines +75 to +79
#if NET31_OR_GREATER
return ValueTask.FromResult<ItemDescriptor?>(item);
#else
return new ValueTask<ItemDescriptor?>(Task.FromResult<ItemDescriptor?>(item));
#endif
Copy link

Choose a reason for hiding this comment

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

Suggested change
#if NET31_OR_GREATER
return ValueTask.FromResult<ItemDescriptor?>(item);
#else
return new ValueTask<ItemDescriptor?>(Task.FromResult<ItemDescriptor?>(item));
#endif
return new ValueTask<ItemDescriptor?>(item);

Copy link
Author

Choose a reason for hiding this comment

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

that's not a thing in net 46

Copy link

Choose a reason for hiding this comment

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

Add conditional reference to https://www.nuget.org/packages/System.Threading.Tasks.Extensions for net462?

@@ -30,7 +31,7 @@ private async Task ArbitraryTask()
await Task.Delay(TimeSpan.FromTicks(1));
}

public async Task<SerializedItemDescriptor?> GetAsync(DataKind kind, string key)
public new async ValueTask<SerializedItemDescriptor?> GetAsync(DataKind kind, string key,CancellationToken cancellationToken = default)
Copy link

Choose a reason for hiding this comment

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

Haven't pulled the project, but I can't find why you'd need new here?

Comment on lines +704 to +706
return new ValueTask<SerializedItemDescriptor?>(Task.FromResult<SerializedItemDescriptor?>(new SerializedItemDescriptor(0, false, item.SerializedItem)));
}
return new ValueTask<SerializedItemDescriptor?>(Task.FromResult<SerializedItemDescriptor?>(item));
Copy link

Choose a reason for hiding this comment

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

Doesn't really matter for tests, but

Suggested change
return new ValueTask<SerializedItemDescriptor?>(Task.FromResult<SerializedItemDescriptor?>(new SerializedItemDescriptor(0, false, item.SerializedItem)));
}
return new ValueTask<SerializedItemDescriptor?>(Task.FromResult<SerializedItemDescriptor?>(item));
return new ValueTask<SerializedItemDescriptor?>(new SerializedItemDescriptor(0, false, item.SerializedItem));
}
return new ValueTask<SerializedItemDescriptor?>(item);

Comment on lines +93 to +97
return _taskFactory.StartNew(taskFn)
.GetAwaiter()
.GetResult()
.GetAwaiter()
.GetResult();
Copy link

Choose a reason for hiding this comment

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

Since we're trying to avoid allocations, maybe something like...

Suggested change
return _taskFactory.StartNew(taskFn)
.GetAwaiter()
.GetResult()
.GetAwaiter()
.GetResult();
var vt = taskFn();
return vt.IsCompleted ? vt.Result : WaitSafely(() => vt.AsTask());

Comment on lines +194 to +195
var ret = _itemCache is null ? await GetAndDeserializeItemAsync(kind, key, cancellationToken).ConfigureAwait(false) :
_itemCache.Get(new CacheKey(kind, key));
Copy link

Choose a reason for hiding this comment

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

So I was going to say that since the _itemCache branch here is all sync there would probably be a perf benefit to splitting into paths with and without async/await.

But that made me realize that we don't want an all sync path. The cache loader is async over sync, so we haven't actually avoided any blocking unless we disable the cache. 😭 We'd need to update LaunchDarkly.Cache with IAsyncCache/IAsyncSingleValueCache and wire it through.

@louis-launchdarkly
Copy link
Contributor

Hello @jackmott and @dahlbyk, thank you so much for the contribution. While the SDK team cannot get to this immediately, we will look at this contribution, as we want to make the SDK fit the platform's use cases.

To understand the platform better, is async usage more common in .NET server applications these days? Is there a concern that the persistent store data read slows down the SDK's performance?

Filed Internally as SDK-1096

@jackmott
Copy link
Author

jackmott commented Mar 6, 2025

Hello @louis-launchdarkly !

Yes async is now very much the default approach for .NET server application. For some reading on this you can refer to: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/best-practices?view=aspnetcore-9.0#avoid-blocking-calls

While we understand the past position of LaunchDarkly is that caching makes blocking rare enough to mitigate this issue, it would still be better for this to be a fully async path, and we believe ValueTask in particular can make for an efficient async option here.

We have a lot of expertise in house at Olo making our own legacy code async and so we may be able to help work through any tricky issues.

@arielnahom
Copy link

Would be great to have async support! Launch darkly team - Please add it.

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

Successfully merging this pull request may close these issues.

4 participants