cristiklein / simpy

MIT License
107 stars 16 forks source link

Timeout on Store #4

Closed rayman4ever closed 5 years ago

rayman4ever commented 5 years ago

I am trying to implement a code where if the packet receiver does not receive a packet within a period of time it must report it to the console.

I realized the simpy engine does not process this correctly, digging deep I realized that the engine removes the callbacks from the event but I cannot understand why.

Code below:


from simpy import *

def dbg_log(env, msg):
    tmp = '[%0.2f] %s' % (env.now, msg)
    print(tmp)

def packet_receiver(env, q):
    while True:
        dbg_log(env, 'RX: Queue len: ' + str(len(q.items)))
        event_a = env.timeout(10)
        event_b = q.get()
        condition = yield event_a | event_b
        if event_a in condition:
            dbg_log(env, 'RX: Timeout...')
        else:
            dbg_log(env, 'RX: received a packet...')
        yield env.timeout(20)

def packet_transmitter(env, q):
    yield env.timeout(500)
    while True:
        dbg_log(env, 'TX: Sending')
        q.put(10)
        yield env.timeout(50)

env = Environment()
packet_store = Store(env)
env.process(packet_receiver(env, packet_store))
env.process(packet_transmitter(env, packet_store))

dbg_log(env, "*** SIM START ***")
env.run(until=1000)
dbg_log(env, "*** SIM END ***")

Please help.

rayman4ever commented 5 years ago

Snippet of the output:


[600.00] TX: Sending
[600.00] RX: Queue len: 1
[610.00] RX: Timeout...
[630.00] RX: Queue len: 0
[640.00] RX: Timeout...
[650.00] TX: Sending
[660.00] RX: Queue len: 0
[670.00] RX: Timeout...
[690.00] RX: Queue len: 0
[700.00] TX: Sending
[700.00] RX: Timeout...
[720.00] RX: Queue len: 0
[730.00] RX: Timeout...
[750.00] TX: Sending
[750.00] RX: Queue len: 1
[760.00] RX: Timeout...
[780.00] RX: Queue len: 0
[790.00] RX: Timeout...
cristiklein commented 5 years ago

Hi,

I'm not sure this is great from a user's perspective, but the following happens. Although your code "lost" interest in event_b after event_a fired, the simpy engine still delivers event_b. During your next loop, a new event_b request is sent to the simpy engine, your code having effectively lost the ability to retrieve the one that fired in the previous loop. I slightly modified your code to reuse event_bs across the loop if they are not consumed by your code:

from simpy import *

def dbg_log(env, msg):
    tmp = '[%0.2f] %s' % (env.now, msg)
    print(tmp)

def packet_receiver(env, q):
    event_b = None
    while True:
        dbg_log(env, 'RX: Queue len: ' + str(len(q.items)))
        event_a = env.timeout(10)
        if event_b is None:
            event_b = q.get()
        condition = yield event_a | event_b
        if event_a in condition:
            dbg_log(env, 'RX: Timeout...')
        else:
            dbg_log(env, 'RX: received a packet...')
            event_b = None
        yield env.timeout(20)

def packet_transmitter(env, q):
    yield env.timeout(500)
    while True:
        dbg_log(env, 'TX: Sending')
        q.put(10)
        dbg_log(env, 'TX: Sent')
        yield env.timeout(50)

env = Environment()
packet_store = Store(env)
env.process(packet_receiver(env, packet_store))
env.process(packet_transmitter(env, packet_store))

dbg_log(env, "*** SIM START ***")
env.run(until=1000)
dbg_log(env, "*** SIM END ***")

I understand that this behaviour is counter-intuitive, but I'm wondering how a more intuitive solution should look like. Should AnyOf conditional cancel interest in unfired events? Should there be a way to del event_b?

rayman4ever commented 5 years ago

Interesting point! I think the event should be canceled too.

I think the main problem here is that when the timeout starts and the Store gets an element in the queue, it does not process the store event. It instead waits for the timeout to elapse.

The workaround I am using is:

t1 = env.now
while env.now < t1 + 1000:
    if len(queue.items) > 0:
        item = yield queue.get()
        break
    yield env.timeout(0.001) # small step

it works but it affects the simulation performance drastically.

cristiklein commented 5 years ago

I suggest you take my workaround. My workaround does not "lose" any events and, although looks ugly to read, it uses the simulation engine properly to wait for a timeout or packet arrival. Performance is therefore really good.