6

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 ) 
5
  • Add timeout to your subprocess.check_output call, i.e. response = subprocess.check_output(['ping', ipadd], timeout=7). Read more on subprocess.check_output in the official docs. Commented Dec 8, 2017 at 11:06
  • @zwer: thanks! But Is there another way? Commented Dec 8, 2017 at 11:18
  • Yes there is. Commented Dec 8, 2017 at 11:22
  • another solution: How can I abort a task in a multiprocessing.Pool after a timeout? Commented Dec 9, 2017 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: stackoverflow.com/a/26064238/5403449 Commented Jul 9, 2020 at 9:32

3 Answers 3

12

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Still, some questions remain. How do I get results and store them in a numPy array?
In case of processes, you can see how to share data between processes. The link in your second comment points to this answer.
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?
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: 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?
|
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 

5 Comments

Very nice tip. Thank you! :D But question remains.
@user2978216 If you're talking about getting rid of a hanged process, well, process.kill() is doing exactly that thing.
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 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.
regarding (a) timeouts in WMI calls: if WMI on a remote machine is corrupted timeout doesn't trigger and call hangs indefinitely.
1

use asyncio it's available in python since 3.5.4

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

3 Comments

I'm beginner and there are gaps in my knowledge of Python. No way I can understand that kinda doc
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.
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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.