Skip to content

bpo-5001: More-informative multiprocessing error messages#3079

Merged
pitrou merged 21 commits into
python:masterfrom
drallensmith:master
Aug 29, 2017
Merged

bpo-5001: More-informative multiprocessing error messages#3079
pitrou merged 21 commits into
python:masterfrom
drallensmith:master

Conversation

@drallensmith

@drallensmith drallensmith commented Aug 12, 2017

Copy link
Copy Markdown
Contributor

This is a fix of some of the uninformative asserts in multiprocessing, including in multiprocessing.managers and multiprocessing.queues. Those in the queues.py module remain AssertionErrors, as do some in the managers.py module; others have been changed to more specific errors. In BaseManager's interpretation of method_to_typeid entries, checking for type(key or value) == str has been replaced with isinstance(key or value, str).

In pool.py, various uninformative asserts have been changed to other appropriate errors (generally ValueErrors with a few TypeErrors) and information added; one instance of checking of type (== int) was changed to isinstance([thing], int).

In util.py, two uninformative asserts have been changed to other appropriate errors (one TypeError and one ValueError) and information added; with the TypeError, one instance of checking of type (== int) was changed to isinstance([thing],int).

I have also added a couple of comments. (Git, BTW, seems somewhat confused in that merging from python/cpython as upstream doesn't tell it that some of the commits in question have already been done in the master version. I'm not sure what to do about that - I've tried rebase before, but run into problems with github with it.)

Also a question - is there some way that I should mark pull requests that might be suitable for cherry-picking for other versions of Python? My prior multiprocessing PR is such; the code was identical in 2.7.13 and 3.5.3, for instance (I'm not suggesting applying it to 2.7, as opposed to 3.5-dev). I am more uncertain about this one.

https://bugs.python.org/issue5001

@drallensmith drallensmith changed the title bpo-5001: More-informative multiprocessing error messages (managers.py, queues.py) bpo-5001: More-informative multiprocessing error messages (managers.py, queues.py, pool.py) Aug 17, 2017
@drallensmith drallensmith changed the title bpo-5001: More-informative multiprocessing error messages (managers.py, queues.py, pool.py) bpo-5001: More-informative multiprocessing error messages (managers.py, queues.py, pool.py, util.py) Aug 18, 2017
Comment thread Lib/multiprocessing/managers.py Outdated
pass

def debug_info(self, c):
def debug_info(self, c): # c is unused?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how multiprocessing managers work, but c seems to be an API convention (see number_of_objects below which also has an unused c parameter).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I was wondering whether it could be used for additional debugging information. Admittedly, anything that called it should have the connection anyway!

Comment thread Lib/multiprocessing/managers.py Outdated
with self.mutex:
assert self.id_to_refcount[ident] >= 1
if self.id_to_refcount[ident] <= 0:
raise ProcessError(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep it an assert (or an AssertionError), since this really seems to be testing an internal consistency thing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK; AssertionError, due to line length limits.

Comment thread Lib/multiprocessing/managers.py Outdated
for key, value in list(method_to_typeid.items()):
assert type(key) is str, '%r is not a string' % key
assert type(value) is str, '%r is not a string' % value
assert isinstance(key, str), '%r is not a string' % key

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure there's much point in making this change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Umm... OK. Someone was complaining that this wasn't done with the old 5001 patch...

Comment thread Lib/multiprocessing/pool.py Outdated
def worker(inqueue, outqueue, initializer=None, initargs=(), maxtasks=None,
wrap_exception=False):
assert maxtasks is None or (type(maxtasks) == int and maxtasks > 0)
if maxtasks is not None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worker() is an internal API, I'm not sure it's worth maintaining this more complicated error reporting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to simplify it.

Comment thread Lib/multiprocessing/pool.py Outdated
Equivalent of `func(*args, **kwds)`.
'''
assert self._state == RUN
if self._state != RUN:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apply_sync() seems to already have this check, so we can probably remove it here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

Comment thread Lib/multiprocessing/pool.py Outdated
cls._help_stuff_finish(inqueue, task_handler, len(pool))

assert result_handler.is_alive() or len(cache) == 0
if not result_handler.is_alive():

@pitrou pitrou Aug 18, 2017

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is also an internal error, which makes assert fine here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or AssertionError?

Comment thread Lib/multiprocessing/queues.py Outdated

def put(self, obj, block=True, timeout=None):
assert not self._closed
assert not self._closed, "Queue {0!r} already closed".format(self)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"already" sounds unwanted, since put() isn't asking to close the queue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Substituted "has been".

@@ -0,0 +1,7 @@
This is a fix of some of the uninformative asserts in `multiprocessing`,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think three separate NEWS entries and such a detailed description are warranted for what is a small improvement :-)

@drallensmith drallensmith Aug 18, 2017

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it's now 1 short NEWS entry and I fixed most of the asserts. (Of course, then I had a typo due to an editor save problem... sigh. Unfortunately and ironically, the multiprocessing tests overload my current machine. Everything else works...) I was going nuts trying to figure out why something I knew I'd changed wasn't there, so shut down the pull request so as to not overload travis with both my fork's and cpython's builds. (I'm not paying them anything so I try to be nice to them.)

@drallensmith drallensmith reopened this Aug 18, 2017
@drallensmith drallensmith changed the title bpo-5001: More-informative multiprocessing error messages (managers.py, queues.py, pool.py, util.py) bpo-5001: More-informative multiprocessing error messages Aug 19, 2017
@drallensmith

Copy link
Copy Markdown
Contributor Author

This now fixes, as far as I can tell, all uninformative asserts in multiprocessing except for those in Windows-specific code (I am not sufficiently familiar with processes on Windows to feel confident writing informative error messages).

Comment thread Lib/multiprocessing/forkserver.py Outdated

Returns a pair of fds (status_r, data_w). The calling process can read
the child process's pid and (eventually) its returncode from status_r.
the child process\'s pid and (eventually) its returncode from status_r.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if you changed this because it tripped your text editor, but this escaping is unnecessary inside a triple-quoted string.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your guess is correct - it made xemacs unable to tell what was quoted after it, which got especially inconvenient when trying to make sure everything was indented correctly.

for key, value in list(method_to_typeid.items()):
assert type(key) is str, '%r is not a string' % key
assert type(value) is str, '%r is not a string' % value
for key, value in list(method_to_typeid.items()): # isinstance?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these 3 lines should not be changed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

Comment thread Lib/multiprocessing/pool.py Outdated
def apply(self, func, args=(), kwds={}):
'''
Equivalent of `func(*args, **kwds)`.
self._state should be RUN.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Comments about internal state do not have a place in the docstring of a public method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Pool must be running"?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds ok, though I'm not sure it bears mentioning.

assert result_handler.is_alive() or len(cache) == 0
if (not result_handler.is_alive()) and (len(cache) != 0):
raise AssertionError(
"Cannot have cache with result_hander not alive")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I get the point of adding a message here. The message will not be understandable for the average user, and whoever wants to debug it has to read the source code anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does one tell where the AssertionError is happening, if the pool is being used not by the primary process but by a subprocess (possibly on another machine via a manager)?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to the line number in the traceback, I guess?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish that worked in the circumstances I noted above - see here, noting that it's a "nightly" build so includes the earlier patch, for an example.

Comment thread Lib/multiprocessing/synchronize.py Outdated
while self._woken_count.acquire(False):
res = self._sleeping_count.acquire(False)
assert res
assert res, 'res is {:n}'.format(res)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really useful, as res will simply be False here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

Comment thread Lib/multiprocessing/synchronize.py Outdated
def notify(self, n=1):
assert self._lock._semlock._is_mine(), 'lock is not owned'
assert not self._wait_semaphore.acquire(False)
assert not self._wait_semaphore.acquire(False), 'waiting to acquire'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message is rather ambiguous and unhelpful to the user.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you take a look and see if my alternative phrasing is better?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's better. It's ok to call notify() even if there are no waiters. This seems to be testing an internal invariant rather than an user error.

@drallensmith drallensmith reopened this Aug 28, 2017
Comment thread Lib/multiprocessing/synchronize.py Outdated
while self._woken_count.acquire(False):
res = self._sleeping_count.acquire(False)
assert res, 'res is {:n}'.format(res)
assert res, 'Attempting to notify but no sleepers?'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as for the other assert above. I think the original assert is ok.

Comment thread Lib/multiprocessing/synchronize.py Outdated
def notify(self, n=1):
assert self._lock._semlock._is_mine(), 'lock is not owned'
assert not self._wait_semaphore.acquire(False)
assert not self._wait_semaphore.acquire(False), 'Nobody waiting?'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this is an overlook, but the message here is still wrong (it's ok to have nobody waiting).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm. So exactly what is the assertion testing for? Thanks for the clarification!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It allows waking up the exact desired number of waiters (see below). The logic is a bit difficult to understand, as the various semaphores don't have an obvious meaning, they are just used for careful synchronization between notifier and waiters.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"notify: Should not have succeeded at acquiring _wait_semaphore"?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds ok.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (slightly shorter version).

Comment thread Lib/multiprocessing/synchronize.py Outdated
while self._woken_count.acquire(False):
res = self._sleeping_count.acquire(False)
assert res
assert res, 'Attempting to notify but no sleepers?'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here too (it's more of a sophisticated internal detail that's been tested).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK... but what would the assert failing indicate?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That the implementation has a bug :-) I don't think we can qualify it any further.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh. "notify: Bug in sleeping_count.acquire - res should not be False"?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough :-)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, done.

familiarity), all asserts in `multiprocessing` are now more informative, and
some error types have been changed to more specific ones. - Allen W. Smith
(drallensmith on github)
Many asserts in `multiprocessing` are now more informative, and some error types have been changed to more specific ones.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I was supposed to sign it?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you don't need to. You can add an entry in Misc/ACKS, though (keeping alphabetical order).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already done, but thank you.

@@ -1 +1 @@
Many asserts in `multiprocessing` are now more informative, and some error types have been changed to more specific ones.
Many asserts in `multiprocessing` are now more informative, and some error types have been changed to more specific ones. - Allen W. Smith (drallensmith on github)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had thought I was supposed to sign it?

@pitrou

pitrou commented Aug 29, 2017

Copy link
Copy Markdown
Member

Ok, I'm waiting for the CI results and I'll probably merge afterwards.

@drallensmith

Copy link
Copy Markdown
Contributor Author

Excellent; thanks for all of your help!

P.S. Sorry for my earlier impatience; I did try to make up for it by doing a (non-core, of course) review of someone else's PR...

@pitrou
pitrou merged commit bd73e72 into python:master Aug 29, 2017
@pitrou

pitrou commented Aug 29, 2017

Copy link
Copy Markdown
Member

Thank your for contributing this!

GadgetSteve pushed a commit to GadgetSteve/cpython that referenced this pull request Sep 10, 2017
* Make error message more informative

Replace assertions in error-reporting code with more-informative version that doesn't cause confusion over where and what the error is.

* Additional clarification + get travis to check

* Change from SystemError to TypeError

As suggested in PR comment by @pitrou, changing from SystemError; TypeError appears appropriate.

* NEWS file installation; ACKS addition (will do my best to justify it by additional work)

* Making current AssertionErrors in multiprocessing more informative

* Blurb added re multiprocessing managers.py, queues.py cleanup

* Further multiprocessing cleanup - went through pool.py

* Fix two asserts in multiprocessing/util.py

* Most asserts in multiprocessing more informative

* Didn't save right version

* Further work on multiprocessing error messages

* Correct typo

* Correct typo v2

* Blasted colon... serves me right for trying to work on two things at once

* Simplify NEWS entry

* Update 2017-08-18-17-16-38.bpo-5001.gwnthq.rst

* Update 2017-08-18-17-16-38.bpo-5001.gwnthq.rst

OK, never mind.

* Corrected (thanks to pitrou) error messages for notify

* Remove extraneous backslash in docstring.
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.

5 participants