Reneging

Back Up

Typical model scenario: a process requests a resource and wants to leave before acquiring the resource, e.g. because the wait gets too long ("timeout reneging") or because it could get quicker service by another resource with a shorter queue ("conditional reneging").

Problem: SimPy passivates processes waiting for a resource. Waiting processes therefore cannot take any decisions, such as reneging.

A solution approach: One solution is to associate another process with a waiting process which can take the decision to renege, remove that process from the waiting queue and reactivate the process.

Implementation
This approach will be demonstrated by two examples, one for timeout reneging and one for conditional reneging.

Timeout reneging example
The scenario is simple: Resource customers arrive and request the resource. after a period of patience time units, a process waiting for a resource reneges (leaves the queue). Here is the code:

001 #! /usr/local/bin/python
002 """ Example for reneging with timeout.
003 Customers leave a queue when they have not been served after
004 a certain time.
005 """
006 from SimPy.Simulation import *
007
008 class Requestor(Process):
009     def gotResource(self,resource):
010         """Tests whether the resource has been acquired"""
011         result=self in resource.activeQ
012         # If this process got the resource
013         if result:
014             #Acquired, so cancel alarm
015             self.cancel(self.al)
016         else:
017             #not acquired, so get out of queue, renege
018             resource.waitQ.remove(self)
019             if resource.monitored:
020                 resource.waitMon.observe(len(resource.waitQ),t=now())
021         return result
022
023     def setAlarm(self,delay):
024         """Activates the Alarm process"""
025         self.al=Alarm("Alarm")
026         activate(self.al,self.al.wakeup(delay=1,whom=self))
027
028     def use(self,resource,patience):
029         """The process which requests a resource and reneges when it has
030         not acquired it within a certain time"""
031         self.setAlarm(delay=patience)
032         yield request,self,resource
033         if self.gotResource(resource):
034             print "%s +++ %s got resource"%(now(),self.name)
035             yield hold,self,2
036             yield release,self,resource
037             print "%s --- %s released resource"%(now(),self.name)
038         else:
039             print "%s <<< %s reneged"%(now(),self.name)
040
041 class Alarm(Process):
042     """The trigger process which reactivates a process
043     when the reneging delay time has passed"""
044     def wakeup(self,delay,whom):
045         yield hold,self,delay
046         reactivate(whom)
047         yield hold,self
048
049 initialize()
050 res=Resource(capacity=3,monitored=True)
051 for i in range(6):
052     r=Requestor("Requestor %s"%i)
053     activate(r,r.use(resource=res,patience=1))
054 simulate(until=20)
Output:
0 +++ Requestor 0 got resource
0 +++ Requestor 1 got resource
0 +++ Requestor 2 got resource
1 <<< Requestor 3 reneged
1 <<< Requestor 4 reneged
1 <<< Requestor 5 reneged
2 --- Requestor 0 released resource
2 --- Requestor 1 released resource
2 --- Requestor 2 released resource

Explanation: Each of the six Requestor (line 008) instances has a use process which requests a resource res (l. 050) which has a capacity for serving three processes at the same time. A Requestor process is prepared to wait at most patience (=1) time units for the resource. When requesting the resource (l. 032), it starts (l. 031) an Alarm.wakeup process (l.  which reactivates the Requestor process after patience time units (l. 044). When the Requestor.use process gets reactivated (l. 033), there are two possibilities

bulletIt has acquired the resource (self.gotResource(resource)==True, l. 033) before the timeout. It has to cancel the Alarm.use process. gotResource has already done that: After determining that the resource was acquired (self in resource.activeQ, l. 011), it cancels the Alarm.use process (l. 015).
bulletIt has timed out after patience time units and reneges. gotResource already has taken the waiting process out of the queue (l. 018) and, in case the res resource is monitored, records the change in wait queue length in the monitor (l. 020).

The output shows that only the first three Requestor.use processes acquire the resource. The next three all time out and renege.

Conditional  reneging example
The scenario for this example is that there are two equivalent resources. Resource customers arrive and queue for the first resource. When the queue in the other resource is shorter than the number of waiting processes ahead of them in the first queue, they renege and request the other resource.

001 #! /usr/local/bin/python
002 """Example for conditional reneging.
003 Customers change queues when less people in other queue
004 than number ahead of them in current queue.
005 """
006 from SimPy.Simulation import *
007
008 class Requestor(Process):
009     def gotResource(self,resource):
010         """Tests whether self got the requested resource"""
011         result=self in resource.activeQ
012         # result is True if self got the resource
013         if result:
014             #self got resource, so cancel trigger process
015             self.cancel(self.al)
016         else:
017             #trigger process fired before self got resource, 
018             #get out of queue (renege)
019             resource.waitQ.remove(self)
020             if resource.monitored:
021                 resource.waitMon.observe(len(resource.waitQ),t=now())
022         return result
023
024     def setReneger(self,condProc):
025         """Activates the reneging trigger process"""
026         self.al=ConditionalReneger("CondReneg")
027         activate(self.al,self.al.wait(condProc,self))
028
029     def use(self,resource,otherResource):
030         """The process requesting a resource and reneging when queue for 
031         other resource shorter"""
032         self.otherResource=otherResource
033         self.resource=resource
034
035         def renegeCondition():
036             """return True, if nr ahead of self greater or equal nr in other queue"""
037             if self in self.resource.waitQ:
038                 return len(self.otherResource.waitQ)<self.resource.waitQ.index(self)
039             else:
040                 return False
041
042         self.setReneger(renegeCondition)
043         yield request,self,resource
044         print now(),"waitQ resource:",[x.name for x in self.resource.waitQ],\
045                "waitQ otherResource:",[x.name for x in self.otherResource.waitQ]
046         if self.gotResource(resource):
047             print "%s +++ %s got resource"%(now(),self.name)
048             yield hold,self,2
049             yield release,self,resource
050             print "%s --- %s released resource"%(now(),self.name)
051         else:
052             print "%s <<< %s reneged"%(now(),self.name)
053             yield request,self,self.otherResource
054             print now(),"waitQ resource:",[x.name for x in self.resource.waitQ],\
055                "waitQ otherResource:",[x.name for x in self.otherResource.waitQ]
056             print "%s %s got otherResource"%(now(),self.name)
057             yield hold,self,2
058             yield release,self,self.otherResource
059
060 class ConditionalReneger(Process):
061     """The trigger process which reactivates another process when the 
062     reneging condition is True"""
063     def wait(self,condProc,whom):
064         yield waituntil,self,condProc
065         reactivate(whom)
066         yield hold,self
067
068 initialize()
069 res=Resource(capacity=1,monitored=True)
070 resOther=Resource(capacity=1,monitored=True)
071 for i in range(6):
072     r=Requestor("Requestor %s"%i)
073     activate(r,r.use(resource=res,otherResource=resOther),at=i*0.25)
074 simulate(until=10)
Output:
0 waitQ resource: [] waitQ otherResource: []
0 +++ Requestor 0 got resource
0.5 waitQ resource: ['Requestor 1', 'Requestor 2'] waitQ otherResource: []
0.5 <<< Requestor 2 reneged
0.5 waitQ resource: ['Requestor 1'] waitQ otherResource: []
0.5 Requestor 2 got otherResource
0.75 waitQ resource: ['Requestor 1', 'Requestor 3'] waitQ otherResource: []
0.75 <<< Requestor 3 reneged
1.25 waitQ resource: ['Requestor 1', 'Requestor 4', 'Requestor 5'] waitQ otherResource: ['Requestor 3']
1.25 <<< Requestor 5 reneged
2 --- Requestor 0 released resource
2 waitQ resource: ['Requestor 4'] waitQ otherResource: ['Requestor 3', 'Requestor 5']
2 +++ Requestor 1 got resource
2.5 waitQ resource: ['Requestor 4'] waitQ otherResource: ['Requestor 5']
2.5 Requestor 3 got otherResource
4 --- Requestor 1 released resource
4 waitQ resource: [] waitQ otherResource: ['Requestor 5']
4 +++ Requestor 4 got resource
4.5 waitQ resource: [] waitQ otherResource: []
4.5 Requestor 5 got otherResource
6 --- Requestor 4 released resource

 Explanation: This example has the same structure as the timeout example. The main difference is that the trigger process associated with Requestor instances here fires based on a wait until condition (l. 064).  The condition for the wait until is encoded in method renegeCondition (l. 035) which returns True if the number of waiting processes in the first queue ahead of self is greater or equal the length of the other queue.

The output shows the changing from one queue to another based on queue length (e.g. Requestor 2 at time 0.5).

horizontal rule

©Copyright 2004, 2005, 2006, 2007, 2008 SimPy Developer Team.
For problems or questions regarding this web contact SimPy webmaster.
Last updated: 20/06/08.