Skip to content

Commit 50511c2

Browse files
authored
fix: improve ActivityPausedTest checks (#755)
* fix: improve ActivityPausedTest checks * fix: adjust timeouts in ActivityPausedTest for consistency * fix: replace hardcoded namespace with runtime value in ActivityPausedTest * fix: bump symfony/http-client dependency to ^5.4.53 * feat: add support for pending activity info and retry policy mapping
1 parent b524264 commit 50511c2

12 files changed

Lines changed: 651 additions & 19 deletions

src/Client/Workflow/WorkflowExecutionDescription.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Temporal\Client\Workflow;
66

7+
use Temporal\Workflow\PendingActivityInfo;
78
use Temporal\Workflow\WorkflowExecutionConfig;
89
use Temporal\Workflow\WorkflowExecutionInfo;
910

@@ -15,10 +16,13 @@
1516
final class WorkflowExecutionDescription
1617
{
1718
/**
19+
* @param list<PendingActivityInfo> $pendingActivities
20+
*
1821
* @internal
1922
*/
2023
public function __construct(
2124
public readonly WorkflowExecutionConfig $config,
2225
public readonly WorkflowExecutionInfo $info,
26+
public readonly array $pendingActivities = [],
2327
) {}
2428
}

src/Internal/Client/WorkflowStub.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
use Temporal\Interceptor\WorkflowClientCallsInterceptor;
7070
use Temporal\Internal\Interceptor\HeaderCarrier;
7171
use Temporal\Internal\Interceptor\Pipeline;
72+
use Temporal\Internal\Mapper\PendingActivityInfoMapper;
7273
use Temporal\Internal\Mapper\WorkflowExecutionConfigMapper;
7374
use Temporal\Internal\Mapper\WorkflowExecutionInfoMapper;
7475
use Temporal\Workflow\WorkflowExecution;
@@ -460,12 +461,20 @@ function (DescribeInput $input): WorkflowExecutionDescription {
460461

461462
$response = $this->serviceClient->DescribeWorkflowExecution($request);
462463

464+
$activityMapper = new PendingActivityInfoMapper($this->converter);
465+
$pendingActivities = [];
466+
/** @psalm-suppress TooManyTemplateParams */
467+
foreach ($response->getPendingActivities() as $pendingActivity) {
468+
$pendingActivities[] = $activityMapper->fromMessage($pendingActivity);
469+
}
470+
463471
/** @psalm-suppress PossiblyNullArgument */
464472
return new WorkflowExecutionDescription(
465473
config: (new WorkflowExecutionConfigMapper($this->converter))
466474
->fromMessage($response->getExecutionConfig()),
467475
info: (new WorkflowExecutionInfoMapper($this->converter))
468476
->fromMessage($response->getWorkflowExecutionInfo()),
477+
pendingActivities: $pendingActivities,
469478
);
470479
},
471480
/** @see WorkflowClientCallsInterceptor::describe() */
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Temporal\Internal\Mapper;
6+
7+
use Temporal\Activity\ActivityType as ActivityTypeDto;
8+
use Temporal\Api\Activity\V1\ActivityOptions as ActivityOptionsMessage;
9+
use Temporal\Api\Common\V1\Priority as PriorityMessage;
10+
use Temporal\Api\Common\V1\RetryPolicy;
11+
use Temporal\Api\Deployment\V1\WorkerDeploymentVersion as WorkerDeploymentVersionMessage;
12+
use Temporal\Api\Failure\V1\Failure;
13+
use Temporal\Api\Workflow\V1\PendingActivityInfo;
14+
use Temporal\Api\Workflow\V1\PendingActivityInfo\PauseInfo;
15+
use Temporal\Common\Priority as PriorityDto;
16+
use Temporal\Common\Versioning\WorkerDeploymentVersion;
17+
use Temporal\DataConverter\DataConverterInterface;
18+
use Temporal\DataConverter\EncodedValues;
19+
use Temporal\DataConverter\ValuesInterface;
20+
use Temporal\Exception\Failure\FailureConverter;
21+
use Temporal\Exception\Failure\TemporalFailure;
22+
use Temporal\Internal\Support\DateInterval;
23+
use Temporal\Workflow\PendingActivityInfo as PendingActivityInfoDto;
24+
use Temporal\Workflow\PendingActivityOptions;
25+
use Temporal\Workflow\PendingActivityPauseInfo;
26+
use Temporal\Workflow\PendingActivityPauseInfoManual;
27+
use Temporal\Workflow\PendingActivityPauseInfoRule;
28+
use Temporal\Workflow\PendingActivityRetryPolicy;
29+
use Temporal\Workflow\PendingActivityState;
30+
31+
final class PendingActivityInfoMapper
32+
{
33+
public function __construct(
34+
private readonly DataConverterInterface $converter,
35+
) {}
36+
37+
/**
38+
* @psalm-suppress DocblockTypeContradiction, RedundantConditionGivenDocblockType
39+
*/
40+
public function fromMessage(PendingActivityInfo $message): PendingActivityInfoDto
41+
{
42+
$activityType = new ActivityTypeDto();
43+
/** @psalm-suppress InaccessibleProperty */
44+
$activityType->name = $message->getActivityType()?->getName() ?? '';
45+
46+
$retryInterval = $message->getCurrentRetryInterval();
47+
$retryInterval === null or $retryInterval = DateInterval::parse($retryInterval);
48+
49+
return new PendingActivityInfoDto(
50+
activityId: $message->getActivityId(),
51+
activityType: $activityType,
52+
state: PendingActivityState::from($message->getState()),
53+
heartbeatDetails: $this->prepareHeartbeatDetails($message),
54+
lastHeartbeatTime: $message->getLastHeartbeatTime()?->toDateTime(),
55+
lastStartedTime: $message->getLastStartedTime()?->toDateTime(),
56+
attempt: $message->getAttempt(),
57+
maximumAttempts: $message->getMaximumAttempts(),
58+
scheduledTime: $message->getScheduledTime()?->toDateTime(),
59+
expirationTime: $message->getExpirationTime()?->toDateTime(),
60+
lastFailure: $this->prepareFailure($message->getLastFailure()),
61+
lastWorkerIdentity: $message->getLastWorkerIdentity(),
62+
currentRetryInterval: $retryInterval,
63+
lastAttemptCompleteTime: $message->getLastAttemptCompleteTime()?->toDateTime(),
64+
nextAttemptScheduleTime: $message->getNextAttemptScheduleTime()?->toDateTime(),
65+
paused: $message->getPaused(),
66+
lastDeploymentVersion: $this->prepareDeploymentVersion($message->getLastDeploymentVersion()),
67+
priority: $this->preparePriority($message->getPriority()),
68+
pauseInfo: $this->preparePauseInfo($message->getPauseInfo()),
69+
activityOptions: $this->prepareActivityOptions($message->getActivityOptions()),
70+
);
71+
}
72+
73+
private function prepareHeartbeatDetails(PendingActivityInfo $message): ValuesInterface
74+
{
75+
$details = $message->getHeartbeatDetails();
76+
77+
return $details === null
78+
? EncodedValues::empty()
79+
: EncodedValues::fromPayloads($details, $this->converter);
80+
}
81+
82+
private function prepareFailure(?Failure $failure): ?TemporalFailure
83+
{
84+
return $failure === null
85+
? null
86+
: FailureConverter::mapFailureToException($failure, $this->converter);
87+
}
88+
89+
/**
90+
* @psalm-suppress ArgumentTypeCoercion
91+
*/
92+
private function prepareDeploymentVersion(?WorkerDeploymentVersionMessage $version): ?WorkerDeploymentVersion
93+
{
94+
return $version === null
95+
? null
96+
: WorkerDeploymentVersion::new($version->getDeploymentName(), $version->getBuildId());
97+
}
98+
99+
private function preparePriority(?PriorityMessage $priority): ?PriorityDto
100+
{
101+
if ($priority === null) {
102+
return null;
103+
}
104+
105+
$result = PriorityDto::new($priority->getPriorityKey())
106+
->withFairnessKey($priority->getFairnessKey());
107+
108+
$weight = $priority->getFairnessWeight();
109+
$weight >= 0.001 && $weight <= 1000.0 and $result = $result->withFairnessWeight($weight);
110+
111+
return $result;
112+
}
113+
114+
private function preparePauseInfo(?PauseInfo $pauseInfo): ?PendingActivityPauseInfo
115+
{
116+
if ($pauseInfo === null) {
117+
return null;
118+
}
119+
120+
$manual = $pauseInfo->getManual();
121+
$rule = $pauseInfo->getRule();
122+
123+
return new PendingActivityPauseInfo(
124+
pauseTime: $pauseInfo->getPauseTime()?->toDateTime(),
125+
manual: $manual === null
126+
? null
127+
: new PendingActivityPauseInfoManual($manual->getIdentity(), $manual->getReason()),
128+
rule: $rule === null
129+
? null
130+
: new PendingActivityPauseInfoRule($rule->getRuleId(), $rule->getIdentity(), $rule->getReason()),
131+
);
132+
}
133+
134+
/**
135+
* @psalm-suppress DocblockTypeContradiction, RedundantConditionGivenDocblockType
136+
*/
137+
private function prepareActivityOptions(?ActivityOptionsMessage $options): ?PendingActivityOptions
138+
{
139+
if ($options === null) {
140+
return null;
141+
}
142+
143+
$scheduleToClose = $options->getScheduleToCloseTimeout();
144+
$scheduleToStart = $options->getScheduleToStartTimeout();
145+
$startToClose = $options->getStartToCloseTimeout();
146+
$heartbeat = $options->getHeartbeatTimeout();
147+
$retryPolicy = $options->getRetryPolicy();
148+
149+
return new PendingActivityOptions(
150+
taskQueue: $options->getTaskQueue()?->getName(),
151+
scheduleToCloseTimeout: $scheduleToClose === null ? null : DateInterval::parse($scheduleToClose),
152+
scheduleToStartTimeout: $scheduleToStart === null ? null : DateInterval::parse($scheduleToStart),
153+
startToCloseTimeout: $startToClose === null ? null : DateInterval::parse($startToClose),
154+
heartbeatTimeout: $heartbeat === null ? null : DateInterval::parse($heartbeat),
155+
retryPolicy: $retryPolicy === null ? null : $this->prepareRetryPolicy($retryPolicy),
156+
);
157+
}
158+
159+
/**
160+
* @psalm-suppress DocblockTypeContradiction, RedundantConditionGivenDocblockType, TooManyTemplateParams
161+
*/
162+
private function prepareRetryPolicy(RetryPolicy $policy): PendingActivityRetryPolicy
163+
{
164+
$initialInterval = $policy->getInitialInterval();
165+
$maximumInterval = $policy->getMaximumInterval();
166+
167+
$nonRetryableErrorTypes = [];
168+
foreach ($policy->getNonRetryableErrorTypes() as $errorType) {
169+
$nonRetryableErrorTypes[] = $errorType;
170+
}
171+
172+
return new PendingActivityRetryPolicy(
173+
initialInterval: $initialInterval === null ? null : DateInterval::parse($initialInterval),
174+
backoffCoefficient: $policy->getBackoffCoefficient(),
175+
maximumInterval: $maximumInterval === null ? null : DateInterval::parse($maximumInterval),
176+
maximumAttempts: $policy->getMaximumAttempts(),
177+
nonRetryableErrorTypes: $nonRetryableErrorTypes,
178+
);
179+
}
180+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Temporal\Workflow;
6+
7+
use JetBrains\PhpStorm\Immutable;
8+
use Temporal\Activity\ActivityType;
9+
use Temporal\Common\Priority;
10+
use Temporal\Common\Versioning\WorkerDeploymentVersion;
11+
use Temporal\DataConverter\ValuesInterface;
12+
use Temporal\Exception\Failure\TemporalFailure;
13+
14+
/**
15+
* DTO that contains information about a pending activity of a Workflow Execution.
16+
*
17+
* @see \Temporal\Api\Workflow\V1\PendingActivityInfo
18+
* @psalm-immutable
19+
*/
20+
#[Immutable]
21+
final class PendingActivityInfo
22+
{
23+
/**
24+
* @internal
25+
*/
26+
public function __construct(
27+
public readonly string $activityId,
28+
public readonly ActivityType $activityType,
29+
public readonly PendingActivityState $state,
30+
public readonly ValuesInterface $heartbeatDetails,
31+
public readonly ?\DateTimeInterface $lastHeartbeatTime,
32+
public readonly ?\DateTimeInterface $lastStartedTime,
33+
public readonly int $attempt,
34+
public readonly int $maximumAttempts,
35+
public readonly ?\DateTimeInterface $scheduledTime,
36+
public readonly ?\DateTimeInterface $expirationTime,
37+
38+
/**
39+
* The failure of the last activity attempt. Null if the activity has not failed yet.
40+
*/
41+
public readonly ?TemporalFailure $lastFailure,
42+
public readonly string $lastWorkerIdentity,
43+
44+
/**
45+
* The time activity will wait until the next retry.
46+
* If the activity is currently running it will be the next retry interval if the activity failed.
47+
* If the activity is currently waiting it will be the current retry interval.
48+
* If there will be no retry it will be null.
49+
*/
50+
public readonly ?\DateInterval $currentRetryInterval,
51+
52+
/**
53+
* The time when the last activity attempt was completed. Null if the activity has not been
54+
* completed yet.
55+
*/
56+
public readonly ?\DateTimeInterface $lastAttemptCompleteTime,
57+
58+
/**
59+
* Next time when the activity will be scheduled.
60+
* If the activity is currently scheduled or started it will be null.
61+
*/
62+
public readonly ?\DateTimeInterface $nextAttemptScheduleTime,
63+
64+
/**
65+
* Indicates if the activity is paused.
66+
*/
67+
public readonly bool $paused,
68+
69+
/**
70+
* The Worker Deployment Version this activity was dispatched to most recently.
71+
* Null if the activity has not yet been dispatched or was last dispatched to an unversioned worker.
72+
*/
73+
public readonly ?WorkerDeploymentVersion $lastDeploymentVersion,
74+
75+
/**
76+
* Priority metadata.
77+
*/
78+
public readonly ?Priority $priority,
79+
80+
/**
81+
* Information about why and when the activity was paused. Null if the activity is not paused.
82+
*/
83+
public readonly ?PendingActivityPauseInfo $pauseInfo,
84+
85+
/**
86+
* Current activity options. May be different from the ones used to start the activity.
87+
*/
88+
public readonly ?PendingActivityOptions $activityOptions,
89+
) {}
90+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Temporal\Workflow;
6+
7+
use JetBrains\PhpStorm\Immutable;
8+
9+
/**
10+
* Current activity options as reported for a pending activity. May differ from the options used to
11+
* start the activity.
12+
*
13+
* @see \Temporal\Api\Activity\V1\ActivityOptions
14+
* @psalm-immutable
15+
*/
16+
#[Immutable]
17+
final class PendingActivityOptions
18+
{
19+
/**
20+
* @internal
21+
*/
22+
public function __construct(
23+
public readonly ?string $taskQueue,
24+
public readonly ?\DateInterval $scheduleToCloseTimeout,
25+
public readonly ?\DateInterval $scheduleToStartTimeout,
26+
public readonly ?\DateInterval $startToCloseTimeout,
27+
public readonly ?\DateInterval $heartbeatTimeout,
28+
public readonly ?PendingActivityRetryPolicy $retryPolicy,
29+
) {}
30+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Temporal\Workflow;
6+
7+
use JetBrains\PhpStorm\Immutable;
8+
9+
/**
10+
* Information about why and when a pending activity was paused.
11+
*
12+
* @see \Temporal\Api\Workflow\V1\PendingActivityInfo\PauseInfo
13+
* @psalm-immutable
14+
*/
15+
#[Immutable]
16+
final class PendingActivityPauseInfo
17+
{
18+
/**
19+
* @internal
20+
*/
21+
public function __construct(
22+
public readonly ?\DateTimeInterface $pauseTime,
23+
public readonly ?PendingActivityPauseInfoManual $manual,
24+
public readonly ?PendingActivityPauseInfoRule $rule,
25+
) {}
26+
}

0 commit comments

Comments
 (0)