Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use React\Promise\PromiseInterface;
use Temporal\Workflow\Mutex;
use Temporal\Workflow\TimerOptions;

/**
* @psalm-immutable
Expand All @@ -28,6 +29,7 @@ final class AwaitWithTimeoutInput
public function __construct(
public readonly \DateInterval $interval,
public readonly array $conditions,
public readonly ?TimerOptions $timerOptions = null,
) {}

/**
Expand All @@ -40,6 +42,7 @@ public function with(
return new self(
$interval ?? $this->interval,
$conditions ?? $this->conditions,
$this->timerOptions,
);
}
}
2 changes: 1 addition & 1 deletion src/Internal/Transport/Request/NewTimer.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
namespace Temporal\Internal\Transport\Request;

use Carbon\CarbonInterval;
use Temporal\Internal\Workflow\AwaitOptions;
use Temporal\Worker\Transport\Command\Client\Request;
use Temporal\Workflow\AwaitOptions;

/**
* @psalm-immutable
Expand Down
33 changes: 0 additions & 33 deletions src/Internal/Workflow/AwaitOptions.php

This file was deleted.

11 changes: 7 additions & 4 deletions src/Internal/Workflow/WorkflowContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
use Temporal\Worker\Transport\Command\RequestInterface;
use Temporal\Workflow;
use Temporal\Workflow\ActivityStubInterface;
use Temporal\Workflow\AwaitOptions;
use Temporal\Workflow\ChildWorkflowOptions;
use Temporal\Workflow\ChildWorkflowStubInterface;
use Temporal\Workflow\ContinueAsNewOptions;
Expand Down Expand Up @@ -669,14 +670,16 @@ public function await(callable|Mutex|PromiseInterface ...$conditions): PromiseIn
)(new AwaitInput($conditions));
}

public function awaitWithTimeout($interval, callable|Mutex|PromiseInterface ...$conditions): PromiseInterface
public function awaitWithTimeout($intervalOrOptions, callable|Mutex|PromiseInterface ...$conditions): PromiseInterface
{
$intervalObject = DateInterval::parse($interval, DateInterval::FORMAT_SECONDS);
$options = $intervalOrOptions instanceof AwaitOptions
? $intervalOrOptions
: AwaitOptions::new($intervalOrOptions);

return $this->callsInterceptor->with(
function (AwaitWithTimeoutInput $input): PromiseInterface {
/** Bypassing {@see timer()} to acquire a timer request ID */
$request = new NewTimer(new AwaitOptions($input->interval, null));
$request = new NewTimer(new AwaitOptions($input->interval, $input->timerOptions));
$requestId = $request->getID();
$timer = $this->request($request);
\assert($timer instanceof CompletableResultInterface);
Expand Down Expand Up @@ -707,7 +710,7 @@ static function (\Throwable $failure) use ($cancelPendingTimer): never {
},
/** @see WorkflowOutboundCallsInterceptor::awaitWithTimeout() */
'awaitWithTimeout',
)(new AwaitWithTimeoutInput($intervalObject, $conditions));
)(new AwaitWithTimeoutInput($options->interval, $conditions, $options->options));
}

/**
Expand Down
19 changes: 16 additions & 3 deletions src/Workflow.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use Temporal\Internal\Workflow\ContinueAsNewProxy;
use Temporal\Internal\Workflow\ExternalWorkflowProxy;
use Temporal\Workflow\ActivityStubInterface;
use Temporal\Workflow\AwaitOptions;
use Temporal\Workflow\CancellationScopeInterface;
use Temporal\Workflow\ChildWorkflowOptions;
use Temporal\Workflow\ChildWorkflowStubInterface;
Expand Down Expand Up @@ -335,12 +336,24 @@ public static function await(callable|Mutex|PromiseInterface ...$conditions): Pr
* }
* ```
*
* @param DateIntervalValue $interval
* Pass {@see AwaitOptions} instead of a timeout value to configure the underlying timer,
* for example to set its summary displayed in UI/CLI:
*
* ```php
* yield Workflow::awaitWithTimeout(
* AwaitOptions::new(42)->withTimerOptions(
* TimerOptions::new()->withSummary('continued-wait'),
* ),
* fn() => $this->continued,
* );
* ```
*
* @param DateIntervalValue|AwaitOptions $intervalOrOptions Timeout value or await options.
* @return PromiseInterface<bool>
*/
public static function awaitWithTimeout($interval, callable|Mutex|PromiseInterface ...$conditions): PromiseInterface
public static function awaitWithTimeout($intervalOrOptions, callable|Mutex|PromiseInterface ...$conditions): PromiseInterface

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the existing interval parameter name

Existing PHP 8 callers may use the public API as Workflow::awaitWithTimeout(interval: 5, conditions: fn() => true). Renaming $interval to $intervalOrOptions makes that call fail with an ArgumentCountError because the required first argument is no longer recognized. Keep the parameter named $interval while allowing it to contain either a timeout value or AwaitOptions to avoid this backward-compatibility break.

Useful? React with 👍 / 👎.

{
return self::getCurrentContext()->awaitWithTimeout($interval, ...$conditions);
return self::getCurrentContext()->awaitWithTimeout($intervalOrOptions, ...$conditions);
}

/**
Expand Down
70 changes: 70 additions & 0 deletions src/Workflow/AwaitOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

/**
* This file is part of Temporal package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Temporal\Workflow;

use Temporal\Internal\Support\DateInterval;
use Temporal\Workflow;

/**
* Options for {@see Workflow::awaitWithTimeout()}.
*
* ```php
* yield Workflow::awaitWithTimeout(
* AwaitOptions::new(30)->withTimerOptions(
* TimerOptions::new()->withSummary('rtds-resolution-wait'),
* ),
* fn(): bool => $this->resolution !== null,
* );
* ```
*
* @psalm-import-type DateIntervalValue from DateInterval
*/
final class AwaitOptions
{
public function __construct(
/**
* Await timeout.
*/
public readonly \DateInterval $interval,

/**
* Options set for the underlying timer created.
*/
public readonly ?TimerOptions $options = null,
) {}

/**
* @param DateIntervalValue $interval Await timeout.
*/
public static function new(mixed $interval, ?TimerOptions $options = null): self
{
return new self(DateInterval::parse($interval, DateInterval::FORMAT_SECONDS), $options);
}

/**
* Await timeout.
*
* @param DateIntervalValue $interval
*/
public function withInterval(mixed $interval): self
{
return new self(DateInterval::parse($interval, DateInterval::FORMAT_SECONDS), $this->options);
}

/**
* Options set for the underlying timer created.
*/
public function withTimerOptions(?TimerOptions $options): self
{
return new self($this->interval, $options);
}
}
4 changes: 2 additions & 2 deletions src/Workflow/WorkflowContextInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,10 @@ public function await(callable|Mutex|PromiseInterface ...$conditions): PromiseIn
*
* @see Workflow::awaitWithTimeout()
*
* @param DateIntervalValue $interval
* @param DateIntervalValue|AwaitOptions $intervalOrOptions Timeout value or await options.
* @return PromiseInterface<bool>
*/
public function awaitWithTimeout($interval, callable|Mutex|PromiseInterface ...$conditions): PromiseInterface;
public function awaitWithTimeout($intervalOrOptions, callable|Mutex|PromiseInterface ...$conditions): PromiseInterface;

/**
* Returns a complete trace of the last calls (for debugging).
Expand Down
47 changes: 47 additions & 0 deletions tests/Acceptance/Extra/Workflow/UserMetadataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,42 @@ public function localActivityMetadata(
}
}

#[Test]
public function awaitWithTimeoutMetadata(
#[Stub('Extra_Workflow_UserMetadata')]
WorkflowStubInterface $stub,
WorkflowClientInterface $client,
DataConverterInterface $dataConverter,
): void {
try {
/** @see TestWorkflow::awaitWithTimeout() */
$timedOut = $stub->update('await_with_timeout', 'await timer summary')->getValue(0);
self::assertFalse($timedOut);

# Check that the timer created by awaitWithTimeout() carries the summary
$found = false;
foreach ($client->getWorkflowHistory($stub->getExecution()) as $event) {
if (!$event->hasTimerStartedEventAttributes()) {
continue;
}

$payload = $event->getUserMetadata()?->getSummary();
if (!$payload instanceof Payload) {
continue;
}

if ($dataConverter->fromPayload($payload, 'string') === 'await timer summary') {
$found = true;
break;
}
}

self::assertTrue($found, 'Await timer metadata not found in workflow history');
} finally {
self::terminate($stub);
}
}

private static function terminate(WorkflowStubInterface $stub): void
{
try {
Expand Down Expand Up @@ -249,6 +285,17 @@ public function ping(): string
return 'pong';
}

#[Workflow\UpdateMethod('await_with_timeout')]
public function awaitWithTimeout(string $summary)
{
return yield Workflow::awaitWithTimeout(
Workflow\AwaitOptions::new(1)->withTimerOptions(
Workflow\TimerOptions::new()->withSummary($summary),
),
fn(): bool => $this->exit,
);
}

#[Workflow\UpdateMethod('start_child')]
public function startChild(string $summary, string $details)
{
Expand Down
42 changes: 42 additions & 0 deletions tests/Fixtures/src/Workflow/AwaitWithTimeoutOptionsWorkflow.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/**
* This file is part of Temporal package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Temporal\Tests\Workflow;

use Temporal\Workflow;
use Temporal\Workflow\AwaitOptions;
use Temporal\Workflow\TimerOptions;
use Temporal\Workflow\WorkflowMethod;

#[Workflow\WorkflowInterface]
class AwaitWithTimeoutOptionsWorkflow
{
/**
* Awaits a condition that is never met, so the await is always settled by the timer.
*
* @param null|non-empty-string $summary Summary for the timer created by the await.
* NULL means no {@see TimerOptions} are passed.
* @param int $timeout Await timeout in seconds.
* @param bool $useAwaitOptions FALSE means the await is called with a plain timeout value.
* @return \Generator<mixed, mixed, mixed, bool> FALSE because of the timeout.
*/
#[WorkflowMethod]
public function handler(?string $summary = null, int $timeout = 1, bool $useAwaitOptions = true)
{
$intervalOrOptions = $useAwaitOptions
? AwaitOptions::new($timeout)->withTimerOptions(
$summary === null ? null : TimerOptions::new()->withSummary($summary),
)
: $timeout;

return yield Workflow::awaitWithTimeout($intervalOrOptions, static fn(): bool => false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/**
* This file is part of Temporal package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Temporal\Tests\Workflow;

use React\Promise\PromiseInterface;
use Temporal\Promise;
use Temporal\Workflow;
use Temporal\Workflow\AwaitOptions;
use Temporal\Workflow\TimerOptions;
use Temporal\Workflow\WorkflowMethod;

#[Workflow\WorkflowInterface]
class ConcurrentAwaitWithTimeoutOptionsWorkflow
{
/**
* Runs two concurrent awaits, each with its own timer summary.
*
* @param non-empty-string $first Summary for the timer of the first await.
* @param non-empty-string $second Summary for the timer of the second await.
* @return \Generator<mixed, mixed, mixed, array{bool, bool}> Both awaits are settled by timers.
*/
#[WorkflowMethod]
public function handler(string $first, string $second)
{
return yield Promise::all([
$this->await($first, 1),
$this->await($second, 2),
]);
}

/**
* @param non-empty-string $summary
*/
private function await(string $summary, int $timeout): PromiseInterface
{
return Workflow::async(static function () use ($summary, $timeout): \Generator {
return yield Workflow::awaitWithTimeout(
AwaitOptions::new($timeout)->withTimerOptions(
TimerOptions::new()->withSummary($summary),
),
static fn(): bool => false,
);
});
}
}
Loading