cadence-client icon indicating copy to clipboard operation
cadence-client copied to clipboard

Bug: inferred func names are ambiguous, cause misbehavior due to alias

Open Groxx opened this issue 7 years ago • 3 comments

RegisterActivityWithOptions registers an inferred func name into a map containing {inferred-name: registered-name}, I assume to allow you to execute activities with either their registered-name or their function, for simplicity.

Unfortunately this is unsafe - reflected function names are not guaranteed to be unique. And it's relatively easy to encounter if you generate workflow or activity functions (as can easily occur in tests, and could occur IRL as well, though generally you'd just pass those args to a shared activity).


TBH I'm not fond of allowing ambiguous calls like this enables, but that's mostly irrelevant. This silently produces incorrect behavior, which it should not do.

Making it error on duplicate-inferred-names would prevent a silent failure... but it'd also mean you cannot generate activities / workflows at all. And trying to allow generated activities / workflows while also allowing ambiguous calls doesn't seem easily possible... func addresses would work for anonymous funcs, but for named ones you can only get the address of the var, not the "canonical" one. Maybe something with reflection -> function pointer address?

I dunno. I submit this as evidence of bad behavior, and to trigger discussion.

Groxx avatar Aug 07 '18 01:08 Groxx

Most people will write the code as the following, and it won't be an issue.

func init() {
	RegisterActivityWithOptions(f1, RegisterActivityOptions{"f1"})
	RegisterActivityWithOptions(f2, RegisterActivityOptions{"f2"})
}

func f1() error {
	return nil
}

func f2() error {
	return nil
}

meiliang86 avatar Dec 05 '19 22:12 meiliang86

Part of the reason I bring this up is that it did bite me in some of my code. Most will do that, sure, but not all.

One area where it's particularly common is if you do any dynamic workflow or activity wrappers. I'm adding context info per request to my activity calls, and initializing a per-request cache, and doing so with a reflection-generated func wrapper that looks something like this:

func Register(ctx, fn, name) {
  wrapped := reflect.MakeFunc(reflect.TypeOf(fn), func(in []reflect.Value) []reflect.Value) {
    ctx := context.WithValue(in[0].Interface().(Context), "cache key", cache{})
    in[0] = reflect.ValueOf(ctx)
    return reflect.Call(reflect.ValueOf(fn), in)
  })
  activity.Register(wrapped, RegisterActivityOptions{"name"})
}

which fits the example test exactly. I make a bunch of activities that, as far as reflection is concerned, are all defined at the same location, so they all get the same inferred name.

Groxx avatar Dec 06 '19 00:12 Groxx

CLA assistant check
All committers have signed the CLA.

CLAassistant avatar Jun 13 '20 13:06 CLAassistant