Closed erikbaranowski closed 1 year ago
Adding some findings.
makeValue
is converting the pointer to a value,
I changed the code in value.go:292 to the below and it works. Though tests fail.
if v.Kind() == reflect.Pointer {
if v.IsNil() {
return Null
}
_ = v.Elem()
}
Copying the arguments transforms it from a pointer to a component and not sure why that only happens in certain cases.
Specifically any
seems to be the cause. The below test fails.
func TestPointerToValue(t *testing.T) {
type fss struct{}
f := &fss{}
mm := make(map[string]any)
mm["t"] = f
out := make(map[string]any)
err := value.Decode(value.Encode(mm), &out)
require.NoError(t, err)
require.True(t, mm["t"] == out["t"])
}
the below succeeds
func TestPointerToValueSpecific(t *testing.T) {
type fss struct{}
f := &fss{}
mm := make(map[string]*fss)
mm["t"] = f
out := make(map[string]*fss)
err := value.Decode(value.Encode(mm), &out)
require.NoError(t, err)
require.True(t, mm["t"] == out["t"])
}
I think the issue is here:
I think this should be assigning the pointer value if it's addressable:
case TypeFunction, TypeCapsule:
// Functions and capsules must be directly assigned since there's no
// "generic" representation for either.
if val.rv.CanAddr() {
into.Set(val.rv.Addr())
} else {
into.Set(val.rv)
}
return nil
makeValue
always fully deferences pointers for consistency in the implementation, so if decoding needs to retain the pointer value (which it should here), the individual decode functions need to be updated.
You can see a few places in decode.go where we use the CanAddr()
/Addr()
pattern to not lose the pointer during decode.
Note that CanAddr()
will only be true if the value was originally a pointer when passed to makeValue
, which is the case here.
Will look into the above. Following the two examples the containsAny
in creates two different code paths in the above. Since map[string]any
says it has any
then it cant directly assign.
I have a working solution, but its a bit bespokish. It basically handles map[string]any
in a more specific use case, basically says that is the value of a map is any then you can assign. Primarily because I wanted to fiddle and limit the scope. Either way its an edge case and we can refine it next week.
Created a PR to handle this use case, its might be to narrow of a fix but that also means its impact is more limited than trying to fix other places.
Background: This issue occurs in a very specific set of circumstances. I will show a couple working examples followed by 1 that demonstrates the issue. After that I have some additional technical details on the error and a possible hint on where things are having issues in the code.
Working Example 1
In this example we get the receiver as an argument to the module loader. Working as expected!
parent.river
module.river
Working Example 2
In this example we get the receiver as the export of another module loader. Working as expected!
parent.river
prometheus_scrape/module.river
prometheus_receiver/module.river
Broken Example
In this example we do a combination of the above examples. From the parent config we will get the receiver as an export from a module loader then pass it as an argument to a different module loader.
parent.river
prometheus_scrape/module.river
prometheus_receiver/module.river
Error starting the agent:
Error: module.file.agent_metrics:26:21: argument.receiver.value expected capsule("storage.Appendable"), got capsule("prometheus.Interceptor")
Debugging
Diving in a little deeper into the code,
prometheus.remote_write
exportsreceiver
as*prometheus.Interceptor
notprometheus.Interceptor
. Somewhere along the path it is getting translated from a pointer into its value and causing the error.I stepped through the code in
pkg/flowinternal/controller/component.go
and confirmed that when evaluate is being called it does in fact incorrectly set the argument to the value instead of the pointer in this case.