From 0831ea20fb1639d53ae9d3705ec2cb8d45fe35bf Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 14 Oct 2024 20:52:38 +0400 Subject: [PATCH 1/6] Update dependencies --- app/composer.json | 13 +++++++------ app/worker.php | 3 +++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/composer.json b/app/composer.json index a4941ac..3c848c7 100644 --- a/app/composer.json +++ b/app/composer.json @@ -2,15 +2,15 @@ "minimum-stability": "dev", "prefer-stable": true, "require": { - "temporal/sdk": "^2.8.0", - "spiral/tokenizer": "^3.7", + "temporal/sdk": "^2.11.0", + "spiral/tokenizer": "^3.14.5", "temporal/open-telemetry-interceptors": "dev-master", - "open-telemetry/exporter-otlp": "^1.0", - "open-telemetry/transport-grpc": "^1.0", + "open-telemetry/exporter-otlp": "^1.1", + "open-telemetry/transport-grpc": "^1.1", "symfony/console": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "buggregator/trap": "^1.3" + "buggregator/trap": "^1.11" }, "autoload": { "psr-4": { @@ -20,7 +20,8 @@ }, "config": { "allow-plugins": { - "php-http/discovery": true + "php-http/discovery": true, + "tbachert/spi": false } } } diff --git a/app/worker.php b/app/worker.php index fa446ad..0b8ef1d 100644 --- a/app/worker.php +++ b/app/worker.php @@ -14,12 +14,15 @@ use Temporal\OpenTelemetry\Interceptor\OpenTelemetryWorkflowOutboundRequestInterceptor; use Temporal\SampleUtils\DeclarationLocator; use Temporal\SampleUtils\TracerFactory; +use Temporal\Worker\FeatureFlags; use Temporal\WorkerFactory; use Temporal\Samples\FileProcessing; ini_set('display_errors', 'stderr'); include "vendor/autoload.php"; +FeatureFlags::$workflowDeferredHandlerStart = true; + // finds all available workflows, activity types and commands in a given directory $declarations = DeclarationLocator::create(__DIR__ . '/src/'); From 86f98fc42a299480b9cf850eec3863dd8aa3818d Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 14 Oct 2024 23:47:52 +0400 Subject: [PATCH 2/6] Add Workflow implementation with DTOs --- .../DTO/AssignNodesToJobInput.php | 16 ++ .../ClusterManagerAssignNodesToJobInput.php | 15 + .../ClusterManagerAssignNodesToJobResult.php | 15 + .../DTO/ClusterManagerDeleteJobInput.php | 10 + .../DTO/ClusterManagerInput.php | 12 + .../DTO/ClusterManagerResult.php | 13 + .../DTO/ClusterManagerState.php | 15 + .../DTO/FindBadNodesInput.php | 15 + .../DTO/UnassignNodesForJobInput.php | 16 ++ app/src/SafeMessageHandlers/WorkflowImpl.php | 263 ++++++++++++++++++ 10 files changed, 390 insertions(+) create mode 100644 app/src/SafeMessageHandlers/DTO/AssignNodesToJobInput.php create mode 100644 app/src/SafeMessageHandlers/DTO/ClusterManagerAssignNodesToJobInput.php create mode 100644 app/src/SafeMessageHandlers/DTO/ClusterManagerAssignNodesToJobResult.php create mode 100644 app/src/SafeMessageHandlers/DTO/ClusterManagerDeleteJobInput.php create mode 100644 app/src/SafeMessageHandlers/DTO/ClusterManagerInput.php create mode 100644 app/src/SafeMessageHandlers/DTO/ClusterManagerResult.php create mode 100644 app/src/SafeMessageHandlers/DTO/ClusterManagerState.php create mode 100644 app/src/SafeMessageHandlers/DTO/FindBadNodesInput.php create mode 100644 app/src/SafeMessageHandlers/DTO/UnassignNodesForJobInput.php create mode 100644 app/src/SafeMessageHandlers/WorkflowImpl.php diff --git a/app/src/SafeMessageHandlers/DTO/AssignNodesToJobInput.php b/app/src/SafeMessageHandlers/DTO/AssignNodesToJobInput.php new file mode 100644 index 0000000..c13c775 --- /dev/null +++ b/app/src/SafeMessageHandlers/DTO/AssignNodesToJobInput.php @@ -0,0 +1,16 @@ + A [Node => Job Name] array*/ + public array $nodes = []; + /** @var array */ + public array $jobsAssigned = []; +} diff --git a/app/src/SafeMessageHandlers/DTO/FindBadNodesInput.php b/app/src/SafeMessageHandlers/DTO/FindBadNodesInput.php new file mode 100644 index 0000000..4081489 --- /dev/null +++ b/app/src/SafeMessageHandlers/DTO/FindBadNodesInput.php @@ -0,0 +1,15 @@ +state = new ClusterManagerState(); + // Protects workflow state from interleaved access + $this->nodes_lock = new Mutex(); + $this->max_history_length = null; + $this->sleep_interval_seconds = 600; + } + + #[SignalMethod('start_cluster')] + public function startCluster(): void + { + $this->state->clusterStarted = true; + $this->state->nodes = \array_fill_keys(\range(0, 24), null); + trap("Cluster started"); + } + + #[SignalMethod('shutdown_cluster')] + public function shutdownCluster() + { + yield Workflow::await(fn() => $this->state->clusterStarted); + $this->state->clusterShutdown = true; + trap("Cluster shut down"); + } + + /** + * This is an update as opposed to a signal because the client may want to wait for nodes to be allocated + * before sending work to those nodes. + * Returns the list of node names that were allocated to the job. + */ + #[Workflow\UpdateMethod('assign_nodes_to_job')] + #[Workflow\ReturnType(ClusterManagerAssignNodesToJobResult::class)] + public function assignNodesToJob(ClusterManagerAssignNodesToJobInput $input) + { + yield Workflow::await(fn() => $this->state->clusterStarted); + // If you want the client to receive a failure, either add an update validator + // and throw the exception from there. + // Other exceptions in the main handler will cause the workflow to keep retrying and get it stuck. + $this->state->clusterShutdown and throw new ApplicationFailure( + 'Cannot assign nodes to a job: Cluster is already shut down', 'CannotAssignNodesToJob', true, + ); + + return yield Workflow::runLocked($this->nodes_lock, function () use ($input) { + // Idempotency guard. + if (\in_array($input->jobName, $this->state->jobsAssigned, true)) { + return new ClusterManagerAssignNodesToJobResult( + $this->getAssignedNodes($input->jobName), + ); + } + + $unassignedNodes = $this->getUnassignedNodes(); + if (\count($unassignedNodes) < $input->totalNumNodes) { + throw new ApplicationFailure( + \sprintf( + 'Cannot assign %d nodes; have only %d available', + $input->totalNumNodes, + \count($unassignedNodes), + ), + 'CannotAssignNodesToJob', + true, + ); + } + + $nodesToAssign = \array_slice($unassignedNodes, 0, $input->totalNumNodes); + /** + * This await would be dangerous without {@see self::$nodes_lock} because it yields control + * and allows interleaving with {@see self::deleteJob()} and {@see self::performHealthChecks()}, + * which both touch {@see ClusterManagerState::$nodes}. + */ + yield $this->_assignNodesToJob($nodesToAssign, $input->jobName); + return new ClusterManagerAssignNodesToJobResult($this->getAssignedNodes($input->jobName)); + }); + } + + private function _assignNodesToJob(array $assignedNodes, string $jobName): \Generator + { + yield Workflow::executeActivity( + 'assign_nodes_to_job', + [new AssignNodesToJobInput($assignedNodes, $jobName)], + ActivityOptions::new()->withStartToCloseTimeout('10 seconds'), + ); + foreach ($assignedNodes as $node) { + $this->state->nodes[$node] = $jobName; + } + + $this->state->jobsAssigned[] = $jobName; + } + + /** + * Even though it returns nothing, this is an update because the client may want to track it, for example + * to wait for nodes to be unassigned before reassigning them. + */ + #[Workflow\UpdateMethod('delete_job')] + public function deleteJob(ClusterManagerDeleteJobInput $input): \Generator + { + yield Workflow::await(fn() => $this->state->clusterStarted); + // If you want the client to receive a failure, either add an update validator + // and throw the exception from there + $this->state->clusterShutdown and throw new ApplicationFailure( + 'Cannot delete a job: Cluster is already shut down', 'CannotDeleteJob', true, + ); + + return yield Workflow::runLocked($this->nodes_lock, function () use ($input) { + $nodesToUnassign = \array_keys(\array_filter($this->state->nodes, fn($v) => $v === $input->jobName)); + /** + * This await would be dangerous without {@see self::$nodes_lock} because it yields control + * and allows interleaving with {@see self::assignNodesToJob()} and {@see self::performHealthChecks()}, + * which all touch {@see ClusterManagerState::$nodes}. + */ + yield $this->_unassignNodesForJob($nodesToUnassign, $input->jobName); + }); + } + + private function _unassignNodesForJob(array $nodesToUnassign, string $jobName): \Generator + { + yield Workflow::executeActivity( + 'unassign_nodes_for_job', + [new UnassignNodesForJobInput($nodesToUnassign, $jobName)], + ActivityOptions::new()->withStartToCloseTimeout('10 seconds'), + ); + + foreach ($nodesToUnassign as $node) { + $this->state->nodes[$node] = null; + } + } + + public function getUnassignedNodes(): array + { + return \array_keys(\array_filter($this->state->nodes, fn($v) => $v === null)); + } + + public function getBadNodes(): array + { + return \array_keys(\array_filter($this->state->nodes, fn($v) => $v === 'BAD!')); + } + + public function getAssignedNodes(?string $jobName = null): array + { + return $jobName === null + ? \array_keys(\array_filter($this->state->nodes, fn($v) => $v !== null && $v !== 'BAD!')) + : \array_keys(\array_filter($this->state->nodes, fn($v) => $v === $jobName)); + } + + public function performHealthChecks(): \Generator + { + return yield Workflow::runLocked($this->nodes_lock, function () { + $assignedNodes = $this->getAssignedNodes(); + try { + /** + * This await would be dangerous without {@see self::$nodes_lock} because it yields control + * and allows interleaving with {@see self::assignNodesToJob()} and {@see self::deleteJob()}, + * which both touch {@see ClusterManagerState::$nodes}. + */ + $badNodes = yield Workflow::executeActivity( + 'find_bad_nodes', + [new FindBadNodesInput($assignedNodes)], + ActivityOptions::new()->withStartToCloseTimeout('10 seconds') + // This health check is optional, and our lock would block the whole workflow + ->withRetryOptions(RetryOptions::new()->withMaximumAttempts(1)), + ); + + foreach ($badNodes as $node) { + $this->state->nodes[$node] = 'BAD!'; + } + } catch (\Throwable $e) { + trap(\sprintf('Health check failed with error %s: %s', $e::class, $e->getMessage())); + } + }); + } + + public function init(ClusterManagerInput $input): void + { + $input->state === null or $this->state = $input->state; + $input->testContinueAsNew and $this->max_history_length = 120; + $input->testContinueAsNew and $this->sleep_interval_seconds = 1; + } + + public function shouldContinueAsNew(): bool + { + if (Workflow::getInfo()->shouldContinueAsNew) { + return true; + } + + // This is just for ease-of-testing. In production, we trust temporal to tell us when to continue as new. + if ($this->max_history_length !== null && Workflow::getInfo()->historyLength > $this->max_history_length) { + return true; + } + + return false; + } + + #[WorkflowMethod] + public function run(ClusterManagerInput $input) + { + // todo rewrite after https://github.com/temporalio/sdk-php/issues/480/ + $this->init($input); + + yield Workflow::await(fn() => $this->state->clusterStarted); + // Perform health checks at intervals. + while (true) { + yield $this->performHealthChecks(); + try { + yield Workflow::await( + fn() => $this->state->clusterShutdown || $this->shouldContinueAsNew(), + ['timeout' => '600 seconds'], + ); + } catch (\Throwable) { + // do nothing + } + + if ($this->state->clusterShutdown) { + break; + } + if ($this->shouldContinueAsNew()) { + // We don't want to leave any job assignment or deletion handlers half-finished when we continue as new. + yield Workflow::await(fn() => Workflow::allHandlersFinished()); + trap("Continuing as new"); + Workflow::continueAsNew( + Workflow::getInfo()->type->name, + [new ClusterManagerInput($this->state, $input->testContinueAsNew)] + ); + } + } + + // Make sure we finish off handlers such as deleting jobs before we complete the workflow. + yield Workflow::await(fn() => Workflow::allHandlersFinished()); + return new ClusterManagerResult( + \count($this->getAssignedNodes()), + \count($this->getBadNodes()), + ); + } +} From f7ffe097871ff3c913c6c6d7937ea81cfd4344ef Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 14 Oct 2024 23:54:31 +0400 Subject: [PATCH 3/6] Refactor Workflow implementation --- app/src/SafeMessageHandlers/WorkflowImpl.php | 166 +++++++++---------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/app/src/SafeMessageHandlers/WorkflowImpl.php b/app/src/SafeMessageHandlers/WorkflowImpl.php index 6418bfc..5d14ac6 100644 --- a/app/src/SafeMessageHandlers/WorkflowImpl.php +++ b/app/src/SafeMessageHandlers/WorkflowImpl.php @@ -26,16 +26,57 @@ class WorkflowImpl { private ClusterManagerState $state; - private Mutex $nodes_lock; - private ?int $max_history_length; - private int $sleep_interval_seconds; + private Mutex $nodesLock; + private ?int $maxHistoryLength; + private int $sleepIntervalSeconds; public function __construct() { $this->state = new ClusterManagerState(); // Protects workflow state from interleaved access - $this->nodes_lock = new Mutex(); - $this->max_history_length = null; - $this->sleep_interval_seconds = 600; + $this->nodesLock = new Mutex(); + $this->maxHistoryLength = null; + $this->sleepIntervalSeconds = 600; + } + + #[WorkflowMethod] + public function run(ClusterManagerInput $input) + { + // todo rewrite after https://github.com/temporalio/sdk-php/issues/480/ + $this->init($input); + + yield Workflow::await(fn() => $this->state->clusterStarted); + // Perform health checks at intervals. + while (true) { + yield $this->performHealthChecks(); + try { + yield Workflow::await( + fn() => $this->state->clusterShutdown || $this->shouldContinueAsNew(), + ['timeout' => '600 seconds'], + ); + } catch (\Throwable) { + // do nothing + } + + if ($this->state->clusterShutdown) { + break; + } + if ($this->shouldContinueAsNew()) { + // We don't want to leave any job assignment or deletion handlers half-finished when we continue as new. + yield Workflow::await(fn() => Workflow::allHandlersFinished()); + trap("Continuing as new"); + Workflow::continueAsNew( + Workflow::getInfo()->type->name, + [new ClusterManagerInput($this->state, $input->testContinueAsNew)] + ); + } + } + + // Make sure we finish off handlers such as deleting jobs before we complete the workflow. + yield Workflow::await(fn() => Workflow::allHandlersFinished()); + return new ClusterManagerResult( + \count($this->getAssignedNodes()), + \count($this->getBadNodes()), + ); } #[SignalMethod('start_cluster')] @@ -71,7 +112,7 @@ public function assignNodesToJob(ClusterManagerAssignNodesToJobInput $input) 'Cannot assign nodes to a job: Cluster is already shut down', 'CannotAssignNodesToJob', true, ); - return yield Workflow::runLocked($this->nodes_lock, function () use ($input) { + return yield Workflow::runLocked($this->nodesLock, function () use ($input) { // Idempotency guard. if (\in_array($input->jobName, $this->state->jobsAssigned, true)) { return new ClusterManagerAssignNodesToJobResult( @@ -94,7 +135,7 @@ public function assignNodesToJob(ClusterManagerAssignNodesToJobInput $input) $nodesToAssign = \array_slice($unassignedNodes, 0, $input->totalNumNodes); /** - * This await would be dangerous without {@see self::$nodes_lock} because it yields control + * This await would be dangerous without {@see self::$nodesLock} because it yields control * and allows interleaving with {@see self::deleteJob()} and {@see self::performHealthChecks()}, * which both touch {@see ClusterManagerState::$nodes}. */ @@ -103,20 +144,6 @@ public function assignNodesToJob(ClusterManagerAssignNodesToJobInput $input) }); } - private function _assignNodesToJob(array $assignedNodes, string $jobName): \Generator - { - yield Workflow::executeActivity( - 'assign_nodes_to_job', - [new AssignNodesToJobInput($assignedNodes, $jobName)], - ActivityOptions::new()->withStartToCloseTimeout('10 seconds'), - ); - foreach ($assignedNodes as $node) { - $this->state->nodes[$node] = $jobName; - } - - $this->state->jobsAssigned[] = $jobName; - } - /** * Even though it returns nothing, this is an update because the client may want to track it, for example * to wait for nodes to be unassigned before reassigning them. @@ -131,10 +158,10 @@ public function deleteJob(ClusterManagerDeleteJobInput $input): \Generator 'Cannot delete a job: Cluster is already shut down', 'CannotDeleteJob', true, ); - return yield Workflow::runLocked($this->nodes_lock, function () use ($input) { + return yield Workflow::runLocked($this->nodesLock, function () use ($input) { $nodesToUnassign = \array_keys(\array_filter($this->state->nodes, fn($v) => $v === $input->jobName)); /** - * This await would be dangerous without {@see self::$nodes_lock} because it yields control + * This await would be dangerous without {@see self::$nodesLock} because it yields control * and allows interleaving with {@see self::assignNodesToJob()} and {@see self::performHealthChecks()}, * which all touch {@see ClusterManagerState::$nodes}. */ @@ -142,43 +169,37 @@ public function deleteJob(ClusterManagerDeleteJobInput $input): \Generator }); } - private function _unassignNodesForJob(array $nodesToUnassign, string $jobName): \Generator + private function init(ClusterManagerInput $input): void { - yield Workflow::executeActivity( - 'unassign_nodes_for_job', - [new UnassignNodesForJobInput($nodesToUnassign, $jobName)], - ActivityOptions::new()->withStartToCloseTimeout('10 seconds'), - ); - - foreach ($nodesToUnassign as $node) { - $this->state->nodes[$node] = null; - } + $input->state === null or $this->state = $input->state; + $input->testContinueAsNew and $this->maxHistoryLength = 120; + $input->testContinueAsNew and $this->sleepIntervalSeconds = 1; } - public function getUnassignedNodes(): array + private function getUnassignedNodes(): array { return \array_keys(\array_filter($this->state->nodes, fn($v) => $v === null)); } - public function getBadNodes(): array + private function getBadNodes(): array { return \array_keys(\array_filter($this->state->nodes, fn($v) => $v === 'BAD!')); } - public function getAssignedNodes(?string $jobName = null): array + private function getAssignedNodes(?string $jobName = null): array { return $jobName === null ? \array_keys(\array_filter($this->state->nodes, fn($v) => $v !== null && $v !== 'BAD!')) : \array_keys(\array_filter($this->state->nodes, fn($v) => $v === $jobName)); } - public function performHealthChecks(): \Generator + private function performHealthChecks(): \Generator { - return yield Workflow::runLocked($this->nodes_lock, function () { + return yield Workflow::runLocked($this->nodesLock, function () { $assignedNodes = $this->getAssignedNodes(); try { /** - * This await would be dangerous without {@see self::$nodes_lock} because it yields control + * This await would be dangerous without {@see self::$nodesLock} because it yields control * and allows interleaving with {@see self::assignNodesToJob()} and {@see self::deleteJob()}, * which both touch {@see ClusterManagerState::$nodes}. */ @@ -199,65 +220,44 @@ public function performHealthChecks(): \Generator }); } - public function init(ClusterManagerInput $input): void - { - $input->state === null or $this->state = $input->state; - $input->testContinueAsNew and $this->max_history_length = 120; - $input->testContinueAsNew and $this->sleep_interval_seconds = 1; - } - - public function shouldContinueAsNew(): bool + private function shouldContinueAsNew(): bool { if (Workflow::getInfo()->shouldContinueAsNew) { return true; } // This is just for ease-of-testing. In production, we trust temporal to tell us when to continue as new. - if ($this->max_history_length !== null && Workflow::getInfo()->historyLength > $this->max_history_length) { + if ($this->maxHistoryLength !== null && Workflow::getInfo()->historyLength > $this->maxHistoryLength) { return true; } return false; } - #[WorkflowMethod] - public function run(ClusterManagerInput $input) + private function _unassignNodesForJob(array $nodesToUnassign, string $jobName): \Generator { - // todo rewrite after https://github.com/temporalio/sdk-php/issues/480/ - $this->init($input); - - yield Workflow::await(fn() => $this->state->clusterStarted); - // Perform health checks at intervals. - while (true) { - yield $this->performHealthChecks(); - try { - yield Workflow::await( - fn() => $this->state->clusterShutdown || $this->shouldContinueAsNew(), - ['timeout' => '600 seconds'], - ); - } catch (\Throwable) { - // do nothing - } + yield Workflow::executeActivity( + 'unassign_nodes_for_job', + [new UnassignNodesForJobInput($nodesToUnassign, $jobName)], + ActivityOptions::new()->withStartToCloseTimeout('10 seconds'), + ); - if ($this->state->clusterShutdown) { - break; - } - if ($this->shouldContinueAsNew()) { - // We don't want to leave any job assignment or deletion handlers half-finished when we continue as new. - yield Workflow::await(fn() => Workflow::allHandlersFinished()); - trap("Continuing as new"); - Workflow::continueAsNew( - Workflow::getInfo()->type->name, - [new ClusterManagerInput($this->state, $input->testContinueAsNew)] - ); - } + foreach ($nodesToUnassign as $node) { + $this->state->nodes[$node] = null; } + } - // Make sure we finish off handlers such as deleting jobs before we complete the workflow. - yield Workflow::await(fn() => Workflow::allHandlersFinished()); - return new ClusterManagerResult( - \count($this->getAssignedNodes()), - \count($this->getBadNodes()), + private function _assignNodesToJob(array $assignedNodes, string $jobName): \Generator + { + yield Workflow::executeActivity( + 'assign_nodes_to_job', + [new AssignNodesToJobInput($assignedNodes, $jobName)], + ActivityOptions::new()->withStartToCloseTimeout('10 seconds'), ); + foreach ($assignedNodes as $node) { + $this->state->nodes[$node] = $jobName; + } + + $this->state->jobsAssigned[] = $jobName; } } From fedee10f4582998bc39f91dd8297b1dca2cdc87e Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Tue, 15 Oct 2024 14:40:58 +0400 Subject: [PATCH 4/6] Add activity implementation --- app/composer.json | 2 +- .../DTO/FindBadNodesInput.php | 4 +- .../DTO/UnassignNodesForJobInput.php | 4 +- .../MessageHandlerActivity.php | 51 +++++++++++++++++++ .../MessageHandlerActivityInterface.php | 27 ++++++++++ ...lowImpl.php => MessageHandlerWorkflow.php} | 23 +-------- .../MessageHandlerWorkflowInterface.php | 40 +++++++++++++++ 7 files changed, 125 insertions(+), 26 deletions(-) create mode 100644 app/src/SafeMessageHandlers/MessageHandlerActivity.php create mode 100644 app/src/SafeMessageHandlers/MessageHandlerActivityInterface.php rename app/src/SafeMessageHandlers/{WorkflowImpl.php => MessageHandlerWorkflow.php} (91%) create mode 100644 app/src/SafeMessageHandlers/MessageHandlerWorkflowInterface.php diff --git a/app/composer.json b/app/composer.json index 3c848c7..3f50b77 100644 --- a/app/composer.json +++ b/app/composer.json @@ -2,8 +2,8 @@ "minimum-stability": "dev", "prefer-stable": true, "require": { - "temporal/sdk": "^2.11.0", "spiral/tokenizer": "^3.14.5", + "temporal/sdk": "dev-mutex as 2.12.0", "temporal/open-telemetry-interceptors": "dev-master", "open-telemetry/exporter-otlp": "^1.1", "open-telemetry/transport-grpc": "^1.1", diff --git a/app/src/SafeMessageHandlers/DTO/FindBadNodesInput.php b/app/src/SafeMessageHandlers/DTO/FindBadNodesInput.php index 4081489..591fb14 100644 --- a/app/src/SafeMessageHandlers/DTO/FindBadNodesInput.php +++ b/app/src/SafeMessageHandlers/DTO/FindBadNodesInput.php @@ -7,9 +7,9 @@ final class FindBadNodesInput { /** - * @param array $assignedNodes + * @param array $nodesToCheck */ public function __construct( - public array $assignedNodes, + public array $nodesToCheck, ) {} } diff --git a/app/src/SafeMessageHandlers/DTO/UnassignNodesForJobInput.php b/app/src/SafeMessageHandlers/DTO/UnassignNodesForJobInput.php index c959b03..f0252f0 100644 --- a/app/src/SafeMessageHandlers/DTO/UnassignNodesForJobInput.php +++ b/app/src/SafeMessageHandlers/DTO/UnassignNodesForJobInput.php @@ -7,10 +7,10 @@ final class UnassignNodesForJobInput { /** - * @param string[] $nodesToUnassign + * @param string[] $nodes */ public function __construct( - public array $nodesToUnassign, + public array $nodes, public string $jobName, ) {} } diff --git a/app/src/SafeMessageHandlers/MessageHandlerActivity.php b/app/src/SafeMessageHandlers/MessageHandlerActivity.php new file mode 100644 index 0000000..a75d891 --- /dev/null +++ b/app/src/SafeMessageHandlers/MessageHandlerActivity.php @@ -0,0 +1,51 @@ +nodes) . ' to job ' . $input->jobName); + \sleep(1); + } + + #[ActivityMethod(name: 'unassign_nodes_for_job')] + public function unassign_nodes_for_job(UnassignNodesForJobInput $input): void + { + \trap('Deallocating nodes ' . \implode(', ', $input->nodes) . ' from job ' . $input->jobName); + \sleep(1); + } + + /** + * @return string[] + */ + #[ActivityMethod(name: 'find_bad_nodes')] + public function find_bad_nodes(FindBadNodesInput $input): array + { + \sleep(1); + $badNodes = []; + foreach ($input->nodesToCheck as $node) { + if ((int) $node % 5 === 0) { + $badNodes[] = $node; + } + } + + \trap(\count($badNodes) > 0 + ? 'Found bad nodes: ' . \implode(', ', $badNodes) + : 'No new bad nodes found.', + ); + + return $badNodes; + } +} diff --git a/app/src/SafeMessageHandlers/MessageHandlerActivityInterface.php b/app/src/SafeMessageHandlers/MessageHandlerActivityInterface.php new file mode 100644 index 0000000..658107a --- /dev/null +++ b/app/src/SafeMessageHandlers/MessageHandlerActivityInterface.php @@ -0,0 +1,27 @@ +sleepIntervalSeconds = 600; } - #[WorkflowMethod] public function run(ClusterManagerInput $input) { - // todo rewrite after https://github.com/temporalio/sdk-php/issues/480/ + // todo use init method with https://github.com/temporalio/sdk-php/issues/480/ $this->init($input); yield Workflow::await(fn() => $this->state->clusterStarted); @@ -79,7 +74,6 @@ public function run(ClusterManagerInput $input) ); } - #[SignalMethod('start_cluster')] public function startCluster(): void { $this->state->clusterStarted = true; @@ -87,7 +81,6 @@ public function startCluster(): void trap("Cluster started"); } - #[SignalMethod('shutdown_cluster')] public function shutdownCluster() { yield Workflow::await(fn() => $this->state->clusterStarted); @@ -95,13 +88,6 @@ public function shutdownCluster() trap("Cluster shut down"); } - /** - * This is an update as opposed to a signal because the client may want to wait for nodes to be allocated - * before sending work to those nodes. - * Returns the list of node names that were allocated to the job. - */ - #[Workflow\UpdateMethod('assign_nodes_to_job')] - #[Workflow\ReturnType(ClusterManagerAssignNodesToJobResult::class)] public function assignNodesToJob(ClusterManagerAssignNodesToJobInput $input) { yield Workflow::await(fn() => $this->state->clusterStarted); @@ -144,11 +130,6 @@ public function assignNodesToJob(ClusterManagerAssignNodesToJobInput $input) }); } - /** - * Even though it returns nothing, this is an update because the client may want to track it, for example - * to wait for nodes to be unassigned before reassigning them. - */ - #[Workflow\UpdateMethod('delete_job')] public function deleteJob(ClusterManagerDeleteJobInput $input): \Generator { yield Workflow::await(fn() => $this->state->clusterStarted); diff --git a/app/src/SafeMessageHandlers/MessageHandlerWorkflowInterface.php b/app/src/SafeMessageHandlers/MessageHandlerWorkflowInterface.php new file mode 100644 index 0000000..e3f890a --- /dev/null +++ b/app/src/SafeMessageHandlers/MessageHandlerWorkflowInterface.php @@ -0,0 +1,40 @@ + Date: Thu, 17 Oct 2024 15:14:38 +0400 Subject: [PATCH 5/6] Add readme --- .../ClusterManagerAssignNodesToJobInput.php | 6 +- .../SafeMessageHandlers/ExecuteCommand.php | 95 +++++++++++++++++++ .../MessageHandlerWorkflow.php | 31 +++--- .../MessageHandlerWorkflowInterface.php | 4 + app/src/SafeMessageHandlers/README.md | 22 +++++ 5 files changed, 142 insertions(+), 16 deletions(-) create mode 100644 app/src/SafeMessageHandlers/ExecuteCommand.php create mode 100644 app/src/SafeMessageHandlers/README.md diff --git a/app/src/SafeMessageHandlers/DTO/ClusterManagerAssignNodesToJobInput.php b/app/src/SafeMessageHandlers/DTO/ClusterManagerAssignNodesToJobInput.php index 061ff31..295175e 100644 --- a/app/src/SafeMessageHandlers/DTO/ClusterManagerAssignNodesToJobInput.php +++ b/app/src/SafeMessageHandlers/DTO/ClusterManagerAssignNodesToJobInput.php @@ -10,6 +10,8 @@ */ final class ClusterManagerAssignNodesToJobInput { - public int $totalNumNodes; - public string $jobName; + public function __construct( + public int $totalNumNodes, + public string $jobName, + ) {} } diff --git a/app/src/SafeMessageHandlers/ExecuteCommand.php b/app/src/SafeMessageHandlers/ExecuteCommand.php new file mode 100644 index 0000000..de961d7 --- /dev/null +++ b/app/src/SafeMessageHandlers/ExecuteCommand.php @@ -0,0 +1,95 @@ +input = $input; + $this->output = $output; + + $jobs = max(1, (int) $input->getArgument('jobs')); + $testContinueAsNew = (bool) $input->getArgument('continue'); + $delay = 0.5; + + $workflow = $this->workflowClient->newWorkflowStub( + MessageHandlerWorkflowInterface::class, + WorkflowOptions::new() + ->withWorkflowId(self::WORKFLOW_ID) + ->withWorkflowIdReusePolicy(IdReusePolicy::TerminateIfRunning), + ); + + $output->writeln("Starting MessageHandlerWorkflow... "); + + $this->workflowClient->start($workflow, new ClusterManagerInput(testContinueAsNew: $testContinueAsNew)); + $this->doClusterLifecycle($workflow, $delay, $jobs); + + return self::SUCCESS; + } + + private function doClusterLifecycle( + MessageHandlerWorkflowInterface|WorkflowProxy $workflow, + float $delay, + int $jobs = 6, + ) { + $untyped = $workflow->__getUntypedStub(); + $workflow->startCluster(); + + $this->output->writeln('Assign jobs to nodes...'); + $handle = []; + for ($i = 0; $i < $jobs; $i++) { + $handle[] = $untyped->startUpdate( + 'assign_nodes_to_job', + new ClusterManagerAssignNodesToJobInput(2, "task-$i"), + ); + } + // await + foreach ($handle as $h) { + $h->getResult(); + } + + $this->output->writeln("Sleeping for $delay second(s)"); + $delay > 0 and \usleep((int) ($delay * 1000000)); + + $this->output->writeln('Deleting jobs...'); + $handle = []; + for ($i = 0; $i < $jobs; $i++) { + $handle[] = $untyped->startUpdate( + 'delete_job', + new ClusterManagerDeleteJobInput("task-$i"), + ); + } + // await + foreach ($handle as $h) { + $h->getResult(); + } + + $workflow->shutdownCluster(); + } +} \ No newline at end of file diff --git a/app/src/SafeMessageHandlers/MessageHandlerWorkflow.php b/app/src/SafeMessageHandlers/MessageHandlerWorkflow.php index a5c6189..612a436 100644 --- a/app/src/SafeMessageHandlers/MessageHandlerWorkflow.php +++ b/app/src/SafeMessageHandlers/MessageHandlerWorkflow.php @@ -44,9 +44,9 @@ public function run(ClusterManagerInput $input) while (true) { yield $this->performHealthChecks(); try { - yield Workflow::await( + yield Workflow::awaitWithTimeout( + '600 seconds', fn() => $this->state->clusterShutdown || $this->shouldContinueAsNew(), - ['timeout' => '600 seconds'], ); } catch (\Throwable) { // do nothing @@ -61,7 +61,7 @@ public function run(ClusterManagerInput $input) trap("Continuing as new"); Workflow::continueAsNew( Workflow::getInfo()->type->name, - [new ClusterManagerInput($this->state, $input->testContinueAsNew)] + [new ClusterManagerInput($this->state, $input->testContinueAsNew)], ); } } @@ -107,17 +107,15 @@ public function assignNodesToJob(ClusterManagerAssignNodesToJobInput $input) } $unassignedNodes = $this->getUnassignedNodes(); - if (\count($unassignedNodes) < $input->totalNumNodes) { - throw new ApplicationFailure( - \sprintf( - 'Cannot assign %d nodes; have only %d available', - $input->totalNumNodes, - \count($unassignedNodes), - ), - 'CannotAssignNodesToJob', - true, - ); - } + \count($unassignedNodes) < $input->totalNumNodes and throw new ApplicationFailure( + \sprintf( + 'Cannot assign %d nodes; have only %d available', + $input->totalNumNodes, + \count($unassignedNodes), + ), + 'CannotAssignNodesToJob', + true, + ); $nodesToAssign = \array_slice($unassignedNodes, 0, $input->totalNumNodes); /** @@ -150,6 +148,11 @@ public function deleteJob(ClusterManagerDeleteJobInput $input): \Generator }); } + public function getState(): ClusterManagerState + { + return $this->state; + } + private function init(ClusterManagerInput $input): void { $input->state === null or $this->state = $input->state; diff --git a/app/src/SafeMessageHandlers/MessageHandlerWorkflowInterface.php b/app/src/SafeMessageHandlers/MessageHandlerWorkflowInterface.php index e3f890a..96dc5ba 100644 --- a/app/src/SafeMessageHandlers/MessageHandlerWorkflowInterface.php +++ b/app/src/SafeMessageHandlers/MessageHandlerWorkflowInterface.php @@ -8,6 +8,7 @@ use Temporal\Samples\SafeMessageHandlers\DTO\ClusterManagerAssignNodesToJobResult; use Temporal\Samples\SafeMessageHandlers\DTO\ClusterManagerDeleteJobInput; use Temporal\Samples\SafeMessageHandlers\DTO\ClusterManagerInput; +use Temporal\Samples\SafeMessageHandlers\DTO\ClusterManagerState; use Temporal\Workflow; #[Workflow\WorkflowInterface] @@ -37,4 +38,7 @@ public function assignNodesToJob(ClusterManagerAssignNodesToJobInput $input); */ #[Workflow\UpdateMethod('delete_job')] public function deleteJob(ClusterManagerDeleteJobInput $input): \Generator; + + #[Workflow\QueryMethod('get_state')] + public function getState(): ClusterManagerState; } \ No newline at end of file diff --git a/app/src/SafeMessageHandlers/README.md b/app/src/SafeMessageHandlers/README.md new file mode 100644 index 0000000..5943062 --- /dev/null +++ b/app/src/SafeMessageHandlers/README.md @@ -0,0 +1,22 @@ +# Atomic message handlers + +This sample shows off important techniques for handling signals and updates, aka messages. In particular, it illustrates how message handlers can interleave or not be completed before the workflow completes, and how you can manage that. + +* Here, using `Workflow::await()`, signal and update handlers will only operate when the workflow is within a certain state--between `cluster_started` and `cluster_shutdown`. +* You can run the Workflow with an initializer signal that you want to run before anything else other than the workflow's constructor. + This pattern is known as "signal-with-start." +* Message handlers can block and their actions can be interleaved with one another and with the main Workflow. + This can easily cause bugs, so you can use a Mutex object to protect shared state from interleaved access. +* An "Entity" workflow, i.e. a long-lived workflow, periodically "continues as new". It must do this to prevent its history from growing too large, and it passes its state to the next workflow. + You can check `Workflow::getInfo()->shouldContinueAsNew` to see when it's time. +* Most people want their message handlers to finish before the workflow run completes or continues as new. + Use `yield Workflow::await(fn() => Workflow::allHandlersFinished())` to achieve this. +* Message handlers can be made idempotent. + See update `MessageHandlerWorkflow::assignNodesToJob()`. + + +From the root of the project, run the following command: + +```bash +php ./app/app.php safe-message-handlers +``` From 5be638c31ea2b6bc23f1dbc731935835de032585 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 13 Feb 2025 00:40:39 +0400 Subject: [PATCH 6/6] Use WorkflowInit to initialize MessageHandlerWorkflow --- app/composer.json | 4 +- app/composer.lock | 743 ++++++++---------- .../MessageHandlerWorkflow.php | 22 +- 3 files changed, 342 insertions(+), 427 deletions(-) diff --git a/app/composer.json b/app/composer.json index be03511..2e2208b 100644 --- a/app/composer.json +++ b/app/composer.json @@ -3,14 +3,14 @@ "prefer-stable": true, "require": { "spiral/tokenizer": "^3.14.5", - "temporal/sdk": "dev-mutex as 2.12.0", + "temporal/sdk": "^2.13", "temporal/open-telemetry-interceptors": "dev-master", "open-telemetry/exporter-otlp": "^1.1", "open-telemetry/transport-grpc": "^1.1", "symfony/console": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "buggregator/trap": "^1.11", + "buggregator/trap": "^1.13", "internal/dload": "^1.0", "phpunit/phpunit": "^10.5" }, diff --git a/app/composer.lock b/app/composer.lock index 294ff5c..ca11345 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "441fbbcd77b123372e5774b65eee3371", + "content-hash": "522b38d4791d64c7d0bf867c94e260cd", "packages": [ { "name": "brick/math", @@ -218,16 +218,16 @@ }, { "name": "google/common-protos", - "version": "4.8.3", + "version": "4.10.0", "source": { "type": "git", "url": "https://github.com/googleapis/common-protos-php.git", - "reference": "38a9a8bb459fa618da797d25d7bf36bb21d1103d" + "reference": "88bb76504910ddfe8fec457b622145c5731ab8ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/common-protos-php/zipball/38a9a8bb459fa618da797d25d7bf36bb21d1103d", - "reference": "38a9a8bb459fa618da797d25d7bf36bb21d1103d", + "url": "https://api.github.com/repos/googleapis/common-protos-php/zipball/88bb76504910ddfe8fec457b622145c5731ab8ea", + "reference": "88bb76504910ddfe8fec457b622145c5731ab8ea", "shasum": "" }, "require": { @@ -241,9 +241,9 @@ "extra": { "component": { "id": "common-protos", - "target": "googleapis/common-protos-php.git", "path": "CommonProtos", - "entry": "README.md" + "entry": "README.md", + "target": "googleapis/common-protos-php.git" } }, "autoload": { @@ -271,22 +271,22 @@ "google" ], "support": { - "source": "https://github.com/googleapis/common-protos-php/tree/v4.8.3" + "source": "https://github.com/googleapis/common-protos-php/tree/v4.10.0" }, - "time": "2024-09-07T01:37:15+00:00" + "time": "2025-02-03T23:47:57+00:00" }, { "name": "google/protobuf", - "version": "v4.28.3", + "version": "v4.29.3", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "c5c311e0f3d89928251ac5a2f0e3db283612c100" + "reference": "ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/c5c311e0f3d89928251ac5a2f0e3db283612c100", - "reference": "c5c311e0f3d89928251ac5a2f0e3db283612c100", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7", + "reference": "ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7", "shasum": "" }, "require": { @@ -315,9 +315,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.28.3" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.3" }, - "time": "2024-10-22T22:27:17+00:00" + "time": "2025-01-08T21:00:13+00:00" }, { "name": "grpc/grpc", @@ -365,16 +365,16 @@ }, { "name": "nesbot/carbon", - "version": "3.8.2", + "version": "3.8.5", "source": { "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947" + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "b1a53a27898639579a67de42e8ced5d5386aa9a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e1268cdbc486d97ce23fef2c666dc3c6b6de9947", - "reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/b1a53a27898639579a67de42e8ced5d5386aa9a4", + "reference": "b1a53a27898639579a67de42e8ced5d5386aa9a4", "shasum": "" }, "require": { @@ -406,10 +406,6 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.x-dev", - "dev-2.x": "2.x-dev" - }, "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -419,6 +415,10 @@ "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" } }, "autoload": { @@ -450,8 +450,8 @@ ], "support": { "docs": "https://carbon.nesbot.com/docs", - "issues": "https://github.com/briannesbitt/Carbon/issues", - "source": "https://github.com/briannesbitt/Carbon" + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" }, "funding": [ { @@ -467,7 +467,7 @@ "type": "tidelift" } ], - "time": "2024-11-07T17:46:48+00:00" + "time": "2025-02-11T16:28:45+00:00" }, { "name": "nyholm/psr7-server", @@ -537,16 +537,16 @@ }, { "name": "open-telemetry/api", - "version": "1.1.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "542064815d38a6df55af7957cd6f1d7d967c99c6" + "reference": "8b925df3047628968bc5be722468db1b98b82d51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/542064815d38a6df55af7957cd6f1d7d967c99c6", - "reference": "542064815d38a6df55af7957cd6f1d7d967c99c6", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/8b925df3047628968bc5be722468db1b98b82d51", + "reference": "8b925df3047628968bc5be722468db1b98b82d51", "shasum": "" }, "require": { @@ -560,13 +560,13 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.1.x-dev" - }, "spi": { "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" ] + }, + "branch-alias": { + "dev-main": "1.1.x-dev" } }, "autoload": { @@ -603,7 +603,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-10-15T22:42:37+00:00" + "time": "2025-02-03T21:49:11+00:00" }, { "name": "open-telemetry/context", @@ -666,16 +666,16 @@ }, { "name": "open-telemetry/exporter-otlp", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/exporter-otlp.git", - "reference": "9b6de12204f25f8ab9540b46d6e7b5151897ce18" + "reference": "243d9657c44a06f740cf384f486afe954c2b725f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/9b6de12204f25f8ab9540b46d6e7b5151897ce18", - "reference": "9b6de12204f25f8ab9540b46d6e7b5151897ce18", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/243d9657c44a06f740cf384f486afe954c2b725f", + "reference": "243d9657c44a06f740cf384f486afe954c2b725f", "shasum": "" }, "require": { @@ -726,20 +726,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-04-30T18:28:30+00:00" + "time": "2025-01-08T23:50:03+00:00" }, { "name": "open-telemetry/gen-otlp-protobuf", - "version": "1.2.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", - "reference": "66c3b98e998a726691c92e6405a82e6e7b8b169d" + "reference": "585bafddd4ae6565de154610b10a787a455c9ba0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/66c3b98e998a726691c92e6405a82e6e7b8b169d", - "reference": "66c3b98e998a726691c92e6405a82e6e7b8b169d", + "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/585bafddd4ae6565de154610b10a787a455c9ba0", + "reference": "585bafddd4ae6565de154610b10a787a455c9ba0", "shasum": "" }, "require": { @@ -789,20 +789,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-10-30T11:49:49+00:00" + "time": "2025-01-15T23:07:07+00:00" }, { "name": "open-telemetry/sdk", - "version": "1.1.2", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "fb0ff8d8279a3776bd604791e2531dd0cc147e8b" + "reference": "37eec0fe47ddd627911f318f29b6cd48196be0c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/fb0ff8d8279a3776bd604791e2531dd0cc147e8b", - "reference": "fb0ff8d8279a3776bd604791e2531dd0cc147e8b", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/37eec0fe47ddd627911f318f29b6cd48196be0c0", + "reference": "37eec0fe47ddd627911f318f29b6cd48196be0c0", "shasum": "" }, "require": { @@ -830,13 +830,13 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.0.x-dev" - }, "spi": { "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" ] + }, + "branch-alias": { + "dev-main": "1.0.x-dev" } }, "autoload": { @@ -879,24 +879,24 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-10-18T21:01:35+00:00" + "time": "2025-01-29T21:40:28+00:00" }, { "name": "open-telemetry/sem-conv", - "version": "1.27.1", + "version": "1.30.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "1dba705fea74bc0718d04be26090e3697e56f4e6" + "reference": "4178c9f390da8e4dbca9b181a9d1efd50cf7ee0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/1dba705fea74bc0718d04be26090e3697e56f4e6", - "reference": "1dba705fea74bc0718d04be26090e3697e56f4e6", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/4178c9f390da8e4dbca9b181a9d1efd50cf7ee0a", + "reference": "4178c9f390da8e4dbca9b181a9d1efd50cf7ee0a", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.0" }, "type": "library", "extra": { @@ -936,20 +936,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-08-28T09:20:31+00:00" + "time": "2025-02-06T00:21:48+00:00" }, { "name": "open-telemetry/transport-grpc", - "version": "1.1.0", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/transport-grpc.git", - "reference": "f0c4e0e1acb55617c5d38239d5f5e4984ac92c52" + "reference": "1d67b0ab37cf01b76bd7e95dfb8f2ca035dd6fe0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/transport-grpc/zipball/f0c4e0e1acb55617c5d38239d5f5e4984ac92c52", - "reference": "f0c4e0e1acb55617c5d38239d5f5e4984ac92c52", + "url": "https://api.github.com/repos/opentelemetry-php/transport-grpc/zipball/1d67b0ab37cf01b76bd7e95dfb8f2ca035dd6fe0", + "reference": "1d67b0ab37cf01b76bd7e95dfb8f2ca035dd6fe0", "shasum": "" }, "require": { @@ -998,7 +998,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-03-14T23:14:21+00:00" + "time": "2025-02-09T22:37:38+00:00" }, { "name": "php-http/discovery", @@ -1795,16 +1795,16 @@ }, { "name": "roadrunner-php/roadrunner-api-dto", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/roadrunner-php/roadrunner-api-dto.git", - "reference": "84ccecbd7a1daeaadda3eb0767001deb2dd13739" + "reference": "dfab9cdba2c6f73223cd0d9e159c39b6b995cff9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/roadrunner-php/roadrunner-api-dto/zipball/84ccecbd7a1daeaadda3eb0767001deb2dd13739", - "reference": "84ccecbd7a1daeaadda3eb0767001deb2dd13739", + "url": "https://api.github.com/repos/roadrunner-php/roadrunner-api-dto/zipball/dfab9cdba2c6f73223cd0d9e159c39b6b995cff9", + "reference": "dfab9cdba2c6f73223cd0d9e159c39b6b995cff9", "shasum": "" }, "require": { @@ -1850,7 +1850,7 @@ "docs": "https://docs.roadrunner.dev", "forum": "https://forum.roadrunner.dev", "issues": "https://github.com/roadrunner-server/roadrunner/issues", - "source": "https://github.com/roadrunner-php/roadrunner-api-dto/tree/v1.9.0" + "source": "https://github.com/roadrunner-php/roadrunner-api-dto/tree/v1.10.0" }, "funding": [ { @@ -1858,7 +1858,7 @@ "type": "github" } ], - "time": "2024-08-06T12:07:48+00:00" + "time": "2025-01-14T10:10:32+00:00" }, { "name": "roadrunner-php/version-checker", @@ -1915,16 +1915,16 @@ }, { "name": "spiral/attributes", - "version": "v3.1.7", + "version": "v3.1.8", "source": { "type": "git", "url": "https://github.com/spiral/attributes.git", - "reference": "fc6657de4ed83913c7f02241e5fe4e8e799af8fa" + "reference": "a7e368a42b079f56c16d7fc513b68190b96842c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spiral/attributes/zipball/fc6657de4ed83913c7f02241e5fe4e8e799af8fa", - "reference": "fc6657de4ed83913c7f02241e5fe4e8e799af8fa", + "url": "https://api.github.com/repos/spiral/attributes/zipball/a7e368a42b079f56c16d7fc513b68190b96842c3", + "reference": "a7e368a42b079f56c16d7fc513b68190b96842c3", "shasum": "" }, "require": { @@ -1936,6 +1936,7 @@ "doctrine/annotations": "^1.14 || ^2.0", "jetbrains/phpstorm-attributes": "^1.0", "phpunit/phpunit": "^9.5.20", + "spiral/code-style": "^2.2", "vimeo/psalm": "^5.17" }, "type": "library", @@ -1983,7 +1984,6 @@ "support": { "chat": "https://discord.gg/V6EK4he", "docs": "https://spiral.dev/docs", - "forum": "https://forum.spiral.dev", "issues": "https://github.com/spiral/attributes/issues", "source": "https://github.com/spiral/attributes" }, @@ -1993,34 +1993,34 @@ "type": "github" } ], - "time": "2024-08-22T10:18:52+00:00" + "time": "2024-12-09T15:33:18+00:00" }, { "name": "spiral/core", - "version": "3.14.6", + "version": "3.15.3", "source": { "type": "git", "url": "https://github.com/spiral/core.git", - "reference": "feee88acc53ac323313bc5af4202fed065144e71" + "reference": "11f3222b90cf70b9ae3ebfd76072a5d0d8f15912" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spiral/core/zipball/feee88acc53ac323313bc5af4202fed065144e71", - "reference": "feee88acc53ac323313bc5af4202fed065144e71", + "url": "https://api.github.com/repos/spiral/core/zipball/11f3222b90cf70b9ae3ebfd76072a5d0d8f15912", + "reference": "11f3222b90cf70b9ae3ebfd76072a5d0d8f15912", "shasum": "" }, "require": { "php": ">=8.1", "psr/container": "^1.1|^2.0", - "spiral/security": "^3.14.6" + "spiral/security": "^3.15.3" }, "provide": { "psr/container-implementation": "^1.1|^2.0" }, "require-dev": { - "mockery/mockery": "^1.5", - "phpunit/phpunit": "^10.1", - "vimeo/psalm": "^5.9" + "mockery/mockery": "^1.6.12", + "phpunit/phpunit": "^10.5.41", + "vimeo/psalm": "^6.0" }, "type": "library", "extra": { @@ -2067,7 +2067,7 @@ "type": "github" } ], - "time": "2024-10-22T21:48:36+00:00" + "time": "2025-02-11T11:20:45+00:00" }, { "name": "spiral/goridge", @@ -2158,28 +2158,28 @@ }, { "name": "spiral/hmvc", - "version": "3.14.6", + "version": "3.15.3", "source": { "type": "git", "url": "https://github.com/spiral/hmvc.git", - "reference": "168b432680661834b3f0aa10653b0b04dc2e9628" + "reference": "cfbb53c8ed1561636e9d78a5c5d5a94227962370" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spiral/hmvc/zipball/168b432680661834b3f0aa10653b0b04dc2e9628", - "reference": "168b432680661834b3f0aa10653b0b04dc2e9628", + "url": "https://api.github.com/repos/spiral/hmvc/zipball/cfbb53c8ed1561636e9d78a5c5d5a94227962370", + "reference": "cfbb53c8ed1561636e9d78a5c5d5a94227962370", "shasum": "" }, "require": { "php": ">=8.1", "psr/event-dispatcher": "^1.0", - "spiral/core": "^3.14.6", - "spiral/interceptors": "^3.14.6" + "spiral/core": "^3.15.3", + "spiral/interceptors": "^3.15.3" }, "require-dev": { - "phpunit/phpunit": "^10.1", - "spiral/testing": "^2.8", - "vimeo/psalm": "^5.9" + "phpunit/phpunit": "^10.5.41", + "spiral/testing": "^2.8.3", + "vimeo/psalm": "^6.0" }, "type": "library", "extra": { @@ -2228,31 +2228,31 @@ "type": "github" } ], - "time": "2024-10-22T21:48:51+00:00" + "time": "2025-02-11T11:20:46+00:00" }, { "name": "spiral/interceptors", - "version": "3.14.6", + "version": "3.15.3", "source": { "type": "git", "url": "https://github.com/spiral/interceptors.git", - "reference": "a262479abbe12dc7e4a8e6e2aeac5460f1a7a590" + "reference": "1c4fbf70b40ec4de3f02130aab503e2c01d271a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spiral/interceptors/zipball/a262479abbe12dc7e4a8e6e2aeac5460f1a7a590", - "reference": "a262479abbe12dc7e4a8e6e2aeac5460f1a7a590", + "url": "https://api.github.com/repos/spiral/interceptors/zipball/1c4fbf70b40ec4de3f02130aab503e2c01d271a8", + "reference": "1c4fbf70b40ec4de3f02130aab503e2c01d271a8", "shasum": "" }, "require": { "php": ">=8.1", "psr/event-dispatcher": "^1.0", - "spiral/core": "^3.14.6" + "spiral/core": "^3.15.3" }, "require-dev": { - "phpunit/phpunit": "^10.1", - "spiral/testing": "^2.8", - "vimeo/psalm": "^5.9" + "phpunit/phpunit": "^10.5.41", + "spiral/testing": "^2.8.3", + "vimeo/psalm": "^6.0" }, "type": "library", "extra": { @@ -2306,31 +2306,31 @@ "type": "github" } ], - "time": "2024-10-22T21:48:52+00:00" + "time": "2025-02-11T11:21:00+00:00" }, { "name": "spiral/logger", - "version": "3.14.6", + "version": "3.15.3", "source": { "type": "git", "url": "https://github.com/spiral/logger.git", - "reference": "e39592fa4ee5f5eaaa5b5a75d85ed30d424cbef5" + "reference": "402e9d1530e7810ad387b383c6717e8b415b99c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spiral/logger/zipball/e39592fa4ee5f5eaaa5b5a75d85ed30d424cbef5", - "reference": "e39592fa4ee5f5eaaa5b5a75d85ed30d424cbef5", + "url": "https://api.github.com/repos/spiral/logger/zipball/402e9d1530e7810ad387b383c6717e8b415b99c2", + "reference": "402e9d1530e7810ad387b383c6717e8b415b99c2", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "1 - 3", - "spiral/core": "^3.14.6" + "spiral/core": "^3.15.3" }, "require-dev": { - "mockery/mockery": "^1.5", - "phpunit/phpunit": "^10.1", - "vimeo/psalm": "^5.9" + "mockery/mockery": "^1.6.12", + "phpunit/phpunit": "^10.5.41", + "vimeo/psalm": "^6.0" }, "type": "library", "extra": { @@ -2377,20 +2377,20 @@ "type": "github" } ], - "time": "2024-10-22T21:48:56+00:00" + "time": "2025-02-11T11:22:26+00:00" }, { "name": "spiral/roadrunner", - "version": "v2024.2.1", + "version": "v2024.3.3", "source": { "type": "git", "url": "https://github.com/roadrunner-server/roadrunner.git", - "reference": "651cb40d300e5594b0d69648c20233795c99c9c9" + "reference": "e2d86914579217e717c1a8bfa7afe85ab6aeac6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/roadrunner-server/roadrunner/zipball/651cb40d300e5594b0d69648c20233795c99c9c9", - "reference": "651cb40d300e5594b0d69648c20233795c99c9c9", + "url": "https://api.github.com/repos/roadrunner-server/roadrunner/zipball/e2d86914579217e717c1a8bfa7afe85ab6aeac6c", + "reference": "e2d86914579217e717c1a8bfa7afe85ab6aeac6c", "shasum": "" }, "type": "metapackage", @@ -2419,7 +2419,7 @@ "docs": "https://roadrunner.dev/docs", "forum": "https://forum.roadrunner.dev/", "issues": "https://github.com/roadrunner-server/roadrunner/issues", - "source": "https://github.com/roadrunner-server/roadrunner/tree/v2024.2.1" + "source": "https://github.com/roadrunner-server/roadrunner/tree/v2024.3.3" }, "funding": [ { @@ -2427,46 +2427,42 @@ "type": "github" } ], - "time": "2024-09-12T16:07:07+00:00" + "time": "2025-02-10T20:25:19+00:00" }, { "name": "spiral/roadrunner-cli", - "version": "v2.6.0", + "version": "v2.7.0", "source": { "type": "git", "url": "https://github.com/roadrunner-php/cli.git", - "reference": "a896e1d05a1fc4ebaf14e68b8b901c851c3b38b2" + "reference": "61eec29e2df6ccd2a7243e2bcc21453ba2fa3f4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/roadrunner-php/cli/zipball/a896e1d05a1fc4ebaf14e68b8b901c851c3b38b2", - "reference": "a896e1d05a1fc4ebaf14e68b8b901c851c3b38b2", + "url": "https://api.github.com/repos/roadrunner-php/cli/zipball/61eec29e2df6ccd2a7243e2bcc21453ba2fa3f4d", + "reference": "61eec29e2df6ccd2a7243e2bcc21453ba2fa3f4d", "shasum": "" }, "require": { - "composer/semver": "^3.2", + "composer/semver": "^3.4", "ext-json": "*", - "php": ">=7.4", - "spiral/roadrunner-worker": ">=2.0.2", - "spiral/tokenizer": "^2.13 || ^3.0", + "php": ">=8.1", + "spiral/roadrunner-worker": "^2 || ^3", + "spiral/tokenizer": "^2.13 || ^3.15", "symfony/console": "^5.3 || ^6.0 || ^7.0", - "symfony/http-client": "^4.4.11 || ^5.0 || ^6.0 || ^7.0", - "symfony/polyfill-php80": "^1.22", - "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + "symfony/http-client": "^4.4.51 || ^5.4.49 || ^6.4.17 || ^7.2", + "symfony/yaml": "^5.4.49 || ^6.4.17 || ^7.2" }, "require-dev": { - "jetbrains/phpstorm-attributes": "^1.0", - "vimeo/psalm": "^5.17" + "jetbrains/phpstorm-attributes": "^1.2", + "spiral/code-style": "^2.2.2", + "spiral/dumper": "^3.3", + "vimeo/psalm": "^6.0" }, "bin": [ "bin/rr" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1.x-dev" - } - }, "autoload": { "psr-4": { "Spiral\\RoadRunner\\Console\\": "src" @@ -2487,10 +2483,20 @@ } ], "description": "RoadRunner: Command Line Interface", + "homepage": "https://roadrunner.dev", "support": { - "source": "https://github.com/roadrunner-php/cli/tree/v2.6.0" + "chat": "https://discord.gg/V6EK4he", + "docs": "https://docs.roadrunner.dev", + "issues": "https://github.com/roadrunner-server/roadrunner/issues", + "source": "https://github.com/roadrunner-php/cli/tree/v2.7.0" }, - "time": "2023-12-05T20:46:56+00:00" + "funding": [ + { + "url": "https://github.com/sponsors/roadrunner-server", + "type": "github" + } + ], + "time": "2025-01-28T11:07:47+00:00" }, { "name": "spiral/roadrunner-kv", @@ -2578,16 +2584,16 @@ }, { "name": "spiral/roadrunner-worker", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/roadrunner-php/worker.git", - "reference": "44c6f37c6abc25175c2723bd6daaa17651b18036" + "reference": "dd0571a84b432077447ece947e5f2b19edde574f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/roadrunner-php/worker/zipball/44c6f37c6abc25175c2723bd6daaa17651b18036", - "reference": "44c6f37c6abc25175c2723bd6daaa17651b18036", + "url": "https://api.github.com/repos/roadrunner-php/worker/zipball/dd0571a84b432077447ece947e5f2b19edde574f", + "reference": "dd0571a84b432077447ece947e5f2b19edde574f", "shasum": "" }, "require": { @@ -2651,7 +2657,7 @@ "docs": "https://docs.roadrunner.dev", "forum": "https://forum.roadrunner.dev/", "issues": "https://github.com/roadrunner-server/roadrunner/issues", - "source": "https://github.com/roadrunner-php/worker/tree/v3.6.0" + "source": "https://github.com/roadrunner-php/worker/tree/v3.6.1" }, "funding": [ { @@ -2659,32 +2665,32 @@ "type": "github" } ], - "time": "2024-06-03T15:30:19+00:00" + "time": "2024-11-23T08:32:13+00:00" }, { "name": "spiral/security", - "version": "3.14.6", + "version": "3.15.3", "source": { "type": "git", "url": "https://github.com/spiral/security.git", - "reference": "757bd652271876ed1d3ab2191aa4a98cab026ceb" + "reference": "03b14b906a4ee8e6684beed4d261f4d6b6ffab33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spiral/security/zipball/757bd652271876ed1d3ab2191aa4a98cab026ceb", - "reference": "757bd652271876ed1d3ab2191aa4a98cab026ceb", + "url": "https://api.github.com/repos/spiral/security/zipball/03b14b906a4ee8e6684beed4d261f4d6b6ffab33", + "reference": "03b14b906a4ee8e6684beed4d261f4d6b6ffab33", "shasum": "" }, "require": { "php": ">=8.1", - "spiral/core": "^3.14.6", - "spiral/hmvc": "^3.14.6" + "spiral/core": "^3.15.3", + "spiral/hmvc": "^3.15.3" }, "require-dev": { - "mockery/mockery": "^1.5", - "phpunit/phpunit": "^10.1", - "spiral/console": "^3.14.6", - "vimeo/psalm": "^5.9" + "mockery/mockery": "^1.6.12", + "phpunit/phpunit": "^10.5.41", + "spiral/console": "^3.15.3", + "vimeo/psalm": "^6.0" }, "type": "library", "extra": { @@ -2731,36 +2737,36 @@ "type": "github" } ], - "time": "2024-10-22T21:53:28+00:00" + "time": "2025-02-11T11:22:45+00:00" }, { "name": "spiral/tokenizer", - "version": "3.14.6", + "version": "3.15.3", "source": { "type": "git", "url": "https://github.com/spiral/tokenizer.git", - "reference": "fd895a156fbc77202e548429de6db80d86810e9b" + "reference": "15cd55761b101937929ba35fe49c6c85220e0b50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spiral/tokenizer/zipball/fd895a156fbc77202e548429de6db80d86810e9b", - "reference": "fd895a156fbc77202e548429de6db80d86810e9b", + "url": "https://api.github.com/repos/spiral/tokenizer/zipball/15cd55761b101937929ba35fe49c6c85220e0b50", + "reference": "15cd55761b101937929ba35fe49c6c85220e0b50", "shasum": "" }, "require": { "ext-tokenizer": "*", "php": ">=8.1", - "spiral/core": "^3.14.6", - "spiral/logger": "^3.14.6", - "symfony/finder": "^5.3.7 || ^6.0 || ^7.0" + "spiral/core": "^3.15.3", + "spiral/logger": "^3.15.3", + "symfony/finder": "^5.4.45 || ^6.4.17 || ^7.2" }, "require-dev": { - "mockery/mockery": "^1.6", - "phpunit/phpunit": "^10.1", + "mockery/mockery": "^1.6.12", + "phpunit/phpunit": "^10.5.41", "spiral/attributes": "^2.8|^3.0", - "spiral/boot": "^3.14.6", - "spiral/files": "^3.14.6", - "vimeo/psalm": "^5.9" + "spiral/boot": "^3.15.3", + "spiral/files": "^3.15.3", + "vimeo/psalm": "^6.0" }, "type": "library", "extra": { @@ -2807,7 +2813,7 @@ "type": "github" } ], - "time": "2024-10-22T21:54:31+00:00" + "time": "2025-02-11T11:24:45+00:00" }, { "name": "symfony/clock", @@ -2885,16 +2891,16 @@ }, { "name": "symfony/console", - "version": "v6.4.15", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f1fc6f47283e27336e7cebb9e8946c8de7bff9bd" + "reference": "799445db3f15768ecc382ac5699e6da0520a0a04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f1fc6f47283e27336e7cebb9e8946c8de7bff9bd", - "reference": "f1fc6f47283e27336e7cebb9e8946c8de7bff9bd", + "url": "https://api.github.com/repos/symfony/console/zipball/799445db3f15768ecc382ac5699e6da0520a0a04", + "reference": "799445db3f15768ecc382ac5699e6da0520a0a04", "shasum": "" }, "require": { @@ -2959,7 +2965,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.15" + "source": "https://github.com/symfony/console/tree/v6.4.17" }, "funding": [ { @@ -2975,20 +2981,20 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:19:14+00:00" + "time": "2024-12-07T12:07:30+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "shasum": "" }, "require": { @@ -2996,12 +3002,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -3026,7 +3032,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" }, "funding": [ { @@ -3042,7 +3048,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/filesystem", @@ -3112,16 +3118,16 @@ }, { "name": "symfony/finder", - "version": "v6.4.13", + "version": "v6.4.17", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "daea9eca0b08d0ed1dc9ab702a46128fd1be4958" + "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/daea9eca0b08d0ed1dc9ab702a46128fd1be4958", - "reference": "daea9eca0b08d0ed1dc9ab702a46128fd1be4958", + "url": "https://api.github.com/repos/symfony/finder/zipball/1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", + "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", "shasum": "" }, "require": { @@ -3156,7 +3162,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.13" + "source": "https://github.com/symfony/finder/tree/v6.4.17" }, "funding": [ { @@ -3172,27 +3178,27 @@ "type": "tidelift" } ], - "time": "2024-10-01T08:30:56+00:00" + "time": "2024-12-29T13:51:37+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.15", + "version": "v6.4.18", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "cb4073c905cd12b8496d24ac428a9228c1750670" + "reference": "394b440934056b8d9d6ba250001458e9d7998b7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/cb4073c905cd12b8496d24ac428a9228c1750670", - "reference": "cb4073c905cd12b8496d24ac428a9228c1750670", + "url": "https://api.github.com/repos/symfony/http-client/zipball/394b440934056b8d9d6ba250001458e9d7998b7f", + "reference": "394b440934056b8d9d6ba250001458e9d7998b7f", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "^3.4.1", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -3249,7 +3255,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.15" + "source": "https://github.com/symfony/http-client/tree/v6.4.18" }, "funding": [ { @@ -3265,20 +3271,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:40:18+00:00" + "time": "2025-01-28T15:49:13+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.5.0", + "version": "v3.5.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "20414d96f391677bf80078aa55baece78b82647d" + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d", - "reference": "20414d96f391677bf80078aa55baece78b82647d", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", "shasum": "" }, "require": { @@ -3286,12 +3292,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -3327,7 +3333,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" }, "funding": [ { @@ -3343,7 +3349,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-12-07T08:49:48+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3371,8 +3377,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3447,8 +3453,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3525,8 +3531,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3609,8 +3615,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3663,86 +3669,6 @@ ], "time": "2024-09-09T11:45:10+00:00" }, - { - "name": "symfony/polyfill-php80", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, { "name": "symfony/polyfill-php82", "version": "v1.31.0", @@ -3763,8 +3689,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3839,8 +3765,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3958,16 +3884,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", "shasum": "" }, "require": { @@ -3980,12 +3906,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -4021,7 +3947,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" }, "funding": [ { @@ -4037,7 +3963,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/string", @@ -4222,16 +4148,16 @@ }, { "name": "symfony/translation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", "shasum": "" }, "require": { @@ -4239,12 +4165,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -4280,7 +4206,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" }, "funding": [ { @@ -4296,20 +4222,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.13", + "version": "v6.4.18", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9" + "reference": "bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", - "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", + "url": "https://api.github.com/repos/symfony/yaml/zipball/bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5", + "reference": "bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5", "shasum": "" }, "require": { @@ -4352,7 +4278,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.13" + "source": "https://github.com/symfony/yaml/tree/v6.4.18" }, "funding": [ { @@ -4368,7 +4294,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-01-07T09:44:41+00:00" }, { "name": "tbachert/spi", @@ -4397,10 +4323,10 @@ }, "type": "composer-plugin", "extra": { + "class": "Nevay\\SPI\\Composer\\Plugin", "branch-alias": { "dev-main": "0.2.x-dev" }, - "class": "Nevay\\SPI\\Composer\\Plugin", "plugin-optional": true }, "autoload": { @@ -4483,54 +4409,54 @@ }, { "name": "temporal/sdk", - "version": "v2.11.2", + "version": "v2.13.1", "source": { "type": "git", "url": "https://github.com/temporalio/sdk-php.git", - "reference": "ca4f27d3dd9b34fd6785d24057431a26ef6596ce" + "reference": "2b4202b1721bddd18756e975655adc9b0bdf1692" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/temporalio/sdk-php/zipball/ca4f27d3dd9b34fd6785d24057431a26ef6596ce", - "reference": "ca4f27d3dd9b34fd6785d24057431a26ef6596ce", + "url": "https://api.github.com/repos/temporalio/sdk-php/zipball/2b4202b1721bddd18756e975655adc9b0bdf1692", + "reference": "2b4202b1721bddd18756e975655adc9b0bdf1692", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", - "google/common-protos": "^1.3 || ^2.0 || ^3.0 || ^4.0", - "google/protobuf": "^3.22 || ^4.0", - "grpc/grpc": "^1.42", - "nesbot/carbon": "^2.72 || ^3.0.2", + "google/common-protos": "^1.4 || ^2.2 || ^3.2 || ^4.9", + "google/protobuf": "^3.25.5 || ^4.29.3", + "grpc/grpc": "^1.57", + "nesbot/carbon": "^2.72.6 || ^3.8.4", "php": ">=8.1", - "psr/log": "^2.0 || ^3.0", - "ramsey/uuid": "^4.7", - "react/promise": "^2.9", - "roadrunner-php/roadrunner-api-dto": "^1.9.0", - "roadrunner-php/version-checker": "^1.0", - "spiral/attributes": "^3.1.6", - "spiral/roadrunner": "^2024.1", - "spiral/roadrunner-cli": "^2.5", - "spiral/roadrunner-kv": "^4.2", - "spiral/roadrunner-worker": "^3.5", - "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", - "symfony/http-client": "^5.4 || ^6.0 || ^7.0", - "symfony/process": "^5.4 || ^6.0 || ^7.0" + "psr/log": "^2.0 || ^3.0.2", + "ramsey/uuid": "^4.7.6", + "react/promise": "^2.11", + "roadrunner-php/roadrunner-api-dto": "^1.10.0", + "roadrunner-php/version-checker": "^1.0.1", + "spiral/attributes": "^3.1.8", + "spiral/roadrunner": "^2024.3.3", + "spiral/roadrunner-cli": "^2.6", + "spiral/roadrunner-kv": "^4.3", + "spiral/roadrunner-worker": "^3.6.1", + "symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0", + "symfony/http-client": "^5.4.49 || ^6.4.17 || ^7.0", + "symfony/polyfill-php83": "^1.31.0", + "symfony/process": "^5.4.47 || ^6.4.15 || ^7.0" }, "require-dev": { - "buggregator/trap": "^1.10.1", - "composer/composer": "^2.0", + "buggregator/trap": "^1.13.0", + "composer/composer": "^2.8.4", "dereuromark/composer-prefer-lowest": "^0.1.10", - "doctrine/annotations": "^1.14|^2.0.0", + "doctrine/annotations": "^1.14.4 || ^2.0.2", "internal/dload": "^1.0", "jetbrains/phpstorm-attributes": "dev-master@dev", - "laminas/laminas-code": "^4.0", - "phpunit/phpunit": "^10.5", + "laminas/laminas-code": "^4.16", + "phpunit/phpunit": "^10.5.41", "spiral/code-style": "~2.1.2", - "spiral/core": "^3.13", - "symfony/var-dumper": "^6.0 || ^7.0", + "spiral/core": "^3.14.9", "ta-tikoma/phpunit-architecture-test": "^0.8.4", - "vimeo/psalm": "^4.30 || ^5.4" + "vimeo/psalm": "^5.26.1 || ^6.2" }, "suggest": { "doctrine/annotations": "For Doctrine metadata driver support", @@ -4572,22 +4498,22 @@ "issues": "https://github.com/temporalio/sdk-php/issues", "source": "https://github.com/temporalio/sdk-php" }, - "time": "2024-10-28T22:16:51+00:00" + "time": "2025-02-12T20:26:42+00:00" } ], "packages-dev": [ { "name": "buggregator/trap", - "version": "1.11.0", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/buggregator/trap.git", - "reference": "df2ccbb8327749cd891b5227ba0b5ee794001a7e" + "reference": "500e7ac1313d35bf0a00540f230f134a8fd0ec91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/buggregator/trap/zipball/df2ccbb8327749cd891b5227ba0b5ee794001a7e", - "reference": "df2ccbb8327749cd891b5227ba0b5ee794001a7e", + "url": "https://api.github.com/repos/buggregator/trap/zipball/500e7ac1313d35bf0a00540f230f134a8fd0ec91", + "reference": "500e7ac1313d35bf0a00540f230f134a8fd0ec91", "shasum": "" }, "require": { @@ -4607,7 +4533,6 @@ "require-dev": { "dereuromark/composer-prefer-lowest": "^0.1.10", "ergebnis/phpunit-slow-test-detector": "^2.14", - "friendsofphp/php-cs-fixer": "^3.54", "google/protobuf": "^3.23", "pestphp/pest": "^2.34", "phpstan/extension-installer": "^1.3", @@ -4618,8 +4543,8 @@ "phpunit/phpunit": "^10.5", "rector/rector": "^1.1", "roxblnfk/unpoly": "^1.8.1", - "vimeo/psalm": "^5.11", - "wayofdev/cs-fixer-config": "^1.4" + "spiral/code-style": "*", + "vimeo/psalm": "^5.11" }, "suggest": { "ext-simplexml": "To load trap.xml", @@ -4654,6 +4579,7 @@ "description": "A simple and powerful tool for debugging PHP applications.", "homepage": "https://buggregator.dev/", "keywords": [ + "Fibers", "WebSockets", "binary dump", "cli", @@ -4661,6 +4587,7 @@ "debug", "dev", "dump", + "dumper", "helper", "sentry", "server", @@ -4668,23 +4595,19 @@ ], "support": { "issues": "https://github.com/buggregator/trap/issues", - "source": "https://github.com/buggregator/trap/tree/1.11.0" + "source": "https://github.com/buggregator/trap/tree/1.13.3" }, "funding": [ { - "url": "https://github.com/sponsors/buggregator", - "type": "github" - }, - { - "url": "https://patreon.com/butschster", - "type": "patreon" + "url": "https://boosty.to/roxblnfk", + "type": "boosty" }, { "url": "https://patreon.com/roxblnfk", "type": "patreon" } ], - "time": "2024-09-25T11:27:16+00:00" + "time": "2025-01-23T20:32:15+00:00" }, { "name": "clue/stream-filter", @@ -4825,16 +4748,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "024473a478be9df5fdaca2c793f2232fe788e414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", + "reference": "024473a478be9df5fdaca2c793f2232fe788e414", "shasum": "" }, "require": { @@ -4873,7 +4796,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" }, "funding": [ { @@ -4881,20 +4804,20 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-02-12T12:17:51+00:00" }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -4937,38 +4860,38 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "nunomaduro/termwind", - "version": "v1.16.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "dcf1ec3dfa36137b7ce41d43866644a7ab8fc257" + "reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dcf1ec3dfa36137b7ce41d43866644a7ab8fc257", - "reference": "dcf1ec3dfa36137b7ce41d43866644a7ab8fc257", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/5369ef84d8142c1d87e4ec278711d4ece3cbf301", + "reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.1", - "symfony/console": "^6.4.12" + "symfony/console": "^6.4.15" }, "require-dev": { - "illuminate/console": "^10.48.22", - "illuminate/support": "^10.48.22", - "laravel/pint": "^1.18.1", - "pestphp/pest": "^2", + "illuminate/console": "^10.48.24", + "illuminate/support": "^10.48.24", + "laravel/pint": "^1.18.2", + "pestphp/pest": "^2.36.0", "pestphp/pest-plugin-mock": "2.0.0", - "phpstan/phpstan": "^1.12.6", + "phpstan/phpstan": "^1.12.11", "phpstan/phpstan-strict-rules": "^1.6.1", - "symfony/var-dumper": "^6.4.11", + "symfony/var-dumper": "^6.4.15", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -5008,7 +4931,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v1.16.0" + "source": "https://github.com/nunomaduro/termwind/tree/v1.17.0" }, "funding": [ { @@ -5024,7 +4947,7 @@ "type": "github" } ], - "time": "2024-10-15T15:27:12+00:00" + "time": "2024-11-21T10:36:35+00:00" }, { "name": "nyholm/psr7", @@ -5614,16 +5537,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.38", + "version": "10.5.45", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132" + "reference": "bd68a781d8e30348bc297449f5234b3458267ae8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132", - "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8", + "reference": "bd68a781d8e30348bc297449f5234b3458267ae8", "shasum": "" }, "require": { @@ -5633,7 +5556,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.0", + "myclabs/deep-copy": "^1.12.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", @@ -5695,7 +5618,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45" }, "funding": [ { @@ -5711,7 +5634,7 @@ "type": "tidelift" } ], - "time": "2024-10-28T13:06:21+00:00" + "time": "2025-02-06T16:08:12+00:00" }, { "name": "react/async", @@ -6778,16 +6701,16 @@ }, { "name": "symfony/var-dumper", - "version": "v6.4.15", + "version": "v6.4.18", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80" + "reference": "4ad10cf8b020e77ba665305bb7804389884b4837" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", - "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/4ad10cf8b020e77ba665305bb7804389884b4837", + "reference": "4ad10cf8b020e77ba665305bb7804389884b4837", "shasum": "" }, "require": { @@ -6843,7 +6766,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.15" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.18" }, "funding": [ { @@ -6859,7 +6782,7 @@ "type": "tidelift" } ], - "time": "2024-11-08T15:28:48+00:00" + "time": "2025-01-17T11:26:11+00:00" }, { "name": "theseer/tokenizer", diff --git a/app/src/SafeMessageHandlers/MessageHandlerWorkflow.php b/app/src/SafeMessageHandlers/MessageHandlerWorkflow.php index 612a436..a07cb14 100644 --- a/app/src/SafeMessageHandlers/MessageHandlerWorkflow.php +++ b/app/src/SafeMessageHandlers/MessageHandlerWorkflow.php @@ -26,26 +26,25 @@ class MessageHandlerWorkflow implements MessageHandlerWorkflowInterface private ?int $maxHistoryLength; private int $sleepIntervalSeconds; - public function __construct() { - $this->state = new ClusterManagerState(); + #[Workflow\WorkflowInit] + public function __construct(ClusterManagerInput $input) { + $this->state = $input->state ?? new ClusterManagerState(); + $this->maxHistoryLength = $input->testContinueAsNew ? 120 : null; + $this->sleepIntervalSeconds = $input->testContinueAsNew ? 1 : 600; + // Protects workflow state from interleaved access $this->nodesLock = new Mutex(); - $this->maxHistoryLength = null; - $this->sleepIntervalSeconds = 600; } public function run(ClusterManagerInput $input) { - // todo use init method with https://github.com/temporalio/sdk-php/issues/480/ - $this->init($input); - yield Workflow::await(fn() => $this->state->clusterStarted); // Perform health checks at intervals. while (true) { yield $this->performHealthChecks(); try { yield Workflow::awaitWithTimeout( - '600 seconds', + $this->sleepIntervalSeconds, fn() => $this->state->clusterShutdown || $this->shouldContinueAsNew(), ); } catch (\Throwable) { @@ -153,13 +152,6 @@ public function getState(): ClusterManagerState return $this->state; } - private function init(ClusterManagerInput $input): void - { - $input->state === null or $this->state = $input->state; - $input->testContinueAsNew and $this->maxHistoryLength = 120; - $input->testContinueAsNew and $this->sleepIntervalSeconds = 1; - } - private function getUnassignedNodes(): array { return \array_keys(\array_filter($this->state->nodes, fn($v) => $v === null));