[issue32317] sys.exc_clear() clears exception in other stack frames

2017-12-13 Thread Garrett Berg

Garrett Berg <googb...@gmail.com> added the comment:

I found a workaround, and probably the reason this has not been detected before:

import sys

def doclear2()
try:
1/0
except ZeroDivisionError:
sys.exc_clear()

try:
1/0
except ZeroDivisionError:
exc = sys.exc_info()[0]
print("third exc (before doclear):", exc)
doclear2()
exc = sys.exc_info()[0]
print("third exc (after  doclear):", exc)
assert sys.exc_info()[0] is ZeroDivisionError

The above passes. It seems that being inside a local exception block allows for 
the spec to hold, only the "local exception" is cleared.

I still think this is a pretty major bug, but it is good to know what the 
workaround/reason it's "been working" is.

--

___
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue32317>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue32317] sys.exc_clear() clears exception in other stack frames

2017-12-13 Thread Garrett Berg

New submission from Garrett Berg <googb...@gmail.com>:

# Summary
In python (2 or 3) it should always be valid to have a bare *raise* statement 
in an exception block.

try:
do_something()
except SomeException:
do_cleanup()
raise # always reraises SomeException with original stack trace

You can see this in the python exception documentation:
https://docs.python.org/2.7/tutorial/errors.html#raising-exceptions


# Reproduction of Failure of invariants
Example python file where this doesn't work:

from __future__ import print_function

import sys

def doclear():
"""This should basically be a noop"""
sys.exc_clear()

try:
1/0
except ZeroDivisionError:
exc = sys.exc_info()[0]
print("first exc:", exc)
assert exc is ZeroDivisionError

try:
1/0
except ZeroDivisionError:
exc = sys.exc_info()[0]
print("second exc (before doclear):", exc)
doclear()
exc = sys.exc_info()[0]
print("second exc (after  doclear):", exc)
assert sys.exc_info()[0] is ZeroDivisionError  # fails


Running in python2.7 gives:

first exc: 
second exc (before doclear): 
second exc (after  doclear): None
Traceback (most recent call last):
  File "doclear.py", line 23, in 
assert sys.exc_info()[0] is ZeroDivisionError  # fails
AssertionError


# Conclusion
There seems to be a bug in python 2.7 where it doesn't follow it's own spec.
[sys.exc_clear()](https://docs.python.org/2/library/sys.html#sys.exc_clear)
states:

> This function clears all information relating to the current or last
> exception that occurred in the current thread. After calling this function,
> exc_info() will return three None values until another exception is raised in
> the current thread or *the execution stack returns to a frame where another
> exception is being handled*.

The above code is clear example where sys.exc_clear() is changing the value of
sys.exc_info() *in a different stack frame*, which is against what is stated in
the above docs.

This bug makes it impossible to reraise with a bare raise statement when doing
cleanup that could use sys.exc_clear(). In other words, calling into functions
can *change your local state* with regards to what exception is being handled.

--
components: Interpreter Core
messages: 308264
nosy: Garrett Berg
priority: normal
severity: normal
status: open
title: sys.exc_clear() clears exception in other stack frames
type: behavior
versions: Python 2.7

___
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue32317>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue32317] sys.exc_clear() clears exception in other stack frames

2017-12-13 Thread Garrett Berg

Garrett Berg <googb...@gmail.com> added the comment:

I forgot to post this:

python --version



Python 2.7.14

--

___
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue32317>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue32208] Improve semaphore documentation

2017-12-04 Thread Garrett Berg

Garrett Berg <googb...@gmail.com> added the comment:

Gave it a shot!

--
keywords: +patch
pull_requests: +4620
stage: needs patch -> patch review

___
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue32208>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue32208] Improve semaphore docmentation

2017-12-03 Thread Garrett Berg

Garrett Berg <googb...@gmail.com> added the comment:

It may be desirable to change:

> Once awoken, decrement the counter by 1.

to

> Once awoke, decrement the counter (which is guaranteed to be positive) by 1.

Although this could be considered obvious since the counter can never go below 
zero.

--

___
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue32208>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue32208] Improve semaphore docmentation

2017-12-03 Thread Garrett Berg

Garrett Berg <googb...@gmail.com> added the comment:

Also, is "The order in which threads are awoken should not be relied on" 
actually true? Would it not be up to the OS specific scheduler? For instance, 
wouldn't the POSIX scheduler use a priority queue of some kind?

--

___
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue32208>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue32208] Improve semaphore docmentation

2017-12-03 Thread Garrett Berg

New submission from Garrett Berg <googb...@gmail.com>:

The definition of threading.Semaphore is confusing (for all versions of python 
docs)

https://docs.python.org/2/library/threading.html#semaphore-objects

 acquire([blocking])

It currently states the following:

> When invoked without arguments: if the internal counter is larger than zero 
> on entry, decrement it by one and return immediately. If it is zero on entry, 
> block, waiting until some other thread has called release() to make it larger 
> than zero. This is done with proper interlocking so that if multiple 
> acquire() calls are blocked, release() will wake exactly one of them up. The 
> implementation may pick one at random, so the order in which blocked threads 
> are awakened should not be relied on. There is no return value in this case.

However, after testing it myself I found that is missing a crutial detail. 
Let's step through the docs:

> If the internal counter is larger than zero on entry, decrement it by one and 
> return immediately.

This is exactly accurate and should stay the same

> If it is zero on entry, block, waiting until some other thread has called 
> release() to make it larger than zero. This is done with proper interlocking 
> so that if multiple acquire() calls are blocked, release() will wake exactly 
> one of them up. The implementation may pick one at random, so the order in 
> which blocked threads are awakened should not be relied on. There is no 
> return value in this case.

This is extremely confusing and I would like to rewrite it as follows:

> If it is zero on entry block until awoken by a call to ``release()``. Once 
> awoken, decrement the counter by 1. Exactly one thread will be awoken by a 
> call to ``release()``. The order in which threads are awoken should not be 
> relied on. ``None`` is returned in this case.

The major thing that was missing was that the **counter is decremented after 
the thread is awoken**. For instance, this code *generally* passes assertions:

```
#!/usr/bin/python2
import time
from threading import Thread, Semaphore

s = Semaphore(1)

def doit():
s.acquire()
print("did it")

th1 = Thread(target=doit)
th1.start()

th2 = Thread(target=doit)
th2.start()

time.sleep(0.2)

assert not th1.is_alive()
assert th2.is_alive()

s.release()
assert s._value == 1, "The value is increased to 1 MOMENTARILY"
start = time.time()
while sum([th2.is_alive(), th3.is_alive()]) > 1:
assert time.time() - start < 0.5
time.sleep(0.1)

assert s._value == 0, "when an aquire is awoken, THEN _value is decremented"
```

Obviously this behavior should not be relied on, but it gives a picture of what 
seems to be happening under the hood.

I came across this while trying to work through "The Little Book of 
Semaphores". After reading these docs I mistakingly thought that they didn't 
match Djestra's original semaphore since the values could not be negative. I 
now realize that while they may not match that implementation under the hood, 
they match it perfectly in practice since if you (for instance) ``acquire()`` 5 
times and then ``release()`` 5 times the value of Semaphore._value will be the 
same when all is said and done.

--
components: Library (Lib)
messages: 307536
nosy: Garrett Berg
priority: normal
severity: normal
status: open
title: Improve semaphore docmentation
type: enhancement
versions: Python 2.7, Python 3.4, Python 3.5, Python 3.6, Python 3.7, Python 3.8

___
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue32208>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com