Skip to content

Commit 327e784

Browse files
authored
Backport PR ipython#14693 on branch 8.x (Do not ignore exit code on SIGINT) (ipython#14696)
Backport PR ipython#14693: Do not ignore exit code on SIGINT
2 parents 36ca3e9 + e20dd67 commit 327e784

File tree

3 files changed

+76
-7
lines changed

3 files changed

+76
-7
lines changed

IPython/core/magics/execution.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,21 @@ def _run_with_debugger(
977977
break
978978
finally:
979979
sys.settrace(trace)
980-
980+
981+
# Perform proper cleanup of the session in case if
982+
# it exited with "continue" and not "quit" command
983+
if hasattr(deb, "rcLines"):
984+
# Run this code defensively in case if custom debugger
985+
# class does not implement rcLines, which although public
986+
# is an implementation detail of `pdb.Pdb` and not part of
987+
# the more generic basic debugger framework (`bdb.Bdb`).
988+
deb.set_quit()
989+
deb.rcLines.extend(["q"])
990+
try:
991+
deb.run("", code_ns, local_ns)
992+
except StopIteration:
993+
# Stop iteration is raised on quit command
994+
pass
981995

982996
except:
983997
etype, value, tb = sys.exc_info()

IPython/core/magics/script.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ def script_args(f):
6767
return f
6868

6969

70+
class RaiseAfterInterrupt(Exception):
71+
pass
72+
73+
7074
@magics_class
7175
class ScriptMagics(Magics):
7276
"""Magics for talking to scripts
@@ -176,6 +180,10 @@ def shebang(self, line, cell):
176180
177181
The rest of the cell is run by that program.
178182
183+
.. versionchanged:: 9.0
184+
Interrupting the script executed without `--bg` will end in
185+
raising an exception (unless `--no-raise-error` is passed).
186+
179187
Examples
180188
--------
181189
::
@@ -292,20 +300,33 @@ async def _stream_communicate(process, cell):
292300
p.send_signal(signal.SIGINT)
293301
in_thread(asyncio.wait_for(p.wait(), timeout=0.1))
294302
if p.returncode is not None:
295-
print("Process is interrupted.")
296-
return
303+
print("Process was interrupted.")
304+
if args.raise_error:
305+
raise RaiseAfterInterrupt()
306+
else:
307+
return
297308
p.terminate()
298309
in_thread(asyncio.wait_for(p.wait(), timeout=0.1))
299310
if p.returncode is not None:
300-
print("Process is terminated.")
301-
return
311+
print("Process was terminated.")
312+
if args.raise_error:
313+
raise RaiseAfterInterrupt()
314+
else:
315+
return
302316
p.kill()
303-
print("Process is killed.")
317+
print("Process was killed.")
318+
if args.raise_error:
319+
raise RaiseAfterInterrupt()
320+
except RaiseAfterInterrupt:
321+
pass
304322
except OSError:
305323
pass
306324
except Exception as e:
307325
print("Error while terminating subprocess (pid=%i): %s" % (p.pid, e))
308-
return
326+
if args.raise_error:
327+
raise CalledProcessError(p.returncode, cell) from None
328+
else:
329+
return
309330

310331
if args.raise_error and p.returncode != 0:
311332
# If we get here and p.returncode is still None, we must have

IPython/core/tests/test_magic.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
import os
77
import re
88
import shlex
9+
import signal
910
import sys
1011
import warnings
1112
from importlib import invalidate_caches
1213
from io import StringIO
1314
from pathlib import Path
15+
from subprocess import CalledProcessError
1416
from textwrap import dedent
17+
from time import sleep
18+
from threading import Thread
1519
from unittest import TestCase, mock
1620

1721
import pytest
@@ -1121,6 +1125,36 @@ def test_script_config():
11211125
assert "whoda" in sm.magics["cell"]
11221126

11231127

1128+
def _interrupt_after_1s():
1129+
sleep(1)
1130+
signal.raise_signal(signal.SIGINT)
1131+
1132+
1133+
def test_script_raise_on_interrupt():
1134+
ip = get_ipython()
1135+
1136+
with pytest.raises(CalledProcessError):
1137+
thread = Thread(target=_interrupt_after_1s)
1138+
thread.start()
1139+
ip.run_cell_magic(
1140+
"script", f"{sys.executable}", "from time import sleep; sleep(2)"
1141+
)
1142+
thread.join()
1143+
1144+
1145+
def test_script_do_not_raise_on_interrupt():
1146+
ip = get_ipython()
1147+
1148+
thread = Thread(target=_interrupt_after_1s)
1149+
thread.start()
1150+
ip.run_cell_magic(
1151+
"script",
1152+
f"--no-raise-error {sys.executable}",
1153+
"from time import sleep; sleep(2)",
1154+
)
1155+
thread.join()
1156+
1157+
11241158
def test_script_out():
11251159
ip = get_ipython()
11261160
ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')")

0 commit comments

Comments
 (0)