-
Notifications
You must be signed in to change notification settings - Fork 39
fix: Haskell provider breeze #833
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
base: main
Are you sure you want to change the base?
Conversation
WalkthroughSuperpositionProvider module now exposes Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
clients/haskell/superposition-bindings/lib/FFI/Superposition.hs (2)
4-4: Missing exports for new functions and types.
getApplicableVariants,GetApplicableVariantsParams, anddefaultApplicableVariantsParamsare defined but not exported from the module. If these are intended for external use, add them to the export list; otherwise, this is dead code.Proposed fix
-module FFI.Superposition (getResolvedConfig, ResolveConfigParams (..), defaultResolveParams, MergeStrategy (..)) where +module FFI.Superposition (getResolvedConfig, ResolveConfigParams (..), defaultResolveParams, MergeStrategy (..), getApplicableVariants, GetApplicableVariantsParams (..), defaultApplicableVariantsParams) where
102-135: Critical memory management issues: allocator mismatch and memory leak.Two concerns:
Allocator mismatch:
newCStringandcallocBytesallocate memory using the C runtime allocator, butfree_string(callingcore_free_string) likely uses Rust's allocator. Mixing allocators causes undefined behavior.Memory leak: The
CStringreturned byget_resolved_configis allocated by the C/Rust library but is only peeked withpeekCAString—the original C string is never freed.Proposed fix
+import Foreign.Marshal.Alloc (free) + getResolvedConfig :: ResolveConfigParams -> IO (Either String String) getResolvedConfig params = do ebuf <- callocBytes 256 let ResolveConfigParams {..} = params newOrNull = maybe (pure nullPtr) newCString - freeNonNull p = when (p /= nullPtr) (free_string p) + freeHaskell p = when (p /= nullPtr) (free p) peekMaybe p | p /= nullPtr = Just <$> peekCAString p | otherwise = pure Nothing dc <- newOrNull defaultConfig ctx <- newOrNull context ovrs <- newOrNull overrides di <- newOrNull dimensionInfo qry <- newOrNull query mergeS <- newCString $ show $ fromMaybe Merge mergeStrategy pfltr <- newOrNull prefixFilter exp <- newOrNull experimentation - res <- - peekMaybe - =<< get_resolved_config + resPtr <- get_resolved_config dc ctx ovrs di qry mergeS pfltr exp ebuf + res <- peekMaybe resPtr + when (resPtr /= nullPtr) (free_string resPtr) -- Free C-allocated result err <- peekCAString ebuf - traverse_ freeNonNull [dc, ctx, ovrs, di, qry, mergeS, pfltr, exp, ebuf] + traverse_ freeHaskell [dc, ctx, ovrs, di, qry, mergeS, pfltr, exp, ebuf] -- Free Haskell-allocated pure $ case (res, err) of (Just cfg, []) -> Right cfg (Nothing, []) -> Left "null pointer returned" _ -> Left err
🧹 Nitpick comments (1)
clients/haskell/open-feature-provider/lib/Data/OpenFeature/SuperpositionProvider.hs (1)
115-116: Consider extracting duplicate helper to module level.
getTaskOutputis defined identically in bothevalConfig(line 116) andgetStatus(line 170). Consider extracting it as a top-level helper to reduce duplication.Proposed refactor
Add near other helper definitions:
getTaskOutput :: DynRefreshTask a -> IO (Maybe a) getTaskOutput (DynRefreshTask t) = getCurrent tThen remove the local
wheredefinitions in both functions.Also applies to: 169-170
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
clients/haskell/open-feature-provider/lib/Data/OpenFeature/SuperpositionProvider.hsclients/haskell/superposition-bindings/lib/FFI/Superposition.hs
🔇 Additional comments (5)
clients/haskell/open-feature-provider/lib/Data/OpenFeature/SuperpositionProvider.hs (5)
69-84: LGTM!The prefix parameter is properly added and threaded through to the FFI binding.
98-116: LGTM!The
evalConfigfunction properly composes config and experiments, handles the case when config is unavailable, and maps FFI/parse errors to appropriateEvaluationErrortypes.
118-134: LGTM!Refactoring to use
evalConfigsimplifies the code. The key extraction logic with proper error handling forFlagNotFoundis correct.
153-158: LGTM!Clean API design: exposing
resolveFullConfigpublicly while keepingevalConfigas an internal implementation detail.
164-170: Correctly fixes the status check per PR objectives.The implementation now properly queries the config refresh task's current state to determine provider readiness, rather than using an incorrect status indicator.
| getApplicableVariants :: | ||
| GetApplicableVariantsParams -> | ||
| IO (Either String String) | ||
| getApplicableVariants GetApplicableVariantsParams {..} = do | ||
| ebuf <- callocBytes 256 | ||
| let newOrNull = maybe (pure nullPtr) newCString | ||
| freeNonNull p = when (p /= nullPtr) (free_string p) | ||
| peekMaybe p | p /= nullPtr = Just <$> peekCAString p | ||
| | otherwise = pure Nothing | ||
| exps <- newOrNull experiments | ||
| expGrps <- newOrNull experimentGroups | ||
| dims <- newOrNull dimensions | ||
| qry <- newOrNull queryData | ||
| tkey <- newOrNull targetingKey | ||
| pfltr <- newOrNull filterPrefixes | ||
| res <- | ||
| peekMaybe | ||
| =<< get_applicable_variants | ||
| exps | ||
| expGrps | ||
| dims | ||
| qry | ||
| tkey | ||
| pfltr | ||
| ebuf | ||
| err <- peekCAString ebuf | ||
| traverse_ freeNonNull [exps, expGrps, dims, qry, tkey, pfltr, ebuf] | ||
| pure $ case (res, err) of | ||
| (Just vars, []) -> Right vars | ||
| (Nothing, []) -> Left "null pointer returned" | ||
| _ -> Left err |
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.
Same memory management issues as getResolvedConfig.
This function has the same allocator mismatch and memory leak issues. Apply the equivalent fix: use Haskell's free for strings allocated with newCString/callocBytes, and call free_string on the result pointer returned by get_applicable_variants.
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.
@Datron - this came in my toml PR as well - should we just address 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.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| foreign import capi "superposition_core.h core_free_string" | ||
| free_string :: CString -> IO () | ||
|
|
||
| foreign import capi "superposition_core.h core_get_applicable_variants" |
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.
@Datron - why do we rename the generated core.h file to superposition_core.h in our builds ? Can't we use it as it is ?
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.
@knutties some languages expect the binary name and the header file name to be the same - that is why we do the rename
| runLogger $ Log.logError ("An error occured while resolving a key." Log.:# lctx) | ||
| pure $ Left $ e | ||
|
|
||
| resolveFullConfig :: |
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.
Why two functions with exactly same signature @Datron - resolveFullConfig and evalConfig - or am I missing some Haskell basics here?
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.
@knutties keeping the naming convention the same as the spec - its just that resolveFullConfig and evalConfig does the same thing. This is the same as the rust provider PR I have raised
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.
You mean this one #755 ?
Problem
the status returned by the haskell provider is wrong
Solution
Get the correct status by checking the config data stored in the polling thread
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.