Skip to content

Commit

Permalink
Attempt to terminate threads when pyfa is closed
Browse files Browse the repository at this point in the history
  • Loading branch information
DarkFenX committed Feb 3, 2020
1 parent 9ddfcc8 commit 6527f9e
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 18 deletions.
13 changes: 10 additions & 3 deletions gui/mainFrame.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,14 @@ def OnBkErase(self, event):


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):
Expand All @@ -118,10 +120,15 @@ def run(self):
# 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))

if self.running:
wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fits[-1], startup=2))
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
Expand Down
15 changes: 14 additions & 1 deletion pyfa.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,18 @@ def _process_args(self, largs, rargs, values):
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()
51 changes: 39 additions & 12 deletions service/character.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@


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
Expand All @@ -61,6 +63,8 @@ def run(self):
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)
Expand Down Expand Up @@ -95,6 +99,9 @@ def run(self):

wx.CallAfter(self.callback)

def stop(self):
self.running = False


class SkillBackupThread(threading.Thread):
def __init__(self, path, saveFmt, activeFit, callback):
Expand All @@ -104,25 +111,32 @@ def __init__(self, path, saveFmt, activeFit, callback):
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
Expand Down Expand Up @@ -474,12 +488,14 @@ def _checkRequirements(self, char, subThing, reqs):


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:
Expand All @@ -488,20 +504,31 @@ def run(self):
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
11 changes: 11 additions & 0 deletions service/market.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(self):
threading.Thread.__init__(self)
pyfalog.debug("Initialize ShipBrowserWorkerThread.")
self.name = "ShipBrowser"
self.running = True

def run(self):
self.queue = queue.Queue()
Expand All @@ -60,6 +61,8 @@ def processRequests(self):
cache = self.cache
sMkt = Market.getInstance()
while True:
if not self.running:
break
try:
id_, callback = queue.get()
set_ = cache.get(id_)
Expand All @@ -82,6 +85,9 @@ def processRequests(self):
pyfalog.critical("Queue task done failed.")
pyfalog.critical(e)

def stop(self):
self.running = False


class SearchWorkerThread(threading.Thread):
def __init__(self):
Expand All @@ -91,6 +97,7 @@ def __init__(self):
# 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()
Expand All @@ -101,6 +108,8 @@ def processSearches(self):
cv = self.cv

while True:
if not self.running:
break
cv.acquire()
while self.searchRequest is None:
cv.wait()
Expand Down Expand Up @@ -161,6 +170,8 @@ def scheduleSearch(self, text, callback, filterName=None):
self.cv.notify()
self.cv.release()

def stop(self):
self.running = False

class Market:
instance = None
Expand Down
6 changes: 6 additions & 0 deletions service/price.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,14 @@ def __init__(self):
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()

Expand All @@ -265,6 +268,9 @@ def setToWait(self, prices, callback):
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
8 changes: 6 additions & 2 deletions service/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def __init__(self, callback):
self.callback = callback
self.settings = UpdateSettings.getInstance()
self.network = Network.getInstance()
self.running = True

def run(self):
network = Network.getInstance()
Expand All @@ -49,13 +50,13 @@ def run(self):
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(
Expand Down Expand Up @@ -94,6 +95,9 @@ def run(self):
def versiontuple(v):
return tuple(map(int, (v.split("."))))

def stop(self):
self.running = False


class Update:
instance = None
Expand Down
13 changes: 13 additions & 0 deletions utils/timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,16 @@ def __enter__(self):
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)

0 comments on commit 6527f9e

Please sign in to comment.