Skip to content

Cap the number of requests in the image stage - #109

Closed
beetlebugorg wants to merge 1 commit into
mainfrom
feat/concurrency-cap
Closed

Cap the number of requests in the image stage#109
beetlebugorg wants to merge 1 commit into
mainfrom
feat/concurrency-cap

Conversation

@beetlebugorg

@beetlebugorg beetlebugorg commented Aug 28, 2026

Copy link
Copy Markdown
Owner

What

Add DimsMaxInFlight. It caps the number of requests in the image stage at once, across every worker process. A request over the cap returns 503 before any image work. The default is 0, which means no limit and reproduces today's behavior.

Why

The number of requests decoding at once equals the worker count. Each one can hold a full pixel cache, so a burst exhausts memory. A server-wide cap bounds the concurrent pixel caches independent of the worker count. Over the cap, the server returns 503 without decoding.

The counter lives in the shared memory block that already holds the request counters. The handler takes a slot after verification and before the fetch, so the fetch buffer and the pixel cache are both covered. dims_send_image and dims_cleanup release the slot, guarded by a per-request flag, so every exit path releases exactly once.

Verify

COMPOSE_PROJECT_NAME=dims-cap make -C test test

All 268 cases pass. TestOverTheCapIsRefused starts a slow request that holds the only slot on the port 8008 server, confirms a second request returns 503, then confirms an ordinary request is served again after the slot frees.

COMPOSE_PROJECT_NAME=dims-cap make -C test valgrind

No module allocation is definitely lost.

Breaking

Nothing. The default is no cap.

- Add DimsMaxInFlight. The default 0 keeps no limit.
- Count in-flight requests in shared memory, across every worker.
- Refuse a request over the cap with 503 before it does image work.
@beetlebugorg

Copy link
Copy Markdown
Owner Author

The Apache event MPM already bounds concurrent requests per child at ThreadsPerChild, and server-wide at ServerLimit times ThreadsPerChild. That bound is self-healing: a killed child releases its threads with it.

A per-child DimsMaxInFlight that spans the whole handler duplicates that bound. A server-wide shared counter does not duplicate it, but a child that is killed while it holds a slot leaks the slot. The count then drifts up until every request returns 503, and only a restart clears it. That failure appears under the same memory pressure the cap exists to handle.

The shipped container sizes ThreadsPerChild, ServerLimit, and the ImageMagick memory limits instead. A soak test under a fixed CPU and memory limit sets the numbers.

@beetlebugorg
beetlebugorg deleted the feat/concurrency-cap branch August 28, 2026 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant