Skip to content
19 changes: 19 additions & 0 deletions src/Client/ClientOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use JetBrains\PhpStorm\ExpectedValues;
use JetBrains\PhpStorm\Pure;
use Temporal\Api\Enums\V1\QueryRejectCondition;
use Temporal\Common\PayloadLimitOptions;
use Temporal\Internal\Assert;

/**
Expand All @@ -36,6 +37,11 @@ class ClientOptions
#[ExpectedValues(valuesFromClass: QueryRejectCondition::class)]
public int $queryRejectionCondition = QueryRejectCondition::QUERY_REJECT_CONDITION_NONE;

/**
* @experimental This API is experimental and may change in the future.
*/
public ?PayloadLimitOptions $payloadLimits = null;

/**
* ClientOptions constructor.
*/
Expand All @@ -44,6 +50,19 @@ public function __construct()
$this->identity = \sprintf('%d@%s', (string) \getmypid(), (string) \gethostname());
}

/**
* @experimental This API is experimental and may change in the future.
*/
#[Pure]
public function withPayloadLimits(?PayloadLimitOptions $options): self
{
$self = clone $this;

$self->payloadLimits = $options;

return $self;
}

/**
* @param non-empty-string $namespace
* @return $this
Expand Down
35 changes: 35 additions & 0 deletions src/Client/GRPC/BaseClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@
use Carbon\CarbonInterval;
use Grpc\BaseStub;
use Grpc\UnaryCall;
use Psr\Log\LoggerInterface;
use Temporal\Client\Common\BackoffThrottler;
use Temporal\Client\Common\RpcRetryOptions;
use Temporal\Client\GRPC\Connection\Connection;
use Temporal\Client\GRPC\Connection\ConnectionInterface;
use Temporal\Common\PayloadLimitOptions;
use Temporal\Exception\Client\CanceledException;
use Temporal\Exception\Client\ServiceClientException;
use Temporal\Exception\Client\TimeoutException;
use Temporal\Interceptor\GrpcClientInterceptor;
use Temporal\Internal\Client\PayloadSizeChecker;
use Temporal\Internal\Interceptor\Pipeline;

abstract class BaseClient implements GrpcClientInterface
Expand All @@ -38,6 +41,8 @@ abstract class BaseClient implements GrpcClientInterface
private Connection $connection;
private ContextInterface $context;
private \Stringable|string $apiKey = '';
private ?PayloadSizeChecker $payloadSizeChecker = null;
private bool $payloadLimitsConfigured = false;

/**
* @param BaseStub|\Closure(): BaseStub $serviceClient Service Client or its factory
Expand Down Expand Up @@ -163,6 +168,34 @@ public function close(): void
$this->connection->disconnect();
}

/**
* @experimental This API is experimental and may change in the future.
*/
final public function withPayloadLimits(PayloadLimitOptions $options, LoggerInterface $logger): static
{
$clone = clone $this;
$clone->payloadSizeChecker = $options->isEnabled()
? new PayloadSizeChecker($options, $logger)
: null;
$clone->payloadLimitsConfigured = true;
return $clone;
}

/**
* @internal
*/
final public function withDefaultPayloadLimits(PayloadLimitOptions $options, LoggerInterface $logger): static
{
if ($this->payloadLimitsConfigured) {
return $this;
}

$clone = $this->withPayloadLimits($options, $logger);
$clone->payloadLimitsConfigured = false;

return $clone;
}

/**
* @param null|Pipeline<GrpcClientInterceptor, object> $pipeline
*/
Expand Down Expand Up @@ -208,6 +241,8 @@ protected function invoke(string $method, object $arg, ?ContextInterface $ctx =
] + $ctx->getMetadata());
}

$this->payloadSizeChecker?->check($method, $arg);

return $this->invokePipeline !== null
? ($this->invokePipeline)($method, $arg, $ctx)
: $this->call($method, $arg, $ctx);
Expand Down
15 changes: 14 additions & 1 deletion src/Client/ScheduleClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@
use Temporal\Api\Workflowservice\V1\ListSchedulesRequest;
use Temporal\Client\Common\ClientContextTrait;
use Temporal\Client\Common\Paginator;
use Temporal\Client\GRPC\BaseClient;
use Temporal\Worker\Logger\StderrLogger;
use Temporal\Common\PayloadLimitOptions;
use Temporal\Client\GRPC\ServiceClientInterface;
use Temporal\Client\Schedule\BackfillPeriod;
use Temporal\Client\Schedule\Info\ScheduleListEntry;
use Temporal\Client\Schedule\Schedule;
use Temporal\Client\Schedule\ScheduleHandle;
use Temporal\Client\Schedule\ScheduleOptions;
use Temporal\Common\Uuid;
use Psr\Log\LoggerInterface;
use Temporal\DataConverter\DataConverter;
use Temporal\DataConverter\DataConverterInterface;
use Temporal\Internal\Mapper\ScheduleMapper;
Expand Down Expand Up @@ -57,6 +61,7 @@ public function __construct(
?ClientOptions $options = null,
?DataConverterInterface $converter = null,
?PluginRegistry $pluginRegistry = null,
?LoggerInterface $logger = null,
) {
$this->clientOptions = $options ?? new ClientOptions();
$this->converter = $converter ?? DataConverter::createDefault();
Expand Down Expand Up @@ -88,6 +93,13 @@ public function __construct(
);
$this->protoConverter = new ProtoToArrayConverter($this->converter);

if ($serviceClient instanceof BaseClient) {
$serviceClient = $serviceClient->withDefaultPayloadLimits(
$this->clientOptions->payloadLimits ?? PayloadLimitOptions::new(),
$logger ?? new StderrLogger(),
);
}

// Set Temporal-Namespace metadata
$context = $serviceClient->getContext();
$this->client = $serviceClient->withContext(
Expand All @@ -102,8 +114,9 @@ public static function create(
?ClientOptions $options = null,
?DataConverterInterface $converter = null,
?PluginRegistry $pluginRegistry = null,
?LoggerInterface $logger = null,
): ScheduleClientInterface {
return new self($serviceClient, $options, $converter, $pluginRegistry);
return new self($serviceClient, $options, $converter, $pluginRegistry, $logger);
}

public function createSchedule(
Expand Down
15 changes: 14 additions & 1 deletion src/Client/WorkflowClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Temporal\Client;

use Doctrine\Common\Annotations\Reader;
use Psr\Log\LoggerInterface;
use JetBrains\PhpStorm\Deprecated;
use Spiral\Attributes\AnnotationReader;
use Spiral\Attributes\AttributeReader;
Expand All @@ -23,6 +24,9 @@
use Temporal\Api\Workflowservice\V1\ListWorkflowExecutionsRequest;
use Temporal\Client\Common\ClientContextTrait;
use Temporal\Client\Common\Paginator;
use Temporal\Client\GRPC\BaseClient;
use Temporal\Worker\Logger\StderrLogger;
use Temporal\Common\PayloadLimitOptions;
use Temporal\Client\GRPC\ServiceClientInterface;
use Temporal\Client\Update\LifecycleStage;
use Temporal\Client\Update\UpdateHandle;
Expand Down Expand Up @@ -81,6 +85,7 @@ public function __construct(
?DataConverterInterface $converter = null,
?PipelineProvider $interceptorProvider = null,
?PluginRegistry $pluginRegistry = null,
?LoggerInterface $logger = null,
) {
$this->pluginRegistry = $pluginRegistry ?? new PluginRegistry();
$this->clientOptions = $options ?? new ClientOptions();
Expand Down Expand Up @@ -116,6 +121,13 @@ public function __construct(
$this->interceptorPipeline = $provider->getPipeline(WorkflowClientCallsInterceptor::class);
$this->reader = new WorkflowReader($this->createReader());

if ($serviceClient instanceof BaseClient) {
$serviceClient = $serviceClient->withDefaultPayloadLimits(
$this->clientOptions->payloadLimits ?? PayloadLimitOptions::new(),
$logger ?? new StderrLogger(),
);
}

// Set Temporal-Namespace metadata
$context = $serviceClient->getContext();
$this->client = $serviceClient->withContext(
Expand All @@ -131,8 +143,9 @@ public static function create(
?DataConverterInterface $converter = null,
?PipelineProvider $interceptorProvider = null,
?PluginRegistry $pluginRegistry = null,
?LoggerInterface $logger = null,
): self {
return new self($serviceClient, $options, $converter, $interceptorProvider, $pluginRegistry);
return new self($serviceClient, $options, $converter, $interceptorProvider, $pluginRegistry, $logger);
}

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

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

declare(strict_types=1);

namespace Temporal\Common;

/**
* @experimental This API is experimental and may change in the future.
*/
final class PayloadLimitOptions
{
public const DEFAULT_PAYLOAD_SIZE_WARNING = 512 * 1024;
public const DEFAULT_MEMO_SIZE_WARNING = 2 * 1024;

/**
* @param null|positive-int $payloadSizeWarning
* @param null|positive-int $memoSizeWarning
*/
public function __construct(
public readonly ?int $payloadSizeWarning = self::DEFAULT_PAYLOAD_SIZE_WARNING,
public readonly ?int $memoSizeWarning = self::DEFAULT_MEMO_SIZE_WARNING,
) {
self::assertPositive($payloadSizeWarning, 'payloadSizeWarning');
self::assertPositive($memoSizeWarning, 'memoSizeWarning');
}

/**
* @experimental This API is experimental and may change in the future.
*/
public static function new(): self
{
return new self();
}

/**
* @experimental This API is experimental and may change in the future.
*/
public static function disabled(): self
{
return new self(null, null);
}

/**
* @param null|positive-int $bytes
*
* @experimental This API is experimental and may change in the future.
*/
public function withPayloadSizeWarning(?int $bytes): self
{
return new self($bytes, $this->memoSizeWarning);
}

/**
* @param null|positive-int $bytes
*
* @experimental This API is experimental and may change in the future.
*/
public function withMemoSizeWarning(?int $bytes): self
{
return new self($this->payloadSizeWarning, $bytes);
}

/**
* @experimental This API is experimental and may change in the future.
*/
public function isEnabled(): bool
{
return $this->payloadSizeWarning !== null || $this->memoSizeWarning !== null;
}

private static function assertPositive(?int $value, string $name): void
{
if ($value !== null && $value <= 0) {
throw new \InvalidArgumentException(
"`$name` must be a positive number of bytes or NULL to disable the warning.",
);
}
}
}
Loading