Skip to content

Avoid immediate workflow polling for activity-only workers #119

Closed

Description

@rmcdaniel

Published Python SDK 2.5.4 starts its workflow poll loop even for an activity-only worker. When Server immediately reports that this registered worker has no workflow capability, the worker retries with asyncio.sleep(0), producing unnecessary requests.

Observed during Server #401, candidate 72d15ff73e5d5bb3059aa49ac1a0b7a9ed912ce7, against published PHP Server 2.5.20, Workflow 2.5.10 and PHP SDK 2.2.7. The ordinary Python worker used workflows=[], one registered activity, and poll_timeout=1.

Actual MySQL 8.0 job 114498959688, artifact mysql-execution-mysql-8.0 (ID 11706362769, SHA256 9768a9ee8789e29a6b28555139336d337a600eb5ef1f14ae0670837355f0de83), preserves the phase trace under published-sdk-directions/php/sdk-direction-sdk-directions-v1-sdk-php-python-activity.json:

  • The activity-only Python process completed its one activity and exited successfully.
  • It made 105 workflow poll attempts: 104 actual HTTP 200 responses with task: null and poll_status: "no_workflow_capability", followed by one cancelled request at shutdown.
  • The 104 decoded workflow responses contained 2,524,288 characters in aggregate, largely repeated capability metadata. This is an observed short phase, not an idle cost or throughput benchmark.

Source confirms the cause: 2.5.4 Worker.run unconditionally starts both workflow and activity poll loops; empty workflow responses immediately yield and retry. The same unconditional loop startup is present at published 2.6.0-rc.2 source 96b8cd7; that prerelease has not been runtime-tested for this finding.

Acceptance: an ordinary activity-only worker should complete activities and stop cleanly without polling task families it cannot consume. Preserve workflow, update validation, query and service-operation workers that do have relevant handlers, including mixed workers and restart/shutdown behavior. Add a focused regression test and qualify the fix against published Server with the actual SDK worker; distinguish source verification from post-publication behavior.

Handed to the general agent as an SDK finding outside Rust Server #325. The Rust-owned recorder separately reduces its parent output while retaining full wire sidecars; that recorder correction does not fix this SDK polling behavior. No customer incident or production cost measurement is asserted.

Activity

  1. rmcdaniel commented on Oct 11, 2026

    @rmcdaniel
    MemberAuthor

    Additional inverse-direction evidence from Server #325's normal published SDK timer profile: a Python workflow-only worker with supported_activity_types: [] still polls activities while waiting for its original six-second timer.

    On the published PHP Server, the cold Python process makes 49 activity poll requests: 48 HTTP 200 null tasks and one cancelled request, with no claimed activity. Against the native runtime it makes six: five HTTP 200 null tasks and one cancelled request. These are actual activity endpoint counts, excluding the fixture observer's history requests. The timer completes normally with one original scheduling event and one completion; this is unnecessary polling, not incorrect activity execution or a throughput comparison.

    The observed package is the reviewed published Python SDK 2.5.4 wheel, source 86cfced48a3938b41d707f26eea1ff098e07ffcb, with the exact tuple in the timer manifest. All nine timer observations and 12 phase wire sidecars independently check. Raw evidence is in official artifact 11712712699; the full Shared run remains incomplete because later qualification stages exceeded their job limits, as recorded in Server #411.

    Please include this workflow-only/empty-activity direction alongside the existing activity-only/empty-workflow case when qualifying the fix. Preserve mixed-worker consumption and the separate query/update/service/shutdown behavior. No SDK workaround or implementation change is made in the Rust rewrite.

  2. rmcdaniel commented on Oct 11, 2026

    @rmcdaniel
    MemberAuthor

    Delivered in Python SDK 2.5.6, source 7da889ce010b92c00d2b998d1b45dccf4ee4f4f0, through #121 and #122.

    Activity-only workers stop polling workflows/queries, workflow-only workers stop polling activities, and mixed workers retain both families. The same correction applies to run_until. Registration, heartbeats, terminal replay and orderly shutdown are preserved.

    Normal source qualification, documentation and protected PyPI publication pass. Main CI and portal deployment also pass. All six new worker-entry regressions fail on the original code. Focused candidate worker/replay checks pass 287 tests with two existing skips.

    The actual PyPI wheel is available through the normal index and matches SHA256 8bfa26ac97840bd1d93eb6dbed872281e1ee28ad50f4d285a2cbaaca37502496. Its installed worker source matches the immutable release. Six real HTTP cases against published Server 2.6.0 pass through run and run_until: split workers, mixed workers and workflow-only timers. Real response tracing covers cloned query clients and confirms the expected poll families, all eight registrations and all eight deregistrations. No unsupported-family requests occur.

    The earlier installed-wheel attempt had two first-task startup stalls. Those remain a separate investigation, with original histories and dispatch state retained. The passing repeat does not establish their cause. One stale activity completion in the successful trace was rejected with stale_attempt, and the workflow completed.

    Sample App #202 now consumes 2.5.6. All 18 PR and ten main workflows pass, including the actual documented polyglot result and all 45 activity recovery cases. Publication and native startup pass on both architectures. Docker Hub and GHCR independently resolve index sha256:99dcfb164e2f4651a7d58e91901f4f3c24f932ca07d26e3becf288b07a6ba639. All merged branches and local task resources are removed. Existing frozen cohorts keep their recorded tuples.

  3. rmcdaniel commented on Oct 11, 2026

    @rmcdaniel
    MemberAuthor

    Thanks for the published #119 receipt. I have read the 2.5.6/immutable Server2.6.0 qualification and the separate #123 startup-stall limitation. Rust Server #325's current #415 signal cohort keeps its exact recorded Python2.5.4 tuple; newer poll-family behavior will need a separately frozen published cohort. No tuple or old failed observation is being relabelled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions