Code Monkey home page Code Monkey logo

sdk-php's Introduction

Temporal PHP SDK

CI Status Stable Release FOSSA Status

Introduction

Temporal is a distributed, scalable, durable, and highly available orchestration engine used to execute asynchronous long-running business logic in a scalable and resilient way.

"Temporal PHP SDK" is the framework for authoring workflows and activities using PHP language.

Installation

SDK is available as composer package and can be installed using the following command in a root of your project:

composer require temporal/sdk

Make sure to install RoadRunner to enable workflow and activity consumption in your PHP workers.

Usage

See examples to get started.

Testing

See testing manual to get started.

Documentation

The documentation on how to use the Temporal PHP SDK and client is here.

License

MIT License, please see LICENSE for details.

FOSSA Status

sdk-php's People

Contributors

actuallymab avatar cappuc avatar cv65kr avatar dehbka avatar devvitalii avatar fossabot avatar jackdawm avatar kafkiansky avatar kozlice avatar malthesoelvberg avatar marenich13 avatar mjameswh avatar msmakouz avatar mzavatsky avatar neznaykas avatar olegkuro avatar rachfop avatar root-aza avatar roquie avatar roxblnfk avatar rustatian avatar serafimarts avatar seregazhuk avatar shanginn avatar smelesh avatar tarampampam avatar tlalfano avatar wolfy-j avatar zlodes avatar zylius avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sdk-php's Issues

Is this SDK available in standalone without RoadRunner ?

Hi,

I tried to clone the SDK in my project but I got a question.
I already have a stack in my company for PHP and I want to include Temooral server instead of RabbitMQ.
I want to directy send workflow or receive activities from PHP.

I know it's just the beginning of the SDK, but before to try some use-cases, I want to know if it will be compatible.

The example emulator works well, but when I tried to change the client endpoint to 'temporal:7233' (the available temporal server endpoint).
I got the following error:

I used the default docker-compose from temporal 1.0.0-rc1

Temporal.ERROR: Spiral\Goridge\Exception\ProtocolException: Can not read input stream, chunk must a type of string, but bool given in /var/www/directory/vendor/spiral/goridge/src/Protocol/Protocol.php:104 
Stack trace: #0 /var/www/directory/vendor/spiral/goridge/src/Protocol/Protocol.php(79): Spiral\Goridge\Protocol\Protocol->errorChunkType(false) 
#1 /var/www/directory/vendor/spiral/goridge/src/Protocol/Protocol.php(59): Spiral\Goridge\Protocol\Protocol->stream(2, Object(Closure)) 
#2 /var/www/directory/vendor/spiral/goridge/src/Protocol/Version1/Decoder.php(38): Spiral\Goridge\Protocol\Protocol->streamToString(17) 
#3 [internal function]: Spiral\Goridge\Protocol\Version1\Decoder->decode(Object(Spiral\Goridge\Protocol\Stream\Factory)) 
#4 /var/www/directory/vendor/spiral/goridge/src/Transport/Receiver/ResourceStreamReceiverTrait.php(40): Generator->send(false) 
#5 /var/www/directory/vendor/spiral/goridge/src/Transport/ReactReceiver.php(39): Spiral\Goridge\Transport\StreamReceiver->push(Object(Spiral\Goridge\Protocol\Stream\Factory), Object(Spiral\Goridge\Protocol), Resource id #56) 
#6 /var/www/directory/vendor/react/event-loop/src/StreamSelectLoop.php(245): Spiral\Goridge\Transport\ReactReceiver->Spiral\Goridge\Transport\{closure}(Resource id #56) 
#7 /var/www/directory/vendor/react/event-loop/src/StreamSelectLoop.php(212): React\EventLoop\StreamSelectLoop->waitForStreamActivity(NULL) 
#8 /var/www/directory/vendor/archemed/common-symfony/Worker.php(213): React\EventLoop\StreamSelectLoop->run() 
#9 /var/www/directory/vendor/archemed/common-symfony/RestartableExecutor.php(105): Temporal\Client\Worker->run('default') 
#10 /var/www/directory/public/client.php(45): Temporal\Client\RestartableExecutor->run() #11 {main} [] []

[Bug] Workflow with awaitWithTimeout keeps running after signal

Workflow example to reproduce bug:

class DelayedWorkflow implements DelayedWorkflowInterface
{
    private bool $doCancel;

    public function executeDelayed()
    {
        $this->doCancel = false;

        yield Workflow::awaitWithTimeout(
        	600,
        	fn() => $this->doCancel
        );

        if ($this->doCancel) {
            return 'CANCEL';
        }

        return 'OK';
    }

    public function cancel(): void
    {
        $this->doCancel = true;
    }
}

Expected behavior:

  • Run the workflow, send cancel signal, workflow completes immediataly with result CANCEL.

Actual result:

  • Run the workflow, send cancel signal, workflow stays running. Only after timeout (600 seconds) it completes with result CANCEL.

[Bug] Cannot decode value using Protobuf converter when fetching previous workflow result

What are you really trying to do?

When using CRON scheduled workflow and returning a protobuf message from said workflow, the message cannot be parsed when trying to fetch previous workflow result via Workflow::getLastCompletionResult()

Describe the bug

  1. Create a CRON scheduled workflow
  2. Return a protobuf message from workflow as it's result
  3. Fetch last completion result using Workflow::getLastCompletionResult()
  4. There is an error:
{
  "message": "Unable to decode value using protobuf converter - ",
  "source": "PHP_SDK",
  "stackTrace": "Temporal\\Exception\\DataConverterException: Unable to decode value using protobuf converter -  in /app/vendor/temporal/sdk/src/DataConverter/ProtoJsonConverter.php:50\nStack trace:\n#0 /app/vendor/temporal/sdk/src/DataConverter/DataConverter.php(53)... [omitted for brewity]
  "cause": null,
  "applicationFailureInfo": {
    "type": "Temporal\\Exception\\DataConverterException",
    "nonRetryable": false,
    "details": null
  },
  "failureInfo": "applicationFailureInfo"
}

Minimal Reproduction

benkelukas/samples-php@620139f

SimpleActivity is modified to use CRON schedules.

Environment/Versions

  • OS and processor: Intel MacOS, Catalina
  • Temporal Version: 1.14.4, PHP SDK 1.1.1
  • Docker setup

Additional context

Slack thread

example project not invoking activity

hello,
I was trying to make the example project work for a small experiment, but wasnt able to get the activity invoked. I can explicitly see that the StartActivity is not implemented yet, is there a development version of this codebase where it is working ?

[Bug] Workflow getVersion method return wrong type

What are you really trying to do?

Use versioning as specified in the documentation https://docs.temporal.io/docs/php/versioning

Describe the bug

Method call yield Workflow::getVersion(...) don't return integer type.

Minimal Reproduction

#[WorkflowInterface]
class TestWorkflow
{
    #[WorkflowMethod]
    #[ReturnType(Type::TYPE_STRING)]
    public function start()
    {
        $version = yield Workflow::getVersion(
            'test',
            Workflow::DEFAULT_VERSION,
            Workflow::DEFAULT_VERSION,
        );

        if ($version === Workflow::DEFAULT_VERSION) {
            return 'OK';
        }

        return 'ERROR';
    }
}

Workflow will end with ERROR result, because $version will have Temporal\DataConverter\EncodedValues type.

Environment/Versions

Temporal Version: 1.13.3, SDK version 1.3.0

Additional context

Workaround:

$version = yield EncodedValues::decodePromise(
    Workflow::getVersion(
        'test',
        Workflow::DEFAULT_VERSION,
        Workflow::DEFAULT_VERSION,
    ),
);

DTO Java/Go comparison

ChildWorkflowOptions                                    // = ChildWorkflowOptions
    Namespace string                                    //   String namespace
    WorkflowID string                                   //   String workflowId
    TaskQueue string                                    //   String taskQueue
    WorkflowExecutionTimeout time.Duration              //   Duration workflowExecutionTimeout
    WorkflowRunTimeout time.Duration                    //   Duration workflowRunTimeout
    WorkflowTaskTimeout time.Duration                   //   Duration workflowTaskTimeout
    WaitForCancellation bool                            //
    WorkflowIDReusePolicy enumspb.WorkflowIdReusePolicy //   WorkflowIdReusePolicy workflowIdReusePolicy
    RetryPolicy *RetryPolicy                            //   RetryOptions retryOptions
    CronSchedule string                                 //   String cronSchedule
    Memo map[string]interface{}                         //   Map<String, Object> memo
    SearchAttributes map[string]interface{}             //   Map<String, Object> searchAttributes
    ParentClosePolicy enumspb.ParentClosePolicy         //   ParentClosePolicy parentClosePolicy
                                                        // + List<ContextPropagator> contextPropagators
                                                        // + ChildWorkflowCancellationType cancellationType

RegisterWorkflowOptions                                 // = WorkflowImplementationOptions
    Name                          string                //
    DisableAlreadyRegisteredCheck bool                  //
                                                        // + Class<? extends Throwable>[] failWorkflowExceptionTypes

RetryPolicy                                             // > RetryOptions
    InitialInterval time.Duration                       //   Duration initialInterval
    BackoffCoefficient float64                          //   double backoffCoefficient
    MaximumInterval time.Duration                       //   Duration maximumInterval
    MaximumAttempts int32                               //   int maximumAttempts
    NonRetryableErrorTypes []string                     //   String[] doNotRetry

WorkflowInfo                                            // = WorkflowInfoImpl
    WorkflowExecution        WorkflowExecution          // < String getWorkflowId()
                                                        // < String getRunId()
    WorkflowType             WorkflowType               // < String getWorkflowType()
    TaskQueueName            string                     //   String getTaskQueue()
    WorkflowExecutionTimeout time.Duration              //   Duration getWorkflowExecutionTimeout()
    WorkflowRunTimeout       time.Duration              //   Duration getWorkflowRunTimeout()
    WorkflowTaskTimeout      time.Duration              // -
    Namespace                string                     //   String getNamespace()
    Attempt                  int32                      //   int getAttempt()
    lastCompletionResult     *commonpb.Payloads         // -
    lastFailure              *failurepb.Failure         // -
    CronSchedule             string                     // -
    ContinuedExecutionRunID  string                     //   Optional<String> getContinuedExecutionRunId()
    ParentWorkflowNamespace  string                     // -
    ParentWorkflowExecution  *WorkflowExecution         // < Optional<String> getParentWorkflowId()
                                                        // < Optional<String> getParentRunId()
    Memo                     *commonpb.Memo             // -
    SearchAttributes         *commonpb.SearchAttributes //   SearchAttributes getSearchAttributes()
    BinaryChecksum           string                     // -
                                                        // + long getRunStartedTimestampMillis()

ActivityType                                            // -
    Name string                                         // String getActivityType();

ActivityInfo                                            // = ActivityInfo
    TaskToken         []byte                            //   byte[] getTaskToken();
    WorkflowType      *WorkflowType                     // < String getWorkflowType();
    WorkflowNamespace string                            //   String getWorkflowNamespace();
    WorkflowExecution WorkflowExecution                 // < String getWorkflowId()
                                                        // < String getRunId()
    ActivityID        string                            //   String getActivityId();
    ActivityType      ActivityType                      // < String getActivityType();
    TaskQueue         string                            // -
    HeartbeatTimeout  time.Duration                     //   Duration getHeartbeatTimeout();
    ScheduledTime     time.Time                         //   long getScheduledTimestamp();
    StartedTime       time.Time                         // -
    Deadline          time.Time                         // -
    Attempt           int32                             //   int getAttempt();
                                                        // + boolean isLocal();
                                                        // + String getWorkflowNamespace();
                                                        // + String getActivityNamespace();
                                                        // + Optional<Payloads> getHeartbeatDetails();
                                                        // + Duration getStartToCloseTimeout();
                                                        // + Duration getScheduleToCloseTimeout();

WorkflowType                                            // -
    Name string                                         //   String getWorkflowType()

WorkflowExecution                                       // -
    ID    string                                        //   String getWorkflowId();
    RunID string                                        //   String getRunId();

RegisterActivityOptions                                 // -
    Name                          string                // -
    DisableAlreadyRegisteredCheck bool                  // -

ActivityOptions                                         // = ActivityOptions
    TaskQueue string                                    //   String taskQueue
    ScheduleToCloseTimeout time.Duration                //   Duration scheduleToCloseTimeout
    ScheduleToStartTimeout time.Duration                //   Duration scheduleToStartTimeout
    StartToCloseTimeout time.Duration                   //   Duration startToCloseTimeout
    HeartbeatTimeout time.Duration                      //   Duration heartbeatTimeout
    WaitForCancellation bool                            // -
    ActivityID string                                   // -
    RetryPolicy *RetryPolicy                            //   RetryOptions retryOptions
                                                        // + List<ContextPropagator> contextPropagators
                                                        // + ActivityCancellationType cancellationType

LocalActivityOptions                                    // = LocalActivityOptions
    ScheduleToCloseTimeout time.Duration                //   Duration scheduleToCloseTimeout
    StartToCloseTimeout time.Duration                   //   Duration startToCloseTimeout
    RetryPolicy *RetryPolicy                            //   RetryOptions retryOptions
                                                        // + Duration localRetryThreshold

StartWorkflowOptions                                    // = WorkflowOptions
    ID string                                           //   String workflowId
    TaskQueue string                                    //   String taskQueue
    WorkflowExecutionTimeout time.Duration              //   Duration workflowExecutionTimeout
    WorkflowRunTimeout time.Duration                    //   Duration workflowRunTimeout
    WorkflowTaskTimeout time.Duration                   //   Duration workflowTaskTimeout
    WorkflowIDReusePolicy enumspb.WorkflowIdReusePolicy //   WorkflowIdReusePolicy workflowIdReusePolicy
    RetryPolicy *RetryPolicy                            //   RetryOptions retryOptions
    CronSchedule string                                 //   String cronSchedule
    Memo map[string]interface{}                         //   Map<String, Object> memo
    SearchAttributes map[string]interface{}             //   Map<String, Object> searchAttributes
                                                        // + List<ContextPropagator> contextPropagators

[Bug] Activity code is not invoked inside await

Description of the bug

Trying to execute the same activity method multiple times with different args inside my workflow. Sometimes the code inside the activity does not run, but in the history it says its completed (no exceptions thrown)...

To Reproduce

workflow code (activity mentioned is "notifyCustomer"):

    #[WorkflowMethod]
    public function create($projectPayload)
    {
        $customer = yield $this->externalActivities->execute("createCustomer", [$projectPayload->hostName]);

        $property = yield $this->externalActivities->execute("createProperty", [$projectPayload->zipCode]);


        $createProjectWorkflow = $this->externalProjectWorkflow->execute([false]);


        yield Workflow::await(fn() => $this->projectOfferAccepted);
        yield $this->notificationActivities->notifyCustomer("A cleaner accepted your project offer");


        yield Workflow::await(fn() => $this->projectPreCharged);
        yield $this->notificationActivities->notifyCustomer("We reserved the project amount in you credit card");


        yield Workflow::await(fn() => $this->projectStarted);
        yield $this->notificationActivities->notifyCustomer("The cleaner has started the project");


        yield Workflow::await(fn() => $this->projectFinished);
        yield $this->notificationActivities->notifyCustomer("The cleaner has finished the project");


        yield Workflow::await(fn() => $this->paymentCreated);
        yield $this->notificationActivities->notifyCustomer("We successfully charged your credit card with the project amount");
    }

Activity code:

#[ActivityMethod]
    public function notifyCustomer(string $message)
    {
        Notification::create([
            "message" => $message
        ]);

        return Str::random();
    }

Screenshots/Terminal output

History with one of those been successfully executed and the other returning null:
Captura de Tela 2021-12-10 ร s 13 52 33

Additional context

It might be because of the same activity name method... When changed the code to this:

yield Workflow::await(fn() => $this->projectOfferAccepted);
        yield $this->notificationActivities->notifyCustomer("A cleaner accepted your project offer");


        yield Workflow::await(fn() => $this->projectPreCharged);
        yield $this->notificationActivities->notifyCustomer2("We reserved the project amount in you credit card");


        yield Workflow::await(fn() => $this->projectStarted);
        yield $this->notificationActivities->notifyCustomer3("The cleaner has started the project");


        yield Workflow::await(fn() => $this->projectFinished);
        yield $this->notificationActivities->notifyCustomer4("The cleaner has finished the project");


        yield Workflow::await(fn() => $this->paymentCreated);
        yield $this->notificationActivities->notifyCustomer5("We successfully charged your credit card with the project amount");

And it worked (tried sometimes to make sure it was not a coincidence)

[Bug] PHP 7.4 docblock anotations don't work (or documentation missing?)

Hi, I'm trying to run temporal-sdk with php 7.4 (which is minimum supported version according to composer.json), but the SDK is unable to parse my workflows because I'm not using PHP 8 style attributes, but older PHP 7.4 docblock style.

To Reproduce
Steps to reproduce the behavior:

  1. try to work with this workflow interface:
use Temporal\Workflow\WorkflowInterface;
use Temporal\Workflow\WorkflowMethod;

/**
 * @WorkflowInterface()
 */
interface SomeWorkflow
{
    /**
     * @return void
     * @WorkflowMethod(name="fork")
     */
    public function something();
}
  1. When the workflow client tries to work with the mentioned worklfow, it throws this error: Workflow class SomeWorkflow or one of his parents (i.e. class, interface or trait) must contain #[Temporal\Workflow\WorkflowInterface] attribute

Expected behavior
Well, the attribute/annotation should be found if minimum supported PHP version is 7.4

[Bug] Parent workflow waits for child completion

What are you really trying to do?

I'm trying to divide one process into two workflows: the first one starts the process and then spawns the second one (child workflow) and immediately completes. Then the second workflows works, but the first should be completed.

Describe the bug

Instead of spawning and immediate completion parent workflow waits for child workflow resolution.
I've tried to find any documentation about correct behavior in this case and found golang temporal notes:

To asynchronously spawn a Child Workflow Execution, the Child Workflow must have an "Abandon" Parent Close Policy set in the Child Workflow Options. Additionally, the Parent Workflow Execution must wait for the "ChildWorkflowExecutionStarted" event to appear in its event history before it completes.

If the Parent makes the ExecuteChildWorkflow call and then immediately completes, the Child Workflow Execution will not spawn.

Does it mean that parent workflow should not wait? ๐Ÿค”

Minimal Reproduction

class ExampleWorkflow {

    public function run()
    {
        $child = Workflow::newChildWorkflowStub(
            ChildWorkflowExampleInterface::class,
            ChildWorkflowOptions::new()
                ->withWorkflowId(bin2hex(random_bytes(18)))
                ->withWorkflowIdReusePolicy(WorkflowIdReusePolicy::WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE)
                ->withParentClosePolicy(ParentClosePolicy::POLICY_ABANDON)
        );
        $child->run();

        return 'COMPLETED';
    }
}

Environment/Versions

  • Temporal SDK Version: 1.1.1

Additional context

Workflow also waits for activities completion too... I mean I can start an activity and then do some stuff in workflow body. But it seems to not finish while spawned in this workflow activities won't complete. Is it ok?

[Bug] Workflow is always Running

Workflow example to reproduce the bug:

class ExampleWorkflow implements ExampleWorkflowInterface {

    public function start(): string|\Generator
    {
        $promises = [];

        foreach (range(1, 20) as $i) {
            $promises[] = Workflow::async(function () use ($i) {
                yield Workflow::timer(random_int(5, 10));
                $this->completedIds[] = $i;
            });
        }

        Workflow::async(function () {
            yield Workflow::timer(30);
            $this->completedIds[] = 21;
            $this->continue = true;
        });

        Workflow::await(
            Promise::all($promises),
            fn () => $this->continue,
        );

        return 'FINISHED';
}

Expected behavior:

After 10 seconds of execution the workflow stops since the first clause (Promise::all is resolved)

Actual behavior:

The workflow never stops ๐Ÿ˜ฟ

PHP docs reference wrong function or wrong class

Much of what the template asked for was N/A.

There's the following doc section for WorkerInterface::registerActivity():

* In case an activity class requires some external dependencies provide a callback - factory
* that creates or builds a new activity instance. The factory should be a callable which accepts
* an instance of ReflectionClass with an activity class which should be created.
*
* $worker->registerActivity(MyActivity::class, fn(ReflectionClass $class) => $container->create($class->getClass()));

Let me bring your attention to line 74 which instructs the developer to use a callback function that accepts a ReflectionClass instance, and to call getClass() on the concrete object inside the callback. Where is getClass() coming from? The primitive PHP ReflectionClass doesn't have this method. Was $class->getName() the intended method, if one should be passing the fully qualified name of the class?

I searched the codebase to rule out whether the instance of ReflectionClass being passed is actually a child class that implements getClass() but couldn't find such implementation, and if I missed it, then perhaps the docs should suggest using the child class instead of ReflectionClass to avoid any issues with the IDE which currently complains that getClass() doesn't exist.

Additional context

>>> $r = new \ReflectionClass(\App\User::class)
>>> $r->getClass()
PHP Error:  Call to undefined method ReflectionClass::getClass() in Psy Shell code on line 1
>>> $r->getName()
=> "App\User"

Screen Shot 2022-04-14 at 2 39 18 PM

[Bug] Parent workflow not completed or failed, when start child workflow execution failed

Describe the bug
Parent workflow is not completed or failed, when start child workflow execution failed.
I am using this samples code:
https://github.com/khoerintus/samples-php/tree/master/app/src/Child

To Reproduce
Steps to reproduce the behavior:

  1. First attempt, start parent workflow and it will start child workflow with workflow id

  2. Then the parent workflow will completed and child workflow will completed as expected
    Screenshot from 2021-06-17 20-29-27
    Screenshot from 2021-06-17 20-31-36

  3. Second attempt, start parent workflow and it will start child workflow with the same workflow id

  4. But the parent workflow status: Running, with StartChildWorkflowExecutionFailed event because of START_CHILD_WORKFLOW_EXECUTION_FAILED_CAUSE_WORKFLOW_ALREADY_EXISTS
    Screenshot from 2021-06-17 20-30-39
    Screenshot from 2021-06-17 20-30-45

  5. Workflow instance will stuck at running, and the last event is WorkflowTaskCompleted until it timeout.

Expected behavior
I expected to see workflow instance status is completed.
But the workflow instance status is running until it timeout.
If I do not catch the child workflow execution, then I expect that workflow instance status is failed.

Versions (please complete the following information where relevant):

  • OS: Linux
  • Temporal Version: 1.9.2
  • are you using Docker or Kubernetes or building Temporal from source? I am using Docker
  • RR version: rr version 2.2.1 (build time: 2021-05-13T18:00:32+0000, go1.16.4)

[Question] activity_schedule_to_start_latency metrics

Hi, I can't activity_task_schedule_to_start_latency metrics on php-sdk. Is it not implemented yet ?
I heard the php-sdk is built on top of Go one. So it should be there. Could you please reference me ?

I expect this metrics exist in this php-sdk.

Cancellation Scopes

  • Implement functionality
  • Testing (Implement testing API)
    • Execution
    • Cancellation
    • Nested scopes
    • Hierarchical ัancellation
    • Canceling a nested scope
    • Coroutine stack checks
    • Other cases

[Bug] Workflow timer debouncing does not work

What are you really trying to do?

I'm trying to write debonce workflow equivalent: workflow should start and then wait for the signal N seconds. And then if signal is triggered within these N seconds, timer should be re-launched. If the signal is not triggered workflow should go further.

Describe the bug

It seems to not work at all. Workflow jams in Running state instead of timer cancellation and then resetting another one. Also roadrunner writes this message:

Long golang-ish error from roadrunner container
roadrunner_1            | 2022-04-27T12:17:28.779Z      ERROR   temporal        Workflow panic  {"Namespace": "default", "TaskQueue": "default", "WorkerID": "default:e6ab0423-3304-4edf-a4d9-ef3f2af0d391", "WorkflowType": "ExampleWorkflow", "WorkflowID": "some_id", "RunID": "0d757f3f-5c7c-4b80-bcb0-1ade96fc82cc", "Attempt": 48, "Error": "flush queue: SoftJobError:\n\tcodec_execute:\n\tsync_worker_exec:\n\tsync_worker_exec_payload: LogicException: Got the response to undefined request 9002 in /opt/app/vendor/temporal/sdk/src/Internal/Transport/Client.php:60\nStack trace:\n#0 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(389): Temporal\\Internal\\Transport\\Client->dispatch(Object(Temporal\\Worker\\Transport\\Command\\SuccessResponse))\n#1 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(261): Temporal\\WorkerFactory->dispatch('\\n\\x03\\x08\\xAAF\\n\\x03\\x08\\xABF', Array)\n#2 /opt/app/bin/worker(45): Temporal\\WorkerFactory->run()\n#3 {main}", "StackTrace": "process event for default [panic]:\ngithub.com/temporalio/roadrunner-temporal/workflow.(*process).OnWorkflowTaskStarted(0xc000d7fa40, 0xc001734ec8)\n\tgithub.com/temporalio/[email protected]/workflow/workflow.go:144 +0x2e5\ngithub.com/spiral/sdk-go/internal.(*workflowExecutionEventHandlerImpl).ProcessEvent(0xc000934330, 0xc000abb940, 0xc0, 0x1)\n\tgithub.com/spiral/[email protected]/internal/internal_event_handlers.go:801 +0x1ff\ngithub.com/spiral/sdk-go/internal.(*workflowExecutionContextImpl).ProcessWorkflowTask(0xc00027b490, 0xc001666630)\n\tgithub.com/spiral/[email protected]/internal/internal_task_handlers.go:874 +0xca8\ngithub.com/spiral/sdk-go/internal.(*workflowTaskHandlerImpl).ProcessWorkflowTask(0xc000294210, 0xc001666630, 0xc001578ea0)\n\tgithub.com/spiral/[email protected]/internal/internal_task_handlers.go:723 +0x493\ngithub.com/spiral/sdk-go/internal.(*workflowTaskPoller).processWorkflowTask(0xc000a8add0, 0xc001666630)\n\tgithub.com/spiral/[email protected]/internal/internal_task_pollers.go:284 +0x2cd\ngithub.com/spiral/sdk-go/internal.(*workflowTaskPoller).ProcessTask(0xc000a8add0, {0x1594180, 0xc001666630})\n\tgithub.com/spiral/[email protected]/internal/internal_task_pollers.go:255 +0x6c\ngithub.com/spiral/sdk-go/internal.(*baseWorker).processTask(0xc0005f2200, {0x1593d40, 0xc001807160})\n\tgithub.com/spiral/[email protected]/internal/internal_worker_base.go:362 +0x167\ncreated by github.com/spiral/sdk-go/internal.(*baseWorker).runTaskDispatcher\n\tgithub.com/spiral/[email protected]/internal/internal_worker_base.go:282 +0xba"}

Minimal Reproduction

class ExampleWorkflow {
    private bool $stopWaiting = false;

    public function run()
    {
        while (!$this->stopWaiting) {
            $this->stopWaiting = true;

            yield Workflow::awaitWithTimeout(
                30,
                fn () => $this->stopWaiting === false
            );
        }

        return 'COMPLETED';
    }

    // signal
    public function resetTimer(): void
    {
        $this->stopWaiting = false;
    }
}

Try to trigger the signal several times a second -- this will reproduce the bug for sure ๐Ÿ˜„

Environment/Versions

  • Temporal SDK Version: 1.1.1

Additional context

I've implemented this logic another way. But I'm still wondering why this implementation did not work ๐Ÿคฏ

[Question] Workflow DI

I've a question regarding workflows. I know I'm asking a lot of questions, when I'll have some free time I'll try to propose some PRs for them!

Now the question is: how do I inject something into a Workflow? I understand this could be a definite nono for temporal paradigm? Is it?

If not, then, for example, currently we have our SAGA (workflow) configs for each SAGA step (activity) in YAML files.

It's impossible to inject anything into the Workflow class. The constructor is always called without any arguments.

Should I use activity to get this stuff from container? Kinda sounds like it shouldn't be an "activity" of a specific workflow either.

Here's an example.

#!/usr/bin/env php
<?php

use Temporal\Activity\ActivityOptions;
use Temporal\WorkerFactory;
use Temporal\Workflow\WorkflowInterface;

$myConfigs = ['greet_timeout' => 30];

#[WorkflowInterface]
class GreetingWorkflow
{
    private $greetingActivity;

    public function __construct(array $timeouts = [])
    {
        $this->greetingActivity = Workflow::newActivityStub(
            GreetingActivityInterface::class,
            
            // How to get this timeout setting ? 
            ActivityOptions::new()->withStartToCloseTimeout(
                CarbonInterval::seconds($timeouts['greet_timeout'])
            )
        );
    }

    public function greet(string $name): \Generator
    {
        return yield $this->greetingActivity->composeGreeting('Hello', $name);
    }
}

$factory = WorkerFactory::create();
$worker = $factory->newWorker();

// How do I register the workflow and inject the constructor params?
$worker->registerWorkflowTypes(GreetingWorkflow::class);

$factory->run();

[Bug] No way to setNamespace for a worker to make worker listen for queue in namespace

Describe the bug

There is no way in PHP to ask a worker to listen to specific namespace.

You can create a workflow client as such

$workflowClient = WorkflowClient::create(ServiceClient::create($host), $clientOptions->withNamespace($namespace));

and then create your commands there which will assign them to a specific namespace.

Creating a worker as such

$worker = $factory->newWorker(WorkerFactoryInterface::DEFAULT_TASK_QUEUE, WorkerOptions::new());

only listens to the default namespace. How can we avoid that?

To Reproduce

Mentioned in the bug description

Expected behavior

Somehow in the worker options to set namespace? When I create the workflow client with default namespace, everything runs and there are workers listening. If I create the workflow client with a different namespace, I get

There are no Workers currently listening to the Task Queue: default

Screenshots/Terminal output

Screen Shot 2021-12-01 at 10 51 30

Versions

  • OS: Mac
  • Temporal Version [e.g. 1.7.0?] latest
  • Are you using Docker or Kubernetes or building Temporal from source? Kubernetes

Additional context

Here in go for example: https://github.com/temporalio/samples-go/blob/cac8bb0ee971be97cd2e1f3d48ddfd247a21b7ec/cron/worker/main.go, you can create a client, and pass options which means this worker will listen to this client

[Bug] Workflow type is messed when using `continueAsNew`

Describe the bug
I'm trying to implement a self-restarting workflow. The idea is simple: await until user balance is zero, do stuff, await until it's above zero, then restart. Here's how the workflow looks like:

<?php

declare(strict_types=1);

namespace Application\BusinessProcess\ZeroBalance;

use Application\BusinessProcess\BusinessProcessWorkflowInterface;
use Temporal\Activity\ActivityOptions;
use Temporal\Internal\Workflow\ActivityProxy;
use Temporal\Workflow;

#[Workflow\WorkflowInterface]
class ZeroBalanceWorkflow implements BusinessProcessWorkflowInterface
{
    private ActivityProxy|ZeroBalanceActivity $activity;
    private ?int $balance = null;

    public function __construct()
    {
        $options = ActivityOptions::new()
            ->withScheduleToStartTimeout('PT1H')
            ->withScheduleToCloseTimeout('P1M')
            ->withStartToCloseTimeout('PT5M')
        ;

        $this->activity = Workflow::newActivityStub(ZeroBalanceActivity::class, $options);
    }

    #[Workflow\WorkflowMethod]
    public function execute(int $educationServiceId): \Generator
    {
        yield Workflow::await(fn () => $this->balance === 0);
        yield $this->activity->doStuffWhenZero($educationServiceId);
        yield Workflow::await(fn () => $this->balance > 0);
        yield $this->activity->doStuffWhenAboveZero($educationServiceId);
        return Workflow::continueAsNew(self::class, [$educationServiceId]);
    }

    #[Workflow\SignalMethod]
    public function setBalance(int $balance): void
    {
        $this->balance = $balance;
    }
}

Activity doesn't really do anything, but just for completeness:

<?php

declare(strict_types=1);

namespace Application\BusinessProcess\ZeroBalance;

use Application\BusinessProcess\BusinessProcessActivityInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Temporal\Activity;

#[Activity\ActivityInterface]
class ZeroBalanceActivity implements BusinessProcessActivityInterface, LoggerAwareInterface
{
    use LoggerAwareTrait;

    #[Activity\ActivityMethod]
    public function doStuffWhenZero(int $educationServiceId): int
    {
        $this->logger->info(sprintf('Doing stuff for ID %d, balance is zero', $educationServiceId));

        return 42;
    }

    #[Activity\ActivityMethod]
    public function doStuffWhenAboveZero(int $educationServiceId): int
    {
        $this->logger->info(sprintf('Doing stuff for ID %d, balance is ABOVE zero now', $educationServiceId));

        return 42;
    }
}

The tricky part is that I'm using Symfony as well. Here's the modified worker binary:

#!/usr/bin/env php
<?php

declare(strict_types=1);

use Application\BusinessProcess\BusinessProcessRegistry;
use Infrastructure\Kernel;
use Symfony\Component\Dotenv\Dotenv;
use Temporal\WorkerFactory;

ini_set('display_errors', 'stderr');

set_time_limit(0);
include dirname(__DIR__).'/vendor/autoload.php';

(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
$kernel = new Kernel((string) $_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();

$factory = WorkerFactory::create();
$worker = $factory->newWorker();

/** @psalm-var BusinessProcessRegistry $registry */
$registry = $kernel
    ->getContainer()
    ->get(BusinessProcessRegistry::class)
;

foreach ($registry->getWorkflowTypes() as $workflowType) {
    $worker->registerWorkflowTypes($workflowType);
}

foreach ($registry->getActivityTypes() as $activityInstance) {
    $worker->registerActivityImplementations($activityInstance);
}

$factory->run();

The BusinessProcessRegistry class looks like this:

<?php

declare(strict_types=1);

namespace Application\BusinessProcess;

use Generator;
use Spiral\Tokenizer\ClassLocator;
use Symfony\Component\Finder\Finder;

class BusinessProcessRegistry
{
    public function __construct(
        private string $projectDirectory,
        private iterable $activityTypes,
    )
    {
    }

    /**
     * @return Generator<class-string>
     */
    public function getWorkflowTypes(): Generator
    {
        $finder = Finder::create()
            ->files()
            ->name('*.php')
            ->in($this->projectDirectory . '/src/Application/BusinessProcess')
        ;

        $locator = new ClassLocator($finder);

        foreach ($locator->getClasses(BusinessProcessWorkflowInterface::class) as $class) {
            if ($class->isAbstract() || $class->isInterface()) {
                continue;
            }

            yield $class->getName();
        }
    }

    /**
     * @return Generator<object>
     */
    public function getActivityTypes(): Generator
    {
        /** @var object $activity */
        foreach ($this->activityTypes as $activity) {
            yield $activity;
        }
    }
}

Interfaces BusinessProcessWorkflowInterface and BusinessProcessActivityInterface are just dummy markers. Activities injected into constructor are Symfony services.

For testing I kickstart the workflow and then send signals to it:

        $workflow = $this->workflowClient->newWorkflowStub(ZeroBalanceWorkflow::class,
            WorkflowOptions::new()
                ->withWorkflowId((string) 14069)
                ->withWorkflowIdReusePolicy(IdReusePolicy::POLICY_ALLOW_DUPLICATE)
        );
        $this->workflowClient->startWithSignal($workflow, 'setBalance', [2], [14069]);
        sleep(2);
        $workflow->setBalance(0);
        sleep(2);
        $workflow->setBalance(8);

Once it reaches the continueAsNew, things get messy. It tries to start process with FQDN class name:

2021-04-27_07-03-28

Worker's log is full of errors about non-existent workflow:

2021/04/27 03:09:36 WARN    temporal        Failed to process workflow task.    {"Namespace": "default", "TaskQueue": "default", "WorkerID": "[email protected]@default@1", "WorkflowType": "Application\\BusinessProcess\\ZeroBalance\\ZeroBalanceWorkflow", "WorkflowID": "14069", "RunID": "e3b9ca91-6d80-449e-bfe4-46ba8f294ba9", "Attempt": 70, "Error": "unable to find workflow type: Application\\BusinessProcess\\ZeroBalance\\ZeroBalanceWorkflow. Supported types: [ZeroBalanceWorkflow]"}
2021/04/27 03:09:46 WARN    temporal        Failed to process workflow task.    {"Namespace": "default", "TaskQueue": "default", "WorkerID": "[email protected]@default@1", "WorkflowType": "Application\\BusinessProcess\\ZeroBalance\\ZeroBalanceWorkflow", "WorkflowID": "14069", "RunID": "e3b9ca91-6d80-449e-bfe4-46ba8f294ba9", "Attempt": 71, "Error": "unable to find workflow type: Application\\BusinessProcess\\ZeroBalance\\ZeroBalanceWorkflow. Supported types: [ZeroBalanceWorkflow]"}
2021/04/27 03:09:56 WARN    temporal        Failed to process workflow task.    {"Namespace": "default", "TaskQueue": "default", "WorkerID": "[email protected]@default@1", "WorkflowType": "Application\\BusinessProcess\\ZeroBalance\\ZeroBalanceWorkflow", "WorkflowID": "14069", "RunID": "e3b9ca91-6d80-449e-bfe4-46ba8f294ba9", "Attempt": 72, "Error": "unable to find workflow type: Application\\BusinessProcess\\ZeroBalance\\ZeroBalanceWorkflow. Supported types: [ZeroBalanceWorkflow]"}

The web UI was having seizures:

temporal-ui-seizures

I had to kill the bad workflow instance using tctl wf terminate. It wasn't showing up in tctl wf la for some reason, good thing the run ID was present in worker's log.

Versions (please complete the following information where relevant):

  • Temporal v1.8.1 running in Docker
  • PHP 8.0.1
  • Temporal PHP SDK v1.0.3
  • Symfony 5.2.x

[Question] Arrays not allowed as a return type?

Hello once again, I wanted to ask about this line.

I was curious why is that. I'm sure there's a specific reason for it. I could still wrap it in a simple container object.
An example:

#[ActivityInterface]
class WorkflowConfigActivity
{
    #[ActivityMethod]
    public function getTransactionConfigs(): array // Doesn't work
    {
        return ['my_config' => 'best_config'];
    }
}

[Bug] After countinueAsNew awaitWithTimeout doesn't check for condition

Describe the bug

my workflow waits for some condition via yield Workflow::awaitWithTimeout , but after workflow "restart" via continueAsNewStub seems that condition never goes to true. Let me provide an example code.

To Reproduce

basic code used from https://github.com/wolfy-j/temporal-workshop

<?php

namespace Workshop\App\Test;

use Temporal\Activity\ActivityOptions;
use Temporal\Workflow;
use Temporal\Workflow\WorkflowMethod;

class Test2Workflow implements Test2WorkflowInterface {

    private $photosBySourceEstateToProcess = [];
    private $photosProcessActivity;

    public function __construct()
    {
        $this->photosProcessActivity = Workflow::newActivityStub(
            TestActivity::class,
            ActivityOptions::new()->withStartToCloseTimeout(\DateInterval::createFromDateString('30 seconds'))
        );
    }


    public function run(array $initialPhotosToProcess = [])
    {
        if ($initialPhotosToProcess) {
            $this->photosBySourceEstateToProcess = $initialPhotosToProcess;
        }

        file_put_contents("php://stderr","startttt ".count($this->photosBySourceEstateToProcess));

        yield Workflow::awaitWithTimeout(300, fn()=> (count($this->photosBySourceEstateToProcess) > 10) || (count($initialPhotosToProcess) > 10));
        $photosToProcess = array_splice($this->photosBySourceEstateToProcess, 0, 10);

        foreach ($photosToProcess as $processData) {
            $processData = (array) $processData;
            yield $this->photosProcessActivity->loadSePhotos($processData['id'], $processData['photos'], $processData['withPage']);
        }

        return Workflow::newContinueAsNewStub(self::class)->run( $this->photosBySourceEstateToProcess);
    }
    #[Workflow\SignalMethod]
    public function setSourceEstatePhotos(int $sourceEstateId, $photos = [], $withPage = false)
    {
        $this->photosBySourceEstateToProcess[] = ['id' => $sourceEstateId, 'photos' => $photos, 'withPage' => $withPage];
    }

    #[Workflow\QueryMethod]
    public function getSourceEstatePhotos(): array
    {
        return $this->photosBySourceEstateToProcess;
    }
}

activity code

<?php

namespace Workshop\App\Test;

class TestActivity implements TestActivityInterface {

    public function hello(string $name): string
    {
        return sprintf("Hello, %s", $name);
    }

    public function loadSePhotos(int $sourceEstateId, array $photos, bool $withPage): string
    {
        $rayId = uniqid("v");
        file_put_contents("php://stderr", sprintf("%s, Working on.Load se #%d photos. Photos: %s", $rayId, $sourceEstateId, join(",", $photos)));
        sleep(1);
        file_put_contents("php://stderr", sprintf("%s, done", $rayId));
        return "yes";
    }
}

command used to start workflow:

<?php

namespace Workshop\App\Test;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Workshop\Util\Command;

class Test4Command extends Command {
    protected const NAME = 'run_source_photos';

    public function execute(InputInterface $input, OutputInterface $output)
    {
        try {
            $workflow = $this->workflowClient->newWorkflowStub(Test2Workflow::class);
            $d = $this->workflowClient->start($workflow);


            $output->writeln(sprintf("Id: %s", $d->getExecution()->getID()));
        } catch (\Throwable $ex) {
            $output->writeln($ex->getTraceAsString());
        }
        return 0;
    }
}

command used to send signals:

<?php

namespace Workshop\App\Test;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Workshop\Util\Command;

class Test3Command extends Command {
    protected const NAME = 'push_photos';
    protected const ARGUMENTS = [
        ['name' => 'wfId']
    ];

    public function execute(InputInterface $input, OutputInterface $output)
    {
        try {
            $wf = $this->workflowClient->newRunningWorkflowStub(Test2Workflow::class, $input->getArgument('wfId'));

            for ($i = 0;$i < 30;$i++) {
                $wf->setSourceEstatePhotos($i, ["1.jpg", "2.jpg", "3.jpg"]);
            }

        } catch (\Throwable $ex) {
            $output->writeln($ex->getMessage());
            $output->writeln($ex->getTraceAsString());
        }
        return 0;
    }
}

so first i execute

php ./app.php run_source_photos

get an workflow id and run

php ./app.php push_photos %workflow_id%

Expected behavior

that right after processing of first 10 elements and workflow restart it will start next workflow and yield Workflow::awaitWithTimeout wouldn't wait for timeout, because condition will return true.

Screenshots/Terminal output

Versions

Additional context

.rr.yaml

rpc:
  listen: tcp://127.0.0.1:6001

server:
  command: "php worker.php"
  env:
    XDEBUG_SESSION: 1
    XDEBUG_CONFIG: idekey=PHPSTORM
    PHP_IDE_CONFIG: serverName=temporalphp
    

logs:
  level: info
  channels:
    informer.mode: none

temporal:
  address: host.docker.internal:7233
  activities:
    debug: false
    num_workers: 8

http:
  address: 127.0.0.1:8080
  pool:
    num_workers: 8

Hide unused files

Before release, we need to exclude a set of files from the composer package:

  • .buildkite/
  • .github/
  • docker/
  • proto/
  • tests/
  • .php_cs.dist
  • Makefile
  • phpunit.xml
  • psalm.xml

composer require fail because of an old version of `psr/log`

What are you really trying to do?

trying to require temporal/sdk into my PHP application

Describe the bug

The SDK is using "psr/log": "^1.1" which is an old version and need to be updated, the latest version is 3.0.0
which is making conflict with other packages using the newer version of psr/log

someone try to upgrade it in this PR #169 but not sure why its closed

php version: 8.1

[Feature Request] Implement GetSearchAttributesRequest

I want to be able to search the workflows and activities programatically (it is already possible in java)

I'm currently integrating temporal and I want to be able to write integration tests, where I want to check the states of some workflows. Currently I need to run shell tctl command from PHP to get the data I need. Or maybe I just didn't notice where the API for this is hidden.

Describe the solution you'd like

It would be sufficient to get the same functionality as in the java sdk

[Feature Request] Add metadata.messageType to protobuf payloads

To match these PRs in Java SDK (with explanation) and Go SDK.

Additional context

I'm not sure how to get the message type / full name (my.package.MyMessage) from the message instance. Google's PHP Protobuf library doesn't seem to be documented, but perhaps message.desc.getFullName()? $this->desc is private:

https://github.com/protocolbuffers/protobuf/blob/master/php/src/Google/Protobuf/Internal/Message.php#L91-L94

but perhaps it's still accessible? https://stackoverflow.com/a/2448499/627729

[Question] Injecting custom reader into worker factory.

Hello,

Is there any way to inject a custom reader into WorkerFactory/Worker?

We're switching over to temporal and I'd like to register the old activities/workflows using our old logic for now and move over to Temporal attributes as new activites/workflows appear.

But it seems, cause of all the final classes the only way to do this would be to copy over the WorkerFactory completely.

Is this #88 dealing with the same issue?

Thanks for help!

[Bug] Workflow keeps stuck with awaitWithTimeout and signal

Workflow example to reproduce bug:

class DelayedWorkflow implements DelayedWorkflowInterface
{
    private bool $doCancel;

    public function executeDelayed()
    {
        $this->doCancel = false;

        yield Workflow::awaitWithTimeout(
        	30,
        	fn() => $this->doCancel
        );

        if ($this->doCancel) {
            return 'CANCEL';
        }

        return 'OK';
    }

    public function cancel(): void
    {
        $this->doCancel = true;
    }
}

Expected behavior:

  1. Run the workflow, after 30 seconds workflow completes with result OK.
  2. Run the workflow, send cancel signal, workflow completes with result CANCEL.

Actual result:

  1. Run the workflow, after 30 seconds it still stays running. If after that I send cancel signal, workflow completes with result OK.
  2. Run the workflow, send cancel signal, workflow stays running. After 30 seconds it completes with result CANCEL.

Is the WorkflowClient going to available in the php sdk soon?

Hi,

I noticed that the php-sdk doesn't have a workflow client, similar to how Java has one (where you can also configure the namespace, etc.)

I know this SDK is a work in progress, but just wanted to check if I was missing something and if the client will be available soon/ is planned to be incorporated as part of this project. Thanks!

Anand

Process execution does not continue after await condition is satissfied (through signals)

I am trying to halt process execution until one of two condition are met.
These conditions are set by two distinct signals.

In general, everything works fine but in some cases (and it is often... maybe 10% of the cases), the signal is received and processed but the instance does not continue its execution. I have not been able to identify a pattern.

So I am waiting for the conditions like this:

yield Workflow::await(fn() => !is_null($this->authorizationId) || $this->exitProcess);

Waiting to execute one of these signals:

    public function workflowAuthorizationExecuted(int $taskId)
	{
		$this->authorizationId = $taskId;
	}

    public function indicationHasBeenSuspended()
	{
		$this->exitProcess = true;
	}

Maybe I am doing something wrong, but I find it strange that some instances work and some don't.
For those instances that don't work, even if I resend the signal nothing happens.
How can I make sure the instance is waiting in the "await" command?

I haven't been able to replicate in our DEV or QA environment. It is happening in production.
I have attached my workflow definition.

I am using docker under Alpine Linux v3.11.

[Bug] Namespace not being propagated into ChildWorflowOptions, but probably should be (according to comments)

Describe the bug
I have a parent workflow which creates some child workflows. When using custom namespace (registered properly in the workflow client) it isn't being propagated to the child worflows (or child workflow options), but it seems like it should be propagated according to this comment: https://github.com/temporalio/sdk-php/blob/master/src/Workflow/ChildWorkflowOptions.php#L44

/**
     * Namespace of the child workflow.
     *
     * Optional: the current workflow (parent)'s namespace will be used if this
     * is not provided.
     */
    #[Marshal(name: 'Namespace')]
    public string $namespace = ClientOptions::DEFAULT_NAMESPACE;

It also might be the case that I'm doing something wrong in my parent worflow.

To Reproduce

My broken parent workflow looked like this:

final class DefaultForkingWorkflow implements ForkingWorkflow
{
    public function fork(Event $event, HandlerDefinitions $definitions)
    {
        $promise = Workflow::async(static function () use ($event, $definitions) {
            $promises = [];

            foreach ($definitions as $definition) {
                $options = ChildWorkflowOptions::new()
                    ->withParentClosePolicy(ParentClosePolicy::POLICY_ABANDON)
                    ->withSearchAttributes([
                        SearchAttributes::ATTR_EVENT_ID => $event->getMetadata(Id::class)->toString(),
                        SearchAttributes::ATTR_HANDLER_ID => $definition->getServiceId(),
                        SearchAttributes::ATTR_HANDLER_METHOD => $definition->getHandlerMethod(),
                    ]);
                $handlerWorkflow = Workflow::newChildWorkflowStub(
                    HandlerWorkflow::class,
                    $options
                );

                $promises[] = Workflow::asyncDetached(
                    static fn () => yield $handlerWorkflow->handle($event, $definition)
                );
            }

            return yield Promise::all($promises);
        });

        try {
            yield $promise;
        } catch (TemporalException $e) {
            //$logger->error('child workflow failed');
            throw $e;
        }
    }
}

With this, temporal was logging this error: Unable to schedule child execution across namespace default. The error is correct because I changed the default namespace.

To make it work properly, I needed to propagate the namespace name from outside:

final class DefaultForkingWorkflow implements ForkingWorkflow
{
    public function fork(Event $event, HandlerDefinitions $definitions, string $namespace)
    {
        $promise = Workflow::async(static function () use ($event, $definitions, $namespace) {
            $promises = [];

            foreach ($definitions as $definition) {
                $options = ChildWorkflowOptions::new()
                    ->withParentClosePolicy(ParentClosePolicy::POLICY_ABANDON)
                    ->withNamespace($namespace)
                    ->withSearchAttributes([
                        SearchAttributes::ATTR_EVENT_ID => $event->getMetadata(Id::class)->toString(),
                        SearchAttributes::ATTR_HANDLER_ID => $definition->getServiceId(),
                        SearchAttributes::ATTR_HANDLER_METHOD => $definition->getHandlerMethod(),
                    ]);
                $handlerWorkflow = Workflow::newChildWorkflowStub(
                    HandlerWorkflow::class,
                    $options
                );

                $promises[] = Workflow::asyncDetached(
                    static fn () => yield $handlerWorkflow->handle($event, $definition)
                );
            }

            return yield Promise::all($promises);
        });

        try {
            yield $promise;
        } catch (TemporalException $e) {
            //$logger->error('child workflow failed');
            throw $e;
        }
    }
}

Expected behavior
I think there should be no explicit namespace propagation (at least according to the docblock comment in the ChildWorkflowOptions class).

Versions (please complete the following information where relevant):

  • OS: Linux in Docker for mac
  • Temporal Version: 1.8.2
  • are you using Docker or Kubernetes or building Temporal from source? - nope, only when I need to dig for errors

Integration issue with Laravel

Hi,
I am trying to integrate temporal with Laravel 8.x, but not able to access Laravel facades inside the workflow or activity, exception is thrown from laravel
RuntimeException: A facade root has not been set.

[Bug] Wrong activity response type

Describe the bug
I send an array from my activity as a result, but in promise step then I get stdClass instead of array.

To Reproduce
Steps to reproduce the behavior:

  1. Create activity that will send an associative array.
  2. Create workflow where you call activity and add post-processing in then step.
  3. Try to get value from result as if it array.

Expected behavior
I get value

Expected behavior
I get an error PanicError: Cannot use object of type stdClass as array (type: Error, retryable: true)

Screenshots/Terminal ouput
image

You can use this snippet:

#[\Temporal\Activity\ActivityInterface(prefix: "app.")]
class CommonActivity
{
    public function slow(string $name): array
    {
        return [
            'result' => 'Hello, ' . $name,
        ];
    }
}
#[\Temporal\Workflow\WorkflowInterface]
final class FastWorkflow
{
    #[\Temporal\Workflow\WorkflowMethod("wf")]
    public function run(string $name, int $count): \Generator
    {
        $activity = Workflow::newActivityStub(
            CommonActivity::class
        );

        $activity->slow($name)->then(function ($result) {
            $result['result'];
        });

        return 0;
    }
}

As I understand the problem is locating here. But I don't know how to solve it.

[Question] Sentry Integration for error logging

Is there any best practice to integrate temporal with sentry for error logging?
I know that I can use sentry php sdk, try catch in my code, and send the error.
But is there any global error handler that catch every error (from Workflow or Activity), so I can just send the error from there?
Or I can integrate roadrunner with sentry?

[Bug] Issues with sticky execution/queries/signals

What are you really trying to do?

We've started a quite complex workflow with timers and signals in production, and faced with unstable behaviour: some workflow executions are OK, some lead to workers' crashes and stuck forever.

Describe the bug

After worker's restart the behaviour of queries and signals becomes unstable.

Minimal Reproduction

Test workflow:

#[WorkflowInterface]
class DummyWorkflow
{
    private int $startAtTimestamp;

    private bool $shouldStop = false;

    #[WorkflowMethod]
    #[ReturnType(Type::TYPE_STRING)]
    public function start(): string|\Generator
    {
        $this->startAtTimestamp = Workflow::now()->getTimestamp();

        yield Workflow::awaitWithTimeout(
            120,
            fn () => $this->shouldStop,
        );

        if ($this->shouldStop) {
            return 'Stopped';
        }

        return 'Completed';
    }

    #[SignalMethod]
    public function stop(): void
    {
        $this->shouldStop = true;
    }

    #[QueryMethod('payload')]
    public function query(): object
    {
        return (object)[
            'secondsFromStart' => time() - $this->startAtTimestamp,
        ];
    }
}

Case 1: worker restart (e.g. after crash)

  1. Start workflow
  2. Reload workers with rr reset
  3. Try to query workflow

tctl output:

$ tcl wf query -qt payload -w e5da1aee-a9fb-412b-9b96-35182203526b
Error: Query workflow failed.
Error Details: rpc error: code = InvalidArgument desc = workflow_process_handle_query: Workflow with the specified run identifier "effaf1ce-82d8-4bc3-b9df-f66fd2e9ca61" not found (type: LogicException, retryable: true)

Temporal log:

{"level":"error","ts":"2022-05-12T05:44:49.095Z","msg":"query directly though matching on sticky failed, will not attempt query on non-sticky","service":"history","shard-id":1,"address":"172.19.0.14:7234","shard-item":"0xc0004bac80","component":"history-engine","wf-namespace":"default","wf-id":"e5da1aee-a9fb-412b-9b96-35182203526b","wf-run-id":"55901692-8758-4870-a66a-a637d79d4698","wf-query-type":"payload","error":"workflow_process_handle_query: Workflow with the specified run identifier \"55901692-8758-4870-a66a-a637d79d4698\" not found (type: LogicException, retryable: true)","logging-call-at":"historyEngine.go:884","stacktrace":"go.temporal.io/server/common/log.(*zapLogger).Error\n\t/temporal/common/log/zap_logger.go:142\ngo.temporal.io/server/service/history.(*historyEngineImpl).queryDirectlyThroughMatching\n\t/temporal/service/history/historyEngine.go:884\ngo.temporal.io/server/service/history.(*historyEngineImpl).QueryWorkflow\n\t/temporal/service/history/historyEngine.go:790\ngo.temporal.io/server/service/history.(*Handler).QueryWorkflow.func1\n\t/temporal/service/history/handler.go:968\ngo.temporal.io/server/common/backoff.RetryContext\n\t/temporal/common/backoff/retry.go:125\ngo.temporal.io/server/service/history.(*Handler).QueryWorkflow\n\t/temporal/service/history/handler.go:966\ngo.temporal.io/server/api/historyservice/v1._HistoryService_QueryWorkflow_Handler.func1\n\t/temporal/api/historyservice/v1/service.pb.go:1401\ngo.temporal.io/server/common/rpc/interceptor.(*RateLimitInterceptor).Intercept\n\t/temporal/common/rpc/interceptor/rate_limit.go:83\ngoogle.golang.org/grpc.chainUnaryInterceptors.func1.1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1113\ngo.temporal.io/server/common/rpc/interceptor.(*TelemetryInterceptor).Intercept\n\t/temporal/common/rpc/interceptor/telemetry.go:108\ngoogle.golang.org/grpc.chainUnaryInterceptors.func1.1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1116\ngo.temporal.io/server/common/metrics.NewServerMetricsTrailerPropagatorInterceptor.func1\n\t/temporal/common/metrics/grpc.go:113\ngoogle.golang.org/grpc.chainUnaryInterceptors.func1.1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1116\ngo.temporal.io/server/common/metrics.NewServerMetricsContextInjectorInterceptor.func1\n\t/temporal/common/metrics/grpc.go:66\ngoogle.golang.org/grpc.chainUnaryInterceptors.func1.1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1116\ngo.temporal.io/server/common/rpc.ServiceErrorInterceptor\n\t/temporal/common/rpc/grpc.go:131\ngoogle.golang.org/grpc.chainUnaryInterceptors.func1.1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1116\ngoogle.golang.org/grpc.chainUnaryInterceptors.func1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1118\ngo.temporal.io/server/api/historyservice/v1._HistoryService_QueryWorkflow_Handler\n\t/temporal/api/historyservice/v1/service.pb.go:1403\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1279\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1608\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:923"}

Roadrunner log:

2022-05-12T05:44:49.078Z	DEBUG	temporal    	outgoing message	{"data": "\nR\u0008\t\u0012\u000bInvokeQuery\u001aA{\"runId\":\"55901692-8758-4870-a66a-a637d79d4698\",\"name\":\"payload\"} {\"taskQueue\":\"default\",\"tickTime\":\"2022-05-12T05:44:20Z\"}"}
2022-05-12T05:44:49.094Z	DEBUG	temporal    	received message	{"data": "\n\ufffd\r\u0008\t\"\ufffd\r\n[Workflow with the specified run identifier \"55901692-8758-4870-a66a-a637d79d4698\" not found\u0012\u0007PHP_SDK\u001a\ufffd\u000cLogicException: Workflow with the specified run identifier \"55901692-8758-4870-a66a-a637d79d4698\" not found in /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router/WorkflowProcessAwareRoute.php:58\nStack trace:\n#0 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router/WorkflowProcessAwareRoute.php(44): Temporal\\Internal\\Transport\\Router\\WorkflowProcessAwareRoute->findProcessOrFail('55901692-8758-4...')\n#1 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router/InvokeQuery.php(55): Temporal\\Internal\\Transport\\Router\\WorkflowProcessAwareRoute->findInstanceOrFail('55901692-8758-4...')\n#2 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router.php(81): Temporal\\Internal\\Transport\\Router\\InvokeQuery->handle(Object(Temporal\\Worker\\Transport\\Command\\Request), Array, Object(React\\Promise\\Deferred))\n#3 /opt/app/vendor/temporal/sdk/src/Worker/Worker.php(93): Temporal\\Internal\\Transport\\Router->dispatch(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#4 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(413): Temporal\\Worker\\Worker->dispatch(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#5 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Server.php(60): Temporal\\WorkerFactory->onRequest(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#6 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(387): Temporal\\Internal\\Transport\\Server->dispatch(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#7 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(261): Temporal\\WorkerFactory->dispatch('\\nR\\x08\\t\\x12\\vInvokeQue...', Array)\n#8 /opt/app/bin/worker(45): Temporal\\WorkerFactory->run()\n#9 {main}*\u0010\n\u000eLogicException"}
  1. Try to send signal

Web UI:
Signal has been written into event history, workflow tasks seems to be executed, but nothing actually happens (workflow continues to run):
image

Roadrunner log:

2022-05-12T05:48:07.728Z	DEBUG	temporal    	outgoing message	{"data": "\nY\u0008\n\u0012\u000cInvokeSignal\u001aG{\"runId\":\"55901692-8758-4870-a66a-a637d79d4698\",\"name\":\"stop\"} {\"taskQueue\":\"default\",\"tickTime\":\"2022-05-12T05:48:07Z\"}"}
2022-05-12T05:48:07.730Z	DEBUG	temporal    	received message	{"data": "\n\ufffd\r\u0008\n\"\ufffd\r\n[Workflow with the specified run identifier \"55901692-8758-4870-a66a-a637d79d4698\" not found\u0012\u0007PHP_SDK\u001a\ufffd\u000cLogicException: Workflow with the specified run identifier \"55901692-8758-4870-a66a-a637d79d4698\" not found in /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router/WorkflowProcessAwareRoute.php:58\nStack trace:\n#0 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router/WorkflowProcessAwareRoute.php(44): Temporal\\Internal\\Transport\\Router\\WorkflowProcessAwareRoute->findProcessOrFail('55901692-8758-4...')\n#1 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router/InvokeSignal.php(45): Temporal\\Internal\\Transport\\Router\\WorkflowProcessAwareRoute->findInstanceOrFail('55901692-8758-4...')\n#2 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router.php(81): Temporal\\Internal\\Transport\\Router\\InvokeSignal->handle(Object(Temporal\\Worker\\Transport\\Command\\Request), Array, Object(React\\Promise\\Deferred))\n#3 /opt/app/vendor/temporal/sdk/src/Worker/Worker.php(93): Temporal\\Internal\\Transport\\Router->dispatch(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#4 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(413): Temporal\\Worker\\Worker->dispatch(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#5 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Server.php(60): Temporal\\WorkerFactory->onRequest(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#6 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(387): Temporal\\Internal\\Transport\\Server->dispatch(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#7 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(261): Temporal\\WorkerFactory->dispatch('\\nY\\x08\\n\\x12\\fInvokeSig...', Array)\n#8 /opt/app/bin/worker(45): Temporal\\WorkerFactory->run()\n#9 {main}*\u0010\n\u000eLogicException"}

Case 2: roadrunner restarts (e.g. after redeploy)

  1. Start workflow
  2. Restart roadrunner server with docker restart roadrunner_container
  3. Try to query workflow โ†’ got expected result

Temporal log:

{"level":"info","ts":"2022-05-12T05:59:19.961Z","msg":"query direct through matching failed on sticky, clearing sticky before attempting on non-sticky","service":"history","shard-id":1,"address":"172.19.0.14:7234","shard-item":"0xc0004bac80","component":"history-engine","wf-namespace":"default","wf-id":"teacher_16522_remove_slots","wf-run-id":"dccef09e-51f6-4a94-b852-48244932eccc","wf-query-type":"payload","logging-call-at":"historyEngine.go:893"}
{"level":"info","ts":"2022-05-12T05:59:19.961Z","msg":"query directly through matching on sticky timed out, attempting to query on non-sticky","service":"history","shard-id":1,"address":"172.19.0.14:7234","shard-item":"0xc0004bac80","component":"history-engine","wf-namespace":"default","wf-id":"teacher_16522_remove_slots","wf-run-id":"dccef09e-51f6-4a94-b852-48244932eccc","wf-query-type":"payload","wf-task-queue-name":"c55a0d6a348f:dcc25f94-63bf-4c1e-b42d-cc6e680fede9","wf-next-event-id":6,"logging-call-at":"historyEngine.go:923"}

Roadrunner log (workflow is replaying):

2022-05-12T05:59:19.965Z	DEBUG	temporal    	workflow execute	{"runID": "dccef09e-51f6-4a94-b852-48244932eccc", "workflow info": {"WorkflowExecution":{"ID":"XXXXX","RunID":"dccef09e-51f6-4a94-b852-48244932eccc"},"WorkflowType":{"Name":"DummyWorkflow"},"TaskQueueName":"default","WorkflowExecutionTimeout":0,"WorkflowRunTimeout":0,"WorkflowTaskTimeout":10000000000,"Namespace":"default","Attempt":1,"WorkflowStartTime":"2022-05-12T05:57:38.056903166Z","CronSchedule":"","ContinuedExecutionRunID":"","ParentWorkflowNamespace":"","ParentWorkflowExecution":null,"Memo":null,"SearchAttributes":{"indexed_fields":{"SkyTeacherId":{"metadata":{"encoding":"anNvbi9wbGFpbg==","type":"SW50"},"data":"MTY1MjI="}}},"BinaryChecksum":"366aad4717fdb22d55eebef7c454528a"}}
2022-05-12T05:59:19.965Z	DEBUG	temporal    	sequenceID	{"before": 0}
2022-05-12T05:59:19.965Z	DEBUG	temporal    	sequenceID	{"after": 1}
2022-05-12T05:59:19.965Z	DEBUG	temporal    	workflow task started	{"time": "1s"}
2022-05-12T05:59:19.966Z	DEBUG	temporal    	outgoing message	{"data": "\n\ufffd\u0006\u0008\u0001\u0012\rStartWorkflow\u001a\ufffd\u0005{\"info\":{\"WorkflowExecution\":{\"ID\":\"teacher_16522_remove_slots\",\"RunID\":\"dccef09e-51f6-4a94-b852-48244932eccc\"},\"WorkflowType\":{\"Name\":\"DummyWorkflow\"},\"TaskQueueName\":\"default\",\"WorkflowExecutionTimeout\":0,\"WorkflowRunTimeout\":0,\"WorkflowTaskTimeout\":10000000000,\"Namespace\":\"default\",\"Attempt\":1,\"WorkflowStartTime\":\"2022-05-12T05:57:38.056903166Z\",\"CronSchedule\":\"\",\"ContinuedExecutionRunID\":\"\",\"ParentWorkflowNamespace\":\"\",\"ParentWorkflowExecution\":null,\"Memo\":null,\"SearchAttributes\":{\"indexed_fields\":{\"SkyTeacherId\":{\"metadata\":{\"encoding\":\"anNvbi9wbGFpbg==\",\"type\":\"SW50\"},\"data\":\"MTY1MjI=\"}}},\"BinaryChecksum\":\"366aad4717fdb22d55eebef7c454528a\"}}*D\n\u001f\n\u0016\n\u0008encoding\u0012\njson/plain\u0012\u000516522\n!\n\u0016\n\u0008encoding\u0012\njson/plain\u0012\u00072592000 {\"taskQueue\":\"default\",\"tickTime\":\"2022-05-12T05:57:38Z\",\"replay\":true}"}
2022-05-12T05:59:19.979Z	DEBUG	temporal    	received message	{"data": "\n\"\u0008\ufffdF\u0012\u0008NewTimer\u001a\u0011{\"ms\":2592000000}*\u0000\n\u001f\u0008\u0001*\u001b\n\u0019\n\u0017\n\u0008encoding\u0012\u000bbinary/null"}
2022-05-12T05:59:19.979Z	DEBUG	temporal    	sequenceID	{"before": 1}
2022-05-12T05:59:19.979Z	DEBUG	temporal    	sequenceID	{"after": 2}
2022-05-12T05:59:19.979Z	DEBUG	temporal    	outgoing message	{"data": "\nR\u0008\u0002\u0012\u000bInvokeQuery\u001aA{\"runId\":\"dccef09e-51f6-4a94-b852-48244932eccc\",\"name\":\"payload\"} {\"taskQueue\":\"default\",\"tickTime\":\"2022-05-12T05:57:38Z\",\"replay\":true}"}
2022-05-12T05:59:19.983Z	DEBUG	temporal    	received message	{"data": "\nd\u0008\u0002*`\n^\n\u0016\n\u0008encoding\u0012\njson/plain\u0012D{\"teacherId\":16522,\"removalScheduledOn\":\"2022-06-11T05:57:38+00:00\"}"}
  1. Try to query workflow again โ†’ got context deadline exceeded error

Temporal log:

{"level":"info","ts":"2022-05-12T06:04:51.831Z","msg":"query directly through matching on sticky timed out, attempting to query on non-sticky","service":"history","shard-id":1,"address":"172.19.0.14:7234","shard-item":"0xc0004bac80","component":"history-engine","wf-namespace":"default","wf-id":"XXX","wf-run-id":"dccef09e-51f6-4a94-b852-48244932eccc","wf-query-type":"payload","wf-task-queue-name":"","wf-next-event-id":6,"logging-call-at":"historyEngine.go:923"}
{"level":"error","ts":"2022-05-12T06:05:21.830Z","msg":"query directly though matching on non-sticky failed","service":"history","shard-id":1,"address":"172.19.0.14:7234","shard-item":"0xc0004bac80","component":"history-engine","wf-namespace":"default","wf-id":"XXX","wf-run-id":"dccef09e-51f6-4a94-b852-48244932eccc","wf-query-type":"payload","error":"context deadline exceeded","logging-call-at":"historyEngine.go:941","stacktrace":"go.temporal.io/server/common/log.(*zapLogger).Error\n\t/temporal/common/log/zap_logger.go:142\ngo.temporal.io/server/service/history.(*historyEngineImpl).queryDirectlyThroughMatching\n\t/temporal/service/history/historyEngine.go:941\ngo.temporal.io/server/service/history.(*historyEngineImpl).QueryWorkflow\n\t/temporal/service/history/historyEngine.go:790\ngo.temporal.io/server/service/history.(*Handler).QueryWorkflow.func1\n\t/temporal/service/history/handler.go:968\ngo.temporal.io/server/common/backoff.RetryContext\n\t/temporal/common/backoff/retry.go:125\ngo.temporal.io/server/service/history.(*Handler).QueryWorkflow\n\t/temporal/service/history/handler.go:966\ngo.temporal.io/server/api/historyservice/v1._HistoryService_QueryWorkflow_Handler.func1\n\t/temporal/api/historyservice/v1/service.pb.go:1401\ngo.temporal.io/server/common/rpc/interceptor.(*RateLimitInterceptor).Intercept\n\t/temporal/common/rpc/interceptor/rate_limit.go:83\ngoogle.golang.org/grpc.chainUnaryInterceptors.func1.1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1113\ngo.temporal.io/server/common/rpc/interceptor.(*TelemetryInterceptor).Intercept\n\t/temporal/common/rpc/interceptor/telemetry.go:108\ngoogle.golang.org/grpc.chainUnaryInterceptors.func1.1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1116\ngo.temporal.io/server/common/metrics.NewServerMetricsTrailerPropagatorInterceptor.func1\n\t/temporal/common/metrics/grpc.go:113\ngoogle.golang.org/grpc.chainUnaryInterceptors.func1.1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1116\ngo.temporal.io/server/common/metrics.NewServerMetricsContextInjectorInterceptor.func1\n\t/temporal/common/metrics/grpc.go:66\ngoogle.golang.org/grpc.chainUnaryInterceptors.func1.1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1116\ngo.temporal.io/server/common/rpc.ServiceErrorInterceptor\n\t/temporal/common/rpc/grpc.go:131\ngoogle.golang.org/grpc.chainUnaryInterceptors.func1.1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1116\ngoogle.golang.org/grpc.chainUnaryInterceptors.func1\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1118\ngo.temporal.io/server/api/historyservice/v1._HistoryService_QueryWorkflow_Handler\n\t/temporal/api/historyservice/v1/service.pb.go:1403\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1279\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1608\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:923"}

Roadrunner log:

2022-05-12T06:04:51.834Z	DEBUG	temporal    	workflow execute	{"runID": "dccef09e-51f6-4a94-b852-48244932eccc", "workflow info": {"WorkflowExecution":{"ID":"XXX","RunID":"dccef09e-51f6-4a94-b852-48244932eccc"},"WorkflowType":{"Name":"DummyWorkflow"},"TaskQueueName":"default","WorkflowExecutionTimeout":0,"WorkflowRunTimeout":0,"WorkflowTaskTimeout":10000000000,"Namespace":"default","Attempt":1,"WorkflowStartTime":"2022-05-12T05:57:38.056903166Z","CronSchedule":"","ContinuedExecutionRunID":"","ParentWorkflowNamespace":"","ParentWorkflowExecution":null,"Memo":null,"SearchAttributes":{"indexed_fields":{"SkyTeacherId":{"metadata":{"encoding":"anNvbi9wbGFpbg==","type":"SW50"},"data":"MTY1MjI="}}},"BinaryChecksum":"366aad4717fdb22d55eebef7c454528a"}}
2022-05-12T06:04:51.834Z	DEBUG	temporal    	sequenceID	{"before": 2}
2022-05-12T06:04:51.834Z	DEBUG	temporal    	sequenceID	{"after": 3}
2022-05-12T06:04:51.834Z	DEBUG	temporal    	workflow task started	{"time": "1s"}
2022-05-12T06:04:51.834Z	DEBUG	temporal    	outgoing message	{"data": "\n\ufffd\u0006\u0008\u0003\u0012\rStartWorkflow\u001a\ufffd\u0005{\"info\":{\"WorkflowExecution\":{\"ID\":\"XXX\",\"RunID\":\"dccef09e-51f6-4a94-b852-48244932eccc\"},\"WorkflowType\":{\"Name\":\"DummyWorkflow\"},\"TaskQueueName\":\"default\",\"WorkflowExecutionTimeout\":0,\"WorkflowRunTimeout\":0,\"WorkflowTaskTimeout\":10000000000,\"Namespace\":\"default\",\"Attempt\":1,\"WorkflowStartTime\":\"2022-05-12T05:57:38.056903166Z\",\"CronSchedule\":\"\",\"ContinuedExecutionRunID\":\"\",\"ParentWorkflowNamespace\":\"\",\"ParentWorkflowExecution\":null,\"Memo\":null,\"SearchAttributes\":{\"indexed_fields\":{\"SkyTeacherId\":{\"metadata\":{\"encoding\":\"anNvbi9wbGFpbg==\",\"type\":\"SW50\"},\"data\":\"MTY1MjI=\"}}},\"BinaryChecksum\":\"366aad4717fdb22d55eebef7c454528a\"}}*D\n\u001f\n\u0016\n\u0008encoding\u0012\njson/plain\u0012\u000516522\n!\n\u0016\n\u0008encoding\u0012\njson/plain\u0012\u00072592000 {\"taskQueue\":\"default\",\"tickTime\":\"2022-05-12T05:57:38Z\",\"replay\":true}"}
2022-05-12T06:04:51.837Z	DEBUG	temporal    	received message	{"data": "\n\ufffd\u000c\u0008\u0003\"\ufffd\u000c\n{Workflow \"DummyWorkflow\" with run id \"dccef09e-51f6-4a94-b852-48244932eccc\" has been already started\u0012\u0007PHP_SDK\u001a\ufffd\u000bLogicException: Workflow \"DummyWorkflow\" with run id \"dccef09e-51f6-4a94-b852-48244932eccc\" has been already started in /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router/StartWorkflow.php:95\nStack trace:\n#0 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router/StartWorkflow.php(63): Temporal\\Internal\\Transport\\Router\\StartWorkflow->findWorkflowOrFail(Object(Temporal\\Workflow\\WorkflowInfo))\n#1 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router.php(81): Temporal\\Internal\\Transport\\Router\\StartWorkflow->handle(Object(Temporal\\Worker\\Transport\\Command\\Request), Array, Object(React\\Promise\\Deferred))\n#2 /opt/app/vendor/temporal/sdk/src/Worker/Worker.php(93): Temporal\\Internal\\Transport\\Router->dispatch(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#3 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(413): Temporal\\Worker\\Worker->dispatch(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#4 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Server.php(60): Temporal\\WorkerFactory->onRequest(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#5 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(387): Temporal\\Internal\\Transport\\Server->dispatch(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#6 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(261): Temporal\\WorkerFactory->dispatch('\\n\\x80\\x06\\x08\\x03\\x12\\rStartWor...', Array)\n#7 /opt/app/bin/worker(45): Temporal\\WorkerFactory->run()\n#8 {main}*\u0010\n\u000eLogicException"}
2022-05-12T06:04:51.837Z	ERROR	temporal    	Workflow panic	{"Namespace": "default", "TaskQueue": "default", "WorkerID": "default:7ddcc910-a81a-4d88-95bc-7eda895b5b91", "WorkflowType": "DummyWorkflow", "WorkflowID": "XXX", "RunID": "dccef09e-51f6-4a94-b852-48244932eccc", "Attempt": 1, "Error": "unknown command CommandType: Timer, ID: 5, possible causes are nondeterministic workflow definition code or incompatible change in the workflow definition", "StackTrace": "process event for default [panic]:\ngithub.com/spiral/sdk-go/internal.panicIllegalState(...)\n\tgithub.com/spiral/[email protected]/internal/internal_decision_state_machine.go:395\ngithub.com/spiral/sdk-go/internal.(*commandsHelper).getCommand(0x0, {0x0, {0x26565e8, 0xc000d11950}})\n\tgithub.com/spiral/[email protected]/internal/internal_decision_state_machine.go:856 +0x119\ngithub.com/spiral/sdk-go/internal.(*commandsHelper).handleTimerStarted(0xc000145260, {0x26565e8, 0xc00007cfb0})\n\tgithub.com/spiral/[email protected]/internal/internal_decision_state_machine.go:1250 +0x29\ngithub.com/spiral/sdk-go/internal.(*workflowExecutionEventHandlerImpl).ProcessEvent(0xc0000e5080, 0xc000aaa4c0, 0xc0, 0x0)\n\tgithub.com/spiral/[email protected]/internal/internal_event_handlers.go:833 +0x3fd\ngithub.com/spiral/sdk-go/internal.(*workflowExecutionContextImpl).ProcessWorkflowTask(0xc00015ca10, 0xc000a2c540)\n\tgithub.com/spiral/[email protected]/internal/internal_task_handlers.go:874 +0xca8\ngithub.com/spiral/sdk-go/internal.(*workflowTaskHandlerImpl).ProcessWorkflowTask(0xc00019a580, 0xc000a2c540, 0xc0009eb6b0)\n\tgithub.com/spiral/[email protected]/internal/internal_task_handlers.go:723 +0x493\ngithub.com/spiral/sdk-go/internal.(*workflowTaskPoller).processWorkflowTask(0xc0000d51e0, 0xc000a2c540)\n\tgithub.com/spiral/[email protected]/internal/internal_task_pollers.go:284 +0x2cd\ngithub.com/spiral/sdk-go/internal.(*workflowTaskPoller).ProcessTask(0xc0000d51e0, {0x1594180, 0xc000a2c540})\n\tgithub.com/spiral/[email protected]/internal/internal_task_pollers.go:255 +0x6c\ngithub.com/spiral/sdk-go/internal.(*baseWorker).processTask(0xc000185000, {0x1593d40, 0xc000520cc0})\n\tgithub.com/spiral/[email protected]/internal/internal_worker_base.go:362 +0x167\ncreated by github.com/spiral/sdk-go/internal.(*baseWorker).runTaskDispatcher\n\tgithub.com/spiral/[email protected]/internal/internal_worker_base.go:282 +0xba"}
2022-05-12T06:04:51.837Z	DEBUG	temporal    	sequenceID	{"before": 3}
2022-05-12T06:04:51.837Z	DEBUG	temporal    	sequenceID	{"after": 4}
2022-05-12T06:04:51.837Z	DEBUG	temporal    	outgoing message	{"data": "\nE\u0008\u0004\u0012\u000fDestroyWorkflow\u001a0{\"runId\":\"dccef09e-51f6-4a94-b852-48244932eccc\"} {\"taskQueue\":\"default\",\"tickTime\":\"2022-05-12T05:57:38Z\",\"replay\":true}"}
2022-05-12T06:04:51.838Z	DEBUG	temporal    	received message	{"data": "\n\u001f\u0008\u0004*\u001b\n\u0019\n\u0017\n\u0008encoding\u0012\u000bbinary/null"}
2022-05-12T06:04:51.838Z	WARN	temporal    	Failed to process workflow task.	{"Namespace": "default", "TaskQueue": "default", "WorkerID": "default:7ddcc910-a81a-4d88-95bc-7eda895b5b91", "WorkflowType": "DummyWorkflow", "WorkflowID": "XXX", "RunID": "dccef09e-51f6-4a94-b852-48244932eccc", "Attempt": 1, "Error": "unknown command CommandType: Timer, ID: 5, possible causes are nondeterministic workflow definition code or incompatible change in the workflow definition"}
2022-05-12T06:04:51.840Z	INFO	temporal    	Task processing failed with error	{"Namespace": "default", "TaskQueue": "default", "WorkerID": "default:7ddcc910-a81a-4d88-95bc-7eda895b5b91", "WorkerType": "WorkflowWorker", "Error": "Workflow executionsRow not found.  WorkflowId: default, RunId: 1620e0fd-7a80-4d11-acfb-bb1fe4a509f3"}

If we continue making queries, we'll again get OK answer, then context deadline exceeded error, etc.

  1. Try to query again and get expected answer
  2. Try to send signal7.

Web UI:
Signal is processed correctly, but after an error:
image

Roadrunner log:

2022-05-12T06:15:35.240Z	DEBUG	temporal    	workflow execute	{"runID": "5a1bfd24-70e9-4cef-863e-5a2f0377223c", "workflow info": {"WorkflowExecution":{"ID":"XXX","RunID":"5a1bfd24-70e9-4cef-863e-5a2f0377223c"},"WorkflowType":{"Name":"DummyWorkflow"},"TaskQueueName":"default","WorkflowExecutionTimeout":0,"WorkflowRunTimeout":0,"WorkflowTaskTimeout":10000000000,"Namespace":"default","Attempt":1,"WorkflowStartTime":"2022-05-12T06:14:46.868631545Z","CronSchedule":"","ContinuedExecutionRunID":"","ParentWorkflowNamespace":"","ParentWorkflowExecution":null,"Memo":null,"SearchAttributes":{"indexed_fields":{"SkyTeacherId":{"metadata":{"encoding":"anNvbi9wbGFpbg==","type":"SW50"},"data":"MTY1MjI="}}},"BinaryChecksum":"366aad4717fdb22d55eebef7c454528a"}}
2022-05-12T06:15:35.240Z	DEBUG	temporal    	sequenceID	{"before": 2}
2022-05-12T06:15:35.240Z	DEBUG	temporal    	sequenceID	{"after": 3}
2022-05-12T06:15:35.240Z	DEBUG	temporal    	workflow task started	{"time": "1s"}
2022-05-12T06:15:35.240Z	DEBUG	temporal    	outgoing message	{"data": "\n\ufffd\u0006\u0008\u0003\u0012\rStartWorkflow\u001a\ufffd\u0005{\"info\":{\"WorkflowExecution\":{\"ID\":\"XXX\",\"RunID\":\"5a1bfd24-70e9-4cef-863e-5a2f0377223c\"},\"WorkflowType\":{\"Name\":\"DummyWorkflow\"},\"TaskQueueName\":\"default\",\"WorkflowExecutionTimeout\":0,\"WorkflowRunTimeout\":0,\"WorkflowTaskTimeout\":10000000000,\"Namespace\":\"default\",\"Attempt\":1,\"WorkflowStartTime\":\"2022-05-12T06:14:46.868631545Z\",\"CronSchedule\":\"\",\"ContinuedExecutionRunID\":\"\",\"ParentWorkflowNamespace\":\"\",\"ParentWorkflowExecution\":null,\"Memo\":null,\"SearchAttributes\":{\"indexed_fields\":{\"SkyTeacherId\":{\"metadata\":{\"encoding\":\"anNvbi9wbGFpbg==\",\"type\":\"SW50\"},\"data\":\"MTY1MjI=\"}}},\"BinaryChecksum\":\"366aad4717fdb22d55eebef7c454528a\"}}*D\n\u001f\n\u0016\n\u0008encoding\u0012\njson/plain\u0012\u000516522\n!\n\u0016\n\u0008encoding\u0012\njson/plain\u0012\u00072592000 {\"taskQueue\":\"default\",\"tickTime\":\"2022-05-12T06:14:46Z\",\"replay\":true}"}
2022-05-12T06:15:35.243Z	DEBUG	temporal    	received message	{"data": "\n\ufffd\u000c\u0008\u0003\"\ufffd\u000c\n{Workflow \"DummyWorkflow\" with run id \"5a1bfd24-70e9-4cef-863e-5a2f0377223c\" has been already started\u0012\u0007PHP_SDK\u001a\ufffd\u000bLogicException: Workflow \"DummyWorkflow\" with run id \"5a1bfd24-70e9-4cef-863e-5a2f0377223c\" has been already started in /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router/StartWorkflow.php:95\nStack trace:\n#0 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router/StartWorkflow.php(63): Temporal\\Internal\\Transport\\Router\\StartWorkflow->findWorkflowOrFail(Object(Temporal\\Workflow\\WorkflowInfo))\n#1 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Router.php(81): Temporal\\Internal\\Transport\\Router\\StartWorkflow->handle(Object(Temporal\\Worker\\Transport\\Command\\Request), Array, Object(React\\Promise\\Deferred))\n#2 /opt/app/vendor/temporal/sdk/src/Worker/Worker.php(93): Temporal\\Internal\\Transport\\Router->dispatch(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#3 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(413): Temporal\\Worker\\Worker->dispatch(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#4 /opt/app/vendor/temporal/sdk/src/Internal/Transport/Server.php(60): Temporal\\WorkerFactory->onRequest(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#5 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(387): Temporal\\Internal\\Transport\\Server->dispatch(Object(Temporal\\Worker\\Transport\\Command\\Request), Array)\n#6 /opt/app/vendor/temporal/sdk/src/WorkerFactory.php(261): Temporal\\WorkerFactory->dispatch('\\n\\x80\\x06\\x08\\x03\\x12\\rStartWor...', Array)\n#7 /opt/app/bin/worker(45): Temporal\\WorkerFactory->run()\n#8 {main}*\u0010\n\u000eLogicException"}
2022-05-12T06:15:35.243Z	ERROR	temporal    	Workflow panic	{"Namespace": "default", "TaskQueue": "default", "WorkerID": "default:4fe939cb-4f79-47c6-a1e9-8b6dad29ef7f", "WorkflowType": "DummyWorkflow", "WorkflowID": "XXX", "RunID": "5a1bfd24-70e9-4cef-863e-5a2f0377223c", "Attempt": 1, "Error": "unknown command CommandType: Timer, ID: 5, possible causes are nondeterministic workflow definition code or incompatible change in the workflow definition", "StackTrace": "process event for default [panic]:\ngithub.com/spiral/sdk-go/internal.panicIllegalState(...)\n\tgithub.com/spiral/[email protected]/internal/internal_decision_state_machine.go:395\ngithub.com/spiral/sdk-go/internal.(*commandsHelper).getCommand(0x0, {0x0, {0x26565e8, 0x7fcb0d7c0228}})\n\tgithub.com/spiral/[email protected]/internal/internal_decision_state_machine.go:856 +0x119\ngithub.com/spiral/sdk-go/internal.(*commandsHelper).handleTimerStarted(0x7fcb346c15b8, {0x26565e8, 0xc0005b4000})\n\tgithub.com/spiral/[email protected]/internal/internal_decision_state_machine.go:1250 +0x29\ngithub.com/spiral/sdk-go/internal.(*workflowExecutionEventHandlerImpl).ProcessEvent(0xc00079e750, 0xc000918e00, 0xc0, 0x0)\n\tgithub.com/spiral/[email protected]/internal/internal_event_handlers.go:833 +0x3fd\ngithub.com/spiral/sdk-go/internal.(*workflowExecutionContextImpl).ProcessWorkflowTask(0xc0009288c0, 0xc000116ed0)\n\tgithub.com/spiral/[email protected]/internal/internal_task_handlers.go:874 +0xca8\ngithub.com/spiral/sdk-go/internal.(*workflowTaskHandlerImpl).ProcessWorkflowTask(0xc0001e4a50, 0xc000116ed0, 0xc0009dee70)\n\tgithub.com/spiral/[email protected]/internal/internal_task_handlers.go:723 +0x493\ngithub.com/spiral/sdk-go/internal.(*workflowTaskPoller).processWorkflowTask(0xc0000b45b0, 0xc000116ed0)\n\tgithub.com/spiral/[email protected]/internal/internal_task_pollers.go:284 +0x2cd\ngithub.com/spiral/sdk-go/internal.(*workflowTaskPoller).ProcessTask(0xc0000b45b0, {0x1594180, 0xc000116ed0})\n\tgithub.com/spiral/[email protected]/internal/internal_task_pollers.go:255 +0x6c\ngithub.com/spiral/sdk-go/internal.(*baseWorker).processTask(0xc000200400, {0x1593d40, 0xc000122250})\n\tgithub.com/spiral/[email protected]/internal/internal_worker_base.go:362 +0x167\ncreated by github.com/spiral/sdk-go/internal.(*baseWorker).runTaskDispatcher\n\tgithub.com/spiral/[email protected]/internal/internal_worker_base.go:282 +0xba"}
2022-05-12T06:15:35.243Z	WARN	temporal    	Failed to process workflow task.	{"Namespace": "default", "TaskQueue": "default", "WorkerID": "default:4fe939cb-4f79-47c6-a1e9-8b6dad29ef7f", "WorkflowType": "DummyWorkflow", "WorkflowID": "XXX", "RunID": "5a1bfd24-70e9-4cef-863e-5a2f0377223c", "Attempt": 1, "Error": "unknown command CommandType: Timer, ID: 5, possible causes are nondeterministic workflow definition code or incompatible change in the workflow definition"}
2022-05-12T06:15:35.243Z	DEBUG	temporal    	sequenceID	{"before": 3}
2022-05-12T06:15:35.243Z	DEBUG	temporal    	sequenceID	{"after": 4}
2022-05-12T06:15:35.243Z	DEBUG	temporal    	outgoing message	{"data": "\nE\u0008\u0004\u0012\u000fDestroyWorkflow\u001a0{\"runId\":\"5a1bfd24-70e9-4cef-863e-5a2f0377223c\"} {\"taskQueue\":\"default\",\"tickTime\":\"2022-05-12T06:14:46Z\",\"replay\":true}"}
2022-05-12T06:15:35.244Z	DEBUG	temporal    	received message	{"data": "\n\u001f\u0008\u0004*\u001b\n\u0019\n\u0017\n\u0008encoding\u0012\u000bbinary/null"}
2022-05-12T06:15:35.254Z	DEBUG	temporal    	workflow execute	{"runID": "5a1bfd24-70e9-4cef-863e-5a2f0377223c", "workflow info": {"WorkflowExecution":{"ID":"XXX","RunID":"5a1bfd24-70e9-4cef-863e-5a2f0377223c"},"WorkflowType":{"Name":"DummyWorkflow"},"TaskQueueName":"default","WorkflowExecutionTimeout":0,"WorkflowRunTimeout":0,"WorkflowTaskTimeout":10000000000,"Namespace":"default","Attempt":1,"WorkflowStartTime":"2022-05-12T06:14:46.868631545Z","CronSchedule":"","ContinuedExecutionRunID":"","ParentWorkflowNamespace":"","ParentWorkflowExecution":null,"Memo":null,"SearchAttributes":{},"BinaryChecksum":"366aad4717fdb22d55eebef7c454528a"}}
2022-05-12T06:15:35.254Z	DEBUG	temporal    	sequenceID	{"before": 4}
2022-05-12T06:15:35.254Z	DEBUG	temporal    	sequenceID	{"after": 5}
2022-05-12T06:15:35.254Z	DEBUG	temporal    	workflow task started	{"time": "1s"}
2022-05-12T06:15:35.254Z	DEBUG	temporal    	outgoing message	{"data": "\n\ufffd\u0006\u0008\u0005\u0012\rStartWorkflow\u001a\ufffd\u0005{\"info\":{\"WorkflowExecution\":{\"ID\":\"XXX\",\"RunID\":\"5a1bfd24-70e9-4cef-863e-5a2f0377223c\"},\"WorkflowType\":{\"Name\":\"DummyWorkflow\"},\"TaskQueueName\":\"default\",\"WorkflowExecutionTimeout\":0,\"WorkflowRunTimeout\":0,\"WorkflowTaskTimeout\":10000000000,\"Namespace\":\"default\",\"Attempt\":1,\"WorkflowStartTime\":\"2022-05-12T06:14:46.868631545Z\",\"CronSchedule\":\"\",\"ContinuedExecutionRunID\":\"\",\"ParentWorkflowNamespace\":\"\",\"ParentWorkflowExecution\":null,\"Memo\":null,\"SearchAttributes\":{\"indexed_fields\":{\"SkyTeacherId\":{\"metadata\":{\"encoding\":\"anNvbi9wbGFpbg==\",\"type\":\"SW50\"},\"data\":\"MTY1MjI=\"}}},\"BinaryChecksum\":\"366aad4717fdb22d55eebef7c454528a\"}}*D\n\u001f\n\u0016\n\u0008encoding\u0012\njson/plain\u0012\u000516522\n!\n\u0016\n\u0008encoding\u0012\njson/plain\u0012\u00072592000 {\"taskQueue\":\"default\",\"tickTime\":\"2022-05-12T06:14:46Z\",\"replay\":true}"}
2022-05-12T06:15:35.255Z	DEBUG	temporal    	received message	{"data": "\n\"\u0008\ufffdF\u0012\u0008NewTimer\u001a\u0011{\"ms\":2592000000}*\u0000\n\u001f\u0008\u0005*\u001b\n\u0019\n\u0017\n\u0008encoding\u0012\u000bbinary/null"}
2022-05-12T06:15:35.255Z	DEBUG	temporal    	sequenceID	{"before": 5}
2022-05-12T06:15:35.255Z	DEBUG	temporal    	sequenceID	{"after": 6}
2022-05-12T06:15:35.255Z	DEBUG	temporal    	workflow task started	{"time": "1s"}
2022-05-12T06:15:35.255Z	DEBUG	temporal    	outgoing message	{"data": "\nY\u0008\u0006\u0012\u000cInvokeSignal\u001aG{\"runId\":\"5a1bfd24-70e9-4cef-863e-5a2f0377223c\",\"name\":\"cancelRemoval\"} {\"taskQueue\":\"default\",\"tickTime\":\"2022-05-12T06:15:35Z\"}"}
2022-05-12T06:15:35.257Z	DEBUG	temporal    	received message	{"data": "\n\u001f\u0008\u0006*\u001b\n\u0019\n\u0017\n\u0008encoding\u0012\u000bbinary/null\nB\u0008\ufffdF\u0012\u0010CompleteWorkflow\u001a\u0002{}*'\n%\n\u0016\n\u0008encoding\u0012\njson/plain\u0012\u000b\"CANCELLED\""}
2022-05-12T06:15:35.257Z	DEBUG	temporal    	sequenceID	{"before": 6}
2022-05-12T06:15:35.257Z	DEBUG	temporal    	sequenceID	{"after": 7}
2022-05-12T06:15:35.257Z	DEBUG	temporal    	outgoing message	{"data": "\nE\u0008\u0007\u0012\u000fDestroyWorkflow\u001a0{\"runId\":\"5a1bfd24-70e9-4cef-863e-5a2f0377223c\"} {\"taskQueue\":\"default\",\"tickTime\":\"2022-05-12T06:15:35Z\",\"replay\":true}"}
2022-05-12T06:15:35.257Z	DEBUG	temporal    	received message	{"data": "\n\u001f\u0008\u0007*\u001b\n\u0019\n\u0017\n\u0008encoding\u0012\u000bbinary/null"}

Environment/Versions

  • Temporal Version: 1.13.0, SDK version 1.1.1

[Bug] Non-retryable exceptions list in RetryOptions does not work as expected

Describe the bug
Non-retryable exceptions list in RetryOptions does not work as expected.

To Reproduce
Type hints (@psalm-type ExceptionsList = array<class-string<\Throwable>>) and documentation (https://docs.temporal.io/docs/php-retries) tell us to provide a list of class names:

$retryOptions = RetryOptions::new()
    ->withNonRetryableExceptions([
        \InvalidArgumentException::class,
        \LogicException::class,
    ])
    ->withMaximumAttempts(3)
    ->withInitialInterval(new \DateInterval('PT5S'))
    ->withBackoffCoefficient(1)
;

Then I throw RuntimeException inside an activity method, which is not on the list. However, instead of an expected retry I saw this in Temporal web UI:

PanicError: assert(Assert::valuesInstanceOf($exceptions, \Throwable::class)) (type: AssertionError, retryable: true) 

Expected behavior
Process should respect the class list I provide.

Reason
The problem is with the Temporal\Internal\Assert::valuesSubclassOf method. PHP's instanceof won't work here:

$v = \InvalidArgumentException::class;
$of = \Throwable::class;

var_dump($v instanceof $of); // false, because $v is just a string
var_dump(is_subclass_of($v, $of)); // true, it checks hierarchy internally

I'll submit a PR to fix this shortly.

[Bug?] Workflow not completing unless all async coroutines finish or are cancelled manually

Describe the bug

Still not sure if it a bug or an intended feature.

If there are any unfinished async closures and workflow somehow reaches a finishing condition like a regular return statement or a non-retryable Exception, the workflow will be kept in a running state until all async closures are finished even after evaluating the return/exception.

In case it's an intended feature and the user is supposed to manually cleanup all open resources, it becomes difficult to do so cleanly in certain cases.
The most annoying example is running a long activity with an async completion in a coroutine. Since there's no heartbeat (and it wouldn't make sense to heartbeat for days either), there's doesn't seem to be an easy way to cancel it without sending a manual completion through the client itself.

Minimal Reproduction

Full project with a docker-compose in the attached repro.zip. docker-compose up to run the env, docker-compose exec app php app.php execute to run the workflow.

Even if the workflow evaluates an Exception or does a return, it still is stuck in a Running state until all coroutines are closed. Bug or a feature?

Relevant bits:

Workflow

class BlockedWorkflow implements BlockedWorkflowInterface
{
    private $regularActivity;
    private bool $timerFired = false;
    private $someDataFromCoroutineActivity = null;

    public function __construct(
        private LoggerInterface $logger = new Logger(),
    ) {
        $this->regularActivity = Workflow::newActivityStub(
            RegularActivityInterface::class,
            ActivityOptions::new()
                ->withScheduleToCloseTimeout(CarbonInterval::minutes(10))
                ->withHeartbeatTimeout(3)
                ->withCancellationType(ActivityCancellationType::WAIT_CANCELLATION_COMPLETED)
        );
    }

    public function run()
    {
        $timerScope = Workflow::async(
            fn () => yield Workflow::timer(3)->then(fn() => $this->timerFired = true) // if timer value exceeds time to wf return, that return is also blocked
        );

        $activityScope = Workflow::async(
            fn () => $this->someDataFromCoroutineActivity = yield $this->regularActivity->runActivity()
        );

        yield Workflow::await(fn () => $this->timerFired || $this->someDataFromCoroutineActivity);

        if ($this->timerFired) {
            $this->logger->info('Workflow Terminating with Exception');
            // $scope->cancel(); // unless cancelled, the wf doesn't terminate until activity finishes
            throw new \Exception();
        }

        $this->logger->info('Workflow Terminating with normal return');
        // $timerScope->cancel(); // unless cancelled, the wf doesn't terminate until timer finishes

        return $this->someDataFromCoroutineActivity;
    }
}

Activity

<?php

#[ActivityInterface]
class RegularActivity implements RegularActivityInterface
{
    public function __construct(
        private LoggerInterface $logger = new Logger(),
    ) {
    }

    #[ActivityMethod]
    public function runActivity(): string
    {
        $this->logger->info('Starting regular activity with a 1s sleep loop');

        for ($i = 0; $i < 5; $i++) {
            $this->logger->info('Activity tick');
            sleep(1);
            Activity::heartbeat($i);
        }

        $this->logger->info('Activity terminating');

        return 'Activity result';
    }
}

Environment/Versions

  • OS and processor: Linux
  • Temporal Version: temporalio/auto-setup:1.14.4 with 1.1.0 sdk-php on php:8.1-cli-alpine
  • Running in Docker

repro.zip

[Bug] Got the response to undefined request due to the memory leak in the WF worker

Describe the bug

Sometimes when a child worker process throws an exception, the parent worker process throws the following panic error:

PanicError: flush queue: SoftJobError:
	codec_execute:
	sync_worker_exec:
	sync_worker_exec_payload: LogicException: Got the response to undefined request 10389 in /srv/vendor/temporal/sdk/src/Internal/Transport/Client.php:60

and after it:

PanicError: unknown command CommandType: ChildWorkflow, ID: edfb1479-3d88-407e-a428-7e304e0d7bdf, possible causes are nondeterministic workflow definition code or incompatible change in the workflow definition 

Screenshot 2022-05-26 at 20 57 30

Environment/Versions

  • Temporal Version: 1.16.2 and 1.2.0 SDK
  • We use Kubernetes

Additional context

We tried to scale the pods so that can be split in different zones for fault tolerance. Maybe that causing these problems.

[Bug] Activity return an associative array, but in workflow, the return value is parsed as an object

Describe the bug
Activity return associative array, but when the return result is used in workflow, it became an object, not an array.

To Reproduce
Steps to reproduce the behavior:

  1. In Activity method, return associative array. Example:
        return [
            'Peter' => '123',
            'Ben' => '234',
            'Joe' => '345'
        ];
  1. Use the return value as associative array in workflow. Example:
        $simple = Workflow::newActivityStub(
            SimpleActivity::class,
            ActivityOptions::new()
                ->withStartToCloseTimeout(300)
                ->withRetryOptions(
                    RetryOptions::new()->withMaximumAttempts(1)
                )
        );
        $assocArray = yield $simple->getAssocArray($input);

        return $assocArray['Peter'];

Expected behavior
I can use the return value, as an array, not as an object.

Screenshots/Terminal ouput

WARN    temporal        Failed to process workflow task.        {"Namespace": "default", "TaskQueue": "default", "WorkerID": "209439@khoerintus-Lenovo-G40-80@default@1", "WorkflowType": "SimpleWorkflow.handle", "WorkflowID": "d411c5ae-32a7-4879-873b-704dc270eef8", "RunID": "dcf2fa14-ceb2-4c99-9b17-a7d214e4d84f", "Attempt": 9, "Error": "Cannot use object of type stdClass as array (type: Error, retryable: true)"}

ERROR   temporal        Workflow panic  {"Namespace": "default", "TaskQueue": "default", "WorkerID": "209439@khoerintus-Lenovo-G40-80@default@1", "WorkflowType": "SimpleWorkflow.handle", "WorkflowID": "d411c5ae-32a7-4879-873b-704dc270eef8", "RunID": "dcf2fa14-ceb2-4c99-9b17-a7d214e4d84f", "Attempt": 10, "Error": "Cannot use object of type stdClass as array (type: Error, retryable: true)", "StackTrace": "process event for default [panic]:\ngithub.com/temporalio/roadrunner-temporal/workflow.(*workflowProcess).OnWorkflowTaskStarted(0xc000629400)\n\tgithub.com/temporalio/[email protected]/workflow/process.go:95 +0x2b9\ngo.temporal.io/sdk/internal.(*workflowExecutionEventHandlerImpl).ProcessEvent(0xc000584d08, 0xc0005a3d00, 0x100, 0x0, 0x0)\n\tgo.temporal.io/[email protected]/internal/internal_event_handlers.go:791 +0x3b8\ngo.temporal.io/sdk/internal.(*workflowExecutionContextImpl).ProcessWorkflowTask(0xc000aa6e10, 0xc000314990, 0x1d0f740, 0xc000515c70, 0xc000aa6e10, 0x0)\n\tgo.temporal.io/[email protected]/internal/internal_task_handlers.go:876 +0x72c\ngo.temporal.io/sdk/internal.(*workflowTaskHandlerImpl).ProcessWorkflowTask(0xc000134420, 0xc000314990, 0xc0002e8db0, 0x0, 0x0, 0x0, 0x0)\n\tgo.temporal.io/[email protected]/internal/internal_task_handlers.go:727 +0x6d9\ngo.temporal.io/sdk/internal.(*workflowTaskPoller).processWorkflowTask(0xc0004f8750, 0xc000314990, 0x0, 0x0)\n\tgo.temporal.io/[email protected]/internal/internal_task_pollers.go:288 +0x4ae\ngo.temporal.io/sdk/internal.(*workflowTaskPoller).ProcessTask(0xc0004f8750, 0x1190c80, 0xc000314990, 0xc000aa6d80, 0xc0006c1800)\n\tgo.temporal.io/[email protected]/internal/internal_task_pollers.go:259 +0x85\ngo.temporal.io/sdk/internal.(*baseWorker).processTask(0xc0002fe2d0, 0x1190840, 0xc0004f7f90)\n\tgo.temporal.io/[email protected]/internal/internal_worker_base.go:343 +0xb8\ncreated by go.temporal.io/sdk/internal.(*baseWorker).runTaskDispatcher\n\tgo.temporal.io/[email protected]/internal/internal_worker_base.go:270 +0xff"}

Versions (please complete the following information where relevant):

  • OS: Linux
  • Temporal Version: 1.7.0
  • are you using Docker or Kubernetes or building Temporal from source? Using Docker

Additional context

upsert Search Attribute

Hi, thanks for the php sdk.
Is there any plan on adding upsert Search Attribute in the php sdk ?

[Bug] withEnableSessionWorker causes to ActivityNotRegisteredError

Describe the bug

If worker started with ->withEnableSessionWorker(true) requesting any activity from it causes ActivityNotRegisteredError

To Reproduce

php worker

<?php

require_once __DIR__."/vendor/autoload.php";

use NetworkRequestTemporalPhpServices\Services\RequestLight\GuzzleRequestExecutor;
use NetworkRequestTemporalPhpServices\Services\RequestLight\RequestResultDto;
use Temporal\Activity\ActivityInterface;
use Temporal\WorkerFactory;
use Temporal\Client\GRPC\ServiceClient;
use Temporal\Client\WorkflowClient;

$factory = WorkerFactory::create();

$workflowClient = WorkflowClient::create(ServiceClient::create('host.docker.internal:7233'));
$worker = $factory->newWorker(
    'workers_test_sessions',
    \Temporal\Worker\WorkerOptions::new()
        ->withMaxConcurrentActivityTaskPollers(8)
        ->withMaxConcurrentWorkflowTaskPollers(8)
        ->withEnableSessionWorker(true)
);

#[ActivityInterface(prefix: "test_sessions_act.")]
class SessionTestActivity {
    public function makeRequest(string $name): string
    {
        return "sessioned ".$name;
    }

}

$worker->registerActivityImplementations(new SessionTestActivity());
$factory->run();

golang workflow

package app

import (
	"errors"
	"fmt"
	"go.temporal.io/sdk/workflow"
	"strings"
	"time"
)

func Test123Workflow(ctx workflow.Context) error {
	ao2 := workflow.ActivityOptions{
		TaskQueue: "workers_test_sessions",
		ScheduleToCloseTimeout: time.Second * 60,
		ScheduleToStartTimeout: time.Second * 60,
		StartToCloseTimeout: time.Second * 60,
		HeartbeatTimeout: time.Second * 10,
		WaitForCancellation: false,
	}

	activityRunCtx2 := workflow.WithActivityOptions(ctx, ao2)

	so := &workflow.SessionOptions{
		CreationTimeout:  time.Minute,
		ExecutionTimeout: 10 * time.Minute,
	}

	sessionCtx, errSess := workflow.CreateSession(activityRunCtx2, so)
	if errSess != nil {
		return errSess
	}
	defer workflow.CompleteSession(sessionCtx)

	var t interface{}
	workflow.ExecuteActivity(sessionCtx, "test_sessions_act.makeRequest", "testname").Get(sessionCtx, &t)

	return nil
}

golang workflow worker:

// @@@SNIPSTART hello-world-project-template-go-worker
package main

import (
    "log"

    "go.temporal.io/sdk/client"
    "go.temporal.io/sdk/worker"

    "hello-world-project-template-go"
)

func main() {
    // Create the client object just once per process
    c, err := client.NewClient(client.Options{})
    if err != nil {
        log.Fatalln("unable to create Temporal client", err)
    }
    defer c.Close()
    // This worker hosts both Worker and Activity functions
    w := worker.New(c, "workers_test_sessions_worklow_q", worker.Options{})
    w.RegisterWorkflow(app.Test123Workflow)

    // Start listening to the Task Queue
    err = w.Run(worker.InterruptCh())
    if err != nil {
        log.Fatalln("unable to start Worker", err)
    }
}
// @@@SNIPEND

golang workflow starter:

// @@@SNIPSTART hello-world-project-template-go-start-workflow
package main

import (
	"context"
	"fmt"
	"log"

	"go.temporal.io/sdk/client"

	"hello-world-project-template-go"
)

func main() {
	// Create the client object just once per process
	c, err := client.NewClient(client.Options{})
	if err != nil {
		log.Fatalln("unable to create Temporal client", err)
	}
	defer c.Close()
	options := client.StartWorkflowOptions{
		TaskQueue: "workers_test_sessions_worklow_q",
	}

	we, err := c.ExecuteWorkflow(context.Background(), options, app.Test123Workflow)
	if err != nil {
		log.Fatalln("unable to complete Workflow", err)
	}
	var greeting string
	err = we.Get(context.Background(), &greeting)
	if err != nil {
		log.Fatalln("unable to get Workflow result", err)
	}
	printResults(greeting, we.GetID(), we.GetRunID())
}

func printResults(greeting string, workflowID, runID string) {
	fmt.Printf("\nWorkflowID: %s RunID: %s\n", workflowID, runID)
	fmt.Printf("\n%s\n\n", greeting)
}
// @@@SNIPEND

Expected behavior

Activity written in php (test_sessions_act.makeRquest) executed.

Screenshots/Terminal output

ActivityNotRegisteredError: unable to find activityType=test_sessions_act.makeRequest. Supported types: [internalSessionCreationActivity, internalSessionCompletionActivity]

Full workflow execution exported here: http://feodosian.com/wfd.json

Versions

[Bug] Wrong exception trace

I'm trying to run Temporal with Symfony application and have problem with error tracing.

  1. I'm trying to run non-existent activity method (it exists, but report isn't about that)

I expect to see line in workflow where it calls, but I get error on call workflow method line and some vendor artefacts. See image

image


When I'm trying to roll the same behaviour from code similar to workshop I get the following stacktrace:

image
As you see sdk catches needed lines into stacktrace.

Similar situation when I dump exception directly in \Temporal\Internal\Client\WorkflowStub::mapWorkflowFailureToException
image


But as you can see in previous screenshot it's placed in originalStackTrace property.
Property trace doesn't contain that information so it's displays wrong.

Is it expected behaviour? Would be good to see "original stack trace" in usual "trace" property.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.