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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions app/Support/RuntimePayloadCompletionLease.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ public function __construct(

public function expiresAt(string $namespace, RuntimePayloadCompletionContext $context): CarbonImmutable
{
$worker = WorkerRegistration::query()->where('namespace', $namespace)
->where('worker_id', $context->leaseOwner)->first();
if (! $worker instanceof WorkerRegistration || ! WorkerPollFence::isFresh($worker)) {
throw self::rejected();
// Activities heartbeat their own leases while application code runs;
// their worker roster can age or drain without revoking that authority.
if ($context->kind !== 'activity') {
$worker = WorkerRegistration::query()->where('namespace', $namespace)
->where('worker_id', $context->leaseOwner)->first();
if (! $worker instanceof WorkerRegistration || ! WorkerPollFence::isFresh($worker)) {
throw self::rejected();
}
}

$expires = match ($context->kind) {
Expand Down
9 changes: 6 additions & 3 deletions app/Support/RuntimePayloadCompletionUploads.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,12 @@ public function upload(
$scope = $context->scope($namespace);
$this->locks->transaction($scope, function () use ($namespace, $context, $sha256, $data, $expiresAt, $scope): void {
$this->requireWritable();
$budget = RuntimePayloadCompletionBudget::query()->lockForUpdate()->find($scope)
?? new RuntimePayloadCompletionBudget(['id' => $scope, 'namespace' => $namespace,
'context' => $context->toArray(), 'slots' => [], 'objects' => []]);
// Insert before a locking read: missing-row locks on different IDs
// can share an InnoDB gap and deadlock concurrent first reservations.
$budget = RuntimePayloadCompletionBudget::query()->lockForUpdate()->createOrFirst(
['id' => $scope], ['namespace' => $namespace, 'context' => $context->toArray(),
'slots' => [], 'objects' => [], 'expires_at' => $expiresAt->addSeconds($this->retryRetention())],
);
$slots = $budget->slots;
$objects = $budget->objects;
$slot = $context->slotIdentity();
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
"extra": {
"durable-workflow": {
"product-train": "2.3.5"
"product-train": "2.3.6"
},
"laravel": {
"dont-discover": []
Expand Down
2 changes: 1 addition & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions docker-compose.dedicated-matching.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ name: durable-workflow-server
# daemon reports `shape: dedicated`.

# Generated by scripts/ci/sync-source-release.mjs. Do not edit the fallback.
x-server-image: &server-image ${DW_SERVER_IMAGE:-durableworkflow/server:${DW_SERVER_TAG:-2.3.5}}
x-server-image: &server-image ${DW_SERVER_IMAGE:-durableworkflow/server:${DW_SERVER_TAG:-2.3.6}}

x-server-environment: &server-environment
APP_NAME: "Durable Workflow Server"
APP_ENV: ${APP_ENV:-local}
DW_SERVER_KEY: ${DW_SERVER_KEY:-}
APP_VERSION: ${APP_VERSION:-${DW_SERVER_TAG:-2.3.5}}
APP_VERSION: ${APP_VERSION:-${DW_SERVER_TAG:-2.3.6}}
APP_DEBUG: ${APP_DEBUG:-false}
DB_CONNECTION: mysql
DB_HOST: mysql
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.memo-rolling.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ services:
command: ["server-bootstrap"]
environment:
<<: *runtime-environment
APP_VERSION: ${APP_VERSION:-2.3.5}
APP_VERSION: ${APP_VERSION:-2.3.6}

successor:
image: ${DW_MEMO_SUCCESSOR_IMAGE:-durable-workflow/server-memo-rolling:local}
ports: !override []
environment:
<<: *runtime-environment
APP_VERSION: ${APP_VERSION:-2.3.5}
APP_VERSION: ${APP_VERSION:-2.3.6}
DW_SERVER_ID: memo-successor
DW_SERVER_TOPOLOGY_SHAPE: standalone_server
DW_SERVER_PROCESS_CLASS: server_http_node
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.published.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
name: durable-workflow-server

# Generated by scripts/ci/sync-source-release.mjs. Do not edit the fallback.
x-server-image: &server-image ${DW_SERVER_IMAGE:-durableworkflow/server:${DW_SERVER_TAG:-2.3.5}}
x-server-image: &server-image ${DW_SERVER_IMAGE:-durableworkflow/server:${DW_SERVER_TAG:-2.3.6}}

x-server-environment: &server-environment
APP_NAME: "Durable Workflow Server"
APP_ENV: ${APP_ENV:-local}
DW_SERVER_KEY: ${DW_SERVER_KEY:-}
APP_VERSION: ${APP_VERSION:-${DW_SERVER_TAG:-2.3.5}}
APP_VERSION: ${APP_VERSION:-${DW_SERVER_TAG:-2.3.6}}
APP_DEBUG: ${APP_DEBUG:-false}
LOG_CHANNEL: ${LOG_CHANNEL:-stderr}
LOG_LEVEL: ${LOG_LEVEL:-info}
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.small-cluster.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ x-server-build: &server-build
x-server-environment: &server-environment
APP_NAME: "Durable Workflow Server"
APP_ENV: testing
APP_VERSION: ${APP_VERSION:-2.3.5}
APP_VERSION: ${APP_VERSION:-2.3.6}
APP_DEBUG: "false"
DW_SERVER_KEY: ${DW_SERVER_KEY:-base64:5Zt4nUhlCm3DD0nLXZJQdHiwPfb56yGo9gNV/g3jYbY=}
DB_CONNECTION: ${DW_SMALL_CLUSTER_DB:-mysql}
Expand Down
8 changes: 4 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ services:
DW_SERVER_KEY: "${DW_SERVER_KEY:-}"
DW_SERVER_TOPOLOGY_SHAPE: standalone_server
DW_SERVER_PROCESS_CLASS: server_http_node
APP_VERSION: "${APP_VERSION:-2.3.5}"
APP_VERSION: "${APP_VERSION:-2.3.6}"
APP_DEBUG: "false"
DB_CONNECTION: mysql
DB_HOST: mysql
Expand Down Expand Up @@ -62,7 +62,7 @@ services:
APP_NAME: "Durable Workflow Server"
APP_ENV: local
DW_SERVER_KEY: "${DW_SERVER_KEY:-}"
APP_VERSION: "${APP_VERSION:-2.3.5}"
APP_VERSION: "${APP_VERSION:-2.3.6}"
APP_DEBUG: "false"
DB_CONNECTION: mysql
DB_HOST: mysql
Expand Down Expand Up @@ -123,7 +123,7 @@ services:
DW_SERVER_KEY: "${DW_SERVER_KEY:-}"
DW_SERVER_TOPOLOGY_SHAPE: standalone_server
DW_SERVER_PROCESS_CLASS: worker_node
APP_VERSION: "${APP_VERSION:-2.3.5}"
APP_VERSION: "${APP_VERSION:-2.3.6}"
DB_CONNECTION: mysql
DB_HOST: mysql
DB_PORT: 3306
Expand Down Expand Up @@ -176,7 +176,7 @@ services:
DW_SERVER_KEY: "${DW_SERVER_KEY:-}"
DW_SERVER_TOPOLOGY_SHAPE: standalone_server
DW_SERVER_PROCESS_CLASS: scheduler_node
APP_VERSION: "${APP_VERSION:-2.3.5}"
APP_VERSION: "${APP_VERSION:-2.3.6}"
DB_CONNECTION: mysql
DB_HOST: mysql
DB_PORT: 3306
Expand Down
10 changes: 5 additions & 5 deletions k8s/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ The checked-in manifests are synchronized with the repository's stable source
release and pin its Docker Hub tag:

```text
durableworkflow/server:2.3.5
durableworkflow/server:2.3.6
```

Before production use, patch every workload image to the exact published tag or
digest you intend to run:

```bash
kubectl set image -n durable-workflow deploy/durable-workflow-server \
server=durableworkflow/server:2.3.5
server=durableworkflow/server:2.3.6
kubectl set image -n durable-workflow deploy/durable-workflow-worker \
worker=durableworkflow/server:2.3.5
worker=durableworkflow/server:2.3.6
kubectl set image -n durable-workflow cronjob/durable-workflow-scheduler \
scheduler=durableworkflow/server:2.3.5
scheduler=durableworkflow/server:2.3.6
```

GitHub Container Registry publishes the same release line at
`ghcr.io/durable-workflow/server:2.3.5`. Digest pinning is preferred for strict
`ghcr.io/durable-workflow/server:2.3.6`. Digest pinning is preferred for strict
change control.

The manifests expect you to provide:
Expand Down
6 changes: 3 additions & 3 deletions k8s/helm/durable-workflow/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ type: application
# The chart's own semver version. Bumped on every chart release; treated as
# independent of the server image version (appVersion). Breaking-change rules
# for this version live in docs/helm-upgrading.md alongside the chart.
version: 0.1.84
version: 0.1.85
# The immutable Durable Workflow Server identity this chart release packages.
# The onboarding default in values.yaml and appVersion are generated from the
# checked-in source release record.
appVersion: "2.3.5"
appVersion: "2.3.6"
kubeVersion: ">=1.27.0-0"
home: https://durable-workflow.github.io/docs/2.0/deployment
sources:
Expand All @@ -30,7 +30,7 @@ annotations:
# exact commit that most recently changed the packaged chart.
org.opencontainers.image.source: https://github.com/durable-workflow/server
dev.durable-workflow.source-revision: "unreleased"
dev.durable-workflow.image-reference: "docker.io/durableworkflow/server:2.3.5"
dev.durable-workflow.image-reference: "docker.io/durableworkflow/server:2.3.6"
artifacthub.io/license: MIT
artifacthub.io/category: integration-delivery
# Free-form changelog for the current chart release shown by Artifact Hub.
Expand Down
2 changes: 1 addition & 1 deletion k8s/helm/durable-workflow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ helm install durable-workflow ./k8s/helm/durable-workflow \

```yaml
image:
tag: "2.3.5"
tag: "2.3.6"
# Pin a digest in production:
# digest: "sha256:abc123..."
# memoPayloadStorage: "raw-json-v1" # Required for a digest or custom image.
Expand Down
2 changes: 1 addition & 1 deletion k8s/helm/durable-workflow/ci/existing-secrets-values.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# CI fixture: GitOps / externally-managed-secret path. The chart consumes
# existing Secrets and renders no Secret resources of its own.
image:
tag: "2.3.5"
tag: "2.3.6"

externalDatabase:
connection: pgsql
Expand Down
2 changes: 1 addition & 1 deletion k8s/helm/durable-workflow/ci/ingress-and-hpa-values.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CI fixture: ingress + autoscaling enabled. Exercises optional templates.
image:
tag: "2.3.5"
tag: "2.3.6"

externalDatabase:
connection: mysql
Expand Down
2 changes: 1 addition & 1 deletion k8s/helm/durable-workflow/ci/inline-secrets-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# chart's render path is exercised end-to-end. Real deployments should use
# existingSecret instead.
image:
tag: "2.3.5"
tag: "2.3.6"

externalDatabase:
connection: mysql
Expand Down
2 changes: 1 addition & 1 deletion k8s/helm/durable-workflow/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ resolved by an explicit capability declaration or an existing workload marker.
{{- define "durable-workflow.memoPayloadStorageForImage" -}}
{{- $image := toString . -}}
{{- $normalized := regexReplaceAll "^index\\.docker\\.io/" $image "docker.io/" -}}
{{- if eq $normalized "docker.io/durableworkflow/server:2.3.5" -}}
{{- if eq $normalized "docker.io/durableworkflow/server:2.3.6" -}}
dual-v1
{{- else if regexMatch "^docker\\.io/durableworkflow/server:2\\.0\\.0-rc\\.[0-9]+$" $normalized -}}
{{- $releaseCandidate := atoi (regexFind "[0-9]+$" $normalized) -}}
Expand Down
2 changes: 1 addition & 1 deletion k8s/helm/durable-workflow/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ image:
registry: docker.io
repository: durableworkflow/server
# Generated by scripts/ci/sync-source-release.mjs. Do not edit this default.
tag: "2.3.5"
tag: "2.3.6"
# Optional digest pin. When set, takes precedence over tag for change control.
# Example: "sha256:abc123..."
digest: ""
Expand Down
2 changes: 1 addition & 1 deletion k8s/helm/examples/values-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# shape in production.

image:
tag: "2.3.5"
tag: "2.3.6"

externalDatabase:
connection: mysql
Expand Down
2 changes: 1 addition & 1 deletion k8s/helm/examples/values-external-secrets-operator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# concern.

image:
tag: "2.3.5"
tag: "2.3.6"

externalDatabase:
connection: pgsql
Expand Down
2 changes: 1 addition & 1 deletion k8s/helm/examples/values-production-existing-secrets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ image:
repository: durable-workflow/server
# Pin a digest in production for change-control auditability.
digest: "" # e.g. "sha256:abc123..."
tag: "2.3.5"
tag: "2.3.6"

externalDatabase:
connection: pgsql
Expand Down
2 changes: 1 addition & 1 deletion k8s/migration-job.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: durableworkflow/server:2.3.5
image: durableworkflow/server:2.3.6
command: ["server-entrypoint"]
args: ["server-bootstrap"]
envFrom:
Expand Down
2 changes: 1 addition & 1 deletion k8s/scheduler-cronjob.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ spec:
restartPolicy: Never
containers:
- name: scheduler
image: durableworkflow/server:2.3.5
image: durableworkflow/server:2.3.6
command: ["server-entrypoint"]
args: ["sh", "-c", "php artisan schedule:evaluate --limit=100 --json; php artisan activity:timeout-enforce --limit=100; if php artisan list --raw | grep -q '^external-payloads:cleanup '; then php artisan external-payloads:cleanup --limit=100 --json; fi; php artisan history:prune --limit=100"]
envFrom:
Expand Down
2 changes: 1 addition & 1 deletion k8s/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ metadata:
app.kubernetes.io/name: durable-workflow
data:
APP_NAME: "Durable Workflow Server"
APP_VERSION: "2.3.5"
APP_VERSION: "2.3.6"
APP_ENV: production
APP_DEBUG: "false"
DB_CONNECTION: mysql
Expand Down
2 changes: 1 addition & 1 deletion k8s/server-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ spec:
spec:
containers:
- name: server
image: durableworkflow/server:2.3.5
image: durableworkflow/server:2.3.6
ports:
- containerPort: 8080
name: http
Expand Down
2 changes: 1 addition & 1 deletion k8s/worker-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ spec:
spec:
containers:
- name: worker
image: durableworkflow/server:2.3.5
image: durableworkflow/server:2.3.6
command: ["server-entrypoint"]
args: ["php", "artisan", "queue:work", "--sleep=1", "--tries=3", "--max-time=3600"]
envFrom:
Expand Down
4 changes: 2 additions & 2 deletions resources/release/source-release.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"schema": "durable-workflow.server.source-release/v1",
"server": {
"version": "2.3.5"
"version": "2.3.6"
},
"helm_chart": {
"version": "0.1.84"
"version": "0.1.85"
}
}
2 changes: 1 addition & 1 deletion scripts/k8s-kind-smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ cluster="${K8S_SMOKE_CLUSTER:-durable-workflow-server-smoke}"
image="${K8S_SMOKE_IMAGE:-durableworkflow/server:k8s-smoke}"
# Generated by scripts/ci/sync-source-release.mjs so the smoke replaces the
# same default shipped by the public manifests.
manifest_image="durableworkflow/server:2.3.5"
manifest_image="durableworkflow/server:2.3.6"
kind_node_image="${K8S_SMOKE_KIND_NODE_IMAGE:-kindest/node:v1.29.4}"
artifact_dir="${K8S_SMOKE_ARTIFACT_DIR:-/tmp/durable-workflow-k8s-kind-smoke-artifacts}"
rendered_dir="${artifact_dir}/rendered-manifests"
Expand Down
38 changes: 37 additions & 1 deletion tests/Feature/RuntimePayloadCompletionProcessTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,42 @@ public function test_concurrent_uploads_and_cold_retries_preserve_one_bounded_le
self::assertSame(409, $this->runProbe('upload', $kind, $loser, '1')['status']);
}

public static function nativeDatabases(): array
{
return [['mysql'], ['pgsql']];
}

#[DataProvider('nativeDatabases')]
public function test_independent_activity_budgets_can_be_created_concurrently(string $driver): void
{
$this->initialize($driver);
$this->runProbe('init-independent', 'activity');
$uploads = [];
foreach (range(0, 7) as $member) {
$uploads[$member] = $this->probe('upload-independent', 'activity', sprintf('%05d', $member),
(string) $member, 'member-'.$member);
$uploads[$member]->start();
}
$deadline = microtime(true) + 10;
foreach ($uploads as $member => $process) {
while (! is_file($this->directory.'/member-'.$member.'.ready') && $process->isRunning() && microtime(true) < $deadline) {
usleep(10000);
}
self::assertFileExists($this->directory.'/member-'.$member.'.ready', $process->getErrorOutput());
}
touch($this->directory.'/go');
$responses = [];
foreach ($uploads as $member => $process) {
$responses[$member] = $this->completedResult($process);
self::assertSame(201, $responses[$member]['status'], json_encode($responses[$member]));
}
foreach ($responses as $member => $response) {
self::assertSame($response, $this->runProbe('upload-independent', 'activity', sprintf('%05d', $member), (string) $member));
}
self::assertSame(['budgets' => 8, 'slots' => 8, 'objects' => 8, 'rows' => 8],
$this->runProbe('status-independent', 'activity'));
}

protected function tearDown(): void
{
foreach ($this->processes as $process) {
Expand Down Expand Up @@ -122,7 +158,7 @@ private function probe(string $action, string $kind, string $variant = 'alpha',
{
$process = new Process([PHP_BINARY, 'tests/Support/RuntimePayloadCompletionProcess.php', $action,
$this->directory, $kind, $variant, $slot, $barrier], dirname(__DIR__, 2), $this->environment,
timeout: $action === 'init' ? 120 : 30);
timeout: str_starts_with($action, 'init') ? 120 : 30);
$this->processes[] = $process;

return $process;
Expand Down
Loading