Repository navigation
Conversation
…-900)
A handler returning {"refresh_worker": True} sent stopPod with its result
but kept the job-take loop running. Once the slot freed, the worker could
take the next queued job before the platform restarted it, and that job
was lost (stale_job). The start-time flag already stopped intake first.
handle_job now returns the result it sent, and the scaler stops intake
whenever that result carries stopPod, before the slot frees.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…900) Python 3.14 defaults to forkserver on Linux, which re-imports __main__ in the heartbeat child and re-ran serverless.start() there, exiting the worker before it took a job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When a handler returns
{"refresh_worker": True}, the SDK sendsstopPod: Truewith the job result but keeps its job-take loop running. Once the slot frees,get_jobscan take the next queued job before the platform acts on the stop. The restart then kills that job, which is retried or fails asstale_job.The start-time flag (
runpod.serverless.start({"handler": h, "refresh_worker": True})) never had this race:JobScaler.handle_jobcallskill_worker()before itsfinallyfrees the slot. The per-job return value skipped that step.Prod example (endpoint
9d7pu3kccylu0w, pod5ys0tdv6mxhb07, 2026-10-28 UTC):stopPod; ai-api removes the worker and stops the podai-api can't close this on its side: the old and new process send identical job-take requests, and ai-api has no signal for when the old container is gone. Only the worker process knows it is the one being replaced.
Fix
rp_job.handle_jobreturns the result it sent to job-done.JobScaler.handle_jobcallskill_worker()when that result carriesstopPod, as well as for the start-time flag. The call runs before the slot frees and with noawaitin between, soget_jobscan't take another job.Refresh requested, taking no more jobs.Until now the per-job path logged nothing, which made affected endpoints hard to find.Tests
tests/test_serverless/test_refresh_worker.py, 32 tests on four levels:JobScalerwith a fake queue:stopPod: FalseorstopPodnested inoutput.rp_job.handle_jobreturns exactly the payload it sent.JobScalerover real HTTP against a fake ai-api, which asserts that no job-take arrives afterstopPod.runpod.serverless.start, with platform-style env vars, against the same fake ai-api.Without the fix, 17 of these fail and the 15 control tests pass. The real-process test reproduces the prod sequence: job B is taken after
stopPod. With the fix:fork);Not covered / worth knowing
mainis on the 2.0.0rc line, so this ships in 2.0.0. The reporting customer is on 1.10.0. If they shouldn't have to take a major upgrade for this, it needs a 1.12.1 backport fromv1.12.0, which has the same code. Until then they can use the start-time flag.ResumePodafter anyStopPodon a non-FlashBoot endpoint can still hand a job to the old process. That lives in ai-api and is out of scope here.Linear: SLS-900
🤖 Generated with Claude Code