Timeout specifications

An efficient ping test application should not be waiting for responses from its websites for a long time; it should have a set threshold for timeout that, if a server fails to return a response under that threshold, the application will deem that server non-responsive. We therefore need to implement a way to keep track of how much time has passed since a request is sent to a server. We will do this by counting down from the timeout threshold and, once that threshold is passed, all responses (whether returned or not yet returned) will be printed out.

Additionally, we will also be keeping track of how many requests are still pending and have not had their responses returned. We will be using the isAlive() method from the threading.Thread class to indirectly determine whether a response has been returned for a specific request: if, at one point, the thread processing a specific request is alive, we can conclude that that specific request is still pending.

Navigate to the Chapter05/example6.py file and consider the process_requests() function first:

# Chapter05/example6.py

import time

UPDATE_INTERVAL = 0.01

def process_requests(threads, timeout=5):
def alive_count():
alive = [1 if thread.isAlive() else 0 for thread in threads]
return sum(alive)

while alive_count() > 0 and timeout > 0:
timeout -= UPDATE_INTERVAL
time.sleep(UPDATE_INTERVAL)
for thread in threads:
print(thread.result)

The function takes in a list of threads that we have been using to make web requests in the previous examples, as well as an optional argument specifying the timeout threshold. Inside this function, we have an inner function, alive_count(), which returns the count of the threads that are still alive at the time of the function call.

In the process_requests() function, as long as there are threads that are currently alive and processing requests, we will allow the threads to continue with their execution (this is done in the while loop with the double condition). The UPDATE_INTERVAL variable, as you can see, specifies how often we check for this condition. If either condition fails (if there are no alive threads left or if the threshold timeout is passed), then we will proceed with printing out the responses (even if some might have not been returned).

Let's turn our attention to the new MyThread class:

# Chapter05/example6.py

import threading
import requests

class MyThread(threading.Thread):
def __init__(self, url):
threading.Thread.__init__(self)
self.url = url
self.result = f'{self.url}: Custom timeout'

def run(self):
res = requests.get(self.url)
self.result = f'{self.url}: {res.text}'

This class is almost identical to the one we considered in the previous example, except that the initial value for the result attribute is a message indicating a timeout. In the case that we discussed earlier where the timeout threshold specified in the process_requests() function is passed, this initial value will be used when the responses are printed out.

Finally, let's consider our main program:

# Chapter05/example6.py

urls = [
'http://httpstat.us/200',
'http://httpstat.us/200?sleep=4000',
'http://httpstat.us/200?sleep=20000',
'http://httpstat.us/400'
]

start = time.time()

threads = [MyThread(url) for url in urls]
for thread in threads:
thread.setDaemon(True)
thread.start()
process_requests(threads)

print(f'Took {time.time() - start : .2f} seconds')

print('Done.')

Here, in our URL list, we have a request that would take 4 seconds and another that would take 20 seconds, aside from the ones that would respond immediately. As the timeout threshold that we are using is 5 seconds, theoretically we should be able to see that the 4-second-delay request will successfully obtain a response, while the 20-second-delay one will not.

There is another point to be made about this program: daemon threads. In the process_requests() function, if the timeout threshold is passed while there is still at least one thread processing, then the function will proceed to print out the result attribute of each thread:

 while alive_count() > 0 and timeout > 0:
timeout -= UPDATE_INTERVAL
time.sleep(UPDATE_INTERVAL)
for thread in threads:
print(thread.result)

This means that we do not block our program until all of the threads have finished their execution by using the join() function, and the program therefore can simply move forward if the timeout threshold is reached. However, this means that the threads themselves do not terminate at this point. The 20-second-delay request, specifically, will still most likely be running after our program exits out of the process_requests() function.

If the thread processing this request is not a daemon thread (as we know, daemon threads execute in the background and never terminate), it will block the main program from finishing until the thread itself finishes. By making this thread, and any other thread, a daemon thread, we allow the main program to finish as soon as it executes the last line of its instructions, even if there are threads still running.

Let us see this program in action. Execute the code and your output should be similar to the following:

http://httpstat.us/200: 200 OK
http://httpstat.us/200?sleep=4000: 200 OK
http://httpstat.us/200?sleep=20000: Custom timeout
http://httpstat.us/400: 400 Bad Request
Took 5.70 seconds
Done.

As you can see, it took around 5 seconds for our program to finish this time. This is because it spent 5 seconds waiting for the threads that were still running and, as soon as the 5-second threshold was passed, the program printed out the results. Here we see that the result from the 20-second-delay request was simply the default value of the result attribute of the MyThread class, while the rest of the requests were able to obtain the correct response from the server (including the 4-second-delay request, since it had enough time to obtain the response).

If you would like to see the effect of non-daemon threads that we discussed earlier, simply comment out the corresponding line of code in our main program, as follows:

threads = [MyThread(url) for url in urls]
for thread in threads:
#thread.setDaemon(True)
thread.start()
process_requests(threads)

You will see that the main program will hang for around 20 seconds, as the non-daemon thread processing the 20-second-delay request is still running, before being able to finish its execution (even though the output produced will be identical).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.152.58