Skip to content

Commit dd51ea9

Browse files
authored
Rust Server train: command batches, named namespaces, completion checks, external inputs, operator surface, leases, deployments (#696)
Rust Server (#325) oct23 train. See the PR description for its contents and evidence. Every Rust suite passed on 69485a2, and the head adds only a PHP-only main merge (#701).
1 parent c0e3f61 commit dd51ea9

138 files changed

Lines changed: 12890 additions & 1049 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/server-parity.yml‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ on:
3636
- tests/Unit/ServerParityOperatorMetricsContractTest.mjs
3737
- tests/Unit/ServerParityActivityLeaseRenewalContractTest.mjs
3838
- tests/Unit/ServerParityServiceCatalogContractTest.mjs
39+
- tests/Unit/ServerParitySystemMetricsContractTest.mjs
40+
- tests/Unit/ServerParityPrometheusMetricsContractTest.mjs
41+
- tests/Unit/ServerParityPayloadCleanupContractTest.mjs
42+
- tests/Unit/ServerParityRepairPassContractTest.mjs
3943
- tests/Unit/ServerParityNamespaceDeleteContractTest.mjs
4044
- tests/Unit/ServerParityWaitingHistoryContractTest.mjs
4145
- tests/Unit/ServerParityTaskRetryContractTest.mjs
@@ -86,6 +90,10 @@ on:
8690
- tests/Unit/ServerParityOperatorMetricsContractTest.mjs
8791
- tests/Unit/ServerParityActivityLeaseRenewalContractTest.mjs
8892
- tests/Unit/ServerParityServiceCatalogContractTest.mjs
93+
- tests/Unit/ServerParitySystemMetricsContractTest.mjs
94+
- tests/Unit/ServerParityPrometheusMetricsContractTest.mjs
95+
- tests/Unit/ServerParityPayloadCleanupContractTest.mjs
96+
- tests/Unit/ServerParityRepairPassContractTest.mjs
8997
- tests/Unit/ServerParityNamespaceDeleteContractTest.mjs
9098
- tests/Unit/ServerParityWaitingHistoryContractTest.mjs
9199
- tests/Unit/ServerParityTaskRetryContractTest.mjs
@@ -443,8 +451,8 @@ jobs:
443451
-e DB_USERNAME=root -e DB_PASSWORD=parity-disposable-password \
444452
-e CACHE_STORE=file -e QUEUE_CONNECTION=database -e DW_AUTH_TOKEN=parity-test-token \
445453
"$layout" artisan server:bootstrap --force > parity-evidence/published-bootstrap.txt
446-
# Compiling the library tests peaks near 2.5 GB; a 2 GB limit OOM-killed rustc (exit 101).
447-
docker run --rm --user="$(id -u):$(id -g)" --network "$RESOURCE_SCOPE" --cpus=2 --memory=4g --memory-swap=4g \
454+
# Compiling the library tests with two jobs peaks near 4.2 GB; a 4 GB limit OOM-killed rustc (exit 101).
455+
docker run --rm --user="$(id -u):$(id -g)" --network "$RESOURCE_SCOPE" --cpus=2 --memory=6g --memory-swap=6g \
448456
-e CARGO_HOME=/cargo -e CARGO_TARGET_DIR=/target -e CARGO_BUILD_JOBS=2 \
449457
-e DW_TEST_MYSQL_URL=mysql://root:parity-disposable-password@parity-mysql:3306/php_ref \
450458
-e DW_EXECUTION_MYSQL_URL=mysql://root:parity-disposable-password@parity-mysql:3306/php_ref \
@@ -955,8 +963,8 @@ jobs:
955963
-e DB_USERNAME=parity -e DB_PASSWORD=parity-disposable-password \
956964
-e CACHE_STORE=file -e QUEUE_CONNECTION=database -e DW_AUTH_TOKEN=parity-test-token \
957965
"$layout" artisan server:bootstrap --force > parity-evidence/published-bootstrap.txt
958-
# Compiling the library tests peaks near 2.5 GB; a 2 GB limit OOM-killed rustc (exit 101).
959-
docker run --rm --user="$(id -u):$(id -g)" --network "$RESOURCE_SCOPE" --cpus=2 --memory=4g --memory-swap=4g \
966+
# Compiling the library tests with two jobs peaks near 4.2 GB; a 4 GB limit OOM-killed rustc (exit 101).
967+
docker run --rm --user="$(id -u):$(id -g)" --network "$RESOURCE_SCOPE" --cpus=2 --memory=6g --memory-swap=6g \
960968
-e CARGO_HOME=/cargo -e CARGO_TARGET_DIR=/target -e CARGO_BUILD_JOBS=2 \
961969
-e DW_TEST_POSTGRES_URL=postgres://parity:parity-disposable-password@parity-pg:5432/php_ref \
962970
-e DW_EXECUTION_POSTGRES_URL=postgres://parity:parity-disposable-password@parity-pg:5432/php_ref \
@@ -1470,8 +1478,8 @@ jobs:
14701478
}
14711479
trap rust_test_failures ERR
14721480
mkdir -p cargo-home rust-target parity-evidence
1473-
# Compiling the library tests peaks near 2.5 GB; a 2 GB limit OOM-killed rustc (exit 101).
1474-
docker run --rm --user="$(id -u):$(id -g)" --cpus=2 --memory=4g --memory-swap=4g \
1481+
# Compiling the library tests with two jobs peaks near 4.2 GB; a 4 GB limit OOM-killed rustc (exit 101).
1482+
docker run --rm --user="$(id -u):$(id -g)" --cpus=2 --memory=6g --memory-swap=6g \
14751483
-e CARGO_HOME=/cargo -e CARGO_TARGET_DIR=/target -e CARGO_BUILD_JOBS=2 \
14761484
-e DW_UPGRADE_TEST_WRITABLE=/evidence -e DW_UPGRADE_TEST_READONLY=/source/parity-evidence \
14771485
-v "$PWD:/source:ro" -v "$PWD/cargo-home:/cargo" -v "$PWD/rust-target:/target" \
@@ -1510,7 +1518,7 @@ jobs:
15101518
-e COMPOSER_HOME=/tmp/composer -v "$PWD:/source" -w /source "$image" \
15111519
install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress
15121520
docker run --rm --user=1000:1000 --entrypoint node -v "$PWD:/source:ro" -w /source \
1513-
"$image" --test tests/Unit/ServerParityRunnerTest.mjs tests/Unit/ServerParityCooperativeContractTest.mjs tests/Unit/ServerParityChildCancellationContractTest.mjs tests/Unit/ServerParityScheduleContractTest.mjs tests/Unit/ServerParityVisibilityContractTest.mjs tests/Unit/ServerParityAdmissionContractTest.mjs tests/Unit/ServerParityRoleTokenContractTest.mjs tests/Unit/ServerParityWorkerDeregistrationContractTest.mjs tests/Unit/ServerParitySearchAttributeContractTest.mjs tests/Unit/ServerParityWorkerListingContractTest.mjs tests/Unit/ServerParityWorkerReplacementContractTest.mjs tests/Unit/ServerParityPatchDeploymentContractTest.mjs tests/Unit/ServerParityWaitingHistoryContractTest.mjs tests/Unit/ServerParityTaskRetryContractTest.mjs tests/Unit/ServerParityPublishedPatchContractTest.mjs tests/Unit/ServerParityPublishedPatchTransportTest.mjs tests/Unit/ServerParityRecordFileTest.mjs tests/Unit/ServerParityLateWorkflowCompletionContractTest.mjs tests/Unit/ServerParityStorageTestContractTest.mjs tests/Unit/ServerParitySchedulePoliciesContractTest.mjs tests/Unit/ServerParitySignalsBeforeWorkerContractTest.mjs tests/Unit/ServerParityScheduleCatalogContractTest.mjs tests/Unit/ServerParityOperatorMetricsContractTest.mjs tests/Unit/ServerParityActivityLeaseRenewalContractTest.mjs tests/Unit/ServerParityServiceCatalogContractTest.mjs tests/Unit/ServerParityNamespaceDeleteContractTest.mjs > parity-evidence/comparator-tests.txt
1521+
"$image" --test tests/Unit/ServerParityRunnerTest.mjs tests/Unit/ServerParityCooperativeContractTest.mjs tests/Unit/ServerParityChildCancellationContractTest.mjs tests/Unit/ServerParityScheduleContractTest.mjs tests/Unit/ServerParityVisibilityContractTest.mjs tests/Unit/ServerParityAdmissionContractTest.mjs tests/Unit/ServerParityRoleTokenContractTest.mjs tests/Unit/ServerParityWorkerDeregistrationContractTest.mjs tests/Unit/ServerParitySearchAttributeContractTest.mjs tests/Unit/ServerParityWorkerListingContractTest.mjs tests/Unit/ServerParityWorkerReplacementContractTest.mjs tests/Unit/ServerParityPatchDeploymentContractTest.mjs tests/Unit/ServerParityWaitingHistoryContractTest.mjs tests/Unit/ServerParityTaskRetryContractTest.mjs tests/Unit/ServerParityPublishedPatchContractTest.mjs tests/Unit/ServerParityPublishedPatchTransportTest.mjs tests/Unit/ServerParityRecordFileTest.mjs tests/Unit/ServerParityLateWorkflowCompletionContractTest.mjs tests/Unit/ServerParityStorageTestContractTest.mjs tests/Unit/ServerParitySchedulePoliciesContractTest.mjs tests/Unit/ServerParitySignalsBeforeWorkerContractTest.mjs tests/Unit/ServerParityScheduleCatalogContractTest.mjs tests/Unit/ServerParityOperatorMetricsContractTest.mjs tests/Unit/ServerParityActivityLeaseRenewalContractTest.mjs tests/Unit/ServerParityServiceCatalogContractTest.mjs tests/Unit/ServerParitySystemMetricsContractTest.mjs tests/Unit/ServerParityPrometheusMetricsContractTest.mjs tests/Unit/ServerParityPayloadCleanupContractTest.mjs tests/Unit/ServerParityRepairPassContractTest.mjs tests/Unit/ServerParityNamespaceDeleteContractTest.mjs > parity-evidence/comparator-tests.txt
15141522
docker run --rm --user=1000:1000 --cpus=1 --memory=256m --memory-swap=256m \
15151523
--network=none --entrypoint node -v "$PWD:/source:ro" -w /source \
15161524
"$image" --test tests/Unit/ServerParityCodecDeadlineLoadTest.mjs > parity-evidence/codec-load-tests.txt
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# October 22, external payload cleanup
2+
3+
- **Source.** Server 2.11.1 `SystemController::externalPayloadCleanupStatus`
4+
and `externalPayloadCleanupPass`, `RuntimeExternalPayloadCleanup::runPass`,
5+
`RuntimeExternalPayloadCleanupMetrics::snapshot`,
6+
`RuntimePayloadCompletionUploads::cleanup`, `RuntimePayloadCompletionLease::
7+
mayResume` and `ExternalPayloadBackupHold::deleting`.
8+
- **Served.** Native answers admin-only
9+
`GET /api/system/external-payload-cleanup` and
10+
`POST /api/system/external-payload-cleanup/pass` in the default or a named
11+
namespace.
12+
- The status is the cleanup section native's operator metrics already
13+
report: the expired backlog, batch bounds, totals and the last pass.
14+
- The pass takes `limit` (`nullable|integer|min:1|max:1000`, default 100).
15+
It first visits the namespace's expired completion-upload allowances: one
16+
whose activity can still complete (its task is leased to the same owner
17+
and its attempt can continue) gets another abandoned-upload period, and
18+
any other is dropped. It then reclaims the namespace's expired, unclaimed
19+
references, oldest first, each under its object lock. A reference that
20+
shares its object with another loses only its row; a sole reference also
21+
deletes its object. The report lists processed, deleted, preserved,
22+
skipped and blocked references, the remaining backlog and scan pressure,
23+
and answers 207 when anything was blocked. Each pass is added to the
24+
namespace's totals, with whole-second times as Eloquent stores them.
25+
- **Decisions.**
26+
- Native stores objects only with the local driver. A reference without an
27+
enabled local policy, or whose object lies outside the current local root,
28+
is blocked rather than deleted. PHP would delete through its configured
29+
remote driver or old root.
30+
- Native never takes an online-backup hold. A hold row left active blocks
31+
deletion as in PHP; a missing row means no hold, where PHP would report a
32+
storage failure because its migration seeds the row.
33+
- Native's completion allowances hold activity contexts only. An allowance
34+
whose context native cannot read cannot resume and is dropped once
35+
expired; PHP would fail the pass on a context it cannot parse.
36+
- **Fixture.** `payload-cleanup` checks a fresh namespace's status, a pass,
37+
the recorded totals, limit validation (0, 1001, text and a fraction answer
38+
422; null and numeric text are accepted) and the final count of five
39+
passes. Deletion needs expired rows and stored objects, which an HTTP probe
40+
cannot make within the abandoned-upload period, so native tests cover it on
41+
SQLite, PostgreSQL and MySQL: shared, live and retained references, the
42+
limit and its backlog, an active backup hold, a disabled policy, and an
43+
allowance renewed while its activity runs and dropped after it completes.
44+
- **Evidence.** The new tests pass on SQLite, PostgreSQL and MySQL, and the
45+
native suite with `--all-targets` passes on SQLite (652 tests). A local
46+
copy of the SQLite parity step passed all 54 fixtures on embedded PHP and
47+
native, including `payload-cleanup`. The published PHP fixture answers
48+
were checked against a fresh 2.11.1 server. Its record differed only on
49+
`namespace-delete`, a retryable 503 `backend_lock_pressure` that its probe
50+
did not retry; that probe and the storage diagnostic now retry it.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# October 22, Prometheus metrics summary
2+
3+
- **Source.** Server 2.11.1 `SystemController::prometheusMetrics` and
4+
`PrometheusMetricsSummary::snapshot`, with `BoundedMetricPolicy` and the
5+
declared dimensions in `config/dw-bounded-growth.php`.
6+
- **Served.** Native answers admin-only `GET /api/system/prometheus-metrics`
7+
in the default or a named namespace:
8+
- workflow series by task queue and workflow type: started, completed,
9+
failed, cancelled and terminated totals, running runs and a latency
10+
histogram from run summaries;
11+
- activity series by task queue, workflow type and activity type, with the
12+
same counters and a latency histogram from activity executions;
13+
- task-queue runtime state: ready, due, delayed, leased and expired tasks,
14+
tasks added and dispatched in the last minute, and fresh worker pollers,
15+
slot capacity and available slots.
16+
17+
Each family keeps the first 100 series in PHP's order. Native reads at
18+
most 101 candidate label sets, then aggregates only the series up to the
19+
last candidate. The disclosure reports observed, reported and suppressed
20+
series and the suppressed starts, with `lower_bound` precision once the
21+
limit is reached. Native refuses the series-limit settings, so the limit is
22+
always PHP's default of 100.
23+
- **Times.** Task and worker comparisons bind whole seconds, as Laravel does
24+
and as native's operator metrics already do. A task made available within
25+
the current second still counts as delayed. Workers are fresh within
26+
`DW_WORKER_STALE_AFTER_SECONDS`, by default `max(3 × heartbeat, 30)`.
27+
- **Decision: cumulative latency buckets.** Published PHP binds each bucket
28+
bound as text. SQLite orders every number before every text value, so PHP
29+
on SQLite counts every duration in every bucket
30+
([server#683](https://github.com/durable-workflow/server/issues/683)).
31+
Native compares durations with numeric bounds and reports the cumulative
32+
histogram on every backend. The fixture accepts PHP's SQLite shape as a
33+
recorded reference defect; native's tests pin the cumulative buckets with
34+
fixed durations on SQLite, PostgreSQL and MySQL.
35+
- **Correction: undispatched activity tasks.** PHP's worker completion
36+
publishes only the timer tasks it creates. An activity task scheduled by a
37+
workflow task therefore keeps null `last_dispatched_at` and
38+
`last_dispatch_attempt_at` until a poller claims it, and its queue's
39+
dispatched-in-the-last-minute counter stays 0. Native stamped it
40+
dispatched. It now leaves those columns null for that path, as PHP does.
41+
Other task paths keep their stamps.
42+
- **Fixture.** `prometheus-metrics` reads a fresh namespace, then:
43+
- registers a worker with four workflow and three activity slots on the
44+
first queue;
45+
- completes a run there after one activity;
46+
- terminates a second run there;
47+
- starts a run on a second queue that has no worker.
48+
49+
Counters, task-queue state, worker capacity and the disclosure are exact.
50+
Each counted run and activity lasts at least 1.1 seconds, so latency sums
51+
and the empty low buckets are checked against that bound.
52+
- **Evidence.** Native tests pass on SQLite, PostgreSQL and MySQL, and the
53+
native suite with `--all-targets` passes on SQLite (649 tests).
54+
A local copy of the SQLite parity step passed all 53 fixtures on embedded
55+
PHP and native, including `prometheus-metrics`. Published PHP passed
56+
`prometheus-metrics` too; its only difference was `storage-test`, an
57+
intermittent 503 from PHP's external payload registration on that loaded
58+
host, which passed in the previous local run.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# October 23, activity heartbeats on a closed run
2+
3+
- **The gap.** When a run was terminated or cancelled while its activity
4+
ran, published Server 2.11.1 answers the activity's next heartbeat with
5+
200:
6+
- `cancel_requested: true` and `can_continue: false`;
7+
- the closing reason, such as `run_terminated`, with
8+
`run_closed_reason` and `run_closed_at`;
9+
- `heartbeat_recorded: false` and a null `lease_expires_at`.
10+
11+
The worker then stops the activity as cancelled. Native answered 409
12+
with its activity-outcome body, which a worker can read as a transport
13+
error rather than a cancellation.
14+
- **What native does now.** For a closed run, native answers PHP's
15+
structured 200 from the same observation its status route reads.
16+
Nothing is recorded and no lease is renewed. An ordinary heartbeat also
17+
carries PHP's fields:
18+
- `reason`, `run_closed_reason` and `run_closed_at`, all null;
19+
- `worker_session`, null;
20+
- `protocol_version` and `server_capabilities`.
21+
- **How it was checked.** A probe ran on native and the published image.
22+
It scheduled an activity with all four timeouts, claimed it, sent a
23+
heartbeat, terminated the run, and sent another heartbeat. The claim's
24+
`deadlines` map and both heartbeat bodies matched.
25+
- **Not checked yet.** A heartbeat while a cooperative cancellation is
26+
pending on a run that is still open.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# October 23, activity lease expiry
2+
3+
- **Source.** Server 2.11.1 `ActivityTaskPoller::claimReadyTask` and
4+
`ActivityTaskClaimer`, `TaskRepair::recoverLeasedTask`,
5+
`ActivityTimeoutEnforcer::enforceLeaseExpiry` (through
6+
`enforceLockedTimeout`, `leaseLossMetadata`, `closeTimedOutAttempt`,
7+
`settleTimeout`, `scheduleRetry` and `recordTerminalTimeout`) and
8+
`ActivityRetryPolicy`.
9+
- **Published behavior.** PHP claims only ready activity tasks, so a worker
10+
never receives a task again after its lease expires. PHP's maintenance runs
11+
the repair pass every second, and the pass settles each expired activity
12+
lease:
13+
- The original attempt closes as expired, and its task is cancelled with
14+
`last_error` `lease_expired`.
15+
- When the activity's retry policy has an attempt left, PHP records
16+
`ActivityRetryScheduled`. It carries `retry_reason` and `failure_reason`
17+
`lease_expired`, the lease's owner and expiry, and no `timeout_kind`.
18+
The retry task waits out the policy's backoff.
19+
- Otherwise PHP records a terminal `ActivityFailed` with
20+
`Workflow\V2\Exceptions\ActivityLeaseExpiredException`, failure category
21+
`internal` and propagation `activity`. Its `non_retryable` is true when
22+
the policy lists that exception. The workflow then runs again to see the
23+
failure.
24+
- An activity whose own deadline also expired is left to the timeout sweep.
25+
- Without a retry policy an activity has one attempt.
26+
- **Evidence for PHP.** A fresh 2.11.1 server on SQLite, with its
27+
one-second repair loop, claimed two activities and had their leases
28+
expired in the database. The activity without a policy recorded
29+
`ActivityFailed` as above. The one with two attempts recorded
30+
`ActivityRetryScheduled` with `retry_reason` `lease_expired`, then attempt
31+
two started on a new task.
32+
- **Native before.** Native's claim query took a leased activity task whose
33+
lease had expired and ran it again as the next attempt. That skipped the
34+
retry policy, its backoff and the history events above. An activity
35+
without retries ran twice.
36+
- **Served.** Native's claim query and its cross-kind poll take only ready
37+
activity tasks. Native's scheduler settles expired activity leases on each
38+
tick, after its deadline sweep, exactly as PHP's enforcer does. The repair
39+
pass now settles an expired running-activity lease the same way; it no
40+
longer reports it as unported. The same check against native recorded the
41+
same events, classes, reasons, lease fields and attempt numbers as PHP.
42+
- Workflow task leases are unchanged: PHP's workflow poller also takes a
43+
workflow task whose lease expired.
44+
- **Decisions.**
45+
- PHP's lease events also embed the closed task's snapshot. Native's
46+
activity events do not carry task snapshots yet; this is the same known
47+
payload difference as its timeout events.
48+
- A run that already closed is skipped as `run_already_terminal`. Native
49+
never delivers an activity task of a closed run, so it does not continue
50+
an abandoned activity there.
51+
- **Restart probe contract.** `restart.php` kills a native process while it
52+
holds an activity lease, waits out the real five-minute lease and finishes
53+
with a new worker. It expected the recovered worker to run attempt two of
54+
an activity without a retry policy, with no event in between. That
55+
expectation came from native's re-claim, not from PHP: published PHP fails
56+
that activity instead. The probe now declares two attempts for the
57+
activity in `current-activity-restart.json` (`activity_retry_policy`) and
58+
expects `ActivityRetryScheduled` with `retry_reason` `lease_expired`
59+
between the two `ActivityStarted` events.
60+
- **Tests.** Native tests on SQLite, PostgreSQL and MySQL cover a retry,
61+
a terminal failure, a non-retryable lease exception, a poll racing the
62+
repair loop, the repair pass and an external result refused after the
63+
lease expired.

0 commit comments

Comments
 (0)