5

This code pings various machines. Could you please help me change this code so if a process of pinging hangs for more then 7 seconds it shuts down and returns some flag?

(I'd like to pull various data from machines using WMI. For that I'll change ping function to something else. The issue is on some machines WMI is corrupted and process of pulling data hangs indefinitely. Timeout is needed.)

import multiprocessing.dummy
import subprocess
import numpy as np
import time

start_time = time.time()

def ping(ipadd):
    try:
        response = subprocess.check_output(['ping', ipadd])
        return True
    except subprocess.CalledProcessError as e:
        return False
#print(ping('10.25.59.20'))
machine_names = \
'''
ya.ru
microsoft.com
www.google.com
www.amazon.com
www.nasa.com
'''.split()

np_machine_names = np.array(machine_names)
p = multiprocessing.dummy.Pool(7)
ping_status = p.map(ping, machine_names)
np_ping_status = np.fromiter(ping_status, dtype=bool)
print(*np_machine_names[np_ping_status], sep = '\n')


run_time = time.time() - start_time
print(f'Runtime: {run_time:.0f}')

UPDATE: While I appreciate for the tip on adding timeout to subprocess the question remains. How do I shutdown hanged function? Let's say I've changed pinging to pulling WMI data from a machine (this one pulls list of installed software from Windows machine). There is no subprocess to set timer on:

#pip install pypiwin32
import win32com.client 
strComputer = "." 
objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator") 
objSWbemServices = objWMIService.ConnectServer(strComputer,"root\cimv2") 
colItems = objSWbemServices.ExecQuery("Select * from Win32_Product") 
for objItem in colItems: 
    print( "Caption: ", objItem.Caption )
user2978216
  • 474
  • 2
  • 6
  • 19
  • Add `timeout` to your `subprocess.check_output` call, i.e. `response = subprocess.check_output(['ping', ipadd], timeout=7)`. Read more on [`subprocess.check_output`](https://docs.python.org/3/library/subprocess.html#subprocess.check_output) in the official docs. – zwer Dec 08 '17 at 11:06
  • @zwer: thanks! But Is there another way? – user2978216 Dec 08 '17 at 11:18
  • Yes [there is](https://stackoverflow.com/a/44522570). – zwer Dec 08 '17 at 11:22
  • another solution: [How can I abort a task in a multiprocessing.Pool after a timeout?](https://stackoverflow.com/a/29495039/2978216) – user2978216 Dec 09 '17 at 19:09
  • 1
    Here is a nice way to add a timeout to your multiprocesses. Timeout can just be set to 7 seconds in this example: https://stackoverflow.com/a/26064238/5403449 – Josh Jul 09 '20 at 09:32

3 Answers3

11

There are several ways to tackle long running executions. Each way has its benefits and drawbacks.

APIs

As already suggested, the simplest ways is to rely on the APIs timeouts. Modules such as subprocess, socket, requests etc... expose timeout parameters within their APIs.

This is the preferable approach whenever feasible.

Threads

The long-running/hanging logic is executed within a separate thread. The main loop can continue undisturbed and ignore the hanging execution.

import threading

TIMEOUT = 60

def hanging_function():
    hang_here()

thread = threading.Tread(target=hanging_function)
thread.daemon = True
thread.start()

thread.join(TIMEOUT)
if thread.is_alive():
    print("Function is hanging!")

One of the issue with this approach is that the hanging thread will continue to execute in the background consuming resources.

Another limitation is due to the fact that threads share memory. If your your function happens to crash badly it might affect your main execution as well.

Processes

My favourite approach is to execute the problematic logic in a separate process using the multiprocessing facilities. As processes do not share memory, whatever happens in the problematic function remains limited to the child process which you can terminate at any point in time.

import multiprocessing

TIMEOUT = 60

def hanging_function():
    hang_here()

process = multiprocessing.Process(target=hanging_function)
process.daemon = True
process.start()

process.join(TIMEOUT)
if process.is_alive():
    print("Function is hanging!")
    process.terminate()
    print("Kidding, just terminated!")

The pebble library was built on top of this principle. Allowing to easily separate problematic code and deal with failures and catastrophes.

The drawback of using processes is that they are a bit heavier than the other two approaches. Moreover, as memory between processes is isolated, it's a bit more complicated to share data.

noxdafox
  • 14,439
  • 4
  • 33
  • 45
  • Still, some questions remain. How do I get results and store them in a numPy array? – user2978216 Dec 09 '17 at 18:52
  • In case of processes, you can see [how to share data between processes](https://docs.python.org/3.6/library/multiprocessing.html#sharing-state-between-processes). The link in your second comment points to this answer. – noxdafox Dec 09 '17 at 18:57
  • Quick question, if you don't mind clarifying. does process.is_alive() not run straight after the process is started which means it is TRUE straight away? And then gets terminated? And if the join makes it false does the whole function need to be in a loop so the if statement is continuously checked? – Josh Jul 06 '20 at 09:56
  • `process.join(TIMEOUT)` blocks until either the process is expired or `TIMEOUT` occurs. So `process.is_alive()` will not be run straight after but only when the above conditions are met. – noxdafox Jul 06 '20 at 11:23
  • @noxdafox: Can you please specify why `multiprocess.Process` is different than `subprocess` api, and if you can show example of achieving the same functionality using subprocess? – JavaSa Oct 14 '20 at 16:54
  • This is not pertinent with the question the OP asked. You can find plenty of material in regards such as, for example, [this question](https://stackoverflow.com/questions/13606867/what-is-the-difference-between-multiprocessing-and-subprocess). – noxdafox Oct 14 '20 at 18:07
2

Popen is a default choice when it comes to using subprocess module. It allows you to create a process and then read its stdout and stderr with specified timeout:

def ping(ipadd):
    process = subprocess.Popen(['ping', ipadd])
    try:
        response, stderr_response = process.communicate(timeout=10)
        return True
    except subprocess.TimeoutExpired:
        return False
    finally:
        process.kill()

Also, beware that ping on a linux or osx may never exit and continue to ping so this is going to return false on these OSes:

>>> ping('127.0.0.1')
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.058 ms
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.033 ms
...
64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.064 ms
64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.031 ms
False
u354356007
  • 3,205
  • 15
  • 25
  • Very nice tip. Thank you! :D But question remains. – user2978216 Dec 08 '17 at 11:20
  • @user2978216 If you're talking about getting rid of a hanged process, well, `process.kill()` is doing exactly that thing. – u354356007 Dec 08 '17 at 11:23
  • I'm sorry that I wasn't clear. I've added UPDATE section to my question. What if ping function were to change to something else and there is no subprocess in it? – user2978216 Dec 08 '17 at 11:26
  • @user2978216 wow, that's another question entirely. In this case you should (a) look for timeouts in WMI calls, or (b) have separate timer threads in your child processes that will kill the process, or (c) give up using process pool and call `process.join(timeout)` on individual child processes. – u354356007 Dec 08 '17 at 11:46
  • regarding (a) timeouts in WMI calls: if WMI on a remote machine is corrupted timeout doesn't trigger and call hangs indefinitely. – user2978216 Dec 09 '17 at 05:15
1

use asyncio it's available in python since 3.5.4

https://docs.python.org/3/library/asyncio-task.html

ShpielMeister
  • 1,417
  • 10
  • 23
  • 1
    I'm beginner and there are gaps in my knowledge of Python. No way I can understand that kinda doc – user2978216 Dec 08 '17 at 10:46
  • managing subprocesses is complex. and subtle. pinging (or otherwise) servers has ethical and performance considerations. it often necessary to throttle requests, so as not to be banned. – ShpielMeister Dec 09 '17 at 05:16
  • I've added ping as an example. I don't want actually ping remote machines but to read their registry, pull WMI data, create shortcuts on desktop etc. To do this fast multiprocessing or asyncio module is needed. But killing process is considered to be a bad practice. I still haven't found an example how to do it on timeout – user2978216 Dec 09 '17 at 06:43