Search code examples
pythonsimulationsimpy

How to service multiple resources in one process (SimPy simulation)?


I have a problem with simulating simple work conserving scheduler in SimPy. I want this scheduler to have 2 queues to work as simple round robin which services packet from queue number 1 and then services packet from queue number 2. If one of the queues is empty it goes to service packets from other queue (work conserving). Packets that were already serviced are sent to 1 common output.

I've already written a code based on this code (https://www.grotto-networking.com/DiscreteEventPython.html) to create such solution but it doesn't work as i wanted. Packets which are sent to queue number 1 are being serviced but packets in queue number 2 aren't. I think the problem might be with having multiple resources for 1 process and i don't know how to resolve this problem.

class RoundRobinQueue(object):
    def __init__(self, env, rate, qlimit=None, limit_bytes=True):
        self.store = simpy.Store(env)
        self.store2 = simpy.Store(env)
        self.rate = rate
        self.env = env
        self.out = None
        self.packets_rec = 0
        self.packets_drop = 0
        self.qlimit = qlimit
        self.limit_bytes = limit_bytes
        self.byte_size = 0  # Current size of the queue in bytes
        self.busy = 0  # Used to track if a packet is currently being sent
        self.action = env.process(self.run())  # starts the run() method as a SimPy process
        self.trigger = 1

    def run(self):
        while True:
            if (self.trigger == 0 and len(self.store.items)>=0):
                self.trigger = 1
                msg = (yield self.store.get())
                self.byte_size -= msg.size
                self.busy = 1
                yield self.env.timeout(msg.size * 8.0 / self.rate)
                self.out.put(msg)
                self.busy = 0
            else:
                self.trigger = 1

            if (self.trigger == 1 and len(self.store2.items)>=0):
                self.trigger = 0
                msg2 = (yield self.store2.get())
                self.byte_size -= msg2.size
                self.busy = 1
                yield self.env.timeout(msg2.size * 8.0 / self.rate)
                self.out.put(msg2)
                self.busy = 0
            else:
                self.trigger = 0

Solution

  • looks like when both queues are empty it falls into a infinite loop, added a check for that. Also includes the fix where it only pulls from a queue if it is not empty

    def run(self):
        while True:
            if len(self.store.items) + len(self.store2.items) ==0:
                # both queue are empty, wait a sec to avoid a infinate loop
                yield self.env.timeout(1)
            else:
                if len(self.store.items)>0:
                    self.trigger = 1
                    msg = (yield self.store.get())
                    self.byte_size -= msg.size
                    self.busy = 1
                    yield self.env.timeout(msg.size * 8.0 / self.rate)
                    self.out.put(msg)
                    self.busy = 0
                else:
                    self.trigger = 1
    
                if len(self.store2.items)>0:
                    self.trigger = 0
                    msg2 = (yield self.store2.get())
                    self.byte_size -= msg2.size
                    self.busy = 1
                    yield self.env.timeout(msg2.size * 8.0 / self.rate)
                    self.out.put(msg2)
                    self.busy = 0
                else:
                    self.trigger = 0