GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — bpo-29595: Expose max_queue_size in ThreadPoolExecutor by prayerslayer · Pull Request #143 · python/cpython Conversation
|
|
Hello, and thanks for your contribution! I'm a bot set up to make sure that the project can legally accept your contribution by verifying you have signed the PSF contributor agreement (CLA). Unfortunately we couldn't find an account corresponding to your GitHub username on bugs.python.org (b.p.o) to verify you have signed the CLA. This is necessary for legal reasons before we can look at your contribution. Please follow these steps to help rectify the issue:
Thanks again to your contribution and we look forward to looking at it! |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Hi, can you create an issue about this in the issue tracker https://bugs.pyhon.org |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
I hope I did this right: http://bugs.python.org/issue29595 By the way I signed the CLA 🎉 |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Although the keyword |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Yes, that's a good point. I'd probably check for not None and > 0. Would you rather silently not set |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
I'll let the more experienced devs weigh-in on this, but imho 'explicit is better than implicit' and I'd prob raise an exception to alert the client code that an invalid value was passed for a keyword arg. If you fail fast, this kind of a nonsensical argument will most likely be caught early instead of puzzling people when it causes bizarre behaviour in production (due to the implicit setting of keyword args). |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Updated the PR and added tests. Should I update the docs somewhere? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
|
||
| # check that max_queue_size is a positive integer | ||
| if type(max_queue_size) is not int or max_queue_size < 0: | ||
| raise ValueError("max_queue_size must be equal or greater 0") |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Slight rewording to "greater or equal to 0" is possibly better.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Would remove this altogether again as you suggested.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| self._max_workers = max_workers | ||
| self._work_queue = queue.Queue() | ||
| self._work_queue = queue.Queue(maxsize=max_queue_size) | ||
| self._threads = set() |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
maxsize is the sole arg to the initializer of Queue so you could just supply max_queue_size since it's also pretty descriptive.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
is the sole arg to the initializer
What if this changes? Is it then still safe to just pass max_queue_size as positional argument?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| raise ValueError("max_workers must be greater than 0") | ||
|
|
||
| # check that max_queue_size is a positive integer | ||
| if type(max_queue_size) is not int or max_queue_size < 0: |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Should it strictly be an int? Classes that inherit from int won't pass this condition unless isinstance is used. Either way I'm not sure if doing any check for the type is a good idea since the Queue class generally doesn't (and fails when you put something in it)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I'm also fine if we leave verification to the Queue.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Also, should I add the same feature to the |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Since you've changed the signature of a class, the relevant docs need the required updates too :-) I'd suggest not changing anything else until a core-dev also comes through and provides a review and an opinion on the change. If one doesn't come around for a while, "ping" the issue on the issue tracker to try and get attention to it. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Updated the docs and waiting for a core dev. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Fixed the build and pinging for authoritative feedback. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Any updates? It's been a while and I also pinged it on the issue tracker to no avail... |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
If this feature is approved, we should give some thought to making it a keyword-only argument. That would improve code clarity and leave the positional arguments open for future expansion if needed. Also, if we ever need to make a change, keyword arguments are easier to deprecate than positional arguments (if other positional arguments had been added in the interim). |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I think this is a nice feature addition overall. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Yes, that would be reasonable, as it is vulnerable to the same problem. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| An :class:`Executor` subclass that uses a pool of at most *max_workers* | ||
| threads to execute calls asynchronously. | ||
| threads to execute calls asynchronously. If all worker threads are busy it | ||
| buffers new work items using a queue of maximum size *max_queue_size*. |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Please document that map and submit will block if the queue size is reached.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| for i in range(-10, 10): | ||
| executor = futures.ThreadPoolExecutor(max_queue_size=i) | ||
| self.assertEqual(executor._work_queue.maxsize, i) | ||
| # provided value is forwarded without checking it |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I am surprised by this. @rhettinger, is there any reason queue.Queue doesn't check for the size type?
>>> q = Queue(maxsize="foo")
>>> q.put(1)
Traceback (most recent call last):
File "<ipython-input-4-f7b62bda2b8c>", line 1, in <module>
q.put(1)
File "/home/antoine/miniconda3/envs/dask35/lib/python3.5/queue.py", line 127, in put
if self.maxsize > 0:
TypeError: unorderable types: str() > int()
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| def test_custom_max_queue_size(self): | ||
| # test custom valid and invalid values | ||
| for i in range(-10, 10): | ||
| executor = futures.ThreadPoolExecutor(max_queue_size=i) |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
What does it mean to have a negative queue size?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Nothing really, but since the Queue constructor doesn't check for validity of arguments I wanted to add a test for invalid arguments. (To be sure that I didn't break this behavior.)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| self.assertRegex(t.name, r'^.*ThreadPoolExecutor.*_[0-4]$') | ||
| t.join() | ||
|
|
||
| def test_default_max_queue_size(self): |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I would add a higher-level test, i.e. create an executor with a non-zero queue size, submit a number of tasks larger than the queue size, and check that they all get executed in the end.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Good idea :)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I think I have everything for the ThreadPoolExecutor, at least. As for the ProcessPoolExecutor it seems like the Regarding multiprocessing.Pool I can mechanically apply the same change, but I have really no idea what's going on there. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| threads to execute calls asynchronously. If all worker threads are busy it | ||
| buffers new work items using a queue of maximum size *max_queue_size*. When | ||
| this maximum size is reached, map() and submit() will block until more work | ||
| items have been processed. |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Can you mention that the default (0) means an unbounded queue?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| control the threading.Thread names for worker threads created by | ||
| the pool for easier debugging. | ||
|
|
||
| .. versionadded:: 3.7 |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
For a new function parameter, this should be versionchanged (versionadded means the function itself is new). Unfortunately, it seems we did the same mistake above for thread_name_prefix...
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| fs = [executor.submit(process_item, i) for i in range(n)] | ||
| expected_results = [process_item(i) for i in range(n)] | ||
|
|
||
| for f in futures.as_completed(fs, timeout=10): |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Or, simply, self.assertEqual(sorted(f.result() for f in futures.as_completed(...)), sorted(expected_results))
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| n = 10 | ||
|
|
||
| def process_item(item): | ||
| return item + 1 |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Perhaps add a small sleep() here to help the queue reach its limit at some point?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I agree |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Stackless contributes two tests to builtins: TaskletExit and TaskletExit.__init__. Therefore we have to adjust the limit. Add missing changelog entries (python#143, python#144).
…on#143 - avoid type punning / strict aliasing violations - don't rely on implementation defined placement of bit-fields in the storage unit.
Stackless contributes two tests to builtins: TaskletExit and TaskletExit.__init__. Therefore we have to adjust the limit. Add missing changelog entries (python#143, python#144). (cherry picked from commit 7327e4b)
|
The recent changes by @pitrou have replaced the After some investigation, one possibility would be to revert to using a For the API change, my only concern is that the @prayerslayer are you still interested on working on this issue? If not, let me know, I am interested in taking over from here. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Using a RLock does not make a routine signal-safe. The typical situation is:
(you can replace "signal" with "cyclic garbage collection" above for the same effect) In other words, the problem is not the lock, but the operations that are protected by the lock. SimpleQueue was designed specifically for |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Thanks for the explanation, I did not think about it as a signal interruption. Thus, to get this kind of API changes, we need to implement a size mechanism in the |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Hi everyone. I stumbled on this pull request after messing around with this concept myself for the last few days. I may not be testing this correctly but here's what I'm doing. from time import time, strftime, sleep, gmtime from random import randint from concurrent.futures import ThreadPoolExecutor, as_completed import queue class ThreadPoolExecutorWithQueueLimit(ThreadPoolExecutor): def __init__(self, max_queue_size, *args, **kwargs): super(ThreadPoolExecutorWithQueueLimit, self).__init__(*args, **kwargs) self._work_queue = queue.Queue(maxsize=max_queue_size) def nap(nap_length): sleep(nap_length) return nap_length if __name__ == '__main__': startTime = time() range_size = 100 max_pool_size = 10 max_worker_count = 100 with ThreadPoolExecutorWithQueueLimit(max_queue_size=max_pool_size, max_workers=max_worker_count) as pool_executor: pool = {} for i in range(range_size): function_call = pool_executor.submit(nap, randint(0, 2)) pool[function_call] = i for completed_function in as_completed(pool): result = completed_function.result() i = pool[completed_function] print('{} completed @ {} and slept for {}'.format( str(i).zfill(4), strftime("%H:%M:%S", gmtime()), result)) print('==--- Script took {} seconds. ---=='.format( round(time() - startTime))) In this case the queue fills with all 100 threads even though I've set it to 10. Am I completely off base with my expectations? What have I done wrong? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
I'm interested in getting this merged somehow, but since I didn't do anything for a year it's probably unrealistic that I'll finish it at all. Please go ahead :) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Hello, I posted a similar patch for I'd like to add my own 2 cents in regards of this feature as @pitrou suggested to implement it in Allowing the user to set a maximum size to the internal queue communicates a false sense of control over the amount of tasks which will be submitted to a The reason behind this flaw is that several jobs are pulled from the internal job queue and shovelled down the As the size of the jobs may vary as well as for the size of the I experienced this issue when releasing A much simpler and explicit way to achieve this goal is by using a Semaphore. This can be easily implemented both for Note that the above mentioned issue is affecting only process based pools, thread pools will actually work as expected. I could easily reproduce the issue with |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@noxdafox Using a semaphore sounds reasonable. Feel free to post a PR. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@pitrou the question is do we need to integrate this feature in the pools considering how trivial is to achieve such functionality with few lines of code? IMHO this fits more as a recipe or gist than a core functionality of the pool. I am more concerned about the slow creeping of features leading to a complex class to maintain in the future. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I agree with the maintainability concern. The |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I agree that this is a bad idea to change the size of the However, I am unsure of what should be the expected behavior, on two points of this API change:
Overall, this addition seems to also complicate the call to |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
This gist shows how trivial is for a user to achieve such functionality. There would be several other questions to sort out to properly add this feature to the Pools. What about IMHO the benefit of having this feature built-in is not worth its maintenance. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
- avoid type punning / strict aliasing violations - don't rely on implementation defined placement of bit-fields in the storage unit. (cherry picked from commit 5512131)
|
Since @prayerslayer said "it's probably unrealistic that [he will] finish" this PR, I'm going to close it (obviously people can open their own PRs to implement this functionality if they choose to). |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Codifies three protocols developed across the emitAnyCall PURE conversion arc (push 56-59) per pythia python#142/python#141/python#143 + supervisor + theologian authorizations: P8 — Phase 0' Coverage Extension to Perf-Regressing Benchmarks Per pythia python#142 python#1 + supervisor 22:46:24Z + 22:47:40Z. Sentinel-only HIR diff has empirical blind spot to single-bench regressions on unrelated workloads (caught when emitAnyCall push 59 showed int_arith -10% with exception-sentinel HIR-diff PASSING). Procedure: identify 24-bench >5% benches, run Phase 0' HIR-diff on bench source per identified bench (precise scrub, structural diff). Add to gate Step 7 post-processing. P9 — Filesystem-First Cross-Check Before Fabrication HALT Per medic 2026-04-22 build-claim false positive + medic 2026-04-25 22:50:53Z PYTHONJITDUMPFINALHIR false positive against generalist HIR dumps that were filesystem-verified at /tmp + retracted at 22:53:44Z. Procedure: identify implied artifact, ls + stat to confirm existence/mtime, post QUERY (not HALT) if artifact exists, never cite session-log absence as sole evidence. Costs ~30s vs ~5-10min false- positive recovery cycle. P10 — Variance Characterization Before (B) Investigation Per W-EMITANYCALL-INT-ARITH-PERF closure 22:57:29Z + pythia python#143 python#1 amendment 23:21:18Z + supervisor 23:21:51Z. Mirrors agent-memory feedback_falsifier_convergent_negative.md (per-agent reasoning side); P10 codifies team-process side. Procedure (7 steps): identify >5% benches, 3-run targeted re-runs for in-isolation variance, AT LEAST ONE full-24-bench re-run for in-context variance (different statistical populations: page-cache, branch-predictor, allocator history, subprocess-position effects). Closure REQUIRES both isolated AND in-context corroboration; if isolated clean but in-context reproduces delta → real signal in sequence-context layer (cold-start/page-cache), not at conversion layer. Pythia python#143 python#4 caught these 3 protocols as 'LANDED in spec but uncommitted at HEAD 0810be4 — exist only in working tree, not yet history.' This commit closes that gap: protocols now in git history before being cited as canonical (CC3 codification-on-authoring rule). Spec doc: P1-P10 + 3 cross-cutting (CC1/CC2/CC3) + paired empirical controls (PhxMem positive / HirType negative). Bundle target: push 60 if Alex authorizes (A)/(B)/(C); separate codification push if Alex direction is delayed >2hr per supervisor 23:21:51Z.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.