How to shut down already-running threads in an add-on if Blender is closed/closing?

I’m creating an add-on which acts as a server and receives client information from an outside application through sockets. The add-on is spawning a thread when the Enable button is clicked in the add-on. However, if you try shutting down Blender when the thread is running, the thread is not killed and Blender still runs in the background (even though it looks like Blender is no longer running).

The add-on code follows. To replicate this behavior, run this add-on, go to the 3D view -> left panel -> Miscellaneous tab, and open up the Kinect to Blender option. Click the Enable button. Now shut down Blender. Even though Blender will have disappeared, it will still be running in the background. (You’ll be able to see it in Windows Task Manager.) If you go through all of these steps but don’t click the Enable button, Blender will shut down correctly.

What do I need to do in my add on so that closing Blender will kill off the thread?


# Code based on pyEphestos (https://github.com/kilon/pyEphestos) by Kilon Alios ([email protected])

bl_info = {
    'name': "Simple Carnival Kinect to Blender",
    'author': "Jeff Boller",
    'version': (2, 0, 9),
    'blender': (2, 6, 7),
    'api': 44136,
    'location': "View3D > Left panel",
    'description': "Allows Blender's camera to be controlled by a motion tracked object via Kinect",
    'warning': "",
    'wiki_url': "3d.simplecarnival.com",
    'tracker_url': "",
    'category': "Object"}

import sys
import time
import bpy
import threading
import socket
from bpy.props import *
from time import sleep
import atexit
import re

kinectToBlender_running = False
thread_created = False
threadSocket = 0
socketServer = 0
receivedSocket = "none"
listening = False
socketMessages = []
shutDown = False
receivedData = ''


def cleanup():
    global kinectToBlender_running, threadSocket, listening, socketServer
    kinectToBlender_running = False
    listening = False

    socketServer.settimeout(0.1)
    threadSocket.join()
    #socketServer.shutdown(socket.SHUT_RDWR)
    socketServer.close()
    del socketServer


    thread_created = False
    shutDown = False

atexit.register(cleanup)
def create_thread():

    global threadSocket,listening
    threadSocket = threading.Thread(name='threadSocket', target= socket_listen)
    listening = True
    create_socket_connection()
    threadSocket.start()
    #threadSocket.join()

def socket_listen():
    global receivedSocket,listening, receivedData,socketServer, socketMessages
    socketServer.listen(5)


    while listening:
        (receivedSocket , adreess) = socketServer.accept()
        receivedData = (receivedSocket.recv(1024)).decode("utf-8")[:-2]
        #exec(receivedData)
        socketMessages.append(receivedData)
        sleep(0.03)
        receivedSocket.close()




def create_socket_connection():
    global socketServer
    socketServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    socketServer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    socketServer.bind(('127.0.0.1',4000))


class open_ephestos(bpy.types.Operator):
    bl_idname = "ephestos_button.modal"
    bl_label = "Enable"
    _timer = None

    def modal(self, context, event):
        global kinectToBlender_running, thread_created, listening, socketServer, socketMessages, shutDown
        result =  {'PASS_THROUGH'}


        if context.area.type == 'VIEW_3D' and kinectToBlender_running and event.type in {'ESC',}:

            kinectToBlender_running = False
            listening = False
            #time.sleep(1)
            socketServer.close()
            context.window_manager.event_timer_remove(self._timer)
            #time.sleep(1)
            thread_created = False
            result = {'CANCELLED'}
            self.report({'WARNING'}, "Kinect to Blender has been disabled")

        if context.area.type == 'VIEW_3D' and kinectToBlender_running and event.type == 'TIMER' :

            if shutDown:
                kinectToBlender_running = False
                listening = False

                socketServer.settimeout(0.01)
                #socketServer.shutdown(socket.SHUT_RDWR)
                socketServer.close()
                threadSocket.join()
                del socketServer
                context.window_manager.event_timer_remove(self._timer)

                thread_created = False
                shutDown = False
                result = {'CANCELLED'}
                self.report({'WARNING'}, "Kinect to Blender has been disabled")

            for msg in socketMessages:
                print('******** GOT THIS: ', msg)
                socketMessages.remove(msg)
          # create_thread()
          # thread_created = True

        return result

    def invoke(self, context, event):
        global kinectToBlender_running,thread_created
        if context.area.type == 'VIEW_3D' and kinectToBlender_running == False :
            self.cursor_on_handle = 'None'

            # Add the region OpenGL drawing callback
            # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
            #self._handle =bpy.types.SpaceView3D.draw_handler_add(draw_ephestos,(self,context), 'WINDOW', 'POST_PIXEL')

            self._timer = context.window_manager.event_timer_add(0.01,context.window)
            kinectToBlender_running = True
            context.window_manager.modal_handler_add(self)
            create_thread()
            thread_created = True
            return {'RUNNING_MODAL'}
        else:
            global shutDown
            shutDown = True
            return {'FINISHED'}


class ephestos_panel(bpy.types.Panel):
    bl_label = "Kinect to Blender"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    def draw(self, context):
        global receivedSocket,listening

        sce = context.scene
        layout = self.layout
        box = layout.box()

        if listening:
            box.label(text="Enabled")
        else:
            box.label(text="Disabled")

        box.operator("ephestos_button.modal")
        box.operator("ephestos.close")


def register():
    bpy.utils.register_module(__name__)

def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()