# Why does this multithreaded add-on not call the cleanup function when Blender exits?

I'm working on an add-on that has multithreading. I've created a stripped down version that has very little functionality in order to demonstrate the issue: http://simplecarnival.com/forum/simple_carnival_thread_test.py

I'm running this on Blender 2.78 on Windows. This add-on will write a test file at c:\junk.txt that demonstrates the various functions that the add-on reaches.

First, I'll describe the problem. Install the add-on. Go to the 3D View. Open the T panel. Open the Misc tab. You'll see a control there that says Thread Test, and a button that says "Enable". Click Enable. The add-on will start running a thread. If you click the same button (which now says "Disable"), the add-on will stop running the thread. At this point, if you quit out of Blender, everything works as it should. No background thread is being run.

On the other hand, if you click the button when it says "Enable" (the text above the button will then say "* * * ENABLED * * *") and then quit out of Blender, Blender will still be running in the background. You will need to go into Task Manager and manually kill the Blender process.

If you open the c:\junk.txt file that this add-on creates, you'll see the various points of the add-on that have written to this file.

What should happen -- because I'm importing atexit and setting up atexit.register(cleanup) -- is that, when Blender quits, it should call that cleanup() function. At least, that's how I understand atexit is supposed to work (though maybe it doesn't work that way with Blender). If you look at the cleanup() function, it should theoretically write some text to the c:\junk.txt file. However, that text is never written, as cleanup() apparently is never called.

Any idea what is wrong with this code? Am I taking the wrong approach by using atexit and there's another way I can run some code from an add-on before Blender exits?

Here's the code:

bl_info = {
'author': "simplecarnival",
'version': (0, 0, 3),
'blender': (2, 7, 8),
'api': 44136,
'location': "View3D > Left panel",
'description': "Test for trying to get threads to clean up when shutting down Blender without disabling the add-on",
'warning': "",
'wiki_url': "",
'tracker_url': "",
'category': "Object"}

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

socketServer = 0
listening = False
socketMessages = []
shutDown = False

def cleanup():
with open("c:\junk.txt", "a") as text_file:
text_file.write("Cleanup was automatically called when shutting down...but this never gets called!\n")

listening = False

socketServer.settimeout(0.1)
socketServer.close()
del socketServer

shutDown = False

atexit.register(cleanup)

with open("c:\junk.txt", "a") as text_file:

listening = True
create_socket_connection()

def socket_listen():
socketServer.listen(5)
while listening:
sleep(0.03)

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

bl_label = "Enable"
_timer = None

def modal(self, context, event):
result =  {'PASS_THROUGH'}

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

if shutDown:
with open("c:\junk.txt", "a") as text_file:

listening = False

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

shutDown = False
result = {'CANCELLED'}
self.report({'WARNING'}, "Thread Test has been disabled")

return result

def invoke(self, context, event):
if context.area.type == 'VIEW_3D' and threadTest_running == False :
self.cursor_on_handle = 'None'
return {'RUNNING_MODAL'}
else:
global shutDown
shutDown = True
return {'FINISHED'}

bl_label = "Disable"
bl_options = {'REGISTER', 'UNDO'}

def execute(self, context):
global shutDown
shutDown = True
return {'FINISHED'}

bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
def draw(self, context):

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

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