Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
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
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
592ada9b
Commit
592ada9b
authored
Sep 25, 2014
by
Yury Selivanov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
asyncio: Improve canceled timer handles cleanup. Closes issue #22448.
Patch by Joshua Moore-Oliva.
parent
8c0e0ab7
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
159 additions
and
26 deletions
+159
-26
Lib/asyncio/base_events.py
Lib/asyncio/base_events.py
+37
-7
Lib/asyncio/events.py
Lib/asyncio/events.py
+20
-9
Lib/test/test_asyncio/test_base_events.py
Lib/test/test_asyncio/test_base_events.py
+77
-7
Lib/test/test_asyncio/test_events.py
Lib/test/test_asyncio/test_events.py
+11
-3
Misc/NEWS
Misc/NEWS
+14
-0
No files found.
Lib/asyncio/base_events.py
View file @
592ada9b
...
@@ -40,6 +40,13 @@ __all__ = ['BaseEventLoop', 'Server']
...
@@ -40,6 +40,13 @@ __all__ = ['BaseEventLoop', 'Server']
# Argument for default thread pool executor creation.
# Argument for default thread pool executor creation.
_MAX_WORKERS
=
5
_MAX_WORKERS
=
5
# Minimum number of _scheduled timer handles before cleanup of
# cancelled handles is performed.
_MIN_SCHEDULED_TIMER_HANDLES
=
100
# Minimum fraction of _scheduled timer handles that are cancelled
# before cleanup of cancelled handles is performed.
_MIN_CANCELLED_TIMER_HANDLES_FRACTION
=
0.5
def
_format_handle
(
handle
):
def
_format_handle
(
handle
):
cb
=
handle
.
_callback
cb
=
handle
.
_callback
...
@@ -145,6 +152,7 @@ class Server(events.AbstractServer):
...
@@ -145,6 +152,7 @@ class Server(events.AbstractServer):
class
BaseEventLoop
(
events
.
AbstractEventLoop
):
class
BaseEventLoop
(
events
.
AbstractEventLoop
):
def
__init__
(
self
):
def
__init__
(
self
):
self
.
_timer_cancelled_count
=
0
self
.
_closed
=
False
self
.
_closed
=
False
self
.
_ready
=
collections
.
deque
()
self
.
_ready
=
collections
.
deque
()
self
.
_scheduled
=
[]
self
.
_scheduled
=
[]
...
@@ -349,6 +357,7 @@ class BaseEventLoop(events.AbstractEventLoop):
...
@@ -349,6 +357,7 @@ class BaseEventLoop(events.AbstractEventLoop):
if
timer
.
_source_traceback
:
if
timer
.
_source_traceback
:
del
timer
.
_source_traceback
[
-
1
]
del
timer
.
_source_traceback
[
-
1
]
heapq
.
heappush
(
self
.
_scheduled
,
timer
)
heapq
.
heappush
(
self
.
_scheduled
,
timer
)
timer
.
_scheduled
=
True
return
timer
return
timer
def
call_soon
(
self
,
callback
,
*
args
):
def
call_soon
(
self
,
callback
,
*
args
):
...
@@ -964,9 +973,7 @@ class BaseEventLoop(events.AbstractEventLoop):
...
@@ -964,9 +973,7 @@ class BaseEventLoop(events.AbstractEventLoop):
assert
isinstance
(
handle
,
events
.
Handle
),
'A Handle is required here'
assert
isinstance
(
handle
,
events
.
Handle
),
'A Handle is required here'
if
handle
.
_cancelled
:
if
handle
.
_cancelled
:
return
return
if
isinstance
(
handle
,
events
.
TimerHandle
):
assert
not
isinstance
(
handle
,
events
.
TimerHandle
)
heapq
.
heappush
(
self
.
_scheduled
,
handle
)
else
:
self
.
_ready
.
append
(
handle
)
self
.
_ready
.
append
(
handle
)
def
_add_callback_signalsafe
(
self
,
handle
):
def
_add_callback_signalsafe
(
self
,
handle
):
...
@@ -974,6 +981,11 @@ class BaseEventLoop(events.AbstractEventLoop):
...
@@ -974,6 +981,11 @@ class BaseEventLoop(events.AbstractEventLoop):
self
.
_add_callback
(
handle
)
self
.
_add_callback
(
handle
)
self
.
_write_to_self
()
self
.
_write_to_self
()
def
_timer_handle_cancelled
(
self
,
handle
):
"""Notification that a TimerHandle has been cancelled."""
if
handle
.
_scheduled
:
self
.
_timer_cancelled_count
+=
1
def
_run_once
(
self
):
def
_run_once
(
self
):
"""Run one full iteration of the event loop.
"""Run one full iteration of the event loop.
...
@@ -981,9 +993,26 @@ class BaseEventLoop(events.AbstractEventLoop):
...
@@ -981,9 +993,26 @@ class BaseEventLoop(events.AbstractEventLoop):
schedules the resulting callbacks, and finally schedules
schedules the resulting callbacks, and finally schedules
'call_later' callbacks.
'call_later' callbacks.
"""
"""
# Remove delayed calls that were cancelled if their number is too high
sched_count
=
len
(
self
.
_scheduled
)
if
(
sched_count
>
_MIN_SCHEDULED_TIMER_HANDLES
and
self
.
_timer_cancelled_count
/
sched_count
>
_MIN_CANCELLED_TIMER_HANDLES_FRACTION
):
for
handle
in
self
.
_scheduled
:
if
handle
.
_cancelled
:
handle
.
_scheduled
=
False
self
.
_scheduled
=
[
x
for
x
in
self
.
_scheduled
if
not
x
.
_cancelled
]
self
.
_timer_cancelled_count
=
0
heapq
.
heapify
(
self
.
_scheduled
)
else
:
# Remove delayed calls that were cancelled from head of queue.
# Remove delayed calls that were cancelled from head of queue.
while
self
.
_scheduled
and
self
.
_scheduled
[
0
].
_cancelled
:
while
self
.
_scheduled
and
self
.
_scheduled
[
0
].
_cancelled
:
heapq
.
heappop
(
self
.
_scheduled
)
self
.
_timer_cancelled_count
-=
1
handle
=
heapq
.
heappop
(
self
.
_scheduled
)
handle
.
_scheduled
=
False
timeout
=
None
timeout
=
None
if
self
.
_ready
:
if
self
.
_ready
:
...
@@ -1024,6 +1053,7 @@ class BaseEventLoop(events.AbstractEventLoop):
...
@@ -1024,6 +1053,7 @@ class BaseEventLoop(events.AbstractEventLoop):
if
handle
.
_when
>=
end_time
:
if
handle
.
_when
>=
end_time
:
break
break
handle
=
heapq
.
heappop
(
self
.
_scheduled
)
handle
=
heapq
.
heappop
(
self
.
_scheduled
)
handle
.
_scheduled
=
False
self
.
_ready
.
append
(
handle
)
self
.
_ready
.
append
(
handle
)
# This is the only place where callbacks are actually *called*.
# This is the only place where callbacks are actually *called*.
...
...
Lib/asyncio/events.py
View file @
592ada9b
...
@@ -105,11 +105,12 @@ class Handle:
...
@@ -105,11 +105,12 @@ class Handle:
return
'<%s>'
%
' '
.
join
(
info
)
return
'<%s>'
%
' '
.
join
(
info
)
def
cancel
(
self
):
def
cancel
(
self
):
if
not
self
.
_cancelled
:
self
.
_cancelled
=
True
self
.
_cancelled
=
True
if
self
.
_loop
.
get_debug
():
if
self
.
_loop
.
get_debug
():
# Keep a representation in debug mode to keep callback and
# Keep a representation in debug mode to keep callback and
# parameters. For example, to log the warning "Executing <Handle
# parameters. For example, to log the warning
#
...> took 2.5 second"
# "Executing <Handle
...> took 2.5 second"
self
.
_repr
=
repr
(
self
)
self
.
_repr
=
repr
(
self
)
self
.
_callback
=
None
self
.
_callback
=
None
self
.
_args
=
None
self
.
_args
=
None
...
@@ -134,7 +135,7 @@ class Handle:
...
@@ -134,7 +135,7 @@ class Handle:
class
TimerHandle
(
Handle
):
class
TimerHandle
(
Handle
):
"""Object returned by timed callback registration methods."""
"""Object returned by timed callback registration methods."""
__slots__
=
[
'_when'
]
__slots__
=
[
'_
scheduled'
,
'_
when'
]
def
__init__
(
self
,
when
,
callback
,
args
,
loop
):
def
__init__
(
self
,
when
,
callback
,
args
,
loop
):
assert
when
is
not
None
assert
when
is
not
None
...
@@ -142,6 +143,7 @@ class TimerHandle(Handle):
...
@@ -142,6 +143,7 @@ class TimerHandle(Handle):
if
self
.
_source_traceback
:
if
self
.
_source_traceback
:
del
self
.
_source_traceback
[
-
1
]
del
self
.
_source_traceback
[
-
1
]
self
.
_when
=
when
self
.
_when
=
when
self
.
_scheduled
=
False
def
_repr_info
(
self
):
def
_repr_info
(
self
):
info
=
super
().
_repr_info
()
info
=
super
().
_repr_info
()
...
@@ -180,6 +182,11 @@ class TimerHandle(Handle):
...
@@ -180,6 +182,11 @@ class TimerHandle(Handle):
equal
=
self
.
__eq__
(
other
)
equal
=
self
.
__eq__
(
other
)
return
NotImplemented
if
equal
is
NotImplemented
else
not
equal
return
NotImplemented
if
equal
is
NotImplemented
else
not
equal
def
cancel
(
self
):
if
not
self
.
_cancelled
:
self
.
_loop
.
_timer_handle_cancelled
(
self
)
super
().
cancel
()
class
AbstractServer
:
class
AbstractServer
:
"""Abstract server returned by create_server()."""
"""Abstract server returned by create_server()."""
...
@@ -238,6 +245,10 @@ class AbstractEventLoop:
...
@@ -238,6 +245,10 @@ class AbstractEventLoop:
# Methods scheduling callbacks. All these return Handles.
# Methods scheduling callbacks. All these return Handles.
def
_timer_handle_cancelled
(
self
,
handle
):
"""Notification that a TimerHandle has been cancelled."""
raise
NotImplementedError
def
call_soon
(
self
,
callback
,
*
args
):
def
call_soon
(
self
,
callback
,
*
args
):
return
self
.
call_later
(
0
,
callback
,
*
args
)
return
self
.
call_later
(
0
,
callback
,
*
args
)
...
...
Lib/test/test_asyncio/test_base_events.py
View file @
592ada9b
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
import
errno
import
errno
import
logging
import
logging
import
math
import
socket
import
socket
import
sys
import
sys
import
time
import
time
...
@@ -73,13 +74,6 @@ class BaseEventLoopTests(test_utils.TestCase):
...
@@ -73,13 +74,6 @@ class BaseEventLoopTests(test_utils.TestCase):
self
.
assertFalse
(
self
.
loop
.
_scheduled
)
self
.
assertFalse
(
self
.
loop
.
_scheduled
)
self
.
assertIn
(
h
,
self
.
loop
.
_ready
)
self
.
assertIn
(
h
,
self
.
loop
.
_ready
)
def
test__add_callback_timer
(
self
):
h
=
asyncio
.
TimerHandle
(
time
.
monotonic
()
+
10
,
lambda
:
False
,
(),
self
.
loop
)
self
.
loop
.
_add_callback
(
h
)
self
.
assertIn
(
h
,
self
.
loop
.
_scheduled
)
def
test__add_callback_cancelled_handle
(
self
):
def
test__add_callback_cancelled_handle
(
self
):
h
=
asyncio
.
Handle
(
lambda
:
False
,
(),
self
.
loop
)
h
=
asyncio
.
Handle
(
lambda
:
False
,
(),
self
.
loop
)
h
.
cancel
()
h
.
cancel
()
...
@@ -283,6 +277,82 @@ class BaseEventLoopTests(test_utils.TestCase):
...
@@ -283,6 +277,82 @@ class BaseEventLoopTests(test_utils.TestCase):
self
.
assertTrue
(
processed
)
self
.
assertTrue
(
processed
)
self
.
assertEqual
([
handle
],
list
(
self
.
loop
.
_ready
))
self
.
assertEqual
([
handle
],
list
(
self
.
loop
.
_ready
))
def
test__run_once_cancelled_event_cleanup
(
self
):
self
.
loop
.
_process_events
=
mock
.
Mock
()
self
.
assertTrue
(
0
<
base_events
.
_MIN_CANCELLED_TIMER_HANDLES_FRACTION
<
1.0
)
def
cb
():
pass
# Set up one "blocking" event that will not be cancelled to
# ensure later cancelled events do not make it to the head
# of the queue and get cleaned.
not_cancelled_count
=
1
self
.
loop
.
call_later
(
3000
,
cb
)
# Add less than threshold (base_events._MIN_SCHEDULED_TIMER_HANDLES)
# cancelled handles, ensure they aren't removed
cancelled_count
=
2
for
x
in
range
(
2
):
h
=
self
.
loop
.
call_later
(
3600
,
cb
)
h
.
cancel
()
# Add some cancelled events that will be at head and removed
cancelled_count
+=
2
for
x
in
range
(
2
):
h
=
self
.
loop
.
call_later
(
100
,
cb
)
h
.
cancel
()
# This test is invalid if _MIN_SCHEDULED_TIMER_HANDLES is too low
self
.
assertLessEqual
(
cancelled_count
+
not_cancelled_count
,
base_events
.
_MIN_SCHEDULED_TIMER_HANDLES
)
self
.
assertEqual
(
self
.
loop
.
_timer_cancelled_count
,
cancelled_count
)
self
.
loop
.
_run_once
()
cancelled_count
-=
2
self
.
assertEqual
(
self
.
loop
.
_timer_cancelled_count
,
cancelled_count
)
self
.
assertEqual
(
len
(
self
.
loop
.
_scheduled
),
cancelled_count
+
not_cancelled_count
)
# Need enough events to pass _MIN_CANCELLED_TIMER_HANDLES_FRACTION
# so that deletion of cancelled events will occur on next _run_once
add_cancel_count
=
int
(
math
.
ceil
(
base_events
.
_MIN_SCHEDULED_TIMER_HANDLES
*
base_events
.
_MIN_CANCELLED_TIMER_HANDLES_FRACTION
))
+
1
add_not_cancel_count
=
max
(
base_events
.
_MIN_SCHEDULED_TIMER_HANDLES
-
add_cancel_count
,
0
)
# Add some events that will not be cancelled
not_cancelled_count
+=
add_not_cancel_count
for
x
in
range
(
add_not_cancel_count
):
self
.
loop
.
call_later
(
3600
,
cb
)
# Add enough cancelled events
cancelled_count
+=
add_cancel_count
for
x
in
range
(
add_cancel_count
):
h
=
self
.
loop
.
call_later
(
3600
,
cb
)
h
.
cancel
()
# Ensure all handles are still scheduled
self
.
assertEqual
(
len
(
self
.
loop
.
_scheduled
),
cancelled_count
+
not_cancelled_count
)
self
.
loop
.
_run_once
()
# Ensure cancelled events were removed
self
.
assertEqual
(
len
(
self
.
loop
.
_scheduled
),
not_cancelled_count
)
# Ensure only uncancelled events remain scheduled
self
.
assertTrue
(
all
([
not
x
.
_cancelled
for
x
in
self
.
loop
.
_scheduled
]))
def
test_run_until_complete_type_error
(
self
):
def
test_run_until_complete_type_error
(
self
):
self
.
assertRaises
(
TypeError
,
self
.
assertRaises
(
TypeError
,
self
.
loop
.
run_until_complete
,
'blah'
)
self
.
loop
.
run_until_complete
,
'blah'
)
...
...
Lib/test/test_asyncio/test_events.py
View file @
592ada9b
...
@@ -1890,7 +1890,15 @@ class HandleTests(test_utils.TestCase):
...
@@ -1890,7 +1890,15 @@ class HandleTests(test_utils.TestCase):
# cancelled handle
# cancelled handle
h
.
cancel
()
h
.
cancel
()
self
.
assertEqual
(
repr
(
h
),
self
.
assertEqual
(
repr
(
h
),
'<Handle cancelled noop(1, 2) at %s:%s created at %s:%s>'
%
(
filename
,
lineno
,
create_filename
,
create_lineno
))
# double cancellation won't overwrite _repr
h
.
cancel
()
self
.
assertEqual
(
repr
(
h
),
'<Handle cancelled noop(1, 2) at %s:%s created at %s:%s>'
'<Handle cancelled noop(1, 2) at %s:%s created at %s:%s>'
%
(
filename
,
lineno
,
create_filename
,
create_lineno
))
%
(
filename
,
lineno
,
create_filename
,
create_lineno
))
...
...
Misc/NEWS
View file @
592ada9b
...
@@ -2,6 +2,20 @@
...
@@ -2,6 +2,20 @@
Python
News
Python
News
+++++++++++
+++++++++++
What
's New in Python 3.4.3?
===========================
Core and Builtins
-----------------
Library
-------
- Issue #22448: Improve canceled timer handles cleanup to prevent
unbound memory usage. Patch by Joshua Moore-Oliva.
What'
s
New
in
Python
3.4.2
?
What'
s
New
in
Python
3.4.2
?
===========================
===========================
...
...
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