Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gevent
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
gevent
Commits
491ff95b
Commit
491ff95b
authored
Dec 16, 2020
by
Jason Madden
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Handle more cases, and be more careful with hubs.
Implement timeouts for the no-hub case using spin locks.
parent
e3667c71
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
251 additions
and
92 deletions
+251
-92
docs/changes/1698.bugfix
docs/changes/1698.bugfix
+11
-1
src/gevent/_abstract_linkable.py
src/gevent/_abstract_linkable.py
+26
-4
src/gevent/_gevent_c_abstract_linkable.pxd
src/gevent/_gevent_c_abstract_linkable.pxd
+6
-4
src/gevent/_gevent_c_semaphore.pxd
src/gevent/_gevent_c_semaphore.pxd
+17
-2
src/gevent/_semaphore.py
src/gevent/_semaphore.py
+140
-41
src/gevent/libev/corecffi.py
src/gevent/libev/corecffi.py
+4
-0
src/gevent/testing/errorhandler.py
src/gevent/testing/errorhandler.py
+13
-8
src/gevent/tests/test__semaphore.py
src/gevent/tests/test__semaphore.py
+24
-15
src/gevent/thread.py
src/gevent/thread.py
+10
-17
No files found.
docs/changes/1698.bugfix
View file @
491ff95b
...
@@ -2,4 +2,14 @@ Improve the ability to use monkey-patched locks, and
...
@@ -2,4 +2,14 @@ Improve the ability to use monkey-patched locks, and
`gevent.lock.BoundedSemaphore`, across threads, especially when the
`gevent.lock.BoundedSemaphore`, across threads, especially when the
various threads might not have a gevent hub or any other active
various threads might not have a gevent hub or any other active
greenlets. In particular, this handles some cases that previously
greenlets. In particular, this handles some cases that previously
raised ``LoopExit``.
raised ``LoopExit`` or would hang.
The semaphore tries to avoid creating a hub if it seems unnecessary,
automatically creating one in the single-threaded case when it would
block, but not in the multi-threaded case. While the differences
should be correctly detected, it's possible there are corner cases
where they might not be.
If your application appears to hang acquiring semaphores, but adding a
call to ``gevent.get_hub()`` in the thread attempting to acquire the
semaphore before doing so fixes it, please file an issue.
src/gevent/_abstract_linkable.py
View file @
491ff95b
...
@@ -145,11 +145,16 @@ class AbstractLinkable(object):
...
@@ -145,11 +145,16 @@ class AbstractLinkable(object):
def
_getcurrent
(
self
):
def
_getcurrent
(
self
):
return
getcurrent
()
# pylint:disable=undefined-variable
return
getcurrent
()
# pylint:disable=undefined-variable
def
_get_thread_ident
(
self
):
return
_get_thread_ident
()
def
_capture_hub
(
self
,
create
):
def
_capture_hub
(
self
,
create
):
# Subclasses should call this as the first action from any
# Subclasses should call this as the first action from any
# public method that could, in theory, block and switch
# public method that could, in theory, block and switch
# to the hub. This may release the GIL. It may
# to the hub. This may release the GIL. It may
# raise InvalidThreadUseError if the result would
# raise InvalidThreadUseError if the result would
# First, detect a dead hub and drop it.
while
1
:
while
1
:
my_hub
=
self
.
hub
my_hub
=
self
.
hub
if
my_hub
is
None
:
if
my_hub
is
None
:
...
@@ -178,20 +183,30 @@ class AbstractLinkable(object):
...
@@ -178,20 +183,30 @@ class AbstractLinkable(object):
get_hub_if_exists
(),
get_hub_if_exists
(),
getcurrent
()
# pylint:disable=undefined-variable
getcurrent
()
# pylint:disable=undefined-variable
)
)
return
1
return
self
.
hub
def
_check_and_notify
(
self
):
def
_check_and_notify
(
self
):
# If this object is ready to be notified, begin the process.
# If this object is ready to be notified, begin the process.
if
self
.
ready
()
and
self
.
_links
and
not
self
.
_notifier
:
if
self
.
ready
()
and
self
.
_links
and
not
self
.
_notifier
:
hub
=
None
try
:
try
:
self
.
_capture_hub
(
Tru
e
)
# Must create, we need it.
hub
=
self
.
_capture_hub
(
Fals
e
)
# Must create, we need it.
except
InvalidThreadUseError
:
except
InvalidThreadUseError
:
# The current hub doesn't match self.hub. That's OK,
# The current hub doesn't match self.hub. That's OK,
# we still want to start the notifier in the thread running
# we still want to start the notifier in the thread running
# self.hub (because the links probably contains greenlet.switch
# self.hub (because the links probably contains greenlet.switch
# calls valid only in that hub)
# calls valid only in that hub)
pass
pass
self
.
_notifier
=
self
.
hub
.
loop
.
run_callback
(
self
.
_notify_links
,
[])
if
hub
is
not
None
:
self
.
_notifier
=
hub
.
loop
.
run_callback
(
self
.
_notify_links
,
[])
else
:
# Hmm, no hub. We must be the only thing running. Then its OK
# to just directly call the callbacks.
self
.
_notifier
=
1
try
:
self
.
_notify_links
([])
finally
:
self
.
_notifier
=
None
def
_notify_link_list
(
self
,
links
):
def
_notify_link_list
(
self
,
links
):
# The core of the _notify_links method to notify
# The core of the _notify_links method to notify
...
@@ -201,6 +216,9 @@ class AbstractLinkable(object):
...
@@ -201,6 +216,9 @@ class AbstractLinkable(object):
only_while_ready
=
not
self
.
_notify_all
only_while_ready
=
not
self
.
_notify_all
final_link
=
links
[
-
1
]
final_link
=
links
[
-
1
]
done
=
set
()
# of ids
done
=
set
()
# of ids
hub
=
self
.
hub
if
hub
is
None
:
hub
=
get_hub_if_exists
()
while
links
:
# remember this can be mutated
while
links
:
# remember this can be mutated
if
only_while_ready
and
not
self
.
ready
():
if
only_while_ready
and
not
self
.
ready
():
break
break
...
@@ -222,7 +240,11 @@ class AbstractLinkable(object):
...
@@ -222,7 +240,11 @@ class AbstractLinkable(object):
except
:
# pylint:disable=bare-except
except
:
# pylint:disable=bare-except
# We're running in the hub, errors must not escape.
# We're running in the hub, errors must not escape.
self
.
hub
.
handle_error
((
link
,
self
),
*
sys
.
exc_info
())
if
hub
is
not
None
:
hub
.
handle_error
((
link
,
self
),
*
sys
.
exc_info
())
else
:
import
traceback
traceback
.
print_exc
()
if
link
is
final_link
:
if
link
is
final_link
:
break
break
...
...
src/gevent/_gevent_c_abstract_linkable.pxd
View file @
491ff95b
...
@@ -51,13 +51,10 @@ cdef class AbstractLinkable(object):
...
@@ -51,13 +51,10 @@ cdef class AbstractLinkable(object):
cpdef
unlink
(
self
,
callback
)
cpdef
unlink
(
self
,
callback
)
cdef
_check_and_notify
(
self
)
cdef
_check_and_notify
(
self
)
cdef
int
_capture_hub
(
self
,
bint
create
)
except
-
1
cdef
SwitchOutGreenletWithLoop
_capture_hub
(
self
,
bint
create
)
cdef
__wait_to_be_notified
(
self
,
bint
rawlink
)
cdef
__wait_to_be_notified
(
self
,
bint
rawlink
)
cdef
void
_quiet_unlink_all
(
self
,
obj
)
# suppress exceptions
cdef
void
_quiet_unlink_all
(
self
,
obj
)
# suppress exceptions
cdef
_allocate_lock
(
self
)
cdef
greenlet
_getcurrent
(
self
)
cdef
int
_switch_to_hub
(
self
,
the_hub
)
except
-
1
cdef
int
_switch_to_hub
(
self
,
the_hub
)
except
-
1
@
cython
.
nonecheck
(
False
)
@
cython
.
nonecheck
(
False
)
...
@@ -72,3 +69,8 @@ cdef class AbstractLinkable(object):
...
@@ -72,3 +69,8 @@ cdef class AbstractLinkable(object):
cdef
_wait_core
(
self
,
timeout
,
catch
=*
)
cdef
_wait_core
(
self
,
timeout
,
catch
=*
)
cdef
_wait_return_value
(
self
,
bint
waited
,
bint
wait_success
)
cdef
_wait_return_value
(
self
,
bint
waited
,
bint
wait_success
)
cdef
_wait
(
self
,
timeout
=*
)
cdef
_wait
(
self
,
timeout
=*
)
# Unreleated utilities
cdef
_allocate_lock
(
self
)
cdef
greenlet
_getcurrent
(
self
)
cdef
_get_thread_ident
(
self
)
src/gevent/_gevent_c_semaphore.pxd
View file @
491ff95b
...
@@ -5,16 +5,22 @@ from gevent._gevent_c_abstract_linkable cimport AbstractLinkable
...
@@ -5,16 +5,22 @@ from gevent._gevent_c_abstract_linkable cimport AbstractLinkable
from
gevent._gevent_c_hub_local
cimport
get_hub_if_exists
from
gevent._gevent_c_hub_local
cimport
get_hub_if_exists
from
gevent._gevent_c_hub_local
cimport
get_hub_noargs
as
get_hub
from
gevent._gevent_c_hub_local
cimport
get_hub_noargs
as
get_hub
cdef
Timeout
cdef
InvalidThreadUseError
cdef
InvalidThreadUseError
cdef
LoopExit
cdef
LoopExit
cdef
spawn_raw
cdef
Timeout
cdef
_native_sleep
cdef
_native_sleep
cdef
monotonic
cdef
spawn_raw
cdef
class
_LockReleaseLink
(
object
):
cdef
object
lock
cdef
class
Semaphore
(
AbstractLinkable
):
cdef
class
Semaphore
(
AbstractLinkable
):
cdef
public
int
counter
cdef
public
int
counter
cdef
long
_multithreaded
cpdef
bint
locked
(
self
)
cpdef
bint
locked
(
self
)
cpdef
int
release
(
self
)
except
-
1000
cpdef
int
release
(
self
)
except
-
1000
# We don't really want this to be public, but
# We don't really want this to be public, but
...
@@ -38,6 +44,15 @@ cdef class Semaphore(AbstractLinkable):
...
@@ -38,6 +44,15 @@ cdef class Semaphore(AbstractLinkable):
cdef
__acquire_from_other_thread
(
self
,
tuple
args
,
bint
blocking
,
timeout
)
cdef
__acquire_from_other_thread
(
self
,
tuple
args
,
bint
blocking
,
timeout
)
cpdef
__acquire_from_other_thread_cb
(
self
,
list
results
,
bint
blocking
,
timeout
,
thread_lock
)
cpdef
__acquire_from_other_thread_cb
(
self
,
list
results
,
bint
blocking
,
timeout
,
thread_lock
)
cdef
__add_link
(
self
,
link
)
cdef
__acquire_using_two_hubs
(
self
,
SwitchOutGreenletWithLoop
hub_for_this_thread
,
current_greenlet
,
timeout
)
cdef
__acquire_using_other_hub
(
self
,
SwitchOutGreenletWithLoop
owning_hub
,
timeout
)
cdef
bint
__acquire_without_hubs
(
self
,
timeout
)
cdef
bint
__spin_on_native_lock
(
self
,
thread_lock
,
timeout
)
cdef
class
BoundedSemaphore
(
Semaphore
):
cdef
class
BoundedSemaphore
(
Semaphore
):
cdef
readonly
int
_initial_value
cdef
readonly
int
_initial_value
...
...
src/gevent/_semaphore.py
View file @
491ff95b
...
@@ -16,8 +16,9 @@ __all__ = [
...
@@ -16,8 +16,9 @@ __all__ = [
from
time
import
sleep
as
_native_sleep
from
time
import
sleep
as
_native_sleep
from
gevent.
exceptions
import
LoopExit
from
gevent.
_compat
import
monotonic
from
gevent.exceptions
import
InvalidThreadUseError
from
gevent.exceptions
import
InvalidThreadUseError
from
gevent.exceptions
import
LoopExit
from
gevent.timeout
import
Timeout
from
gevent.timeout
import
Timeout
def
_get_linkable
():
def
_get_linkable
():
...
@@ -30,7 +31,16 @@ from gevent._hub_local import get_hub_if_exists
...
@@ -30,7 +31,16 @@ from gevent._hub_local import get_hub_if_exists
from
gevent._hub_local
import
get_hub
from
gevent._hub_local
import
get_hub
from
gevent.hub
import
spawn_raw
from
gevent.hub
import
spawn_raw
class
_LockReleaseLink
(
object
):
__slots__
=
(
'lock'
,
)
def
__init__
(
self
,
lock
):
self
.
lock
=
lock
def
__call__
(
self
,
_
):
self
.
lock
.
release
()
class
Semaphore
(
AbstractLinkable
):
# pylint:disable=undefined-variable
class
Semaphore
(
AbstractLinkable
):
# pylint:disable=undefined-variable
"""
"""
...
@@ -61,10 +71,17 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
...
@@ -61,10 +71,17 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
.. versionchanged:: 1.5a3
.. versionchanged:: 1.5a3
The low-level ``rawlink`` method (most users won't use this) now automatically
The low-level ``rawlink`` method (most users won't use this) now automatically
unlinks waiters before calling them.
unlinks waiters before calling them.
.. versionchanged:: NEXT
Improved support for multi-threaded usage. When multi-threaded usage is detected,
instances will no longer create the thread's hub if it's not present.
"""
"""
__slots__
=
(
__slots__
=
(
'counter'
,
'counter'
,
# Integer. Set to 0 initially. Set to the ident of the first
# thread that acquires us. If we later see a different thread
# ident, set to -1.
'_multithreaded'
,
)
)
def
__init__
(
self
,
value
=
1
,
hub
=
None
):
def
__init__
(
self
,
value
=
1
,
hub
=
None
):
...
@@ -73,6 +90,7 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
...
@@ -73,6 +90,7 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
raise
ValueError
(
"semaphore initial value must be >= 0"
)
raise
ValueError
(
"semaphore initial value must be >= 0"
)
super
(
Semaphore
,
self
).
__init__
(
hub
)
super
(
Semaphore
,
self
).
__init__
(
hub
)
self
.
_notify_all
=
False
self
.
_notify_all
=
False
self
.
_multithreaded
=
0
def
__str__
(
self
):
def
__str__
(
self
):
return
'<%s at 0x%x counter=%s _links[%s]>'
%
(
return
'<%s at 0x%x counter=%s _links[%s]>'
%
(
...
@@ -176,21 +194,29 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
...
@@ -176,21 +194,29 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
the semaphore was acquired, False will be returned. (Note that this can still
the semaphore was acquired, False will be returned. (Note that this can still
raise a ``Timeout`` exception, if some other caller had already started a timer.)
raise a ``Timeout`` exception, if some other caller had already started a timer.)
"""
"""
# pylint:disable=too-many-return-statements,too-many-branches
# Sadly, the body of this method is rather complicated.
if
self
.
_multithreaded
==
0
:
self
.
_multithreaded
=
self
.
_get_thread_ident
()
elif
self
.
_multithreaded
!=
self
.
_get_thread_ident
():
self
.
_multithreaded
=
-
1
# We conceptually now belong to the hub of the thread that
# We conceptually now belong to the hub of the thread that
# called this, whether or not we have to block. Note that we
# called this, whether or not we have to block. Note that we
# cannot force it to be created yet, because Semaphore is used
# cannot force it to be created yet, because Semaphore is used
# by importlib.ModuleLock which is used when importing the hub
# by importlib.ModuleLock which is used when importing the hub
# itself! This also checks for cross-thread issues.
# itself! This also checks for cross-thread issues.
invalid_thread_use
=
None
try
:
try
:
self
.
_capture_hub
(
False
)
self
.
_capture_hub
(
False
)
except
InvalidThreadUseError
as
e
:
except
InvalidThreadUseError
as
e
:
# My hub belongs to some other thread. We didn't release the GIL/object lock
# My hub belongs to some other thread. We didn't release the GIL/object lock
# by raising the exception, so we know this is still true.
# by raising the exception, so we know this is still true.
args
=
e
.
args
invalid_thread_use
=
e
.
args
e
=
None
e
=
None
if
not
self
.
counter
and
blocking
:
if
not
self
.
counter
and
blocking
:
# We would need to block. So coordinate with the main hub.
# We would need to block. So coordinate with the main hub.
return
self
.
__acquire_from_other_thread
(
args
,
blocking
,
timeout
)
return
self
.
__acquire_from_other_thread
(
invalid_thread_use
,
blocking
,
timeout
)
if
self
.
counter
>
0
:
if
self
.
counter
>
0
:
self
.
counter
-=
1
self
.
counter
-=
1
...
@@ -199,6 +225,19 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
...
@@ -199,6 +225,19 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
if
not
blocking
:
if
not
blocking
:
return
False
return
False
if
self
.
_multithreaded
!=
-
1
and
self
.
hub
is
None
:
# pylint:disable=access-member-before-definition
self
.
hub
=
get_hub
()
# pylint:disable=attribute-defined-outside-init
if
self
.
hub
is
None
and
not
invalid_thread_use
:
# Someone else is holding us. There's not a hub here,
# nor is there a hub in that thread. We'll need to use regular locks.
# This will be unfair to yet a third thread that tries to use us with greenlets.
return
self
.
__acquire_from_other_thread
(
(
None
,
None
,
self
.
_getcurrent
(),
"NoHubs"
),
blocking
,
timeout
)
# self._wait may drop both the GIL and the _lock_lock.
# self._wait may drop both the GIL and the _lock_lock.
# By the time we regain control, both have been reacquired.
# By the time we regain control, both have been reacquired.
try
:
try
:
...
@@ -237,8 +276,13 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
...
@@ -237,8 +276,13 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
def
__exit__
(
self
,
t
,
v
,
tb
):
def
__exit__
(
self
,
t
,
v
,
tb
):
self
.
release
()
self
.
release
()
def
__add_link
(
self
,
link
):
if
not
self
.
_notifier
:
self
.
rawlink
(
link
)
else
:
self
.
_notifier
.
args
[
0
].
append
(
link
)
def
__acquire_from_other_thread
(
self
,
ex_args
,
blocking
,
timeout
):
def
__acquire_from_other_thread
(
self
,
ex_args
,
blocking
,
timeout
):
# pylint:disable=too-many-branches
assert
blocking
assert
blocking
# Some other hub owns this object. We must ask it to wake us
# Some other hub owns this object. We must ask it to wake us
# up. In general, we can't use a Python-level ``Lock`` because
# up. In general, we can't use a Python-level ``Lock`` because
...
@@ -248,6 +292,9 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
...
@@ -248,6 +292,9 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
#
#
# So we need to do so in a way that cooperates with *two*
# So we need to do so in a way that cooperates with *two*
# hubs. That's what an async watcher is built for.
# hubs. That's what an async watcher is built for.
#
# Of course, if we don't actually have two hubs, then we must find some other
# solution. That involves using a lock.
# We have to take an action that drops the GIL and drops the object lock
# We have to take an action that drops the GIL and drops the object lock
# to allow the main thread (the thread for our hub) to advance.
# to allow the main thread (the thread for our hub) to advance.
...
@@ -255,45 +302,27 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
...
@@ -255,45 +302,27 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
hub_for_this_thread
=
ex_args
[
1
]
hub_for_this_thread
=
ex_args
[
1
]
current_greenlet
=
ex_args
[
2
]
current_greenlet
=
ex_args
[
2
]
if
hub_for_this_thread
is
None
and
timeout
is
None
:
if
owning_hub
is
None
and
hub_for_this_thread
is
None
:
return
self
.
__acquire_without_hubs
(
timeout
)
if
hub_for_this_thread
is
None
:
# Probably a background worker thread. We don't want to create
# Probably a background worker thread. We don't want to create
# the hub if not needed, and since it didn't exist there are no
# the hub if not needed, and since it didn't exist there are no
# other greenlets that we could yield to anyway, so there's nothing
# other greenlets that we could yield to anyway, so there's nothing
# to block and no reason to try to avoid blocking, so using a native
# to block and no reason to try to avoid blocking, so using a native
# lock is the simplest way to go.
# lock is the simplest way to go.
thread_lock
=
self
.
_allocate_lock
()
return
self
.
__acquire_using_other_hub
(
owning_hub
,
timeout
)
thread_lock
.
acquire
()
results
=
[]
owning_hub
.
loop
.
run_callback
(
spawn_raw
,
self
.
__acquire_from_other_thread_cb
,
results
,
blocking
,
timeout
,
thread_lock
)
# This can't actually do anything until we drop the object lock.
self
.
_drop_lock_for_switch_out
()
# We MUST use a blocking acquire here, or at least be sure we keep going
# until we acquire it. If we timed out waiting here,
# just before the callback runs, then we would be out of sync.
# However, Python 2 has terrible behaviour where lock acquires can't
# be interrupted, so we use a spin loop
try
:
while
not
thread_lock
.
acquire
(
0
):
_native_sleep
(
0.005
)
finally
:
self
.
_acquire_lock_for_switch_in
()
return
results
[
0
]
# We either already had a hub, or we wanted a timeout, in which case
# we need to use the hub.
if
hub_for_this_thread
is
None
:
hub_for_this_thread
=
get_hub
()
# We have a hub we don't want to block. Use an async watcher
# We have a hub we don't want to block. Use an async watcher
# and ask the next releaser of this object to wake us up.
# and ask the next releaser of this object to wake us up.
return
self
.
__acquire_using_two_hubs
(
hub_for_this_thread
,
current_greenlet
,
timeout
)
def
__acquire_using_two_hubs
(
self
,
hub_for_this_thread
,
current_greenlet
,
timeout
):
# Allocating and starting the watcher *could* release the GIL.
# Allocating and starting the watcher *could* release the GIL.
# with the libev corcext, allocating won't, but starting briefly will.
# with the libev corcext, allocating won't, but starting briefly will.
# With other backends, allocating might, and starting might also.
# With other backends, allocating might, and starting might also.
...
@@ -312,10 +341,7 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
...
@@ -312,10 +341,7 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
assert
self
.
counter
>=
0
,
(
self
,)
assert
self
.
counter
>=
0
,
(
self
,)
return
True
return
True
if
not
self
.
_notifier
:
self
.
__add_link
(
send
)
self
.
rawlink
(
send
)
else
:
self
.
_notifier
.
args
[
0
].
append
(
send
)
# Releases the object lock
# Releases the object lock
self
.
_switch_to_hub
(
hub_for_this_thread
)
self
.
_switch_to_hub
(
hub_for_this_thread
)
...
@@ -343,6 +369,76 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
...
@@ -343,6 +369,76 @@ class Semaphore(AbstractLinkable): # pylint:disable=undefined-variable
thread_lock
.
release
()
thread_lock
.
release
()
return
result
return
result
def
__acquire_using_other_hub
(
self
,
owning_hub
,
timeout
):
assert
owning_hub
is
not
get_hub_if_exists
()
thread_lock
=
self
.
_allocate_lock
()
thread_lock
.
acquire
()
results
=
[]
owning_hub
.
loop
.
run_callback
(
spawn_raw
,
self
.
__acquire_from_other_thread_cb
,
results
,
1
,
# blocking,
timeout
,
# timeout,
thread_lock
)
# We MUST use a blocking acquire here, or at least be sure we keep going
# until we acquire it. If we timed out waiting here,
# just before the callback runs, then we would be out of sync.
self
.
__spin_on_native_lock
(
thread_lock
,
None
)
return
results
[
0
]
def
__acquire_without_hubs
(
self
,
timeout
):
thread_lock
=
self
.
_allocate_lock
()
thread_lock
.
acquire
()
absolute_expiration
=
0
begin
=
0
if
timeout
:
absolute_expiration
=
monotonic
()
+
timeout
# Cython won't compile a lambda here
link
=
_LockReleaseLink
(
thread_lock
)
while
1
:
self
.
__add_link
(
link
)
if
absolute_expiration
:
begin
=
monotonic
()
got_native
=
self
.
__spin_on_native_lock
(
thread_lock
,
timeout
)
self
.
_quiet_unlink_all
(
link
)
if
got_native
:
if
self
.
acquire
(
0
):
return
True
if
absolute_expiration
:
now
=
monotonic
()
if
now
>=
absolute_expiration
:
return
False
duration
=
now
-
begin
timeout
-=
duration
if
timeout
<=
0
:
return
False
def
__spin_on_native_lock
(
self
,
thread_lock
,
timeout
):
expiration
=
0
if
timeout
:
expiration
=
monotonic
()
+
timeout
self
.
_drop_lock_for_switch_out
()
try
:
# TODO: When timeout is given and the lock supports that
# (Python 3), pass that.
# Python 2 has terrible behaviour where lock acquires can't
# be interrupted, so we use a spin loop
while
not
thread_lock
.
acquire
(
0
):
if
expiration
and
monotonic
()
>=
expiration
:
return
False
_native_sleep
(
0.001
)
return
True
finally
:
self
.
_acquire_lock_for_switch_in
()
class
BoundedSemaphore
(
Semaphore
):
class
BoundedSemaphore
(
Semaphore
):
"""
"""
BoundedSemaphore(value=1) -> BoundedSemaphore
BoundedSemaphore(value=1) -> BoundedSemaphore
...
@@ -356,6 +452,10 @@ class BoundedSemaphore(Semaphore):
...
@@ -356,6 +452,10 @@ class BoundedSemaphore(Semaphore):
If not given, *value* defaults to 1.
If not given, *value* defaults to 1.
"""
"""
__slots__
=
(
'_initial_value'
,
)
#: For monkey-patching, allow changing the class of error we raise
#: For monkey-patching, allow changing the class of error we raise
_OVER_RELEASE_ERROR
=
ValueError
_OVER_RELEASE_ERROR
=
ValueError
...
@@ -370,7 +470,6 @@ class BoundedSemaphore(Semaphore):
...
@@ -370,7 +470,6 @@ class BoundedSemaphore(Semaphore):
"""
"""
if
self
.
counter
>=
self
.
_initial_value
:
if
self
.
counter
>=
self
.
_initial_value
:
raise
self
.
_OVER_RELEASE_ERROR
(
"Semaphore released too many times"
)
raise
self
.
_OVER_RELEASE_ERROR
(
"Semaphore released too many times"
)
counter
=
Semaphore
.
release
(
self
)
counter
=
Semaphore
.
release
(
self
)
# When we are absolutely certain that no one holds this semaphore,
# When we are absolutely certain that no one holds this semaphore,
# release our hub and go back to floating. This assists in cross-thread
# release our hub and go back to floating. This assists in cross-thread
...
...
src/gevent/libev/corecffi.py
View file @
491ff95b
...
@@ -294,6 +294,10 @@ class loop(AbstractLoop):
...
@@ -294,6 +294,10 @@ class loop(AbstractLoop):
libev
.
ev_timer_stop
(
self
.
_timer0
)
libev
.
ev_timer_stop
(
self
.
_timer0
)
def
_setup_for_run_callback
(
self
):
def
_setup_for_run_callback
(
self
):
# XXX: libuv needs to start the callback timer to be sure
# that the loop wakes up and calls this. Our C version doesn't
# do this.
# self._start_callback_timer()
self
.
ref
()
# we should go through the loop now
self
.
ref
()
# we should go through the loop now
def
destroy
(
self
):
def
destroy
(
self
):
...
...
src/gevent/testing/errorhandler.py
View file @
491ff95b
...
@@ -22,31 +22,36 @@ from functools import wraps
...
@@ -22,31 +22,36 @@ from functools import wraps
def
wrap_error_fatal
(
method
):
def
wrap_error_fatal
(
method
):
import
gevent
from
gevent._hub_local
import
get_hub_class
system_error
=
ge
vent
.
get_hub
().
SYSTEM_ERROR
system_error
=
ge
t_hub_class
().
SYSTEM_ERROR
@
wraps
(
method
)
@
wraps
(
method
)
def
wrapper
(
self
,
*
args
,
**
kwargs
):
def
wrapper
(
self
,
*
args
,
**
kwargs
):
# XXX should also be able to do gevent.SYSTEM_ERROR = object
# XXX should also be able to do gevent.SYSTEM_ERROR = object
# which is a global default to all hubs
# which is a global default to all hubs
ge
vent
.
get_hub
().
SYSTEM_ERROR
=
object
ge
t_hub_class
().
SYSTEM_ERROR
=
object
try
:
try
:
return
method
(
self
,
*
args
,
**
kwargs
)
return
method
(
self
,
*
args
,
**
kwargs
)
finally
:
finally
:
ge
vent
.
get_hub
().
SYSTEM_ERROR
=
system_error
ge
t_hub_class
().
SYSTEM_ERROR
=
system_error
return
wrapper
return
wrapper
def
wrap_restore_handle_error
(
method
):
def
wrap_restore_handle_error
(
method
):
import
gevent
from
gevent._hub_local
import
get_hub_if_exists
old
=
gevent
.
get_hub
().
handle_error
from
gevent
import
getcurrent
@
wraps
(
method
)
@
wraps
(
method
)
def
wrapper
(
self
,
*
args
,
**
kwargs
):
def
wrapper
(
self
,
*
args
,
**
kwargs
):
try
:
try
:
return
method
(
self
,
*
args
,
**
kwargs
)
return
method
(
self
,
*
args
,
**
kwargs
)
finally
:
finally
:
gevent
.
get_hub
().
handle_error
=
old
# Remove any customized handle_error, if set on the
# instance.
try
:
del
get_hub_if_exists
().
handle_error
except
AttributeError
:
pass
if
self
.
peek_error
()[
0
]
is
not
None
:
if
self
.
peek_error
()[
0
]
is
not
None
:
ge
vent
.
ge
tcurrent
().
throw
(
*
self
.
peek_error
()[
1
:])
getcurrent
().
throw
(
*
self
.
peek_error
()[
1
:])
return
wrapper
return
wrapper
src/gevent/tests/test__semaphore.py
View file @
491ff95b
...
@@ -16,7 +16,6 @@ from gevent.lock import BoundedSemaphore
...
@@ -16,7 +16,6 @@ from gevent.lock import BoundedSemaphore
import
gevent.testing
as
greentest
import
gevent.testing
as
greentest
from
gevent.testing
import
timing
from
gevent.testing
import
timing
from
gevent.testing
import
flaky
class
TestSemaphore
(
greentest
.
TestCase
):
class
TestSemaphore
(
greentest
.
TestCase
):
...
@@ -132,6 +131,7 @@ class TestSemaphoreMultiThread(greentest.TestCase):
...
@@ -132,6 +131,7 @@ class TestSemaphoreMultiThread(greentest.TestCase):
acquired
,
exc_info
,
acquired
,
exc_info
,
**
thread_acquire_kwargs
**
thread_acquire_kwargs
))
))
t
.
daemon
=
True
t
.
start
()
t
.
start
()
thread_running
.
wait
(
10
)
# implausibly large time
thread_running
.
wait
(
10
)
# implausibly large time
if
release
:
if
release
:
...
@@ -151,6 +151,14 @@ class TestSemaphoreMultiThread(greentest.TestCase):
...
@@ -151,6 +151,14 @@ class TestSemaphoreMultiThread(greentest.TestCase):
self
.
assertEqual
(
acquired
,
[
True
])
self
.
assertEqual
(
acquired
,
[
True
])
if
not
release
and
thread_acquire_kwargs
.
get
(
"timeout"
):
# Spin the loop to be sure that the timeout has a chance to
# process. Interleave this with something that drops the GIL
# so the background thread has a chance to notice that.
for
_
in
range
(
3
):
gevent
.
idle
()
if
thread_acquired
.
wait
(
timing
.
LARGE_TICK
):
break
thread_acquired
.
wait
(
timing
.
LARGE_TICK
*
5
)
thread_acquired
.
wait
(
timing
.
LARGE_TICK
*
5
)
if
require_thread_acquired_to_finish
:
if
require_thread_acquired_to_finish
:
self
.
assertTrue
(
thread_acquired
.
is_set
())
self
.
assertTrue
(
thread_acquired
.
is_set
())
...
@@ -210,9 +218,12 @@ class TestSemaphoreMultiThread(greentest.TestCase):
...
@@ -210,9 +218,12 @@ class TestSemaphoreMultiThread(greentest.TestCase):
acquired
,
exc_info
,
acquired
,
exc_info
,
timeout
=
timing
.
LARGE_TICK
timeout
=
timing
.
LARGE_TICK
))
))
thread
.
daemon
=
True
gevent
.
idle
()
gevent
.
idle
()
sem
.
release
()
sem
.
release
()
glet
.
join
()
glet
.
join
()
for
_
in
range
(
3
):
gevent
.
idle
()
thread
.
join
(
timing
.
LARGE_TICK
)
thread
.
join
(
timing
.
LARGE_TICK
)
self
.
assertEqual
(
glet
.
value
,
True
)
self
.
assertEqual
(
glet
.
value
,
True
)
...
@@ -260,13 +271,20 @@ class TestSemaphoreMultiThread(greentest.TestCase):
...
@@ -260,13 +271,20 @@ class TestSemaphoreMultiThread(greentest.TestCase):
sem
.
acquire
(
*
acquire_args
)
sem
.
acquire
(
*
acquire_args
)
sem
.
release
()
sem
.
release
()
results
[
ix
]
=
i
results
[
ix
]
=
i
if
not
create_hub
:
# We don't artificially create the hub.
self
.
assertIsNone
(
get_hub_if_exists
(),
(
get_hub_if_exists
(),
ix
,
i
)
)
if
create_hub
and
i
%
10
==
0
:
if
create_hub
and
i
%
10
==
0
:
gevent
.
sleep
(
timing
.
SMALLEST_RELIABLE_DELAY
)
gevent
.
sleep
(
timing
.
SMALLEST_RELIABLE_DELAY
)
elif
i
%
100
==
0
:
elif
i
%
100
==
0
:
native_sleep
(
timing
.
SMALLEST_RELIABLE_DELAY
)
native_sleep
(
timing
.
SMALLEST_RELIABLE_DELAY
)
except
Exception
as
ex
:
# pylint:disable=broad-except
except
Exception
as
ex
:
# pylint:disable=broad-except
import
traceback
;
traceback
.
print_exc
()
import
traceback
;
traceback
.
print_exc
()
results
[
ix
]
=
ex
results
[
ix
]
=
str
(
ex
)
ex
=
None
finally
:
finally
:
hub
=
get_hub_if_exists
()
hub
=
get_hub_if_exists
()
if
hub
is
not
None
:
if
hub
is
not
None
:
...
@@ -285,23 +303,14 @@ class TestSemaphoreMultiThread(greentest.TestCase):
...
@@ -285,23 +303,14 @@ class TestSemaphoreMultiThread(greentest.TestCase):
while
t1
.
is_alive
()
or
t2
.
is_alive
():
while
t1
.
is_alive
()
or
t2
.
is_alive
():
cur
=
list
(
results
)
cur
=
list
(
results
)
t1
.
join
(
2
)
t1
.
join
(
7
)
t2
.
join
(
2
)
t2
.
join
(
7
)
if
cur
==
results
:
if
cur
==
results
:
# Hmm, after two seconds, no progress
# Hmm, after two seconds, no progress
print
(
"No progress!"
,
cur
,
results
,
t1
,
t2
)
from
gevent.util
import
print_run_info
print_run_info
()
run
=
False
run
=
False
break
break
try
:
self
.
assertEqual
(
results
,
[
count
-
1
,
count
-
1
])
self
.
assertEqual
(
results
,
[
count
-
1
,
count
-
1
])
except
AssertionError
:
if
greentest
.
PY2
:
flaky
.
reraiseFlakyTestRaceCondition
()
else
:
raise
def
test_dueling_threads_timeout
(
self
):
def
test_dueling_threads_timeout
(
self
):
self
.
test_dueling_threads
((
True
,
4
))
self
.
test_dueling_threads
((
True
,
4
))
...
...
src/gevent/thread.py
View file @
491ff95b
...
@@ -116,14 +116,7 @@ class LockType(BoundedSemaphore):
...
@@ -116,14 +116,7 @@ class LockType(BoundedSemaphore):
if
timeout
>
self
.
_TIMEOUT_MAX
:
if
timeout
>
self
.
_TIMEOUT_MAX
:
raise
OverflowError
(
'timeout value is too large'
)
raise
OverflowError
(
'timeout value is too large'
)
acquired
=
BoundedSemaphore
.
acquire
(
self
,
0
)
if
not
acquired
and
getcurrent
()
is
not
get_hub_if_exists
()
and
blocking
and
not
timeout
:
# If we would block forever, and we're not in the hub, and a trivial non-blocking
# check didn't get us the lock, then try to run pending callbacks that might
# release the lock.
sleep
()
if
not
acquired
:
try
:
try
:
acquired
=
BoundedSemaphore
.
acquire
(
self
,
blocking
,
timeout
)
acquired
=
BoundedSemaphore
.
acquire
(
self
,
blocking
,
timeout
)
except
LoopExit
:
except
LoopExit
:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment