0

I am trying to append callbacks to various events in a SimPy simulation, and I am finding that you can't use the yield keyword inside a callback. Is there a way to correctly do this, or do you have to only use callbacks within callbacks?

For example, I would like the put_and_get_eggs function to work in the following code:

import simpy

env = simpy.Environment()
foods = simpy.FilterStore(env)
foods.items = ['spam', 'eggs', 'eggs']

def test_callback(env):
    print("starting")
    yield foods.get(lambda x: x == "spam") & foods.get(lambda x: x == "eggs")
    yield env.timeout(5)

    print("items after first get using AllOf: %s" % foods.items)
    t1, t2 = foods.get(lambda x: x == "spam"), foods.get(lambda x: x == "eggs")

    # add callbacks to the get event. This works
    t1.callbacks.append(lambda x: print(x.value + " gotten at " + str(env.now)))
    t2.callbacks.append(lambda x: print(x.value + " gotten at " + str(env.now)))
    yield env.timeout(1)
    # Spam is put into the environment after 1 second, then immediately the callback gets called on get event
    foods.put("spam")
    print("spam put at %s" % env.now)
    put_eggs = foods.put("eggs")

    # add callbacks that include a yield, this doesn't work
    def get_and_put_eggs():
        print("getting eggs in callback with yield")
        yield foods.get('eggs')
        print("getting eggs in callback with yield")
        yield env.timeout(5)
        print("getting eggs in callback with yield")
        yield foods.put('eggs')
        print("getting eggs in callback with yield")

    put_eggs.callbacks.append(get_and_put_eggs)


proc = env.process(test_callback(env))
env.run()

So far, I am able to get this to work, by defining each of the events to the right of "yield" in get_and_put_eggs as a separate event and adding callbacks to them, but then this creates a really long and confusing callback chain. I would like to be able to do something like yield from but I haven't been able to get this to work (such as using the line put_eggs.callbacks.append(lambda x: (yield from get_and_put_eggs))).

Is this possible? I checked the following question, but it seems a bit different in the callback scenario, since the callback is only appended to the callback list and you can't explicitly yield from it. Python, SimPy: Using yield inside functions

Community
  • 1
  • 1
Allen Wang
  • 2,426
  • 2
  • 24
  • 48

2 Answers2

1

You should never directly append anythig to callbacks and escpecially yield funtions just won't work.

Instead, just spawn a new subprocess and let it wait wait for (yield) the event to which you want to append a callback.

See https://simpy.readthedocs.io/en/latest/topical_guides/process_interaction.html#waiting-for-another-process-to-terminate

Stefan Scherfke
  • 3,012
  • 1
  • 19
  • 28
0

@Stefan I was actually able to get this work by adding a env.process into the callback:

import simpy

env = simpy.Environment()
foods = simpy.FilterStore(env)
foods.items = ['spam', 'eggs', 'eggs']


def test_callback(env):
    print("starting")
    yield foods.get(lambda x: x == "spam") & foods.get(lambda x: x == "eggs")
    yield env.timeout(5)

    print("items after first get using AllOf: %s" % foods.items)
    t1, t2 = foods.get(lambda x: x == "spam"), foods.get(lambda x: x == "eggs")

    # add callbacks to the get event. This works
    t1.callbacks.append(lambda x: print(x.value + " gotten at " + str(env.now)))
    t2.callbacks.append(lambda x: print(x.value + " gotten at " + str(env.now)))
    yield env.timeout(1)
    # Spam is put into the environment after 1 second, then immediately the callback gets called on get event
    foods.put("spam")
    print("spam put at %s" % env.now)
    put_eggs = foods.put("eggs")

    # add callbacks that include a yield, this doesn't work
    def get_and_put_eggs(event):
        print("getting eggs in callback with yield")
        yield foods.get(lambda x: x == 'eggs')
        print("getting eggs in callback with yield")
        yield env.timeout(5)
        print("getting eggs in callback with yield")
        yield foods.put('eggs')
        print("getting eggs in callback with yield")

    put_eggs.callbacks.append(lambda x: env.process(get_and_put_eggs(x)))


proc = env.process(test_callback(env))
env.run()

Would you clarify what you mean by spawning a new subprocess? Do you mean env.process? What if I want each of those spawned processes to do something as soon as they are finished? It seems then that callbacks are necessary, but if they are not, could you give an example?

Allen Wang
  • 2,426
  • 2
  • 24
  • 48