Repository navigation
fix: cap running, retained and caller-tuned FHIR bulk and SMART ingestion jobs - #4199
Open
pardeep-singh wants to merge 1 commit into
Open
pardeep-singh wants to merge 1 commit into
pardeep-singh wants to merge 1 commit into
Conversation
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.
Description
Fixes #3909. On master,
FHIRBulkJobManager.startandSMARTBackendJobManager.startcreated a task for every request. Nothing capped running jobs, and finished records stayed in_jobsfor the life of the process. The request schemas had only lower bounds, andpoll_interval_seconds=0was accepted. Callers could pick unbounded parallelism, buffering and remote polling rates.This PR adds server-owned admission, retention and tuning bounds to both managers.
Type of Change
Changes Made
openmed/service/job_bounds.py(new):BulkJobBounds(running ceiling, retained cap, terminal TTL, retry delay), the typedBulkJobsSaturatedError, and shared admission and pruning helpers. Validatesmax_running_jobs <= max_retained_jobs.openmed/service/bulk_data.py,openmed/service/smart_backend.py: both managers take optionalboundsand an injectedclock.startrejects before creating a task.start,getandcancelprune expired terminal records lazily. Newensure_capacity()andbounds.bulk_job_bounds_from_env()reads threeOPENMED_SERVICE_BULK_*settings with the existing positive-integer parser.openmed/service/app.py: reads bounds once increate_app, passes them to both managers, and callsensure_capacity()before the output directory is reserved. Maps the error to503 bulk_jobs_saturatedwithRetry-After.openmed/service/schemas.py: upper bounds onmax_inflight_downloads(16),max_buffered_resources(1000),request_timeout_seconds(300) andpoll_interval_seconds(1 to 300) for the bulk and SMART requests, both pydantic branches.openmed/service/api_compatibility.py: registersbulk_jobs_saturatedas a stable error category.docs/api/openapi.json: regenerated withscripts/export_openapi.py.docs/interop/fhir-bulk-data.md,docs/rest-service.md,CHANGELOG.md: settings, limits, error codes and migration notes.tests/unit/service/test_bulk_job_bounds.py(new) andtests/unit/service/test_smart_backend.py(fake manager gainsensure_capacity).Testing
New tests cover, for both managers: the N+1th start rejected with no new task, transport call or directory; a slot freed after a job finishes or is cancelled; TTL expiry under an injected clock; 10,000 synthetic jobs with
len(_jobs)never above the cap; running jobs never evicted; an expired id returning the same 404 as an unknown id; every out-of-range tuning value (includingpoll_interval_secondsof 0, 0.5 and infinity) rejected with422and controlled codes; in-range values forwarded unchanged; bounds and env validation. Sentinel URLs, paths, keys, client ids and assertions never appear in rejection bodies, headers orcaplog.Before the fix the new file gave
36 failed, 11 passed(the passing ones are in-range and already-rejected lower-bound cases). After:47 passed.Documentation
Code Quality
make format,make lint, andmake format-check(ran the pinnedruff check .andruff format --check .directly)make format-swiftandmake lint-swiftDependencies
Notes
OPENMED_SERVICE_BULK_MAX_RUNNING_JOBS(default 4),OPENMED_SERVICE_BULK_MAX_RETAINED_JOBS(256) andOPENMED_SERVICE_BULK_JOB_TTL_SECONDS(3600). Each manager applies them on its own. Invalid values, or a running ceiling above the retained cap, fail at startup.KeyErroras an unknown id, so routes return the same 404.ensure_capacity()beforeBulkStoragePolicy.prepare, so a saturated start leaves no output directory.start()checks again beforecreate_task.cancel_allnow cancels records directly, so a concurrent prune cannot cause aKeyErrorduring shutdown.le=/ge=schema bounds, not runtime settings. They show up in the OpenAPI contract and use the existing422 validation_errorpath with pydantic codes such asless_than_equal. The details never echo the submitted value.poll_interval_secondsbelow 1 or values above the new ceilings now get422. The compatibility gate does not flag bound changes; the OpenAPI diff is the three newmaximumvalues and the pollminimumraised from 0 to 1. The direct PythonSMARTBackendConfigandFHIRBulkJobConfigkeep their previous lower bounds, so library tests that poll with an injected sleep still work.BulkJobBounds.retry_after_seconds. It is not an env setting.Retry-After: 0is still honored as before. Path confinement and egress (Enforce explicit egress policy for remote inference, callbacks, and connectors #3581) and durable recovery or idempotency keys (Deduplicate retried bulk-export and ingestion starts with idempotency keys #3923, Make asynchronous jobs durable with leases, checkpoints, poison quarantine, and replay #3574) are not touched.PollIntervalSeconds: 0checks only client serialization against a mock server, so it still passes. A real server now rejects that value.Related Issues
Closes #3909
Related to #3576, #3581, #3923, #3574