Skip to content

Commit b6d0773

Browse files
authored
Merge pull request #451 from The-Strategy-Unit/improve_exception_handling_in_containers
improve exception handling in containers
2 parents 2367dd4 + ffd3e37 commit b6d0773

File tree

4 files changed

+43
-22
lines changed

4 files changed

+43
-22
lines changed

src/nhp/docker/__main__.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@
1010
from nhp.model.run import run_all
1111

1212

13-
# %%
1413
def _exit_container():
1514
logging.error("\nTimed out, killing container")
16-
os._exit(1)
15+
os._exit(2)
1716

1817

1918
def parse_args():
@@ -38,7 +37,6 @@ def parse_args():
3837
return parser.parse_args()
3938

4039

41-
# %%
4240
def main():
4341
"""The main method."""
4442
args = parse_args()
@@ -71,18 +69,25 @@ def main():
7169
logging.info("complete")
7270

7371

74-
# %%
7572
def init():
7673
"""Method for calling main."""
7774
if __name__ == "__main__":
78-
# start a timer to kill the container if we reach a timeout
79-
t = threading.Timer(config.CONTAINER_TIMEOUT_SECONDS, _exit_container)
80-
t.start()
81-
# run the model
82-
main()
83-
# cancel the timer
84-
t.cancel()
75+
exc = None
76+
try:
77+
# start a timer to kill the container if we reach a timeout
78+
t = threading.Timer(config.CONTAINER_TIMEOUT_SECONDS, _exit_container)
79+
t.start()
80+
# run the model
81+
main()
82+
except Exception as e:
83+
logging.error("An error occurred: %s", str(e))
84+
exc = e
85+
finally:
86+
# cancel the timer
87+
t.cancel()
88+
# if there was an exception, raise it
89+
if exc is not None:
90+
raise exc
8591

8692

87-
# %%
8893
init()

src/nhp/model/run.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from multiprocessing import Pool
77
from typing import Any, Callable, Tuple, Type
88

9-
import pandas as pd
109
from tqdm.auto import tqdm as base_tqdm
1110

1211
from nhp.model.aae import AaEModel
@@ -80,13 +79,14 @@ def _run_model(
8079

8180
# model run 0 is the baseline
8281
# model run 1:n are the monte carlo sims
83-
model_runs = list(range(params["model_runs"] + 1))
82+
model_runs = [i + 1 for i in range(params["model_runs"])]
8483

8584
cpus = os.cpu_count()
8685
batch_size = int(os.getenv("BATCH_SIZE", "1"))
8786

8887
with Pool(cpus) as pool:
89-
results: list[ModelRunResult] = list(
88+
baseline = model.go(0) # baseline
89+
model_results: list[ModelRunResult] = list(
9090
tqdm(
9191
pool.imap(
9292
model.go,
@@ -101,7 +101,7 @@ def _run_model(
101101
# ensure that the callback reports all model runs are complete
102102
progress_callback(params["model_runs"])
103103

104-
return results
104+
return [baseline, *model_results]
105105

106106

107107
def noop_progress_callback(_: Any) -> Callable[[Any], None]:

tests/unit/nhp/docker/test___main__.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def test_exit_container(mocker):
1515

1616
r._exit_container()
1717

18-
m.assert_called_once_with(1)
18+
m.assert_called_once_with(2)
1919

2020

2121
@pytest.mark.parametrize(
@@ -139,12 +139,12 @@ def test_init_timeout_call_exit(mocker):
139139
import nhp.docker.__main__ as r
140140

141141
main_mock = mocker.patch("nhp.docker.__main__.main")
142-
exit_mock = mocker.patch("nhp.docker.__main__._exit_container")
142+
exit_container_mock = mocker.patch("nhp.docker.__main__._exit_container")
143143
main_mock.side_effect = lambda: time.sleep(0.2)
144144
with patch.object(r, "__name__", "__main__"):
145145
r.init()
146146

147-
exit_mock.assert_called_once()
147+
exit_container_mock.assert_called_once()
148148

149149

150150
def test_init_timeout_dont_call_exit(mocker):
@@ -153,9 +153,25 @@ def test_init_timeout_dont_call_exit(mocker):
153153
import nhp.docker.__main__ as r
154154

155155
main_mock = mocker.patch("nhp.docker.__main__.main")
156-
exit_mock = mocker.patch("nhp.docker.__main__._exit_container")
156+
exit_container_mock = mocker.patch("nhp.docker.__main__._exit_container")
157157
main_mock.side_effect = lambda: time.sleep(0.02)
158158
with patch.object(r, "__name__", "__main__"):
159159
r.init()
160160

161-
exit_mock.assert_not_called()
161+
exit_container_mock.assert_not_called()
162+
163+
164+
def test_init_catches_exception(mocker):
165+
# arrange
166+
mocker.patch("nhp.docker.__main__.main", side_effect=Exception("Test error"))
167+
import nhp.docker.__main__ as r
168+
169+
m = mocker.patch("logging.error")
170+
171+
# act
172+
with patch.object(r, "__name__", "__main__"):
173+
with pytest.raises(Exception, match="Test error"):
174+
r.init()
175+
176+
# assert
177+
m.assert_called_once_with("An error occurred: %s", "Test error")

tests/unit/nhp/model/test_run.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def test_run_model(mocker):
6262
actual = _run_model(model_m, params, "data", "hsa", "run_params", pc_m, False) # type: ignore
6363

6464
# assert
65-
pool_ctm.imap.assert_called_once_with(model_m().go, [0, 1, 2], chunksize=1)
65+
pool_ctm.imap.assert_called_once_with(model_m().go, [1, 2], chunksize=1)
6666
assert actual == [model_m().go()] * 3
6767
pc_m.assert_called_once_with(2)
6868

0 commit comments

Comments
 (0)