0

I wrote a function fun0 that calls:

  1. a subprocess p1,
  2. a function fun1,
  3. and a function fun2 that calls another process p2.

The two processes p1 and p2 are external files. The code of the function fun0 is:

def fun0():

    # call the 1. process        
    p1 = subprocess.Popen(["python", "script1.py"])
    try:
        p1.wait()
    except KeyboardInterrupt:
        try:
            p1.terminate()
        except OSError:
            pass
        p1.wait()

    # call the 1. function
    fun1()

    # loop 3 times
    for i in range(1, 3, 1):   

        # call the 2. function
        fun2()

def fun2():

    # call 2. process
    p2 = subprocess.Popen(["python", "script2.py"])
    try:
        p2.wait()
    except KeyboardInterrupt:
        try:
            p2.terminate()
        except OSError:
            pass
        p2.wait()

The script_2.py uses threading to run two functions at the same time. The code is as follows:

import threading

def Ref():
    read ref. value
    return ref. value

def Read():
    while read_interval <= some_time:
        read value
        yield value

def Add():
    while delta > error:
        while delta > limit :
            while True:
                value = next(Read())
                delta = change delta
                check conditions
        while True:
            value = next(Read())
            delta = change delta
            check conditions
    return result

if __name__ == '__main__':

    t0 = threading.Thread(target = Ref)
    t0.start()
    t0.join()

    readTime = datetime.now()

    t1 = threading.Thread(target = Read)
    t2 = threading.Thread(target = Add)

    t1.start()
    t2.start()

    t1.join()
    t2.join()

I would like to stop the execution of the function fun0() externally i.e. from another function. When the stop occurs, I would also like the functions fun1, fun2 and processes p1, p2 to stop and possibly retrieve the data from them. I wonder which would be an elegant, clean and Pythonic way to do it. I am considering to:

  1. threading,
  2. multiprocessing,
  3. using another function,
  4. using signals?

I have read in this post 28906558 on stopping the function using multiprocessing should be the way to do it but I would like to hear more opinions, thank you.

parovelb
  • 353
  • 4
  • 19
  • I realize the `p2 subprocess` will not terminate by terminating the functions `Start`. Perhaps the `multiprocessing` method and creating a `process group` is a solution like it is explained in this post https://stackoverflow.com/a/4791612/8536248. – parovelb Feb 21 '19 at 08:01

4 Answers4

0

For the purpose of this problem I used simple countdowns for the function fun1 and for subprocesses p1 and p2. Then I begun experimenting with the function as process solution. The code of the main program is:

#!/usr/bin/python2.7 python2.7
# -*- coding: utf-8 -*-

#An example of how to terminate the execution of a function
#which calls a process using an external trigger.

import time
import subprocess
from multiprocessing import Process, Queue

def fun0():

    # start process1        
    p1 = subprocess.Popen(["python", "eg_script1_countdown1.py"])
    p1_PID = p1.pid
    print "p1_PID:", p1_PID
    try:
        p1.wait()
    except KeyboardInterrupt:
        try:
            p1.terminate()
        except OSError:
            pass
        p1.wait()

    # call function1
    fun1()

    # loop 3 times
    for i in range(1, 3, 1):
        # call fun2
        print "call function 2, loop n.", i
        fun2()

def fun1():
    for i in range(5,0,-1):
        print "fun1 > take five in", i
        time.sleep(1)

def fun2():
    # start process2
    p2 = subprocess.Popen(["python", "eg_script2_countdown2.py"])
    p2_PID = p2.pid
    print "p2_PID:", p2_PID
    try:
        p2.wait()
    except KeyboardInterrupt:
        try:
            p2.terminate()
        except OSError:
            pass
        p2.wait()

if __name__ == '__main__':
    pMain = Process(target=fun0)
    pMain_PID = pMain.pid
    print "pMain_PID:", pMain_PID
    pMain.start()
    time.sleep(20)
    pMain.terminate()

The code of the first called file is:

#!/usr/bin/python2.7 python2.7
# -*- coding: utf-8 -*-
#eg_script1_countdown.py

import time

for i in range(10,0,-1):
    print "script1.py > have a beer in", i
    time.sleep(1)

and of the second file:

#!/usr/bin/python2.7 python2.7
# -*- coding: utf-8 -*-
#eg_script2_countdown.py

import time

for i in range(10,0,-1):
    print "script2.py > give love in", i
    time.sleep(1)

I am changing the line time.sleep(20) in __name__ == '__main__' to see how the internal termination pMain.terminate() affects the result. I found out that:

  1. when triggered while the subprocess p1 is running, it does not terminate it,
  2. when triggered while the the fun1() is running, it does terminate the function,
  3. when triggered while the subprocess p2 is running, it does not terminate the process but terminates the fun2() in the next loop.

How to terminate the subprocesses p1 and p2 while running?

parovelb
  • 353
  • 4
  • 19
  • I have read if you mark the subprocesses as daemons, the subprocesses will be terminated immediately if the parent process (i.e. non-daemonic) is terminated. How does this apply to my case? – parovelb Feb 27 '19 at 12:47
0

I did modify the code of the main program using pMain.daemon = True before pMain.start() as suggested in this thread but the process p1 still runs in the backgroud even after exiting.

import os
import time
import signal
import subprocess
from subprocess import Popen, PIPE
import multiprocessing
from datetime import datetime

def fun0():

    p1 = subprocess.Popen(["python", "eg_script1_countdown1.py"], stdin=PIPE, stdout=PIPE, stderr=PIPE)
    #p1 = subprocess.Popen(["python", "eg_script1_countdown1.py"])
    global p1_pid
    p1_pid = p1.pid
    print "p1_pid:", p1_pid

    try:
        p1.wait()
    except KeyboardInterrupt:
       try:
            p1.terminate()
       except OSError:
            pass
       p1.wait()

    # call fun1
    fun1()

    # loop 3 times
    for i in range(3):
        # call fun2
        print "call fun2, loop n.", i
        with open('/home/parovelb/Desktop/Python2.7/log.txt', 'a') as log:
                log.write(str(datetime.now()) + '  for loop n. ' + str(i) + "\n")
        fun2()

def fun1():
    for i in range(5,0,-1):
        print "fun1 > take five in", i
        with open('/home/parovelb/Desktop/Python2.7/log.txt', 'a') as log:
                log.write(str(datetime.now()) + '  fun1 ' + str(i) + "\n")
        time.sleep(1)

def fun2():

    # start process2
    p2 = subprocess.Popen(["python", "eg_script2_countdown2.py"], stdin=PIPE, stdout=PIPE, stderr=PIPE)
    #p2 = subprocess.Popen(["python", "eg_script2_countdown2.py"])
    global p2_pid
    p2_pid = p2.pid

    try:
        p2.wait()
    except KeyboardInterrupt:
        try:
            p2.terminate()
        except OSError:
            pass
        p2.wait()


if __name__ == '__main__':

    # original code
    pMain = multiprocessing.Process(target=fun0)
    pMain.daemon = True
    pMain.start()
    time.sleep(10)    
    pMain.terminate()
    exit()
parovelb
  • 353
  • 4
  • 19
  • Checking the solution proposed in this thread https://stackoverflow.com/questions/4789837/how-to-terminate-a-python-subprocess-launched-with-shell-true does not solved the problem either. – parovelb Mar 01 '19 at 11:24
0

Following the proposed solutions in the posts delegate-sigint-signal... and delegate-signal-handling... I modified the code of my main program:

#!/usr/bin/python2.7 python2.7
# -*- coding: utf-8 -*-

"""
This is an example of a simple terminaton of a subprocess with a ctrl+c. 
"""

import time
import signal
import subprocess


def signal_handler(signal, frame):
   print "outer signal handler"
   exit(2)


def fun1():
   for i in range(5,0,-1):
        print "fun1 > take five in", i
        time.sleep(1)


def execute():

    # call the process first
    proc = subprocess.Popen("python eg_test.py",shell=True)
    try:
        proc.wait()
    except KeyboardInterrupt:
        try:
            proc.terminate()
        except OSError:
            pass

    # call the function second
    fun1()


def main():

    signal.signal(signal.SIGINT, signal_handler)

    execute()
    time.sleep(5)
    proc.send_signal(signal.SIGINT)


main()

I also modified just one of the external scripts just for a test run:

#!/usr/bin/python2.7 python2.7
# -*- coding: utf-8 -*-

"""
This is an example of a simple for loop countdown run a subprocess and
terminated with ctrl+c. 
"""

import time
import signal
from datetime import datetime


def signal_handler(signal, frame):
    print "exiting: inner function"
    exit(2)



def main():
    #define the signal handler
    signal.signal(signal.SIGINT, signal_handler)

    # simple for loop countdown
    for i in range(20,0,-1):
        print "test.py > countdown", i
        time.sleep(1)


main()

When pressing ctrl + c in mid-process (the external script is running), it terminates it and then continues with executing the fun1(). The question remains: how to terminate the execute() function from another function?

parovelb
  • 353
  • 4
  • 19
0

After some trial & error I got the code working. I sense there is a lot of space for improvement. The code of the main script:

#!/usr/bin/python2.7 python2.7
# -*- coding: utf-8 -*-

"""
This is an example of a simple terminaton of a subprocess with a ctrl+c. 
"""

import os
import time
import signal
import subprocess
import multiprocessing
from datetime import datetime


p1_pid = 0
proc_pid = 0


def signal_handler(signal, frame):
    print " main signal handler "
    # write to log
    with open('.../Python2.7/log.txt', 'a') as log:
            log.write(str(datetime.now()) + ' exiting: main function ' + "\n")
    exit(2)


def f1():
    for i in range(5,0,-1):
        print "f1 > main function", i
        # write to log
        with open('/home/parovelb/Desktop/Python2.7/log.txt', 'a') as log:
            log.write(str(datetime.now()) + ' main function ' + str(i) + "\n")
        time.sleep(1)


def execute():
    # call the function second
    f1()

    # call the process first
    global p1, p1_pid
    p1 = subprocess.Popen(["python", "eg_test.py"], shell=False)
    p1_pid = p1.pid
    print "p1_pid", p1_pid
    try:
        p1.wait()
    except KeyboardInterrupt:
        try:
            p1.terminate()
        except OSError:
           pass


def kill_them_all():
    time.sleep(10)
    print "p1_pid", p1_pid
    os.kill(p1_pid,signal.SIGINT)
    os.kill(proc_pid,signal.SIGINT)


def main():
    # define signal handler
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    global proc, proc_pid    

    proc = multiprocessing.Process(target=execute)
    proc_pid = proc.pid
    print "proc_pid", proc_pid
    proc_end = multiprocessing.Process(target=kill_them_all)

    proc.start()
    proc_end.start()

    proc.join()
    proc_end.join()


main()

The code of the external script:

#!/usr/bin/python2.7 python2.7
# -*- coding: utf-8 -*-


import time
import signal
from datetime import datetime


def signal_handler(signal, frame):
    print " external signal handler "
    with open('.../Python2.7/log.txt', 'a') as log:
            log.write(str(datetime.now()) + ' exiting: external function ' + "\n")
    exit(2)


def main():
    #define the signal handler
    signal.signal(signal.SIGINT, signal_handler)

    # simple for loop countdown
    for i in range(20,0,-1):
        print "eg_test.py > external file > main function", i
        with open('.../Python2.7/log.txt', 'a') as log:
            log.write(str(datetime.now()) + ' external function ' + str(i) + "\n")
        time.sleep(1)


main()
parovelb
  • 353
  • 4
  • 19