Attempt to terminate threads when pyfa is closed

This commit is contained in:
DarkPhoenix
2020-02-03 17:12:23 +03:00
parent 9ddfcc894f
commit 6527f9e11e
7 changed files with 99 additions and 18 deletions

View File

@@ -99,12 +99,14 @@ class PFPanel(wx.Panel):
class OpenFitsThread(threading.Thread):
def __init__(self, fits, callback):
threading.Thread.__init__(self)
self.name = "LoadingOpenFits"
self.mainFrame = MainFrame.getInstance()
self.callback = callback
self.fits = fits
self.running = True
self.start()
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
# have correct calculations displayed at startup
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))
wx.CallAfter(self.callback)
if self.running:
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

15
pyfa.py
View File

@@ -151,5 +151,18 @@ if __name__ == "__main__":
else:
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()

View File

@@ -45,11 +45,13 @@ pyfalog = Logger(__name__)
class CharacterImportThread(threading.Thread):
def __init__(self, paths, callback):
threading.Thread.__init__(self)
self.name = "CharacterImport"
self.paths = paths
self.callback = callback
self.running = True
def run(self):
paths = self.paths
@@ -61,6 +63,8 @@ class CharacterImportThread(threading.Thread):
all_skill_ids.append(skill.itemID)
for path in paths:
if not self.running:
break
try:
charFile = open(path, mode='r').read()
doc = minidom.parseString(charFile)
@@ -95,6 +99,9 @@ class CharacterImportThread(threading.Thread):
wx.CallAfter(self.callback)
def stop(self):
self.running = False
class SkillBackupThread(threading.Thread):
def __init__(self, path, saveFmt, activeFit, callback):
@@ -104,25 +111,32 @@ class SkillBackupThread(threading.Thread):
self.saveFmt = saveFmt
self.activeFit = activeFit
self.callback = callback
self.running = True
def run(self):
path = self.path
sCharacter = Character.getInstance()
if self.saveFmt == "xml" or self.saveFmt == "emp":
backupData = sCharacter.exportXml()
else:
backupData = sCharacter.exportText()
backupData = None
if self.running:
if self.saveFmt == "xml" or self.saveFmt == "emp":
backupData = sCharacter.exportXml()
else:
backupData = sCharacter.exportText()
if self.saveFmt == "emp":
with gzip.open(path, mode='wb') as backupFile:
backupFile.write(backupData.encode())
else:
with open(path, mode='w', encoding='utf-8') as backupFile:
backupFile.write(backupData)
if self.running and backupData is not None:
if self.saveFmt == "emp":
with gzip.open(path, mode='wb') as backupFile:
backupFile.write(backupData.encode())
else:
with open(path, mode='w', encoding='utf-8') as backupFile:
backupFile.write(backupData)
wx.CallAfter(self.callback)
def stop(self):
self.running = False
class Character:
instance = None
@@ -474,12 +488,14 @@ class Character:
class UpdateAPIThread(threading.Thread):
def __init__(self, charID, callback):
threading.Thread.__init__(self)
self.name = "CheckUpdate"
self.callback = callback
self.charID = charID
self.running = True
def run(self):
try:
@@ -488,20 +504,31 @@ class UpdateAPIThread(threading.Thread):
sEsi = Esi.getInstance()
sChar = Character.getInstance()
ssoChar = sChar.getSsoCharacter(char.ID)
if not self.running:
self.callback[0](self.callback[1])
return
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?
char.clearSkills()
for skillRow in resp["skills"]:
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)
char.secStatus = resp['security_status']
self.callback[0](self.callback[1])
except (KeyboardInterrupt, SystemExit):
raise
except Exception as ex:
pyfalog.warn(ex)
self.callback[0](self.callback[1], sys.exc_info())
def stop(self):
self.running = False

View File

@@ -46,6 +46,7 @@ class ShipBrowserWorkerThread(threading.Thread):
threading.Thread.__init__(self)
pyfalog.debug("Initialize ShipBrowserWorkerThread.")
self.name = "ShipBrowser"
self.running = True
def run(self):
self.queue = queue.Queue()
@@ -60,6 +61,8 @@ class ShipBrowserWorkerThread(threading.Thread):
cache = self.cache
sMkt = Market.getInstance()
while True:
if not self.running:
break
try:
id_, callback = queue.get()
set_ = cache.get(id_)
@@ -82,6 +85,9 @@ class ShipBrowserWorkerThread(threading.Thread):
pyfalog.critical("Queue task done failed.")
pyfalog.critical(e)
def stop(self):
self.running = False
class SearchWorkerThread(threading.Thread):
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
self.jargonLoader.get_jargon()
self.jargonLoader.get_jargon().apply('test string')
self.running = True
def run(self):
self.cv = threading.Condition()
@@ -101,6 +108,8 @@ class SearchWorkerThread(threading.Thread):
cv = self.cv
while True:
if not self.running:
break
cv.acquire()
while self.searchRequest is None:
cv.wait()
@@ -161,6 +170,8 @@ class SearchWorkerThread(threading.Thread):
self.cv.notify()
self.cv.release()
def stop(self):
self.running = False
class Market:
instance = None

View File

@@ -235,11 +235,14 @@ class PriceWorkerThread(threading.Thread):
self.name = "PriceWorker"
self.queue = queue.Queue()
self.wait = {}
self.running = True
pyfalog.debug("Initialize PriceWorkerThread.")
def run(self):
queue = self.queue
while True:
if not self.running:
break
# Grab our data
callback, requests, fetchTimeout, validityOverride = queue.get()
@@ -265,6 +268,9 @@ class PriceWorkerThread(threading.Thread):
callbacks = self.wait.setdefault(price.typeID, [])
callbacks.append(callback)
def stop(self):
self.running = False
# Import market sources only to initialize price source modules, they register on their own
from service.marketSources import evemarketer, evemarketdata, evepraisal # noqa: E402

View File

@@ -41,6 +41,7 @@ class CheckUpdateThread(threading.Thread):
self.callback = callback
self.settings = UpdateSettings.getInstance()
self.network = Network.getInstance()
self.running = True
def run(self):
network = Network.getInstance()
@@ -49,13 +50,13 @@ class CheckUpdateThread(threading.Thread):
try:
response = network.get(
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):
raise
except Exception as e:
response = network.get(
url='https://api.github.com/repos/pyfa-org/Pyfa/releases',
type=network.UPDATE)
type=network.UPDATE, timeout=5)
jsonResponse = response.json()
jsonResponse.sort(
@@ -94,6 +95,9 @@ class CheckUpdateThread(threading.Thread):
def versiontuple(v):
return tuple(map(int, (v.split("."))))
def stop(self):
self.running = False
class Update:
instance = None

View File

@@ -35,3 +35,16 @@ class Timer:
def __exit__(self, type, value, traceback):
self.checkpoint('finished')
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)