Skip to content

Commit 79d42ff

Browse files
authored
Merge pull request #657: Fairness Keys & Weights
2 parents 23f57a5 + 0ffd670 commit 79d42ff

32 files changed

+191
-72
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
"google/common-protos": "^4.9",
2828
"google/protobuf": "^4.31.1",
2929
"grpc/grpc": "^1.57",
30-
"internal/promise": "^2.12 || ^3.4",
3130
"internal/destroy": "^1.0",
31+
"internal/promise": "^2.12 || ^3.4",
3232
"nesbot/carbon": "^2.72.6 || ^3.8.4",
3333
"psr/log": "^2.0 || ^3.0.2",
3434
"ramsey/uuid": "^4.7.6",

dload.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
>
66
<actions>
77
<download software="rr" version="^2025.1.3"/>
8-
<download software="temporal" version="^1.4.1"/>
8+
<download software="temporal" version="^1.4-fairness@dev"/>
99
<download software="temporal-tests-server"/>
1010
</actions>
1111
<registry>

src/Common/Priority.php

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
* For all fields, the field not present or equal to zero/empty string means to inherit the value
2525
* from the calling workflow, or if there is no calling workflow, then use the default value.
2626
*
27+
* @see \Temporal\Api\Common\V1\Priority
28+
*
2729
* @internal The feature is experimental and may change in the future.
2830
*/
2931
final class Priority
@@ -45,6 +47,36 @@ final class Priority
4547
#[Marshal(name: 'PriorityKey')]
4648
public int $priorityKey = 0;
4749

50+
/**
51+
* FairnessKey is a short string that's used as a key for a fairness
52+
* balancing mechanism. It may correspond to a tenant id, or to a fixed
53+
* string like "high" or "low". The default is the empty string.
54+
*
55+
* The fairness mechanism attempts to dispatch tasks for a given key in
56+
* proportion to its weight. For example, using a thousand distinct tenant
57+
* ids, each with a weight of 1.0 (the default) will result in each tenant
58+
* getting a roughly equal share of task dispatch throughput.
59+
*
60+
* Fairness keys are limited to 64 bytes.
61+
*/
62+
#[Marshal(name: 'fairness_key')]
63+
#[Marshal(name: 'FairnessKey')]
64+
public string $fairnessKey = '';
65+
66+
/**
67+
* FairnessWeight for a task can come from multiple sources for
68+
* flexibility. From highest to lowest precedence:
69+
* 1. Weights for a small set of keys can be overridden in task queue
70+
* configuration with an API.
71+
* 2. It can be attached to the workflow/activity in this field.
72+
* 3. The default weight of 1.0 will be used.
73+
*
74+
* Weight values are clamped to the range [0.001, 1000].
75+
*/
76+
#[Marshal(name: 'fairness_weight')]
77+
#[Marshal(name: 'FairnessWeight')]
78+
public float $fairnessWeight = 0.0;
79+
4880
/**
4981
* @param int<0, max> $priorityKey
5082
*/
@@ -76,12 +108,50 @@ public function withPriorityKey(int $value): self
76108
return $clone;
77109
}
78110

111+
/**
112+
* FairnessKey is a short string that's used as a key for a fairness
113+
* balancing mechanism. It may correspond to a tenant id, or to a fixed
114+
* string like "high" or "low". The default is the empty string.
115+
*
116+
* @return $this
117+
*/
118+
public function withFairnessKey(string $value): self
119+
{
120+
$clone = clone $this;
121+
$clone->fairnessKey = $value;
122+
return $clone;
123+
}
124+
125+
/**
126+
* FairnessWeight for a task can come from multiple sources for
127+
* flexibility. From highest to lowest precedence:
128+
* 1. Weights for a small set of keys can be overridden in task queue
129+
* configuration with an API.
130+
* 2. It can be attached to the workflow/activity in this field.
131+
* 3. The default weight of 1.0 will be used.
132+
*
133+
* Weight values are clamped to the range [0.001, 1000].
134+
*
135+
* @return $this
136+
*/
137+
public function withFairnessWeight(float $value): self
138+
{
139+
$value < 0.001 or $value > 1000.0 and throw new \InvalidArgumentException(
140+
'FairnessWeight must be in the range [0.001, 1000].',
141+
);
142+
$clone = clone $this;
143+
$clone->fairnessWeight = $value;
144+
return $clone;
145+
}
146+
79147
/**
80148
* @internal for internal use only
81149
*/
82150
public function toProto(): \Temporal\Api\Common\V1\Priority
83151
{
84152
return (new \Temporal\Api\Common\V1\Priority())
85-
->setPriorityKey($this->priorityKey);
153+
->setPriorityKey($this->priorityKey)
154+
->setFairnessKey($this->fairnessKey)
155+
->setFairnessWeight($this->fairnessWeight);
86156
}
87157
}

tests/Acceptance/App/ClassLocator.php

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

55
namespace Temporal\Tests\Acceptance\App;
66

7-
use FilesystemIterator;
8-
use RecursiveDirectoryIterator;
9-
use RecursiveIteratorIterator;
10-
use SplFileInfo;
11-
127
final class ClassLocator
138
{
149
/**
@@ -19,9 +14,9 @@ final class ClassLocator
1914
public static function loadTestCases(string $dir, string $namespace): iterable
2015
{
2116
$dir = \realpath($dir);
22-
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS));
17+
$files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS));
2318

24-
/** @var SplFileInfo $_ */
19+
/** @var \SplFileInfo $_ */
2520
foreach ($files as $path => $_) {
2621
if (!\is_file($path) || !\str_ends_with($path, '.php')) {
2722
continue;
@@ -43,11 +38,11 @@ public static function findTestClasses(string $namespace, string $baseClass = Te
4338
{
4439
yield from \array_filter(
4540
\get_declared_classes(),
46-
static fn(string $class): bool => \str_starts_with($class, $namespace) && \is_a(
47-
$class,
48-
$baseClass,
49-
true
50-
),
41+
static fn(string $class): bool => \str_starts_with($class, "$namespace\\") && \is_a(
42+
$class,
43+
$baseClass,
44+
true,
45+
),
5146
);
5247
}
5348
}

tests/Acceptance/App/RuntimeBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ private static function iterateClasses(string $featuresDir, string $ns): iterabl
9292

9393
yield $feature => \array_filter(
9494
\get_declared_classes(),
95-
static fn(string $class): bool => \str_starts_with($class, $namespace),
95+
static fn(string $class): bool => \str_starts_with($class, "$namespace\\"),
9696
);
9797
}
9898
}

tests/Acceptance/Extra/Workflow/PriorityTest.php

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
class PriorityTest extends TestCase
2020
{
2121
#[Test]
22-
public function instanceInPriority(
22+
public function priorityKey(
2323
WorkflowClientInterface $client,
2424
Feature $feature,
2525
): void {
@@ -34,10 +34,38 @@ public function instanceInPriority(
3434
$client->start($stub, true);
3535
$result = $stub->getResult('array');
3636

37+
self::assertSame(2, $result['activity']['priority_key']);
38+
self::assertSame(1, $result['child']['priority_key']);
39+
self::assertSame(4, $result['workflow']['priority_key']);
40+
}
41+
42+
#[Test]
43+
public function fairness(
44+
WorkflowClientInterface $client,
45+
Feature $feature,
46+
): void {
47+
$stub = $client->newUntypedWorkflowStub(
48+
'Extra_Workflow_Priority',
49+
WorkflowOptions::new()
50+
->withTaskQueue($feature->taskQueue)
51+
->withPriority(
52+
Priority::new()
53+
->withFairnessKey('parent-key')
54+
->withFairnessWeight(2.2),
55+
),
56+
);
3757

38-
self::assertSame([2], $result['activity']);
39-
self::assertSame([1], $result['child']);
40-
self::assertSame([4], $result['workflow']);
58+
/** @see TestWorkflow::handle() */
59+
$client->start($stub, true);
60+
$result = $stub->getResult('array');
61+
62+
63+
self::assertSame('activity-key', $result['activity']['fairness_key']);
64+
self::assertSame(5.4, $result['activity']['fairness_weight']);
65+
self::assertSame('parent-key', $result['workflow']['fairness_key']);
66+
self::assertSame(2.2, $result['workflow']['fairness_weight']);
67+
self::assertSame('child-key', $result['child']['fairness_key']);
68+
self::assertSame(3.3, $result['child']['fairness_weight']);
4169
}
4270
}
4371

@@ -51,7 +79,11 @@ public function handle(bool $runChild = false)
5179
'Extra_Workflow_Priority.handler',
5280
options: Activity\ActivityOptions::new()
5381
->withScheduleToCloseTimeout('10 seconds')
54-
->withPriority(Priority::new(2)),
82+
->withPriority(
83+
Priority::new(2)
84+
->withFairnessKey('activity-key')
85+
->withFairnessWeight(5.4),
86+
),
5587
);
5688

5789
Workflow\ChildWorkflowOptions::new()->priority->priorityKey === Workflow::getInfo()->priority->priorityKey or
@@ -61,14 +93,22 @@ public function handle(bool $runChild = false)
6193
$child = yield Workflow::executeChildWorkflow(
6294
'Extra_Workflow_Priority',
6395
[false],
64-
Workflow\ChildWorkflowOptions::new()->withPriority(Priority::new(1)),
96+
Workflow\ChildWorkflowOptions::new()->withPriority(
97+
Priority::new(1)
98+
->withFairnessKey('child-key')
99+
->withFairnessWeight(3.3),
100+
),
65101
'array',
66102
);
67103
}
68104

69105
return [
70106
'activity' => $activity,
71-
'workflow' => [Workflow::getInfo()->priority->priorityKey],
107+
'workflow' => [
108+
'priority_key' => Workflow::getInfo()->priority->priorityKey,
109+
'fairness_key' => Workflow::getInfo()->priority->fairnessKey,
110+
'fairness_weight' => Workflow::getInfo()->priority->fairnessWeight,
111+
],
72112
'child' => $child['workflow'] ?? null,
73113
];
74114
}
@@ -80,6 +120,10 @@ class TestActivity
80120
#[Activity\ActivityMethod]
81121
public function handler(): array
82122
{
83-
return [Activity::getInfo()->priority->priorityKey];
123+
return [
124+
'priority_key' => Activity::getInfo()->priority->priorityKey,
125+
'fairness_key' => Activity::getInfo()->priority->fairnessKey,
126+
'fairness_weight' => Activity::getInfo()->priority->fairnessWeight,
127+
];
84128
}
85129
}

tests/Fixtures/data/Test_ActivityStubWorkflow.log

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
2021/01/12 15:25:13 DEBUG [{"command":"StartWorkflow","options":{"info":{"WorkflowExecution":{"ID":"abd6d54b-e24e-4790-a7f9-37056176783e","RunID":"3088cdc7-bcae-4f49-940c-8bf30f7f4e18"},"WorkflowType":{"Name":"ActivityStubWorkflow"},"TaskQueueName":"default","WorkflowExecutionTimeout":315360000000000000,"WorkflowRunTimeout":315360000000000000,"WorkflowTaskTimeout":0,"Namespace":"default","Attempt":1,"CronSchedule":"","ContinuedExecutionRunID":"","ParentWorkflowNamespace":"","ParentWorkflowExecution":null,"Memo":null,"SearchAttributes":null,"BinaryChecksum":"8646d54f9f6b22f407d6d22254eea9f5"}},"payloads":"CicKFgoIZW5jb2RpbmcSCmpzb24vcGxhaW4SDSJoZWxsbyB3b3JsZCI="}] {"taskQueue":"default","tickTime":"2021-01-12T15:25:13.3983204Z"}
2-
2021/01/12 15:25:13 DEBUG [{"id":9001,"command":"ExecuteActivity","options":{"name":"SimpleActivity.echo","options":{"TaskQueueName":null,"ScheduleToCloseTimeout":0,"ScheduleToStartTimeout":0,"StartToCloseTimeout":5000000000,"HeartbeatTimeout":0,"WaitForCancellation":false,"ActivityID":"","RetryPolicy":null,"Priority":{"priority_key":0},"Summary":""}},"payloads":"CicKFgoIZW5jb2RpbmcSCmpzb24vcGxhaW4SDSJoZWxsbyB3b3JsZCI=","header":""},{"payloads":"ChkKFwoIZW5jb2RpbmcSC2JpbmFyeS9udWxs"}] {"receive": true}
2+
2021/01/12 15:25:13 DEBUG [{"id":9001,"command":"ExecuteActivity","options":{"name":"SimpleActivity.echo","options":{"TaskQueueName":null,"ScheduleToCloseTimeout":0,"ScheduleToStartTimeout":0,"StartToCloseTimeout":5000000000,"HeartbeatTimeout":0,"WaitForCancellation":false,"ActivityID":"","RetryPolicy":null,"Priority":{"priority_key":0,"fairness_key":"","fairness_weight":0},"Summary":""}},"payloads":"CicKFgoIZW5jb2RpbmcSCmpzb24vcGxhaW4SDSJoZWxsbyB3b3JsZCI=","header":""},{"payloads":"ChkKFwoIZW5jb2RpbmcSC2JpbmFyeS9udWxs"}] {"receive": true}
33
2021/01/12 15:25:13 DEBUG [{"id":9001,"payloads":"CicKFgoIZW5jb2RpbmcSCmpzb24vcGxhaW4SDSJIRUxMTyBXT1JMRCI="}] {"taskQueue":"default","tickTime":"2021-01-12T15:25:13.4849445Z"}
4-
2021/01/12 15:25:13 DEBUG [{"id":9002,"command":"ExecuteActivity","options":{"name":"SimpleActivity.echo","options":{"TaskQueueName":null,"ScheduleToCloseTimeout":0,"ScheduleToStartTimeout":0,"StartToCloseTimeout":1000000000,"HeartbeatTimeout":0,"WaitForCancellation":false,"ActivityID":"","RetryPolicy":null,"Priority":{"priority_key":0},"Summary":""}},"payloads":"CiMKFgoIZW5jb2RpbmcSCmpzb24vcGxhaW4SCSJ1bnR5cGVkIg==","header":""}] {"receive": true}
4+
2021/01/12 15:25:13 DEBUG [{"id":9002,"command":"ExecuteActivity","options":{"name":"SimpleActivity.echo","options":{"TaskQueueName":null,"ScheduleToCloseTimeout":0,"ScheduleToStartTimeout":0,"StartToCloseTimeout":1000000000,"HeartbeatTimeout":0,"WaitForCancellation":false,"ActivityID":"","RetryPolicy":null,"Priority":{"priority_key":0,"fairness_key":"","fairness_weight":0},"Summary":""}},"payloads":"CiMKFgoIZW5jb2RpbmcSCmpzb24vcGxhaW4SCSJ1bnR5cGVkIg==","header":""}] {"receive": true}
55
2021/01/12 15:25:13 DEBUG [{"id":9002,"payloads":"CiMKFgoIZW5jb2RpbmcSCmpzb24vcGxhaW4SCSJVTlRZUEVEIg=="}] {"taskQueue":"default","tickTime":"2021-01-12T15:25:13.5143426Z"}
66
2021/01/12 15:25:13 DEBUG [{"id":9003,"command":"CompleteWorkflow","options":{},"payloads":"CkkKFgoIZW5jb2RpbmcSCmpzb24vcGxhaW4SL1siSEVMTE8gV09STEQiLCJpbnZhbGlkIG1ldGhvZCBjYWxsIiwiVU5UWVBFRCJd","header":""}] {"receive": true}
77
2021/01/12 15:25:13 DEBUG [{"id":9003,"payloads":"CiUKFgoIZW5jb2RpbmcSCmpzb24vcGxhaW4SCyJjb21wbGV0ZWQi"},{"command":"DestroyWorkflow","options":{"runId":"3088cdc7-bcae-4f49-940c-8bf30f7f4e18"}}] {"taskQueue":"default","tickTime":"2021-01-12T15:25:13.5143426Z","replay":true}

0 commit comments

Comments
 (0)