1=================== 2Process Interaction 3=================== 4 5Discrete event simulation is only made interesting by interactions between 6processes. 7 8So this guide is about: 9 10* :ref:`sleep-until-woken-up` (passivate/reactivate) 11* :ref:`waiting-for-another-process-to-terminate` 12* :ref:`interrupting-another-process` 13 14The first two items were already covered in the :doc:`events` guide, but we'll 15also include them here for the sake of completeness. 16 17Another possibility for processes to interact are resources. They are discussed 18in a :doc:`separate guide <resources>`. 19 20 21.. _sleep-until-woken-up: 22 23Sleep until woken up 24==================== 25 26Imagine you want to model an electric vehicle with an intelligent 27battery-charging controller. While the vehicle is driving, the controller can 28be passive but needs to be reactivate once the vehicle is connected to the 29power grid in order to charge the battery. 30 31In SimPy 2, this pattern was known as *passivate / reactivate*. In SimPy 3, 32you can accomplish that with a simple, shared :class:`~simpy.events.Event`: 33 34.. code-block:: python 35 36 >>> from random import seed, randint 37 >>> seed(23) 38 >>> 39 >>> import simpy 40 >>> 41 >>> class EV: 42 ... def __init__(self, env): 43 ... self.env = env 44 ... self.drive_proc = env.process(self.drive(env)) 45 ... self.bat_ctrl_proc = env.process(self.bat_ctrl(env)) 46 ... self.bat_ctrl_reactivate = env.event() 47 ... 48 ... def drive(self, env): 49 ... while True: 50 ... # Drive for 20-40 min 51 ... yield env.timeout(randint(20, 40)) 52 ... 53 ... # Park for 1–6 hours 54 ... print('Start parking at', env.now) 55 ... self.bat_ctrl_reactivate.succeed() # "reactivate" 56 ... self.bat_ctrl_reactivate = env.event() 57 ... yield env.timeout(randint(60, 360)) 58 ... print('Stop parking at', env.now) 59 ... 60 ... def bat_ctrl(self, env): 61 ... while True: 62 ... print('Bat. ctrl. passivating at', env.now) 63 ... yield self.bat_ctrl_reactivate # "passivate" 64 ... print('Bat. ctrl. reactivated at', env.now) 65 ... 66 ... # Intelligent charging behavior here … 67 ... yield env.timeout(randint(30, 90)) 68 ... 69 >>> env = simpy.Environment() 70 >>> ev = EV(env) 71 >>> env.run(until=150) 72 Bat. ctrl. passivating at 0 73 Start parking at 29 74 Bat. ctrl. reactivated at 29 75 Bat. ctrl. passivating at 60 76 Stop parking at 131 77 78Since ``bat_ctrl()`` just waits for a normal event, we no longer call this 79pattern *passivate / reactivate* in SimPy 3. 80 81 82.. _waiting-for-another-process-to-terminate: 83 84Waiting for another process to terminate 85======================================== 86 87The example above has a problem: it may happen that the vehicle wants to park 88for a shorter duration than it takes to charge the battery (this is the case if 89both charging and parking would take 60 to 90 minutes). 90 91To fix this problem we have to slightly change our model. A new ``bat_ctrl()`` 92will be started every time the EV starts parking. The EV then waits until the 93parking duration is over *and* until the charging has stopped: 94 95.. code-block:: python 96 97 >>> class EV: 98 ... def __init__(self, env): 99 ... self.env = env 100 ... self.drive_proc = env.process(self.drive(env)) 101 ... 102 ... def drive(self, env): 103 ... while True: 104 ... # Drive for 20-40 min 105 ... yield env.timeout(randint(20, 40)) 106 ... 107 ... # Park for 1–6 hours 108 ... print('Start parking at', env.now) 109 ... charging = env.process(self.bat_ctrl(env)) 110 ... parking = env.timeout(randint(60, 360)) 111 ... yield charging & parking 112 ... print('Stop parking at', env.now) 113 ... 114 ... def bat_ctrl(self, env): 115 ... print('Bat. ctrl. started at', env.now) 116 ... # Intelligent charging behavior here … 117 ... yield env.timeout(randint(30, 90)) 118 ... print('Bat. ctrl. done at', env.now) 119 ... 120 >>> env = simpy.Environment() 121 >>> ev = EV(env) 122 >>> env.run(until=310) 123 Start parking at 29 124 Bat. ctrl. started at 29 125 Bat. ctrl. done at 83 126 Stop parking at 305 127 128Again, nothing new (if you've read the :doc:`events` guide) and special is 129happening. SimPy processes are events, too, so you can yield them and will thus 130wait for them to get triggered. You can also wait for two events at the same 131time by concatenating them with ``&`` (see 132:ref:`waiting_for_multiple_events_at_once`). 133 134 135.. _interrupting-another-process: 136 137Interrupting another process 138============================ 139 140As usual, we now have another problem: Imagine, a trip is very urgent, but with 141the current implementation, we always need to wait until the battery is fully 142charged. If we could somehow interrupt that ... 143 144Fortunate coincidence, there is indeed a way to do exactly this. You can call 145``interrupt()`` on a :class:`~simpy.events.Process`. This will throw an 146:class:`~simpy.exceptions.Interrupt` exception into that process, resuming it 147immediately: 148 149.. code-block:: python 150 151 >>> class EV: 152 ... def __init__(self, env): 153 ... self.env = env 154 ... self.drive_proc = env.process(self.drive(env)) 155 ... 156 ... def drive(self, env): 157 ... while True: 158 ... # Drive for 20-40 min 159 ... yield env.timeout(randint(20, 40)) 160 ... 161 ... # Park for 1 hour 162 ... print('Start parking at', env.now) 163 ... charging = env.process(self.bat_ctrl(env)) 164 ... parking = env.timeout(60) 165 ... yield charging | parking 166 ... if not charging.triggered: 167 ... # Interrupt charging if not already done. 168 ... charging.interrupt('Need to go!') 169 ... print('Stop parking at', env.now) 170 ... 171 ... def bat_ctrl(self, env): 172 ... print('Bat. ctrl. started at', env.now) 173 ... try: 174 ... yield env.timeout(randint(60, 90)) 175 ... print('Bat. ctrl. done at', env.now) 176 ... except simpy.Interrupt as i: 177 ... # Onoes! Got interrupted before the charging was done. 178 ... print('Bat. ctrl. interrupted at', env.now, 'msg:', 179 ... i.cause) 180 ... 181 >>> env = simpy.Environment() 182 >>> ev = EV(env) 183 >>> env.run(until=100) 184 Start parking at 31 185 Bat. ctrl. started at 31 186 Stop parking at 91 187 Bat. ctrl. interrupted at 91 msg: Need to go! 188 189What ``process.interrupt()`` actually does is scheduling an 190:class:`~simpy.events.Interruption` event for immediate execution. If this 191event is executed it will remove the victim process' ``_resume()`` method from 192the callbacks of the event that it is currently waiting for (see 193:attr:`~simpy.events.Process.target`). Following that it will throw the 194``Interrupt`` exception into the process. 195 196Since we don't do anything special to the original target event of the process, 197the interrupted process can yield the same event again after catching the 198``Interrupt`` – Imagine someone waiting for a shop to open. The person may get 199interrupted by a phone call. After finishing the call, he or she checks if the 200shop already opened and either enters or continues to wait. 201