2

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.

Jamie Marshall
  • 1,885
  • 3
  • 27
  • 50
  • @kabanus the answer provided in that question is what i'm already doing here. Something is hanging my thread, even thought I'm shutting down my server properly, the poster of the other question doesn't seem to know how to shut down the server. – Jamie Marshall Aug 26 '18 at 19:47
  • Did you override `serve_forever`? How are you stopping it from another thread? If you could post a shorter snippet of the part that is properly shutting down the server as well that would be great. – kabanus Aug 26 '18 at 19:51
  • No, I specifically don't want override `server_forever`. I'm using the parent thread in my AwaitCallback method to set the `self.Serve` attribute of the http `Server` instance to False. The overridden `service_actions` method in the `Server` instance then polls the the `self.Serve` attribute in the child thread when it is called from the native `serve_forever` loop. It's essentially what your link is doing, with the exception that I'm trying to keep as much native functionality as possible, where in the link the answer is overriding everything with simpler implementations. – Jamie Marshall Aug 26 '18 at 19:59

0 Answers0