Signal self is erroring. Works with Test framework

With the Actor Model, it is common for an actor to send a signal to itself. Treating a workflow as an actor I attempt the following:

we := workflow.GetInfo(ctx).WorkflowExecution
err = workflow.SignalExternalWorkflow(ctx, we.ID, we.RunID, "messages", message).Get(ctx, nil)
if err != nil {
		panic(fmt.Errorf("could not signal external workflow: %v", err))
}

Unfortunately upon executing this workflow I see the following error:

Worker log:

2020/08/18 00:59:45 ERROR Workflow panic. Namespace default TaskQueue test WorkerID 365071@beast-ubuntu@ WorkflowID TestWorkflow RunID 45d5376f-7b22-46b5-8b7b-6167efc4b909 PanicError could not signal external workflow: UnknownExternalWorkflowExecution PanicStack coroutine root [panic]:
myproject.com/test/domain/root.TestWorkflow(0x1003860, 0xc000047300, 0x0, 0x0)
        /home/paul/myproject.com/test/domain/root/testworkflow.go:67 +0x846
reflect.Value.call(0xd69c60, 0xefbe08, 0x13, 0xeaf39a, 0x4, 0xc00046bde0, 0x1, 0x1, 0xfd2570, 0xe8e4c0, ...)
        /usr/local/go/src/reflect/value.go:460 +0x8ab
reflect.Value.Call(0xd69c60, 0xefbe08, 0x13, 0xc00046bde0, 0x1, 0x1, 0xfd2570, 0xe8e4c0, 0xc00013a4b0)
        /usr/local/go/src/reflect/value.go:321 +0xb4
go.temporal.io/sdk/internal.(*workflowEnvironmentInterceptor).ExecuteWorkflow(0xc00013a4b0, 0x1003860, 0xc000047300, 0xc000110010, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
        /home/paul/go-sdk/internal/workflow.go:370 +0x2b2
go.temporal.io/sdk/internal.(*workflowExecutor).Execute(0xc000047140, 0x1003860, 0xc000047300, 0x0, 0xc000517f38, 0xc68596, 0x0)
        /home/paul/go-sdk/internal/internal_worker.go:759 +0x334
go.temporal.io/sdk/internal.(*syncWorkflowDefinition).Execute.func1(0x1003a20, 0xc000144ff0)
        /home/paul/go-sdk/internal/internal_workflow.go:491 +0xf3

Furthermore, the signal successfully completes when using the Temporal Test framework.

Is this an error or am I trying something outside of intended operation?

I believe it was working, I’ll check and file a bug if it is broken now.

What are you trying to achieve? There are extremely rare cases when sending signal to itself makes sense.

Hi @maxim,

Thanks for looking into it. It’s actually pretty crucial to my use case. I’m working on an Actor Model framework using temporal underneath. Sending a message to self is common practice in the Actor model. In this case the actor sends itself a message every X seconds and performs a task. I’d like to maintain it as an actual signal through the Temporal architecture instead of just directly appending to the mailbox within the workflow. This allows me to have a single code path for all signals, regardless of which actor they came from (even self).

While sending a signal to itself would work, it is not a zero-cost abstraction. It requires a roundtrip to the service, multiple DB updates, and grows history size without any need.

I would recommend creating a separate channel for sending messages within a single workflow and avoid signals for this purpose.

I’m not sure about the value of the actor model framework on top of Temporal. IMHO Temporal APIs are much more friendly to developers than standard actor ones. The main reason is that actors are, by definition, asynchronous and can use only one thread while Temporal workflows perform long blocking operations all the time and can employ multiple threads.

What is the problem with existing Temporal SDK are you trying to solve adding this layer on top of it?