I'm unable to explain the following behavior of store

14 views
Skip to first unread message

Faiz Halde

unread,
Sep 30, 2025, 4:39:24 AM (7 days ago) Sep 30
to python-simpy
simpy 4.1.1

import simpy

env = simpy.Environment()
store = simpy.Store(env)


def consumer():
    for i in range(10):
        job = store.get()
        timer = env.timeout(1, i)
        x = yield env.any_of([timer, job])
        print(x)
        if job in x:
            print(f"{env.now:.2f}: get {job.value}")
        if timer in x:
            print(f"{env.now:.2f}: timeout {timer.value}")


def producer():
    global store
    for i in range(10):
        print(f"{env.now:.2f}: put {i}")
        yield env.timeout(1)
        yield store.put({i: i})


env.process(producer())
env.process(consumer())
env.run()

My code produces the following
0.00: put 0
1.00: put 1
1.00: x: <ConditionValue {<Timeout(1, value=0) object at 0x1030b00b0>: 0}>
1.00: timeout 0
2.00: put 2
2.00: x: <ConditionValue {<Timeout(1, value=1) object at 0x1030b0da0>: 1}>
2.00: timeout 1
3.00: put 3
3.00: x: <ConditionValue {<Timeout(1, value=2) object at 0x1030b00b0>: 2}>
3.00: timeout 2
4.00: put 4
4.00: x: <ConditionValue {<Timeout(1, value=3) object at 0x1030b0da0>: 3}>
4.00: timeout 3
5.00: put 5
5.00: x: <ConditionValue {<Timeout(1, value=4) object at 0x1030b00b0>: 4}>
5.00: timeout 4
6.00: put 6
6.00: x: <ConditionValue {<Timeout(1, value=5) object at 0x1030b0da0>: 5}>
6.00: timeout 5
7.00: put 7
7.00: x: <ConditionValue {<Timeout(1, value=6) object at 0x1030b00b0>: 6}>
7.00: timeout 6
8.00: put 8
8.00: x: <ConditionValue {<Timeout(1, value=7) object at 0x1030b0da0>: 7}>
8.00: timeout 7
9.00: put 9
9.00: x: <ConditionValue {<Timeout(1, value=8) object at 0x1030b00b0>: 8}>
9.00: timeout 8
10.00: x: <ConditionValue {<Timeout(1, value=9) object at 0x1030b0da0>: 9}>
10.00: timeout 9

I was expecting that any_of would also trigger when the store.get() succeeds (by a put call at some point)

What I want to achieve was the following
A consumer must wait for a message in a store for "x" seconds, whichever comes first

Thanks
Faiz
Message has been deleted
Message has been deleted

Faiz Halde

unread,
Sep 30, 2025, 5:34:34 AM (7 days ago) Sep 30
to python-simpy
Ok, so the following works

import simpy

env = simpy.Environment()
store = simpy.Store(env)


def consumer():
    job = store.get()
    timer = env.timeout(1)
    while True:

        x = yield env.any_of([timer, job])
        if job.triggered:

            print(f"{env.now:.2f}: get {job.value}")
            job = store.get()
            timer = env.timeout(1)
            continue

        if timer in x:
            print(f"{env.now:.2f}: timeout")
            timer = env.timeout(1)


def producer():
    for i in range(10):
        yield env.timeout(2)

        print(f"{env.now:.2f}: put {i}")
        yield store.put({i: i})


env.process(producer())
env.process(consumer())
env.run(until=15)

I still don't know why ( Claude fixed it for me – happy and sad at the same time )

Harry Munro

unread,
Sep 30, 2025, 8:09:42 AM (7 days ago) Sep 30
to python-simpy, Faiz Halde
Hi Faiz,

This is my quick interpretation of what’s happening with your code here...

Your old code created a newstore.get() request on every loop iteration. If the timeout happened first, the get request was abandoned. Kind of like a customer leaving a queue every second, and then rejoining at the back of the queue.

The new code creates the job = store.get() request once, before the main loop starts.
  • If a timeout occurs: The code resets the timer for the next interval. The original, pending job request remains active.
  • If a job arrives: The code processes the job and only then creates a new job = store.get() request to wait for the next item.
This correctly models a consumer that will wait indefinitely for its specific request to be fulfilled.

I hope this helps.

Best,
Harry

Harry Munro CEng

Independent Simulation Consultant, Aspegio Ltd

Founder, School of Simulation

LinkedIn: https://www.linkedin.com/in/harryjmunro/

--
You received this message because you are subscribed to the Google Groups "python-simpy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python-simpy...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/python-simpy/5af7497e-63c9-4218-ac89-332e123322d1n%40googlegroups.com.

Faiz Halde

unread,
Sep 30, 2025, 8:14:15 AM (7 days ago) Sep 30
to Harry Munro, python-simpy
Awesome!

Thanks Harry. Do you think there's a better way to achieve this pattern? Mine almost felt like I was hacking through the API

If not, I'm happy to settle on this

caam...@yahoo.com

unread,
Sep 30, 2025, 7:19:23 PM (6 days ago) Sep 30
to Faiz Halde, python-simpy

Hello Faiz.  I prompted your code and got this refactored code back.  Not sure if it covers your needs.

Please comment.

Cesar

 

import simpy

def consumer(env, store, num_jobs=10, timeout_duration=1):

    """Consumes jobs from the store or times out if none are available."""

    for i in range(num_jobs):

        job_event = store.get()

        timeout_event = env.timeout(timeout_duration, value=i)

        result = yield env.any_of([job_event, timeout_event])

 

        if job_event in result:

            print(f"{env.now:.2f}: got job {job_event.value}")

        if timeout_event in result:

            print(f"{env.now:.2f}: timeout {timeout_event.value}")

 

 

def producer(env, store, num_jobs=10, interval=1):

    """Produces jobs and puts them into the store at regular intervals."""

    for i in range(num_jobs):

        print(f"{env.now:.2f}: putting job {i}")

        yield env.timeout(interval)

        yield store.put({i: i})

 

 

def main():

    env = simpy.Environment()

    store = simpy.Store(env)

 

    env.process(producer(env, store))

    env.process(consumer(env, store))

    env.run()

 

 

if __name__ == "__main__":

    main()

--

Reply all
Reply to author
Forward
0 new messages