Skip to content
This repository was archived by the owner on Sep 22, 2023. It is now read-only.

Commit 31957db

Browse files
committed
Merge branch '19.09'
2 parents 4196520 + 0c9ef95 commit 31957db

File tree

7 files changed

+74
-15
lines changed

7 files changed

+74
-15
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@ env_*.sh
3838
.ipynb_checkpoints
3939

4040
.vscode/
41+
.mypy_cache/

CHANGELOG.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ Changes
4848
* BREAKING CHANGE: Now the client SDK runs on Python 3.6, 3.7, and 3.8 and
4949
dropped support for Python 3.5.
5050

51+
19.09.7 (2020-03-31)
52+
--------------------
53+
54+
* FIX: Not-implemented-error in ``backend.ai app`` command on Windows, due
55+
to manually set event loop UNIX signal handlers. (#93)
56+
57+
* FIX: Now *all* CLI commands set exit codes correctly for interrupts
58+
(Ctrl+C on Windows or SIGINT on POSIX systems) so that batch/shell
59+
scripts that use ``backend.ai`` commands get interrupted properly.
60+
(#93)
61+
5162
19.09.6 (2020-03-16)
5263
--------------------
5364

setup.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@
77
]
88
install_requires = [
99
'backend.ai-cli~=0.3',
10-
'Click>=7.0',
11-
'PyYAML~=5.1.2',
12-
'appdirs~=1.4.3',
1310
'aiohttp~=3.6.2',
11+
'appdirs~=1.4.3',
1412
'async_timeout~=3.0', # to avoid pip10 resolver issue
1513
'attrs>=19.3', # to avoid pip10 resolver issue
14+
'click~=7.1.1',
1615
'colorama~=0.4.3',
1716
'humanize~=1.0.0',
1817
'multidict~=4.7.4',
1918
'python-dateutil~=2.8.1',
19+
'PyYAML~=5.1.2',
2020
'tabulate~=0.8.6',
2121
'tqdm~=4.42',
2222
'yarl~=1.4.2',
@@ -86,6 +86,7 @@ def read_src_version():
8686
'Programming Language :: Python :: 3.8',
8787
'Operating System :: POSIX',
8888
'Operating System :: MacOS :: MacOS X',
89+
'Operating System :: Microsoft :: Windows',
8990
'Environment :: No Input/Output (Daemon)',
9091
'Topic :: Scientific/Engineering',
9192
'Topic :: Software Development',

src/ai/backend/client/cli/__init__.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
from pathlib import Path
2+
import os
3+
import signal
24
import sys
35
import warnings
46

57
import click
8+
from click.exceptions import ClickException, Abort
69

10+
from .. import __version__
711
from ..config import APIConfig, set_config
812
from ai.backend.cli.extensions import AliasGroup
913

@@ -13,7 +17,7 @@
1317
@click.option('--skip-sslcert-validation',
1418
help='Skip SSL certificate validation for all API requests.',
1519
is_flag=True)
16-
@click.version_option()
20+
@click.version_option(version=__version__)
1721
def main(skip_sslcert_validation):
1822
"""
1923
Backend.AI command line interface.
@@ -40,7 +44,7 @@ def run_alias():
4044
sys.argv.insert(1, 'run')
4145
if help:
4246
sys.argv.append('--help')
43-
main.main(prog_name='backend.ai')
47+
run_main()
4448

4549

4650
def _attach_command():
@@ -51,3 +55,48 @@ def _attach_command():
5155

5256

5357
_attach_command()
58+
59+
60+
def run_main():
61+
try:
62+
_interrupted = False
63+
main.main(
64+
standalone_mode=False,
65+
prog_name='backend.ai',
66+
)
67+
except KeyboardInterrupt:
68+
# For interruptions outside the Click's exception handling block.
69+
print("Interrupted!", end="", file=sys.stderr)
70+
sys.stderr.flush()
71+
_interrupted = True
72+
except Abort as e:
73+
# Click wraps unhandled KeyboardInterrupt with a plain
74+
# sys.exit(1) call and prints "Aborted!" message
75+
# (which would look non-sense to users).
76+
# This is *NOT* what we want.
77+
# Instead of relying on Click, mark the _interrupted
78+
# flag to perform our own exit routines.
79+
if isinstance(e.__context__, KeyboardInterrupt):
80+
print("Interrupted!", end="", file=sys.stderr)
81+
sys.stderr.flush()
82+
_interrupted = True
83+
else:
84+
print("Aborted!", end="", file=sys.stderr)
85+
sys.stderr.flush()
86+
sys.exit(1)
87+
except ClickException as e:
88+
e.show()
89+
sys.exit(e.exit_code)
90+
finally:
91+
if _interrupted:
92+
# Override the exit code when it's interrupted,
93+
# referring https://github.com/python/cpython/pull/11862
94+
if sys.platform.startswith('win'):
95+
# Use STATUS_CONTROL_C_EXIT to notify cmd.exe
96+
# for interrupted exit
97+
sys.exit(-1073741510)
98+
else:
99+
# Use the default signal handler to set the exit
100+
# code properly for interruption.
101+
signal.signal(signal.SIGINT, signal.SIG_DFL)
102+
os.kill(os.getpid(), signal.SIGINT)

src/ai/backend/client/cli/__main__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
from . import main
1+
from . import run_main
22

3-
main()
3+
4+
run_main()

src/ai/backend/client/cli/app.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,7 @@ def app(session_name, app, protocol, bind, arg, env):
274274
args=arg,
275275
envs=env,
276276
)
277-
stop_signals = {signal.SIGINT, signal.SIGTERM}
278-
asyncio_run_forever(proxy_ctx, stop_signals=stop_signals)
277+
asyncio_run_forever(proxy_ctx)
279278
sys.exit(proxy_ctx.exit_code)
280279

281280

src/ai/backend/client/compat.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
'''
44

55
import asyncio
6-
import signal
76

87

98
if hasattr(asyncio, 'get_running_loop'): # Python 3.7+
@@ -49,6 +48,7 @@ def _asyncio_run(coro, *, debug=False):
4948
if hasattr(loop, 'shutdown_asyncgens'): # Python 3.6+
5049
loop.run_until_complete(loop.shutdown_asyncgens())
5150
finally:
51+
loop.stop()
5252
loop.close()
5353
asyncio.set_event_loop(None)
5454

@@ -59,8 +59,7 @@ def _asyncio_run(coro, *, debug=False):
5959
asyncio_run = _asyncio_run
6060

6161

62-
def asyncio_run_forever(server_context, *,
63-
stop_signals={signal.SIGINT}, debug=False):
62+
def asyncio_run_forever(server_context, *, debug=False):
6463
'''
6564
A proposed-but-not-implemented asyncio.run_forever() API based on
6665
@vxgmichel's idea.
@@ -79,9 +78,6 @@ async def _run_forever():
7978
except asyncio.CancelledError:
8079
pass
8180

82-
for stop_sig in stop_signals:
83-
loop.add_signal_handler(stop_sig, forever.cancel)
84-
8581
try:
8682
return loop.run_until_complete(_run_forever())
8783
finally:
@@ -90,5 +86,6 @@ async def _run_forever():
9086
if hasattr(loop, 'shutdown_asyncgens'): # Python 3.6+
9187
loop.run_until_complete(loop.shutdown_asyncgens())
9288
finally:
89+
loop.stop()
9390
loop.close()
9491
asyncio.set_event_loop(None)

0 commit comments

Comments
 (0)