I have a little HTTPServer implementation I'm spinning up to listen for a callback from an API. In testing, this implimentation is keeping the innermost thread alive. Here's the server:
import http
import uuid
from http import server
class Server(server.HTTPServer):
RequestLog:list = []
ErrorList:list = []
Serve:bool = True
def __init__(self, server_address, RequestHandlerClass):
self.RequestLog = []
self.ErrorList = []
self.Serve:bool = True
return super().__init__(server_address, RequestHandlerClass)
def LogRequest(self, clientAddress, success, state, params:dict={}):
"""docstring"""
uid = uuid.uuid1()
logItem = {"RequestID" : uid,
"ClientAddress" : clientAddress,
"Success" : success,
"State" : state,
"Params" : params}
self.RequestLog.append(logItem)
def GetRequestItem(self, state):
"""docstring"""
logItem = {}
if self.RequestLog and len(self.RequestLog):
logItem = [d for d in self.RequestLog if d["State"] == state][0]
return logItem
def service_actions(self):
try:
if not self.Serve:
self.shutdown()
self.server_close()
except Exception as e:
err = e
raise e
return super().service_actions()
def handle_error(self, request, client_address):
logItem = {"clientAddress" : client_address,
"success" : False,
"state" : None,
"params" : None}
try:
self.LogRequest(**logItem)
x = request
except Exception as e:
self.shutdown()
err = e
raise e
return super().handle_error(request, client_address)
So what the server implementation above does, is log information about requests in the ResquestLog:list
and then provided a method GetRequestItem
that can be used to pull for the existence of a logged request. In the test I'm throwing and error and catching it with the handle_error()
override. Here is the calling function that spins up the server, polls for request, and then shutdowns the sever by setting its Server.Serve
method to False
def AwaitCallback(self, server_class=Server,
handler_class=OAuthGrantRequestHandler):
"""docstring"""
server_address = ("127.0.0.1", 8080)
self.Httpd = server_class(server_address, handler_class)
self.Httpd.timeout = 200
t1 = threading.Thread(target=self.Httpd.serve_forever)
try:
t1.start()
#poll for request result
result = {}
x = 0
while x < self.Timeout:
if len(self.Httpd.RequestLog) > 0:
break
time.sleep(.5)
finally:
#Terminate Server
if self.Httpd:
self.Httpd.Serve = False
if t1:
t1.join()
return
The above method sticks on the t1.join()
call. Inspecting the self.Httpd
object when its hung tells me that the servers serve_forever()
loop is shutdown but the thread still shows its a live when calling t1.is_alive()
. So what's going on? The only thing I can think of is that when self.shutdown()
is called in the t1 thread it really yeilds the loop instead of shutting it down and keeps the tread alive? Documentation on shutdown just says shutdown() : Tell the serve_forever() loop to stop and wait until it does.
Nice and murky. Any ideas?
Edit 1: the answer suggested at How to stop BaseHTTPServer.serve_forever() in a BaseHTTPRequestHandler subclass? is entirly different. They're suggesting overriding all the native functionality of the socketserver.BaseServer.serve_forever() loop with a simpler implementation whereas I'm trying to correctly use the native implementation. To the best of my understanding so far, the example of my working code above, should achieve the same thing that answer is suggesting, but the child thread isn't terminating. Thus this question.