2222
2323import abc
2424import asyncio
25+ import contextlib
2526import errno
2627import logging
2728import os
5253from .simulations import Simulator as _Simulator
5354
5455Container : typing_extensions .TypeAlias = docker .models .containers .Container
56+ Remove = typing .Literal ["always" , "if_exit_success" , "never" ]
5557
5658
5759@attrs .define ()
@@ -427,10 +429,25 @@ async def send(self, component: ConnectedComponent[MsgT, DataT], msg: MsgT) -> D
427429 return retval .data
428430
429431 @typing_extensions .override
430- def stop (self ) -> None :
432+ def stop (self , * , remove : Remove = "if_exit_success" ) :
431433 for child in self .children .values ():
432434 child .container .stop ()
433435
436+ match remove :
437+ case "always" :
438+ child .container .remove ()
439+ case "never" :
440+ pass
441+ case _:
442+ status = child .container .wait ()
443+
444+ # 0 -> ExitSucess, 137 -> SIGKILL, 143 -> SIGTERM
445+ if status ["StatusCode" ] in {0 , 137 , 143 }:
446+ child .container .remove ()
447+
448+ if remove is not "never" :
449+ self .context .network .remove ()
450+
434451
435452@attrs .define ()
436453class _Registration :
@@ -445,6 +462,9 @@ def start(self, context: Context) -> _ComponentSimulation:
445462 return _ComponentSimulation (self .component .start (context ), self .dependencies )
446463
447464
465+ _SimulationGenerator : typing .TypeAlias = typing .Generator [Simulation , None , None ]
466+
467+
448468class Simulator (_Simulator [Context , Simulation ]):
449469 """Simulator implementation using container-based components."""
450470
@@ -499,6 +519,17 @@ def start(self) -> Simulation:
499519
500520 raise e
501521
522+ @contextlib .contextmanager
523+ @typing_extensions .override
524+ def run (self , * , remove : Remove = "if_exit_success" ) -> _SimulationGenerator :
525+ sim = self .start ()
526+
527+ try :
528+ yield sim
529+ finally :
530+ sim .stop (remove = remove )
531+
532+
502533
503534@attrs .frozen ()
504535class Success (typing .Generic [DataT ]):
0 commit comments