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 )
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With