Skip to content

Conversation

@Datron
Copy link
Collaborator

@Datron Datron commented Jan 13, 2026

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

  • New Features
    • Added full configuration resolution capability.
    • Added optional prefix filtering parameter for configuration lookups.
    • Added variant lookup functionality for querying applicable variants.
    • Improved asynchronous configuration fetching and retrieval.

✏️ Tip: You can customize this high-level summary in your review settings.

@Datron Datron requested a review from a team as a code owner January 13, 2026 07:27
@semanticdiff-com
Copy link

Review changes with  SemanticDiff

@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

Walkthrough

SuperpositionProvider module now exposes resolveFullConfig API, adds optional prefix parameter to mkResolveParams, introduces evalConfig for async full config fetching, and refactors getResolvedKey. FFI bindings added for string freeing and variant retrieval with new parameter types and memory cleanup updates.

Changes

Cohort / File(s) Summary
SuperpositionProvider API Extension
clients/haskell/open-feature-provider/lib/Data/OpenFeature/SuperpositionProvider.hs
New public function resolveFullConfig exposed; mkResolveParams signature extended with optional prefix parameter; new evalConfig function added for asynchronous full configuration retrieval returning Either EvaluationError Value; getResolvedKey reworked to depend on evalConfig; getStatus refactored to use helper function getTaskOutput; import updated to include Value from Data.Aeson.
FFI Bindings and Memory Management
clients/haskell/superposition-bindings/lib/FFI/Superposition.hs
New FFI bindings added: core_free_string and core_get_applicable_variants; new data type GetApplicableVariantsParams introduced with multiple Maybe String fields; defaultApplicableVariantsParams helper added; getResolvedConfig updated to use free_string for cleanup and manage additional dimensionInfo allocation/deallocation; new getApplicableVariants wrapper function introduced following FFI marshalling patterns.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Whiskers twitching with delight,
Config flows from depth to height,
Parameters hop through every layer,
Variants dance without a care,
FFI threads bind clean and bright! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'fix: Haskell provider breeze' is vague and does not clearly convey the primary changes, which include adding resolveFullConfig support, fixing memory management in Rust FFI, and fixing status retrieval in the Haskell provider. Use a more descriptive title that reflects the main changes, such as 'fix: Haskell provider status and add resolveFullConfig support' or specify the most critical fix if prioritization is needed.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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, and defaultApplicableVariantsParams are 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:

  1. Allocator mismatch: newCString and callocBytes allocate memory using the C runtime allocator, but free_string (calling core_free_string) likely uses Rust's allocator. Mixing allocators causes undefined behavior.

  2. Memory leak: The CString returned by get_resolved_config is allocated by the C/Rust library but is only peeked with peekCAString—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.

getTaskOutput is defined identically in both evalConfig (line 116) and getStatus (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 t

Then remove the local where definitions in both functions.

Also applies to: 169-170

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1236863 and 13d258d.

📒 Files selected for processing (2)
  • clients/haskell/open-feature-provider/lib/Data/OpenFeature/SuperpositionProvider.hs
  • clients/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 evalConfig function properly composes config and experiments, handles the case when config is unavailable, and maps FFI/parse errors to appropriate EvaluationError types.


118-134: LGTM!

Refactoring to use evalConfig simplifies the code. The key extraction logic with proper error handling for FlagNotFound is correct.


153-158: LGTM!

Clean API design: exposing resolveFullConfig publicly while keeping evalConfig as 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.

Comment on lines +137 to +167
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
Copy link

@coderabbitai coderabbitai bot Jan 13, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Copy link
Collaborator

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 ?

Copy link

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"
Copy link
Collaborator

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 ?

Copy link
Collaborator Author

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 ::
Copy link
Collaborator

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?

Copy link
Collaborator Author

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

Copy link
Collaborator

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 ?

@knutties
Copy link
Collaborator

@Datron - this is dupe of #786 ?

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.

3 participants