1

I'm trying to write a small script which will use plink.exe (from the same folder) to create a ssh tunnel (on windows). I'm basically using os.system to launch the the command:

import time
import threading
from os.path import join, dirname, realpath
pc_tunnel_command = '-ssh -batch -pw xxxx -N -L 1234:host1:5678 user@host2'

if __name__ == '__main__':
    t = threading.Thread(target = os.system, \
                         args = (join(dirname(realpath(__file__)), 'plink.exe ') + \
                                      pc_tunnel_command,))
    t.daemon = True
    t.start()
    #without this line it will die. I guess that plink doesn't have enough time to start.
    time.sleep(5) 
    print 'Should die now'

However, it seems that the thread (and plink.exe) keep running. Why is this happening? Any way to force the thread to close? Better way to launch plink?

I want plink.exe to die when my program ends. Using a daemon thread was my plan of having the tunnel run in the background, and then dying when my main code exits.

BTW - same thing happens with subprocess.call.

Korem
  • 11,383
  • 7
  • 55
  • 72

2 Answers2

1

You can use the atexit and signal modules to register calls back that will explicitly kill the process when your program exits normally or receives SIGTERM, respectively:

import sys
import time
import atexit
import signal
import subprocess
from functools import partial
from os.path import join, dirname, realpath

pc_tunnel_command = '-ssh -batch -pw xxxx -N -L 1234:host1:5678 user@host2'


def handle_exit(p, *args):
    print("killing it")
    p.terminate()
    sys.exit(0)

if __name__ == '__main__':
    p = subprocess.Popen(join(dirname(realpath(__file__)), 'plink.exe ') + pc_tunnel_command, shell=True)
    func = partial(handle_exit, p)
    signal.signal(signal.SIGTERM, func)
    atexit.register(func)
    print 'Should die now'

The one thing that is odd about the behavior your desrcibed is that I would have expected your program to exit after your sleep call, but leave plink running in the background, rather than having your program hang until the os.system call completes. That's the behavior I see on Linux, at least. In any case, explicitly terminating the child process should solve the issue for you.

dano
  • 91,354
  • 19
  • 222
  • 219
  • It works wonderfully. Thanks. A couple of questions, if I may: 1. Why is the call for `sys.exit(0)` necessary at the end of `handle_exit`? 2. why do you need the `partial` part? (It seems to be working w/o them) – Korem Jul 29 '14 at 19:21
  • @Korem when you use `signal.signal(signal.SIGTERM, ...)`your program will swallow the SIGTERM and call your handler. So if you don't explicitly tell your program to exit in the handler, it will keep running after handling the `SIGTERM`. In this case you want to exit when you receive the `SIGTERM`, but just clean up first. `partial` is used so that we can pass the `Popen` object to the `handle_exit`. The `signal.signal` API doesn't provide a way to pass arguments to the callback function you provide; using `partial` lets use work around that. – dano Jul 29 '14 at 19:25
  • Oh, I wrote my comment before your recent edit, and at that time the `signal.signal(signal.SIGTERM, func)` line was commented out. It seemed to be working without it. Why do you use both? – Korem Jul 29 '14 at 19:27
  • 1
    @Korem `atexit` will run if your program exits normally, but it won't run if it gets terminated by a signal (like `SIGTERM`). On Windows I think this would probably only happen if someone used the task manager to end the process. Using `signal.signal` allows us to handle that case and still clean up properly. – dano Jul 29 '14 at 19:28
  • @Korem Also note that `atexit.register` can take arguments for the callback function, so you could use `atexit.register(handle_exit, p)`. It's just `signal.signal` that needs `partial`. – dano Jul 29 '14 at 19:29
0

os.system does not return until the child process exits. The same is true for subprocess.call. That's why your thread is sitting there, waiting for plink to finish. You can probably use subprocess.Popen to launch the process asynchronously and then exit. In any case, the additional thread you are creating is unnecessary.

flodin
  • 5,215
  • 4
  • 26
  • 39
  • I disagree, and think that you perhaps misunderstand me. I *want* plink.exe to die when my program ends. Using a daemon thread was my plan of having the tunnel run in the background, and then dying when my main code exits. I would've imagined that the daemon thread gets some sort of "kill" signal, and thus the `os.system` call would be abruptly stopped. I knew that it won't return until it exits. And BTW, using Popen hangs as well. – Korem Jul 29 '14 at 18:55
  • @Korem you probably need to kill the process explicitly then, see [this answer](http://stackoverflow.com/questions/17856928/how-to-terminate-process-from-python-using-pid). – flodin Jul 29 '14 at 19:05