Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
镜像
tornadoweb
Tornado
提交
c3b8ee8d
Tornado
项目概览
镜像
/
tornadoweb
/
Tornado
大约 1 年 前同步成功
通知
26
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
Tornado
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
c3b8ee8d
编写于
6月 19, 2023
作者:
B
Ben Darnell
提交者:
GitHub
6月 19, 2023
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #3272 from bdarnell/asyncio-canary
asyncio: Use a canary task to detect end of event loop
上级
111370d9
3cca32d5
变更
2
隐藏空白更改
内联
并排
Showing
2 changed file
with
100 addition
and
28 deletion
+100
-28
tornado/platform/asyncio.py
tornado/platform/asyncio.py
+49
-28
tornado/test/asyncio_test.py
tornado/test/asyncio_test.py
+51
-0
未找到文件。
tornado/platform/asyncio.py
浏览文件 @
c3b8ee8d
...
...
@@ -64,11 +64,12 @@ def _atexit_callback() -> None:
loop
.
_waker_w
.
send
(
b
"a"
)
except
BlockingIOError
:
pass
# If we don't join our (daemon) thread here, we may get a deadlock
# during interpreter shutdown. I don't really understand why. This
# deadlock happens every time in CI (both travis and appveyor) but
# I've never been able to reproduce locally.
loop
.
_thread
.
join
()
if
loop
.
_thread
is
not
None
:
# If we don't join our (daemon) thread here, we may get a deadlock
# during interpreter shutdown. I don't really understand why. This
# deadlock happens every time in CI (both travis and appveyor) but
# I've never been able to reproduce locally.
loop
.
_thread
.
join
()
_selector_loops
.
clear
()
...
...
@@ -443,24 +444,25 @@ class SelectorThread:
def
__init__
(
self
,
real_loop
:
asyncio
.
AbstractEventLoop
)
->
None
:
self
.
_real_loop
=
real_loop
# Create a thread to run the select system call. We manage this thread
# manually so we can trigger a clean shutdown from an atexit hook. Note
# that due to the order of operations at shutdown, only daemon threads
# can be shut down in this way (non-daemon threads would require the
# introduction of a new hook: https://bugs.python.org/issue41962)
self
.
_select_cond
=
threading
.
Condition
()
self
.
_select_args
=
(
None
)
# type: Optional[Tuple[List[_FileDescriptorLike], List[_FileDescriptorLike]]]
self
.
_closing_selector
=
False
self
.
_thread
=
threading
.
Thread
(
name
=
"Tornado selector"
,
daemon
=
True
,
target
=
self
.
_run_select
,
self
.
_thread
=
None
# type: Optional[threading.Thread]
self
.
_thread_manager_handle
=
self
.
_thread_manager
()
async
def
thread_manager_anext
()
->
None
:
# the anext builtin wasn't added until 3.10. We just need to iterate
# this generator one step.
await
self
.
_thread_manager_handle
.
__anext__
()
# When the loop starts, start the thread. Not too soon because we can't
# clean up if we get to this point but the event loop is closed without
# starting.
self
.
_real_loop
.
call_soon
(
lambda
:
self
.
_real_loop
.
create_task
(
thread_manager_anext
())
)
self
.
_thread
.
start
()
# Start the select loop once the loop is started.
self
.
_real_loop
.
call_soon
(
self
.
_start_select
)
self
.
_readers
=
{}
# type: Dict[_FileDescriptorLike, Callable]
self
.
_writers
=
{}
# type: Dict[_FileDescriptorLike, Callable]
...
...
@@ -473,16 +475,6 @@ class SelectorThread:
_selector_loops
.
add
(
self
)
self
.
add_reader
(
self
.
_waker_r
,
self
.
_consume_waker
)
def
__del__
(
self
)
->
None
:
# If the top-level application code uses asyncio interfaces to
# start and stop the event loop, no objects created in Tornado
# can get a clean shutdown notification. If we're just left to
# be GC'd, we must explicitly close our sockets to avoid
# logging warnings.
_selector_loops
.
discard
(
self
)
self
.
_waker_r
.
close
()
self
.
_waker_w
.
close
()
def
close
(
self
)
->
None
:
if
self
.
_closed
:
return
...
...
@@ -490,13 +482,42 @@ class SelectorThread:
self
.
_closing_selector
=
True
self
.
_select_cond
.
notify
()
self
.
_wake_selector
()
self
.
_thread
.
join
()
if
self
.
_thread
is
not
None
:
self
.
_thread
.
join
()
_selector_loops
.
discard
(
self
)
self
.
remove_reader
(
self
.
_waker_r
)
self
.
_waker_r
.
close
()
self
.
_waker_w
.
close
()
self
.
_closed
=
True
async
def
_thread_manager
(
self
)
->
typing
.
AsyncGenerator
[
None
,
None
]:
# Create a thread to run the select system call. We manage this thread
# manually so we can trigger a clean shutdown from an atexit hook. Note
# that due to the order of operations at shutdown, only daemon threads
# can be shut down in this way (non-daemon threads would require the
# introduction of a new hook: https://bugs.python.org/issue41962)
self
.
_thread
=
threading
.
Thread
(
name
=
"Tornado selector"
,
daemon
=
True
,
target
=
self
.
_run_select
,
)
self
.
_thread
.
start
()
self
.
_start_select
()
try
:
# The presense of this yield statement means that this coroutine
# is actually an asynchronous generator, which has a special
# shutdown protocol. We wait at this yield point until the
# event loop's shutdown_asyncgens method is called, at which point
# we will get a GeneratorExit exception and can shut down the
# selector thread.
yield
except
GeneratorExit
:
self
.
close
()
raise
def
_wake_selector
(
self
)
->
None
:
if
self
.
_closed
:
return
try
:
self
.
_waker_w
.
send
(
b
"a"
)
except
BlockingIOError
:
...
...
tornado/test/asyncio_test.py
浏览文件 @
c3b8ee8d
...
...
@@ -11,6 +11,8 @@
# under the License.
import
asyncio
import
threading
import
time
import
unittest
import
warnings
...
...
@@ -157,6 +159,55 @@ class LeakTest(unittest.TestCase):
self
.
assertEqual
(
new_count
,
1
)
class
SelectorThreadLeakTest
(
unittest
.
TestCase
):
# These tests are only relevant on windows, but they should pass anywhere.
def
setUp
(
self
):
# As a precaution, ensure that we've run an event loop at least once
# so if it spins up any singleton threads they're already there.
asyncio
.
run
(
self
.
dummy_tornado_coroutine
())
self
.
orig_thread_count
=
threading
.
active_count
()
def
assert_no_thread_leak
(
self
):
# For some reason we see transient failures here, but I haven't been able
# to catch it to identify which thread is causing it. Whatever thread it
# is, it appears to quickly clean up on its own, so just retry a few times.
deadline
=
time
.
time
()
+
1
while
time
.
time
()
<
deadline
:
threads
=
list
(
threading
.
enumerate
())
if
len
(
threads
)
==
self
.
orig_thread_count
:
break
time
.
sleep
(
0.1
)
self
.
assertEqual
(
self
.
orig_thread_count
,
len
(
threads
),
threads
)
async
def
dummy_tornado_coroutine
(
self
):
# Just access the IOLoop to initialize the selector thread.
IOLoop
.
current
()
def
test_asyncio_run
(
self
):
for
i
in
range
(
10
):
# asyncio.run calls shutdown_asyncgens for us.
asyncio
.
run
(
self
.
dummy_tornado_coroutine
())
self
.
assert_no_thread_leak
()
def
test_asyncio_manual
(
self
):
for
i
in
range
(
10
):
loop
=
asyncio
.
new_event_loop
()
loop
.
run_until_complete
(
self
.
dummy_tornado_coroutine
())
# Without this step, we'd leak the thread.
loop
.
run_until_complete
(
loop
.
shutdown_asyncgens
())
loop
.
close
()
self
.
assert_no_thread_leak
()
def
test_tornado
(
self
):
for
i
in
range
(
10
):
# The IOLoop interfaces are aware of the selector thread and
# (synchronously) shut it down.
loop
=
IOLoop
(
make_current
=
False
)
loop
.
run_sync
(
self
.
dummy_tornado_coroutine
)
loop
.
close
()
self
.
assert_no_thread_leak
()
class
AnyThreadEventLoopPolicyTest
(
unittest
.
TestCase
):
def
setUp
(
self
):
self
.
orig_policy
=
asyncio
.
get_event_loop_policy
()
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录