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
89 changes: 89 additions & 0 deletions .github/workflows/native-image-parity.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Native image parity

# Differential qualification of a native server image (Server #325). It
# records the shared fixture corpus on SQLite against the published PHP
# reference, embedded PHP and the native image itself, and compares the three
# records, as the Shared Server fixtures corpus part does for a source build.
#
# Dispatch it with a published native image by digest to qualify that
# artifact. A pull request that changes the comparison itself builds the image
# from its own source and compares that build, so the comparison is exercised
# before it is relied on. Nothing is pushed.

on:
workflow_dispatch:
inputs:
native_image:
description: 'Native image by digest, for example ghcr.io/durable-workflow/server-native@sha256:<index digest>'
required: true
pull_request:
paths:
- scripts/conformance/server-parity/native-image-corpus.sh
- scripts/conformance/server-parity/update-validation-sqlite.sh
- scripts/conformance/server-parity/php-maintenance-window.sh
- .github/workflows/native-image-parity.yml

permissions:
contents: read

concurrency:
group: native-image-parity-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
corpus:
name: Compare the native image over the shared fixture corpus (SQLite)
runs-on: ubuntu-latest
timeout-minutes: 75
env:
# The name the shared PHP maintenance window expects.
RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
LOCAL_IMAGE: dw-native-corpus-${{ github.run_id }}-${{ github.run_attempt }}:corpus
steps:
- name: Check out
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6

- name: Pull the pinned images, retrying registry errors
run: |
set -euo pipefail
bash scripts/ci/pull-images.sh \
"$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.4.json)" \
composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee

- name: Set up Docker Buildx
if: github.event_name == 'pull_request'
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4

- name: Build this pull request's native image into the local daemon
if: github.event_name == 'pull_request'
timeout-minutes: 45
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
with:
context: .
file: Dockerfile.native
platforms: linux/amd64
pull: true
provenance: false
load: true
push: false
tags: ${{ env.LOCAL_IMAGE }}

- name: Record and compare the corpus against the native image
env:
NATIVE_IMAGE: ${{ github.event_name == 'pull_request' && env.LOCAL_IMAGE || inputs.native_image }}
run: bash scripts/conformance/server-parity/native-image-corpus.sh "$NATIVE_IMAGE"

- name: Remove this job's containers and network
if: always()
run: |
docker rm -f "$RESOURCE_SCOPE-rust" "$RESOURCE_SCOPE-server" "$RESOURCE_SCOPE-php-maintenance" || true
docker network rm "$RESOURCE_SCOPE" || true

- name: Keep the comparison evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: native-image-parity
path: parity-evidence
retention-days: 14
if-no-files-found: ignore
6 changes: 6 additions & 0 deletions .github/workflows/native-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ on:
- Dockerfile.native.dockerignore
- scripts/ci/native-image-smoke.sh
- scripts/ci/native-release-publish.sh
- scripts/ci/check-native-release-tags.sh
- scripts/ci/verify-release-tag-source.sh
- .github/workflows/native-image.yml
- .github/workflows/native-release.yml
- .github/workflows/release.yml

permissions:
contents: read
Expand Down Expand Up @@ -44,6 +47,9 @@ jobs:
- name: Check out
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6

- name: Check that native tags start no PHP release
run: bash scripts/ci/check-native-release-tags.sh

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4

Expand Down
34 changes: 22 additions & 12 deletions .github/workflows/native-release.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
name: Native release candidate

# Publishes the native (Rust) server image for an existing immutable release
# tag (Server #325). Each architecture builds on its own runner and pushes by
# Publishes the native (Rust) server image for an existing immutable native
# Git tag, native-<version> (Server #325). A native- tag never matches the PHP
# Release workflow's tag filter, so it starts no PHP release; the image tag is
# the version without the prefix. Each architecture builds on its own runner and pushes by
# digest only; that exact digest is pulled back and smoke-tested there, so a
# failed smoke leaves no tag. The publish job then refuses tags that already
# exist, gives both smoked digests
# the release tag in both registries as one index, verifies it, and attests
# its build provenance. The PHP image and its Release workflow are unchanged.
#
# Its credentials are its own: the dockerhub-native Environment and the
# NATIVE_DOCKERHUB_* secret names. While they are absent the workflow fails
# closed: each job signs in to Docker Hub before it builds anything, so a
# dispatch stops there and pushes nothing. It can never resolve the PHP
# release's Environment or repository credentials.

run-name: Native ${{ inputs.tag }} at ${{ inputs.release_commit }}

on:
workflow_dispatch:
inputs:
tag:
description: 'Existing immutable release tag to publish natively (for example 2.12.0-rc.1)'
description: 'Existing immutable native Git tag, native-<version> (for example native-2.12.0-rc.1); the image tag drops native-'
required: true
release_commit:
description: 'Full source commit of that tag'
Expand All @@ -37,7 +45,7 @@ jobs:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 75
environment: dockerhub
environment: dockerhub-native
permissions:
contents: read
packages: write
Expand All @@ -62,6 +70,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_TAG_PREFIX: native-
RELEASE_COMMIT: ${{ inputs.release_commit }}
run: |
set -euo pipefail
Expand All @@ -74,8 +83,8 @@ jobs:
- name: Log in to Docker Hub
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
username: ${{ secrets.NATIVE_DOCKERHUB_USERNAME }}
password: ${{ secrets.NATIVE_DOCKERHUB_TOKEN }}

- name: Log in to GHCR
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
Expand Down Expand Up @@ -125,7 +134,7 @@ jobs:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04-arm
timeout-minutes: 75
environment: dockerhub
environment: dockerhub-native
permissions:
contents: read
packages: write
Expand All @@ -150,6 +159,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_TAG_PREFIX: native-
RELEASE_COMMIT: ${{ inputs.release_commit }}
run: |
set -euo pipefail
Expand All @@ -162,8 +172,8 @@ jobs:
- name: Log in to Docker Hub
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
username: ${{ secrets.NATIVE_DOCKERHUB_USERNAME }}
password: ${{ secrets.NATIVE_DOCKERHUB_TOKEN }}

- name: Log in to GHCR
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
Expand Down Expand Up @@ -214,7 +224,7 @@ jobs:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 20
environment: dockerhub
environment: dockerhub-native
permissions:
contents: read
packages: write
Expand All @@ -232,8 +242,8 @@ jobs:
- name: Log in to Docker Hub
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
username: ${{ secrets.NATIVE_DOCKERHUB_USERNAME }}
password: ${{ secrets.NATIVE_DOCKERHUB_TOKEN }}

- name: Log in to GHCR
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/server-parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,8 @@ jobs:
mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0 \
"$MYSQL_IMAGE" \
"$(baseline php-2.11.4)" \
"$(baseline php-2.5.14)"
"$(baseline php-2.5.14)" \
composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee
- name: Fetch this run's published SDK adapters
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
Expand Down Expand Up @@ -1101,7 +1102,8 @@ jobs:
mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0 \
"$POSTGRES_IMAGE" \
"$(baseline php-2.11.4)" \
"$(baseline php-2.5.14)"
"$(baseline php-2.5.14)" \
composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee
- name: Fetch this run's published SDK adapters
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
Expand Down Expand Up @@ -1800,7 +1802,8 @@ jobs:
bash scripts/ci/pull-images.sh \
mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0 \
"$(baseline php-2.11.4)" \
"$(baseline php-2.5.14)"
"$(baseline php-2.5.14)" \
composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee
- name: Fetch this run's published SDK adapters
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
Expand Down
6 changes: 3 additions & 3 deletions rust/src/config_audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,10 +685,10 @@ pub const SETTINGS: &[Setting] = &[
Setting { name: "DW_V2_LIMIT_HISTORY_TRANSACTION_SIZE", legacy: Some("WORKFLOW_V2_LIMIT_HISTORY_TRANSACTION_SIZE"), disposition: Honored },
Setting { name: "DW_V2_LIMIT_MEMO_SIZE_BYTES", legacy: Some("WORKFLOW_V2_LIMIT_MEMO_SIZE_BYTES"), disposition: Honored },
Setting { name: "DW_V2_LIMIT_PAYLOAD_SIZE_BYTES", legacy: Some("WORKFLOW_V2_LIMIT_PAYLOAD_SIZE_BYTES"), disposition: Honored },
Setting { name: "DW_V2_LIMIT_PENDING_ACTIVITIES", legacy: Some("WORKFLOW_V2_LIMIT_PENDING_ACTIVITIES"), disposition: Refused(Int(2000), STRUCTURAL) },
Setting { name: "DW_V2_LIMIT_PENDING_CHILDREN", legacy: Some("WORKFLOW_V2_LIMIT_PENDING_CHILDREN"), disposition: Ignored },
Setting { name: "DW_V2_LIMIT_PENDING_ACTIVITIES", legacy: Some("WORKFLOW_V2_LIMIT_PENDING_ACTIVITIES"), disposition: Honored },
Setting { name: "DW_V2_LIMIT_PENDING_CHILDREN", legacy: Some("WORKFLOW_V2_LIMIT_PENDING_CHILDREN"), disposition: Honored },
Setting { name: "DW_V2_LIMIT_PENDING_SIGNALS", legacy: Some("WORKFLOW_V2_LIMIT_PENDING_SIGNALS"), disposition: Honored },
Setting { name: "DW_V2_LIMIT_PENDING_TIMERS", legacy: Some("WORKFLOW_V2_LIMIT_PENDING_TIMERS"), disposition: Ignored },
Setting { name: "DW_V2_LIMIT_PENDING_TIMERS", legacy: Some("WORKFLOW_V2_LIMIT_PENDING_TIMERS"), disposition: Honored },
Setting { name: "DW_V2_LIMIT_PENDING_UPDATES", legacy: Some("WORKFLOW_V2_LIMIT_PENDING_UPDATES"), disposition: Honored },
Setting { name: "DW_V2_LIMIT_SEARCH_ATTRIBUTE_SIZE_BYTES", legacy: Some("WORKFLOW_V2_LIMIT_SEARCH_ATTRIBUTE_SIZE_BYTES"), disposition: Ignored },
Setting { name: "DW_V2_LIMIT_WARNING_THRESHOLD_PERCENT", legacy: Some("WORKFLOW_V2_LIMIT_WARNING_THRESHOLD_PERCENT"), disposition: Ignored },
Expand Down
47 changes: 47 additions & 0 deletions rust/src/runtime/memo_limits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ fn configured(suffix: &str, default: usize, invalid: &'static str) -> Result<Opt
/// single-payload byte ceiling. `None` is a disabled check.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StructuralLimits {
/// Pending or running activities a run may hold
/// (DW_V2_LIMIT_PENDING_ACTIVITIES; Workflow #933).
pub pending_activities: Option<usize>,
/// Open child workflows a run may hold (DW_V2_LIMIT_PENDING_CHILDREN).
pub pending_children: Option<usize>,
/// Pending timers a run may hold (DW_V2_LIMIT_PENDING_TIMERS).
pub pending_timers: Option<usize>,
/// Received signals a run may hold (DW_V2_LIMIT_PENDING_SIGNALS).
pub pending_signals: Option<usize>,
/// Accepted updates a run may hold (DW_V2_LIMIT_PENDING_UPDATES).
Expand All @@ -106,6 +113,9 @@ pub struct StructuralLimits {
impl Default for StructuralLimits {
fn default() -> Self {
Self {
pending_activities: Some(2000),
pending_children: Some(1000),
pending_timers: Some(2000),
pending_signals: Some(5000),
pending_updates: Some(500),
payload_bytes: Some(2_097_152),
Expand All @@ -121,6 +131,9 @@ impl StructuralLimits {
let defaults = Self::default();
let invalid = "invalid_structural_limit_configuration";
Ok(Self {
pending_activities: configured("PENDING_ACTIVITIES", defaults.pending_activities.unwrap(), invalid)?,
pending_children: configured("PENDING_CHILDREN", defaults.pending_children.unwrap(), invalid)?,
pending_timers: configured("PENDING_TIMERS", defaults.pending_timers.unwrap(), invalid)?,
pending_signals: configured("PENDING_SIGNALS", defaults.pending_signals.unwrap(), invalid)?,
pending_updates: configured("PENDING_UPDATES", defaults.pending_updates.unwrap(), invalid)?,
payload_bytes: configured("PAYLOAD_SIZE_BYTES", defaults.payload_bytes.unwrap(), invalid)?,
Expand All @@ -131,6 +144,14 @@ impl StructuralLimits {
}
}

/// The pending rows a completion command adds one of.
#[derive(Clone, Copy, Debug)]
pub(super) enum Pending {
Activities,
Children,
Timers,
}

/// One exceeded ceiling, as `StructuralLimitExceededException` reports it.
#[derive(Clone, Debug)]
pub(super) struct Exceeded {
Expand All @@ -146,6 +167,32 @@ impl Exceeded {
Self { kind, value: int(value), configured: int(configured), message }
}

/// `guardPendingActivities`, `guardPendingChildren` and
/// `guardPendingTimers` (Workflow #933): a run's pending rows of that kind
/// at or above the ceiling.
pub(super) fn pending(kind: Pending, limit: Option<usize>, count: i64) -> Option<Self> {
let (limit, count) = (limit?, usize::try_from(count).unwrap_or(0));
let (name, noun) = match kind {
Pending::Activities => ("pending_activity_count", "pending activities"),
Pending::Children => ("pending_child_count", "pending child workflows"),
Pending::Timers => ("pending_timer_count", "pending timers"),
};
(count >= limit).then(|| Self::new(name, count, limit,
format!("Structural limit exceeded: {count} {noun} (limit {limit}).")))
}

/// The worker's refusal of a completion that met this ceiling, as
/// `WorkerController` answers `StructuralLimitExceededException`: 422,
/// nothing recorded.
pub(super) fn worker_refusal(&self, task_id: &str, attempt: &serde_json::Value) -> super::RuntimeError {
super::RuntimeError::Protocol {
status: axum::http::StatusCode::UNPROCESSABLE_ENTITY,
response: serde_json::json!({"task_id":task_id,"workflow_task_attempt":attempt,"outcome":"rejected",
"recorded":false,"error":self.message,"reason":"structural_limit_exceeded","limit_kind":self.kind,
"current_value":self.value,"configured_limit":self.configured}),
}
}

/// `guardPendingSignals`: received signals at or above the ceiling.
pub(super) fn pending_signals(limit: Option<usize>, count: i64) -> Option<Self> {
let (limit, count) = (limit?, usize::try_from(count).unwrap_or(0));
Expand Down
6 changes: 3 additions & 3 deletions rust/src/runtime/operator_metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1623,9 +1623,9 @@ fn empty_repair_snapshot() -> Value {
fn structural_limits(connection: &str, limits: &super::StructuralLimits, memo: Option<usize>) -> Value {
let shown = |limit: Option<usize>| limit.unwrap_or(0);
let configured = json!({
"pending_activity_count": 2000,
"pending_child_count": 1000,
"pending_timer_count": 2000,
"pending_activity_count": shown(limits.pending_activities),
"pending_child_count": shown(limits.pending_children),
"pending_timer_count": shown(limits.pending_timers),
"pending_signal_count": shown(limits.pending_signals),
"pending_update_count": shown(limits.pending_updates),
"command_batch_size": 1000,
Expand Down
Loading
Loading