Repository navigation
Conversation
Mixed activityActivity patterns show a mix of organic and automated signals. Evidence
Last 5 PRs:
This is an automated analysis by AgentScan |
Documentation build overview
4 files changed± genindex/index.html± history/index.html± pkg/cheroot.server/index.html± pkg/cheroot.test.test_server/index.html |
041c6ec to
2d4083d
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #858 +/- ##
==========================================
+ Coverage 78.20% 79.34% +1.13%
==========================================
Files 41 41
Lines 4791 4933 +142
Branches 548 570 +22
==========================================
+ Hits 3747 3914 +167
+ Misses 904 883 -21
+ Partials 140 136 -4 |
2d4083d to
f080648
Compare
test_high_number_of_file_descriptors needs the server to accept its connection on a file descriptor above the limit, so nothing may free a lower one while it runs. Its fixture was meant to stop garbage collection, but pypytools' nogc() only pauses the collections that its own hooks start, and nothing enables those hooks, so the collector still ran. That mattered little while cheroot kept every server alive, along with the test clients that fixtures attach to servers. Once stopped servers can be collected, a collection closes those clients' open sockets at any time, and the server can then take one of the freed low file descriptors. This failed the test once on CPython 3.14 in CI. The fixture now disables the collector through the gc module, which CPython and PyPy both support. Generated-By: Anthropic Claude Opus 5.5 Signed-off-by: Adam Harwell <flux.adam@gmail.com>
f080648 to
e286783
Compare
Every HTTPServer registered its statistics in the process-wide logging.statistics when it was constructed, and nothing removed them. Their callbacks hold the server, so every server ever built stayed alive with its gateway: stopped ones, ones whose prepare() failed to bind, and ones that never started. A server is now registered only while it serves, from prepare() to stop(). Every worker thread also left its entry in its server's statistics when it exited, so a pool that grows and shrinks again kept one more entry, and the retired thread with it, each time. A thread that recorded no statistics, as when they are disabled, now removes its entry as it exits. One that recorded some keeps it, so that the totals still count its work. Generated-By: Anthropic Claude Opus 5.5 Signed-off-by: Adam Harwell <flux.adam@gmail.com>
e286783 to
208ba8d
Compare
|
As I posted in my other PR, I will be watching these and will try to respond in a timely fashion to any feedback/concerns. I'm not super attached to this code (as it was AI written) but it does seem to do the job and in my first pass review it didn't seem too bad, though I will admit to not fully understanding the cheroot codebase. If anyone has a better idea about how to fix the bug this tackles, I'm happy to defer to that. If anything, I am not a super huge fan of matching on hardcoded strings, but I don't know a good way around that here, and am open to suggestions. |
Recorded statistics still retain every retired worker. Fold their final values into bounded totals and remove each worker record on exit. Serialize retirement with total reads to count each worker once. Preserve all six totals, including the sum of per-worker throughput, and keep old workers from restoring counters after clear_stats(). Add regressions for enabled statistics, resets and real HTTP worker churn. Generated-By: Codex GPT-6 Signed-off-by: Adam Harwell <flux.adam@gmail.com>
Describe retention cases and test assertions directly. Explain why a worker must retire its record from the collector it registered with when clear_stats() can replace the server's collector. Assisted-By: OpenAI Codex (GPT-6) Signed-off-by: Adam Harwell <flux.adam@gmail.com>
What kind of change does this PR introduce?
What do these changes do?
Every
HTTPServerregistered its statistics in the process-widelogging.statisticswhen it was constructed, and nothing removed them. Their callbacks hold the server, so every server ever built stayed alive with its gateway: stopped ones, ones whoseprepare()failed to bind, and ones that never started. A server is now registered only while it serves, fromprepare()tostop(), andclear_stats()replaces its entry while it serves.Every
WorkerThreadalso left its entry inserver.stats['Worker Threads']when it exited, so a pool that grows and shrinks kept every thread it ever started. On exit, each worker now folds its final numeric values into a fixed set of aggregate totals and removes its record. This releases retired workers even when statistics are enabled, while preserving requests, bytes read and written, work time, and both throughput totals. Throughput retains its existing sum-of-worker-rates definition.Registration, retirement and total reads share a lock, so moving a worker's counters into the retired totals cannot temporarily omit or double-count them. Statistics reporters still receive ordinary dictionaries. Each worker retires into the collector it joined;
clear_stats()creates a fresh collector, so old workers cannot restore discarded counters.With a reproducer that discards 100 servers, then grows a one-thread pool by 10 and shrinks it back:
logging.statisticsRegression tests cover server registration and collection, disabled-statistics cleanup, preservation of all six totals, clearing statistics, and repeated worker retirement after real HTTP requests. The enabled-statistics and HTTP-churn regressions both fail against the previous PR head (
208ba8dd).Additional local validation used 400 grow/shrink cycles and 3,200 real HTTP requests, with statistics enabled, disabled, and toggled. After every cycle, only the single live worker and its statistics record remained; after shutdown, no workers remained reachable through weak references following garbage collection. All six totals were checked before and after every retirement batch. In the previous PR revision, the same enabled-statistics workload retained 141 workers after just 20 cycles with only one worker alive.
Under PyPy, the test case that stops a server is marked as an expected failure. There, coverage.py traces with its Python tracer, which keeps every thread that it traced until the run ends, and the stopped server's worker threads still refer to the server. The case passes under PyPy without coverage (
--no-cov).The first commit makes
test_high_number_of_file_descriptorsdisable garbage collection while it runs, as its_garbage_binfixture meant to:pypytools'nogc()only pauses the collections that its own hooks start, and nothing enables those hooks. Once stopped servers can be collected, along with the test clients that fixtures attach to them, a collection during that test can close those clients' sockets, and the server can then take one of the freed low file descriptors. This failed the test once on CPython 3.14 here. The fixture now uses thegcmodule.pypytoolsis no longer imported; I left its pins independencies/alone to keep this change small, and can drop them here or separately.Are there changes in behavior for the user?
logging.statisticsnow lists a server only while it serves: not beforeprepare(), and not afterstop(). A server prepared again after a stop is listed again. Thestats['Worker Threads']mapping contains live worker records only. Retired workers' contributions remain included in the server totals without retaining the workers or their callbacks.Is it a substantial burden for the maintainers to support this?
The change moves registration into the server's lifecycle and adds a small collector for live records and bounded retired totals. Tests cover the lifecycle, counter preservation, resets and real HTTP worker churn.
Related issue number
Fixes #860. A related problem in
ThreadPool.shrink(), #861, is fixed in #859.Checklist
Tip
New to open source contributions? Making a lovely PR is a great read.
and description in grammatically correct, complete sentences
docs/) and inline docstrings reflect the changes(see good commit messages)
(can be done once the PR number is known; alternatively, you can
use a related issue number if one exists)
code of conduct
The original change, tests and description were written by Anthropic Claude Opus 5.5 on behalf of Adam Harwell (@rm-you). The enabled-statistics follow-up, regression tests, real-worker validation and description update were written by Codex GPT-6. Local validation passed the complete suite on Python 3.11.2 (221 passed, 3 existing expected failures) and all configured pre-commit hooks for the changed files, including
mypy-stubtestwith Python 3.12.🤖 Generated with Claude Code