Attempt to terminate threads when pyfa is closed
This commit is contained in:
@@ -99,12 +99,14 @@ class PFPanel(wx.Panel):
|
|||||||
|
|
||||||
|
|
||||||
class OpenFitsThread(threading.Thread):
|
class OpenFitsThread(threading.Thread):
|
||||||
|
|
||||||
def __init__(self, fits, callback):
|
def __init__(self, fits, callback):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.name = "LoadingOpenFits"
|
self.name = "LoadingOpenFits"
|
||||||
self.mainFrame = MainFrame.getInstance()
|
self.mainFrame = MainFrame.getInstance()
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.fits = fits
|
self.fits = fits
|
||||||
|
self.running = True
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@@ -118,10 +120,15 @@ class OpenFitsThread(threading.Thread):
|
|||||||
# We use 1 for all fits except the last one where we use 2 so that we
|
# We use 1 for all fits except the last one where we use 2 so that we
|
||||||
# have correct calculations displayed at startup
|
# have correct calculations displayed at startup
|
||||||
for fitID in self.fits[:-1]:
|
for fitID in self.fits[:-1]:
|
||||||
wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID, startup=1))
|
if self.running:
|
||||||
|
wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID, startup=1))
|
||||||
|
|
||||||
wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fits[-1], startup=2))
|
if self.running:
|
||||||
wx.CallAfter(self.callback)
|
wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fits[-1], startup=2))
|
||||||
|
wx.CallAfter(self.callback)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
|
||||||
# todo: include IPortUser again
|
# todo: include IPortUser again
|
||||||
|
|||||||
15
pyfa.py
15
pyfa.py
@@ -151,5 +151,18 @@ if __name__ == "__main__":
|
|||||||
else:
|
else:
|
||||||
pyfa.MainLoop()
|
pyfa.MainLoop()
|
||||||
|
|
||||||
# TODO: Add some thread cleanup code here. Right now we bail, and that can lead to orphaned threads or threads not properly exiting.
|
# When main loop is over, threads have 5 seconds to comply...
|
||||||
|
import threading
|
||||||
|
from utils.timer import CountdownTimer
|
||||||
|
|
||||||
|
timer = CountdownTimer(5)
|
||||||
|
stoppableThreads = []
|
||||||
|
for t in threading.enumerate():
|
||||||
|
if t is not threading.main_thread() and hasattr(t, 'stop'):
|
||||||
|
stoppableThreads.append(t)
|
||||||
|
t.stop()
|
||||||
|
for t in stoppableThreads:
|
||||||
|
t.join(timeout=timer.remainder())
|
||||||
|
|
||||||
|
# Nah, just kidding, no way to terminate threads - just try to exit
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|||||||
@@ -45,11 +45,13 @@ pyfalog = Logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class CharacterImportThread(threading.Thread):
|
class CharacterImportThread(threading.Thread):
|
||||||
|
|
||||||
def __init__(self, paths, callback):
|
def __init__(self, paths, callback):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.name = "CharacterImport"
|
self.name = "CharacterImport"
|
||||||
self.paths = paths
|
self.paths = paths
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
self.running = True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
paths = self.paths
|
paths = self.paths
|
||||||
@@ -61,6 +63,8 @@ class CharacterImportThread(threading.Thread):
|
|||||||
all_skill_ids.append(skill.itemID)
|
all_skill_ids.append(skill.itemID)
|
||||||
|
|
||||||
for path in paths:
|
for path in paths:
|
||||||
|
if not self.running:
|
||||||
|
break
|
||||||
try:
|
try:
|
||||||
charFile = open(path, mode='r').read()
|
charFile = open(path, mode='r').read()
|
||||||
doc = minidom.parseString(charFile)
|
doc = minidom.parseString(charFile)
|
||||||
@@ -95,6 +99,9 @@ class CharacterImportThread(threading.Thread):
|
|||||||
|
|
||||||
wx.CallAfter(self.callback)
|
wx.CallAfter(self.callback)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
|
||||||
class SkillBackupThread(threading.Thread):
|
class SkillBackupThread(threading.Thread):
|
||||||
def __init__(self, path, saveFmt, activeFit, callback):
|
def __init__(self, path, saveFmt, activeFit, callback):
|
||||||
@@ -104,25 +111,32 @@ class SkillBackupThread(threading.Thread):
|
|||||||
self.saveFmt = saveFmt
|
self.saveFmt = saveFmt
|
||||||
self.activeFit = activeFit
|
self.activeFit = activeFit
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
self.running = True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
path = self.path
|
path = self.path
|
||||||
sCharacter = Character.getInstance()
|
sCharacter = Character.getInstance()
|
||||||
|
|
||||||
if self.saveFmt == "xml" or self.saveFmt == "emp":
|
backupData = None
|
||||||
backupData = sCharacter.exportXml()
|
if self.running:
|
||||||
else:
|
if self.saveFmt == "xml" or self.saveFmt == "emp":
|
||||||
backupData = sCharacter.exportText()
|
backupData = sCharacter.exportXml()
|
||||||
|
else:
|
||||||
|
backupData = sCharacter.exportText()
|
||||||
|
|
||||||
if self.saveFmt == "emp":
|
if self.running and backupData is not None:
|
||||||
with gzip.open(path, mode='wb') as backupFile:
|
if self.saveFmt == "emp":
|
||||||
backupFile.write(backupData.encode())
|
with gzip.open(path, mode='wb') as backupFile:
|
||||||
else:
|
backupFile.write(backupData.encode())
|
||||||
with open(path, mode='w', encoding='utf-8') as backupFile:
|
else:
|
||||||
backupFile.write(backupData)
|
with open(path, mode='w', encoding='utf-8') as backupFile:
|
||||||
|
backupFile.write(backupData)
|
||||||
|
|
||||||
wx.CallAfter(self.callback)
|
wx.CallAfter(self.callback)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
|
||||||
class Character:
|
class Character:
|
||||||
instance = None
|
instance = None
|
||||||
@@ -474,12 +488,14 @@ class Character:
|
|||||||
|
|
||||||
|
|
||||||
class UpdateAPIThread(threading.Thread):
|
class UpdateAPIThread(threading.Thread):
|
||||||
|
|
||||||
def __init__(self, charID, callback):
|
def __init__(self, charID, callback):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
self.name = "CheckUpdate"
|
self.name = "CheckUpdate"
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.charID = charID
|
self.charID = charID
|
||||||
|
self.running = True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
@@ -488,20 +504,31 @@ class UpdateAPIThread(threading.Thread):
|
|||||||
sEsi = Esi.getInstance()
|
sEsi = Esi.getInstance()
|
||||||
sChar = Character.getInstance()
|
sChar = Character.getInstance()
|
||||||
ssoChar = sChar.getSsoCharacter(char.ID)
|
ssoChar = sChar.getSsoCharacter(char.ID)
|
||||||
|
|
||||||
|
if not self.running:
|
||||||
|
self.callback[0](self.callback[1])
|
||||||
|
return
|
||||||
resp = sEsi.getSkills(ssoChar.ID)
|
resp = sEsi.getSkills(ssoChar.ID)
|
||||||
|
|
||||||
|
if not self.running:
|
||||||
|
self.callback[0](self.callback[1])
|
||||||
|
return
|
||||||
# todo: check if alpha. if so, pop up a question if they want to apply it as alpha. Use threading events to set the answer?
|
# todo: check if alpha. if so, pop up a question if they want to apply it as alpha. Use threading events to set the answer?
|
||||||
char.clearSkills()
|
char.clearSkills()
|
||||||
for skillRow in resp["skills"]:
|
for skillRow in resp["skills"]:
|
||||||
char.addSkill(Skill(char, skillRow["skill_id"], skillRow["trained_skill_level"]))
|
char.addSkill(Skill(char, skillRow["skill_id"], skillRow["trained_skill_level"]))
|
||||||
|
|
||||||
|
if not self.running:
|
||||||
|
self.callback[0](self.callback[1])
|
||||||
|
return
|
||||||
resp = sEsi.getSecStatus(ssoChar.ID)
|
resp = sEsi.getSecStatus(ssoChar.ID)
|
||||||
|
|
||||||
char.secStatus = resp['security_status']
|
char.secStatus = resp['security_status']
|
||||||
|
|
||||||
self.callback[0](self.callback[1])
|
self.callback[0](self.callback[1])
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
raise
|
raise
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
pyfalog.warn(ex)
|
pyfalog.warn(ex)
|
||||||
self.callback[0](self.callback[1], sys.exc_info())
|
self.callback[0](self.callback[1], sys.exc_info())
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class ShipBrowserWorkerThread(threading.Thread):
|
|||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
pyfalog.debug("Initialize ShipBrowserWorkerThread.")
|
pyfalog.debug("Initialize ShipBrowserWorkerThread.")
|
||||||
self.name = "ShipBrowser"
|
self.name = "ShipBrowser"
|
||||||
|
self.running = True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.queue = queue.Queue()
|
self.queue = queue.Queue()
|
||||||
@@ -60,6 +61,8 @@ class ShipBrowserWorkerThread(threading.Thread):
|
|||||||
cache = self.cache
|
cache = self.cache
|
||||||
sMkt = Market.getInstance()
|
sMkt = Market.getInstance()
|
||||||
while True:
|
while True:
|
||||||
|
if not self.running:
|
||||||
|
break
|
||||||
try:
|
try:
|
||||||
id_, callback = queue.get()
|
id_, callback = queue.get()
|
||||||
set_ = cache.get(id_)
|
set_ = cache.get(id_)
|
||||||
@@ -82,6 +85,9 @@ class ShipBrowserWorkerThread(threading.Thread):
|
|||||||
pyfalog.critical("Queue task done failed.")
|
pyfalog.critical("Queue task done failed.")
|
||||||
pyfalog.critical(e)
|
pyfalog.critical(e)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
|
||||||
class SearchWorkerThread(threading.Thread):
|
class SearchWorkerThread(threading.Thread):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -91,6 +97,7 @@ class SearchWorkerThread(threading.Thread):
|
|||||||
# load the jargon while in an out-of-thread context, to spot any problems while in the main thread
|
# load the jargon while in an out-of-thread context, to spot any problems while in the main thread
|
||||||
self.jargonLoader.get_jargon()
|
self.jargonLoader.get_jargon()
|
||||||
self.jargonLoader.get_jargon().apply('test string')
|
self.jargonLoader.get_jargon().apply('test string')
|
||||||
|
self.running = True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.cv = threading.Condition()
|
self.cv = threading.Condition()
|
||||||
@@ -101,6 +108,8 @@ class SearchWorkerThread(threading.Thread):
|
|||||||
cv = self.cv
|
cv = self.cv
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
if not self.running:
|
||||||
|
break
|
||||||
cv.acquire()
|
cv.acquire()
|
||||||
while self.searchRequest is None:
|
while self.searchRequest is None:
|
||||||
cv.wait()
|
cv.wait()
|
||||||
@@ -161,6 +170,8 @@ class SearchWorkerThread(threading.Thread):
|
|||||||
self.cv.notify()
|
self.cv.notify()
|
||||||
self.cv.release()
|
self.cv.release()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
class Market:
|
class Market:
|
||||||
instance = None
|
instance = None
|
||||||
|
|||||||
@@ -235,11 +235,14 @@ class PriceWorkerThread(threading.Thread):
|
|||||||
self.name = "PriceWorker"
|
self.name = "PriceWorker"
|
||||||
self.queue = queue.Queue()
|
self.queue = queue.Queue()
|
||||||
self.wait = {}
|
self.wait = {}
|
||||||
|
self.running = True
|
||||||
pyfalog.debug("Initialize PriceWorkerThread.")
|
pyfalog.debug("Initialize PriceWorkerThread.")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
queue = self.queue
|
queue = self.queue
|
||||||
while True:
|
while True:
|
||||||
|
if not self.running:
|
||||||
|
break
|
||||||
# Grab our data
|
# Grab our data
|
||||||
callback, requests, fetchTimeout, validityOverride = queue.get()
|
callback, requests, fetchTimeout, validityOverride = queue.get()
|
||||||
|
|
||||||
@@ -265,6 +268,9 @@ class PriceWorkerThread(threading.Thread):
|
|||||||
callbacks = self.wait.setdefault(price.typeID, [])
|
callbacks = self.wait.setdefault(price.typeID, [])
|
||||||
callbacks.append(callback)
|
callbacks.append(callback)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
|
||||||
# Import market sources only to initialize price source modules, they register on their own
|
# Import market sources only to initialize price source modules, they register on their own
|
||||||
from service.marketSources import evemarketer, evemarketdata, evepraisal # noqa: E402
|
from service.marketSources import evemarketer, evemarketdata, evepraisal # noqa: E402
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class CheckUpdateThread(threading.Thread):
|
|||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.settings = UpdateSettings.getInstance()
|
self.settings = UpdateSettings.getInstance()
|
||||||
self.network = Network.getInstance()
|
self.network = Network.getInstance()
|
||||||
|
self.running = True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
network = Network.getInstance()
|
network = Network.getInstance()
|
||||||
@@ -49,13 +50,13 @@ class CheckUpdateThread(threading.Thread):
|
|||||||
try:
|
try:
|
||||||
response = network.get(
|
response = network.get(
|
||||||
url='https://www.pyfa.io/update_check?pyfa_version={}&client_hash={}'.format(config.version, config.getClientSecret()),
|
url='https://www.pyfa.io/update_check?pyfa_version={}&client_hash={}'.format(config.version, config.getClientSecret()),
|
||||||
type=network.UPDATE)
|
type=network.UPDATE, timeout=5)
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
response = network.get(
|
response = network.get(
|
||||||
url='https://api.github.com/repos/pyfa-org/Pyfa/releases',
|
url='https://api.github.com/repos/pyfa-org/Pyfa/releases',
|
||||||
type=network.UPDATE)
|
type=network.UPDATE, timeout=5)
|
||||||
|
|
||||||
jsonResponse = response.json()
|
jsonResponse = response.json()
|
||||||
jsonResponse.sort(
|
jsonResponse.sort(
|
||||||
@@ -94,6 +95,9 @@ class CheckUpdateThread(threading.Thread):
|
|||||||
def versiontuple(v):
|
def versiontuple(v):
|
||||||
return tuple(map(int, (v.split("."))))
|
return tuple(map(int, (v.split("."))))
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
|
||||||
class Update:
|
class Update:
|
||||||
instance = None
|
instance = None
|
||||||
|
|||||||
@@ -35,3 +35,16 @@ class Timer:
|
|||||||
def __exit__(self, type, value, traceback):
|
def __exit__(self, type, value, traceback):
|
||||||
self.checkpoint('finished')
|
self.checkpoint('finished')
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CountdownTimer:
|
||||||
|
|
||||||
|
def __init__(self, timeout):
|
||||||
|
self.timeout = timeout
|
||||||
|
self.start = time.time()
|
||||||
|
|
||||||
|
def elapsed(self):
|
||||||
|
return time.time() - self.start
|
||||||
|
|
||||||
|
def remainder(self):
|
||||||
|
return max(self.timeout - self.elapsed(), 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user