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

Fix(flags): False positive when variant rule is an async function #876

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

vitoUwu
Copy link
Contributor

@vitoUwu vitoUwu commented Dec 8, 2024

There are scenarios where we have a multivariant (e.g Pages with variants) and variant.rule can return a Promise even if the matcher module returns a synchronous function, this occurs when I have a matcher in my project and an (asynchronous) middleware in my app site.ts , making variant.rule asynchronous as well. It doesn't impact matchers that are from other apps like website app that have a lot of matchers, but only the local ones

Illustration of my problem.

- Page "Home" 
   |- variants.0.home => Promise<boolean> -> when resolved it returns "false"
   \- variants.1.home => boolean -> always "true"

// looping through the flags.variants
index 0 -> variants.rule(ctx) -> Promise<false> -> true // because Promise is a truly value
early return with wrong variant match

The variant.rule check is always true since Promise is a truly value, even if the final value of the promise is false.

This PR makes it possible for us to detect asynchronous functions in the flags block, where the problem was occurring, and if necessary we make sure the promise is resolved so that we can check if the variant.rule returns true

- Page "Home" 
   |- variants.0.home => Promise<boolean> -> when resolved it returns "false"
   \- variants.1.home => boolean -> always "true"

// looping through the flags.variants
index 0 -> variants.rule(ctx) -> Promise<false> -> await for the result -> don't match
index 1 -> variants.rule(ctx) -> return as it's returning "true"
return with the correct match

Copy link
Contributor

github-actions bot commented Dec 8, 2024

Tagging Options

Should a new tag be published when this PR is merged?

  • 👍 for Patch 1.108.1 update
  • 🎉 for Minor 1.109.0 update
  • 🚀 for Major 2.0.0 update

@@ -64,7 +65,7 @@ const flagBlock: Block<BlockModule<FlagFunc>> = {
>(func: {
default: FlagFunc<TConfig>;
}) =>
($live: TConfig, { request }: HttpContext) => {
async ($live: TConfig, { request }: HttpContext) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

this will impact tracings as only async functions are included on tracing. This shouldn't be though but we should keep an eye on it.

blocks/flag.ts Outdated Show resolved Hide resolved
blocks/flag.ts Outdated
// the rule can sometimes be a promise and we need to await it to check if it's truthy or not
if (isAwaitable(ruleResult)) {
try {
ruleResult = await ruleResult;
Copy link
Contributor

Choose a reason for hiding this comment

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

in the case you provided (when site.ts has an app middleware) this code runs on serial (one await for each matcher) instead of parallel since all matchers will returns a promise, right? Given that, you should instead, parallelize the rule evaluation.

Copy link
Contributor Author

@vitoUwu vitoUwu Dec 12, 2024

Choose a reason for hiding this comment

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

not at all, matchers coming from "website" app continue as sync functions, but my local ones, that depends on my async middleware, turns into async function

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean, anyways introducing a middleware will cause all your matchers to be async, so it worth to make parallel instead, wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

make sense, your proposal is to always use Promise.all on this list, right?

Promise.all(flags.variants.map((variant) => variant.rule(ctx)).find((result) => result)

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

Successfully merging this pull request may close these issues.

2 participants