Using a process to terminate another process

639 views
Skip to first unread message

Ian Cromwell

unread,
Jul 13, 2016, 6:02:54 PM7/13/16
to python-simpy
I am not at all sure I'm using the terminology correctly here, but what I am trying to do is write a script to stop another process from running when a certain condition is met. As a fictitious (and silly) example, imagine a world in which people are at risk of developing incurable and deadly brain fungus. It happens to each person randomly. But sometimes there are aliens who swoop in, kidnap you, and put the fungus directly into your head. A person can't get brain fungus TWICE. And we are trying to keep a record of when it happens to a given person.

I thought the way to do it might be using an interrupt, but I can't seem to get the syntax to work. What I have is two processes running in parallel: 1) A 'develop brain fungus naturally' process; and 2) A "alien abduction" process, where those little green rascals take matters into their own tentacles. What I want to happen is that Process #1 stops when Process #2 happens (i.e., no more natural development when artificial development has occurred). I have written a script, and annotated it with what I think each step does.

import random
import simpy

# Create a brain for the fungus to infect

class Brain(object):
   
def __init__(self, **kwargs):                  # This step allows the class to adopt any attribute defined within the model
   
self.__dict__.update(kwargs)


def devFungus(brain, env):                         # Background, organic development of brain fungus occurs at a random time
    t_Fungus
= random.normalvariate(5000, 100)         # This is the random time for developing brain fungus naturally
   
try:                                               # Develop fungus naturally
       
yield env.timeout(t_Fungus)
       
print(env.now, 'Developed fungus naturally')
        brain
.fungusStatus = 1                             # Set the fungal status of the 'brain' object to '1'
        brain
.time_fungus = env.now                        # Record the time that the fungus grew
        env
.exit()                                         # Stop running Simpy

   
except simpy.Interrupt:                            # But if aliens give you brain fungus first...
       
print(env.now, 'You got headshrooms from aliens')
        brain
.fungusStatus = 1
        brain
.time_fungus = env.now
        env
.exit()                                     # Stop running Simpy


def randFungus(brain, env):                        # Fungus implanted by aliens
   
while True:
       
yield env.timeout(random.normalvariate(2000,200))     # After some period of time, aliens kidnap you and give you brain fungus
        fungusDev
= env.process(devFungus(brain, env))        # Interrupt the natural fungus development function
        fungusDev
.interrupt()
       
break                                                 # Aliens leave, their dastardly work finished


brain
= Brain()                         # Create a brain
env
= simpy.Environment()               # Run the functions
env
.process(devFungus(brain, env))
env
.process(randFungus(brain, env))
env
.run()



The problem is that the interrupt doesn't seem to STOP the 'devFungus' process from continuing. This is the output:

(2088.5308414906685, 'You got headshrooms from aliens')
(4832.6423522407895, 'Developed fungus naturally')

vars(brain)
Out[10]: {'fungusStatus': 1, 'time_fungus': 4832.6423522407895}


Which suggests to me that the 'devFungus' process continues and doesn't register that I'm trying to get the process to stop when interrupted.

Any guidance would be most appreciated.

jpgr...@gmail.com

unread,
Jul 14, 2016, 4:33:53 PM7/14/16
to python-simpy
Hi Ian,

The key problem in your code is that you are running the devFungus process both from module scope (your "main") and from within randFungus. Thus you seeing the natural fungus development occur because of the module-scope devFungus process; not the devFungus process interrupted within randFungus().

To repair this model, but keep its general structure:

def devFungus(brain, env):
   
...  # same, but remove the `env.exit()` calls--they are superfluous

def randFungus(brain, env, devFungProc):
   
yield env.timeout(random.normalvariate(...))
   
if not devFungProc.triggered:
        devFungProc
.interrupt()


brain
= brain()
env
= simpy.Environment()
devFungProc
= env.process(devFungus(brain, env))
env
.process(randFungus(brain, env, devFungProc))
env
.run()

However, I might suggest and alternative modeling strategy that does not use interrupts.

import random
import simpy

def fungus(env):
    background_event
= env.timeout(random.normalvariate(5000, 100))
    alien_event
= env.timeout(random.normalvariate(2000, 200))
    events
= yield background_event | alien_event
   
if background_event in events and alien_event in events:
       
print(env.now, "Simultaneous fungus!")
   
elif background_event in events:

       
print(env.now, "Developed fungus naturally")

   
else:
       
assert alien_event in events
       
print(env.now, "Alien headshrooms")

env
= simpy.Environment()
env
.process(fungus(env))
env
.run()

I'm assuming this will be part of a broader simulation that actually requires discrete event processing; otherwise you could obviously just compare the two normalvariate() results and decide a-priori which kind of fungus would be achieved (no simpy needed).

Cheers,
Pete

Seb Brown

unread,
May 15, 2019, 11:49:35 AM5/15/19
to python-simpy
Bit late, but perhaps someone has figured this out. 
I am facing the exact same problem as the OP, namely that, although Process A was succesfully interrupted by Process B, as evidenced by the printing of a corresponding statement in the "Except simpy.Interrupt:" section, the supposedly interrupted Process A continues to run and execute all timeouts, prints, etc,. The solution suggested by Pete did not apply in my case, as there was only 1 instance of Process A being executed. 

Is anyone aware of a method to specifically interrupt a timeout event, or to interrupt a process altogether?

Cheers,
Sebastian

Peter Grayson

unread,
May 15, 2019, 6:21:36 PM5/15/19
to python...@googlegroups.com
Hi Sebastian,

It would help me if you could provide a code sample that illustrates the problem you are working on. I would like to understand where simpy’s interrupt mechanism is behaving differently than you expect.

Thanks,
Pete

On Wed, May 15, 2019, at 6:19 PM, Peter Grayson wrote:
Hi Sebastian,

It would help me if you could provide a code sample that illustrates the problem you’re working on. I’d like to understand where simpy’s interrupt mechanism is behaving differently than you expect. 

Thanks,
Pete
--
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 post to this group, send email to python...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Seb Brown

unread,
May 21, 2019, 7:36:49 AM5/21/19
to python-simpy
Hello Pete,

I am sorry for the late reply, I've been experimenting with the code a bit. If this problem is too time-consuming to solve, I fully understand. Here is a simpler version of the final script, but it's still relatively lengthy:

import simpy
import random
random.seed(1)

class electricBus(object):
    def __init__(self, env, name):
        self.env = env
        self.name = name

        self.SOC = 1. #State of Charge of the bus' battery (in %)
        self.randomTrip = random.randint(0,30) #generate random Trip times, including initial wait
        self.randomLayover = 0 #generate random Layover times
        self.queueEntry = 1000 #variable recording when a bus enters the charger queue

        driveProc = self.env.process(self.drive_proc()) #start main process
        interruptProc = self.env.process(self.interrupt_proc()) #start secondary process responsible for interrupting and eventually monitoring

    def interrupt_proc(self):
        while True:
            if (int(self.queueEntry) + int(self.randomLayover)) == self.env.now:
                self.env.process(self.charge_proc()).interrupt()
                # print self.env.now, self.name, "CHARGING INTERRUPTED"
                self.queueEntry = 0
                self.randomLayover = 0

            yield self.env.timeout(1)

    def drive_proc(self):
        yield self.env.timeout(self.randomTrip) #wait some time before 1st trip
        print self.env.now, self.name, "STARTS ITS FIRST TRIP NOW"
        while True:
            self.randomTrip = random.randint(20,30)
            print self.env.now, self.name, "Starts Trip lasting %s minutes with a SOC = %.3f" % (self.randomTrip, self.SOC)
            yield self.env.timeout(self.randomTrip) # "Drive" a trip lasting 20-30 minutes
            self.SOC -= (float(self.randomTrip)/200) #deduct consumed energy from Battery, assuming 0.5%/minute driven
            print self.env.now, self.name, "Completes Trip lasting %s minutes with a SOC = %.3f" % (self.randomTrip, self.SOC)

            if self.SOC < 0.9: #go charge if the Battery is below 90%
                yield self.env.process(self.charge_proc())

    def charge_proc(self):
        self.queueEntry = self.env.now #record the time a bus enters the 'queue'
        self.randomLayover = random.randint(5,10) #generate random layover time before next trip, during which the EB can charge
        print self.env.now, self.name, "has %s minute Layover before the next trip" % (self.randomLayover)
        with EBCS.request() as req:
            try:

                yield req
                print self.env.now, self.name, "STARTS CHARGING for %s minutes with a SOC = %.3f" % (self.randomLayover, self.SOC)
                yield self.env.timeout(self.randomLayover)
                self.SOC += (float(self.randomLayover)/100) # add back energy to Battery
                print self.env.now, self.name, "FINISHED CHARGING after %s minutes with a SOC = %.3f" % (self.randomLayover, self.SOC)
                self.queueEntry = 0
                self.randomLayover = 0
            except simpy.Interrupt:
                print self.env.now, self.name, "got interrupted!"
                self.queueEntry = 0
                self.randomLayover = 0

env = simpy.Environment()
EBCS = simpy.PreemptiveResource(env, capacity = 1) #Electric Battery Charging System resource, with 1 charging station
bus1 = electricBus(env, 'bus1')
bus2 = electricBus(env, 'bus2')
# bus3 = electricBus(env, 'bus3')
# bus4 = electricBus(env, 'bus4')
# bus5 = electricBus(env, 'bus5')
env.run(until=720) #run for half a day
print" SIMULATION COMPLETE"

 
The problem occurs when both objects (here: buses) are inside the charging process. There is only one resource (here: charger) available, so if both buses have layovers between 2 of their trips, they will both want to request the charger. Because the timetables of buses are fixed (i.e. they should follow the schedule exactly), a bus that has run out of layover time between 2 trips should cancel the charging process (i.e. regardless of the bus is in the queue or already charging) so that the integrity of the timetable/schedule is maintained. The picture below illustrates the problem I have with my Simpy script, namely that Bus1 should get interrupted at minute 95 and start with its next trip, i.e. should start executing the drive_proc again. Unfortunately, either because of Simpy's limitations or my incompetence, this does not occur. Instead, it does execute an interruption, but for an unknown reason restarts the charge_proc again instead of returning to the drive_proc. 

Again, this is perhaps a little difficult to understand as my coding skills are very basic. In any case, if there is an obvious solution that I am missing, I would be grateful for some advice!

Cheers,
Sebastian



Capture.JPG



On Thursday, 16 May 2019 00:21:36 UTC+2, Peter Grayson wrote:
Hi Sebastian,

It would help me if you could provide a code sample that illustrates the problem you are working on. I would like to understand where simpy’s interrupt mechanism is behaving differently than you expect.

Thanks,
Pete

On Wed, May 15, 2019, at 6:19 PM, Peter Grayson wrote:
Hi Sebastian,

It would help me if you could provide a code sample that illustrates the problem you’re working on. I’d like to understand where simpy’s interrupt mechanism is behaving differently than you expect. 

Thanks,
Pete

On Wed, May 15, 2019, at 11:49 AM, Seb Brown wrote:
Bit late, but perhaps someone has figured this out. 
I am facing the exact same problem as the OP, namely that, although Process A was succesfully interrupted by Process B, as evidenced by the printing of a corresponding statement in the "Except simpy.Interrupt:" section, the supposedly interrupted Process A continues to run and execute all timeouts, prints, etc,. The solution suggested by Pete did not apply in my case, as there was only 1 instance of Process A being executed. 

Is anyone aware of a method to specifically interrupt a timeout event, or to interrupt a process altogether?

Cheers,
Sebastian


--
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...@googlegroups.com.

Peter Grayson

unread,
Jun 3, 2019, 10:00:42 AM6/3/19
to python-simpy
Hi Sebastian,

The key problem in the code sample you present is that the `interrupt_proc` invokes its own `charge_proc` process which it immediately interrupts instead of interrupting the `charge_proc` instance invoked by `drive_proc`.

Instead of `interrupt_proc` invoking its own `charge_proc` instance, your class needs to retain the `charge_proc` instance created within `drive_proc` so that it can be referenced from `interrupt_proc`. Here is an outline of what I'm suggesting:

class ElectricBus:
   
def __init__(self, env, ...):
       
...
       
self.charge_proc = None
       
self.drive_loop()
       
self.interrupt_loop()
   
def drive_loop(self):
       
...
       
while True:
           
...
           
if ...:
               
self.charge_proc = self.env.process(self.charge())
               
yield self.charge_proc
               
self.charge_proc = None
   
def interrupt_loop(self):
       
while True:
           
...
           
if ... and self.charge_proc is not None:
               
self.charge_proc.interrupt()
               
...
   
def charge(self):
       
...
       
try:
           
...
       
except Interrupt:
           
...

Hope this helps.

Cheers,
Pete
Reply all
Reply to author
Forward
0 new messages