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.
