1====== 2Events 3====== 4 5.. currentmodule:: simpy.events 6 7SimPy includes an extensive set of event types for various purposes. All of 8them inherit :class:`simpy.events.Event`. The listing below shows the 9hierarchy of events built into SimPy:: 10 11 events.Event 12 | 13 +— events.Timeout 14 | 15 +— events.Initialize 16 | 17 +— events.Process 18 | 19 +— events.Condition 20 | | 21 | +— events.AllOf 22 | | 23 | +— events.AnyOf 24 . 25 . 26 . 27 28This is the set of basic events. Events are extensible and resources, for 29example, define additional events. In this guide, we'll focus on the events in 30the :mod:`simpy.events` module. The :doc:`guide to resources <resources>` 31describes the various resource events. 32 33 34Event basics 35============ 36 37SimPy events are very similar – if not identical — to deferreds, futures or 38promises. Instances of the class :class:`Event` are used to describe any kind 39of events. Events can be in one of the following states. An event 40 41- might happen (not triggered), 42- is going to happen (triggered) or 43- has happened (processed). 44 45They traverse these states exactly once in that order. Events are also tightly 46bound to time and time causes events to advance their state. 47 48Initially, events are not triggered and just objects in memory. 49 50If an event gets triggered, it is scheduled at a given time and inserted into 51SimPy's event queue. The property :attr:`Event.triggered` becomes ``True``. 52 53As long as the event is not *processed*, you can add *callbacks* to an event. 54Callbacks are callables that accept an event as parameter and are stored in the 55:attr:`Event.callbacks` list. 56 57An event becomes *processed* when SimPy pops it from the event queue and 58calls all of its callbacks. It is now no longer possible to add callbacks. The 59property :attr:`Event.processed` becomes ``True``. 60 61Events also have a *value*. The value can be set before or when the event is 62triggered and can be retrieved via :attr:`Event.value` or, within a process, by 63yielding the event (``value = yield event``). 64 65 66Adding callbacks to an event 67---------------------------- 68 69"What? Callbacks? I've never seen no callbacks!", you might think if you have 70worked your way through the :doc:`tutorial <../simpy_intro/index>`. 71 72That's on purpose. The most common way to add a callback to an event is 73yielding it from your process function (``yield event``). This will add the 74process' *_resume()* method as a callback. That's how your process gets resumed 75when it yielded an event. 76 77However, you can add any callable object (function) to the list of callbacks 78as long as it accepts an event instance as its single parameter: 79 80.. code-block:: python 81 82 >>> import simpy 83 >>> 84 >>> def my_callback(event): 85 ... print('Called back from', event) 86 ... 87 >>> env = simpy.Environment() 88 >>> event = env.event() 89 >>> event.callbacks.append(my_callback) 90 >>> event.callbacks 91 [<function my_callback at 0x...>] 92 93If an event has been *processed*, all of its :attr:`Event.callbacks` have been 94executed and the attribute is set to ``None``. This is to prevent you from 95adding more callbacks – these would of course never get called because the 96event has already happened. 97 98Processes are smart about this, though. If you yield a processed event, 99*_resume()* will immediately resume your process with the value of the event 100(because there is nothing to wait for). 101 102 103Triggering events 104----------------- 105 106When events are triggered, they can either *succeed* or *fail*. For example, if 107an event is to be triggered at the end of a computation and everything works 108out fine, the event will *succeed*. If an exception occurs during that 109computation, the event will *fail*. 110 111To trigger an event and mark it as successful, you can use 112``Event.succeed(value=None)``. You can optionally pass a *value* to it (e.g., 113the results of a computation). 114 115To trigger an event and mark it as failed, call ``Event.fail(exception)`` 116and pass an :class:`Exception` instance to it (e.g., the exception you caught 117during your failed computation). 118 119There is also a generic way to trigger an event: ``Event.trigger(event)``. 120This will take the value and outcome (success or failure) of the event passed 121to it. 122 123All three methods return the event instance they are bound to. This allows you 124to do things like ``yield Event(env).succeed()``. 125 126 127Example usages for ``Event`` 128============================ 129 130The simple mechanics outlined above provide a great flexibility in the way 131events (even the basic :class:`Event`) can be used. 132 133One example for this is that events can be shared. They can be created by a 134process or outside of the context of a process. They can be passed to other 135processes and chained: 136 137.. code-block:: python 138 139 >>> class School: 140 ... def __init__(self, env): 141 ... self.env = env 142 ... self.class_ends = env.event() 143 ... self.pupil_procs = [env.process(self.pupil()) for i in range(3)] 144 ... self.bell_proc = env.process(self.bell()) 145 ... 146 ... def bell(self): 147 ... for i in range(2): 148 ... yield self.env.timeout(45) 149 ... self.class_ends.succeed() 150 ... self.class_ends = self.env.event() 151 ... print() 152 ... 153 ... def pupil(self): 154 ... for i in range(2): 155 ... print(r' \o/', end='') 156 ... yield self.class_ends 157 ... 158 >>> school = School(env) 159 >>> env.run() 160 \o/ \o/ \o/ 161 \o/ \o/ \o/ 162 163This can also be used like the *passivate / reactivate* known from SimPy 2. 164The pupils *passivate* when class begins and are *reactivated* when the bell 165rings. 166 167 168Let time pass by: the ``Timeout`` 169================================= 170 171To actually let time pass in a simulation, there is the *timeout* event. 172A timeout has two parameters: a *delay* and an optional *value*: 173``Timeout(delay, value=None)``. It triggers itself during its creation and 174schedules itself at ``now + delay``. Thus, the ``succeed()`` and ``fail()`` 175methods cannot be called again and you have to pass the event value to it when 176you create the timeout. 177 178The delay can be any kind of number, usually an *int* or *float* as long as it 179supports comparison and addition. 180 181 182Processes are events, too 183========================= 184 185SimPy processes (as created by :class:`Process` or ``env.process()``) have the 186nice property of being events, too. 187 188That means, that a process can yield another process. It will then be resumed 189when the other process ends. The event's value will be the return value of that 190process: 191 192.. code-block:: python 193 194 >>> def sub(env): 195 ... yield env.timeout(1) 196 ... return 23 197 ... 198 >>> def parent(env): 199 ... ret = yield env.process(sub(env)) 200 ... return ret 201 ... 202 >>> env.run(env.process(parent(env))) 203 23 204 205The example above will only work in Python >= 3.3. As a workaround for older 206Python versions, you can use ``env.exit(23)`` with the same effect. 207 208When a process is created, it schedules an :class:`Initialize` event which will 209start the execution of the process when triggered. You usually won't have to 210deal with this type of event. 211 212If you don't want a process to start immediately but after a certain delay, you 213can use :func:`simpy.util.start_delayed()`. This method returns a helper 214process that uses a *timeout* before actually starting a process. 215 216The example from above, but with a delayed start of ``sub()``: 217 218.. code-block:: python 219 220 >>> from simpy.util import start_delayed 221 >>> 222 >>> def sub(env): 223 ... yield env.timeout(1) 224 ... return 23 225 ... 226 >>> def parent(env): 227 ... sub_proc = yield start_delayed(env, sub(env), delay=3) 228 ... ret = yield sub_proc 229 ... return ret 230 ... 231 >>> env.run(env.process(parent(env))) 232 23 233 234Pay attention to the additional ``yield`` needed for the helper process. 235 236.. _waiting_for_multiple_events_at_once: 237 238Waiting for multiple events at once 239=================================== 240 241Sometimes, you want to wait for more than one event at the same time. For 242example, you may want to wait for a resource, but not for an unlimited amount 243of time. Or you may want to wait until a set of events has happened. 244 245SimPy therefore offers the :class:`AnyOf` and :class:`AllOf` events which both 246are a :class:`Condition` event. 247 248Both take a list of events as an argument and are triggered when any (at least one) or 249all of them are triggered. 250 251.. code-block:: python 252 253 >>> from simpy.events import AnyOf, AllOf, Event 254 >>> events = [Event(env) for i in range(3)] 255 >>> a = AnyOf(env, events) # Triggers if at least one of "events" is triggered. 256 >>> b = AllOf(env, events) # Triggers if all each of "events" is triggered. 257 258The value of a condition event is an ordered dictionary with an entry for every 259triggered event. In the case of ``AllOf``, the size of that dictionary will 260always be the same as the length of the event list. The value dict of ``AnyOf`` 261will have at least one entry. In both cases, the event instances are used as 262keys and the event values will be the values. 263 264As a shorthand for ``AllOf`` and ``AnyOf``, you can also use the logical 265operators ``&`` (and) and ``|`` (or): 266 267.. code-block:: python 268 269 >>> def test_condition(env): 270 ... t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='eggs') 271 ... ret = yield t1 | t2 272 ... assert ret == {t1: 'spam'} 273 ... 274 ... t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='eggs') 275 ... ret = yield t1 & t2 276 ... assert ret == {t1: 'spam', t2: 'eggs'} 277 ... 278 ... # You can also concatenate & and | 279 ... e1, e2, e3 = [env.timeout(i) for i in range(3)] 280 ... yield (e1 | e2) & e3 281 ... assert all(e.processed for e in [e1, e2, e3]) 282 ... 283 >>> proc = env.process(test_condition(env)) 284 >>> env.run() 285 286The order of condition results is identical to the order in which the condition 287events were specified. This allows the following idiom for conveniently 288fetching the values of multiple events specified in an *and* condition 289(including ``AllOf``): 290 291.. code-block:: python 292 293 >>> def fetch_values_of_multiple_events(env): 294 ... t1, t2 = env.timeout(1, value='spam'), env.timeout(2, value='eggs') 295 ... r1, r2 = (yield t1 & t2).values() 296 ... assert r1 == 'spam' and r2 == 'eggs' 297 ... 298 >>> proc = env.process(fetch_values_of_multiple_events(env)) 299 >>> env.run() 300