Fix errors during shutdown
==== Tech Notes ==== Skills weren't cleanly shutdown. This ensures that skills are shutdown more cleanly and that shutdown procedure will not be interrupted by broken shutdown methods. Shutdown of reload thread fixed Unused globals removedpull/997/merge
parent
2881b1842f
commit
c7c25cc01b
|
@ -22,7 +22,7 @@ import subprocess
|
|||
import sys
|
||||
import time
|
||||
from os.path import exists, join
|
||||
from threading import Timer, Thread
|
||||
from threading import Timer, Thread, Event
|
||||
|
||||
from mycroft import MYCROFT_ROOT_PATH
|
||||
from mycroft.configuration import ConfigurationManager
|
||||
|
@ -45,7 +45,6 @@ __author__ = 'seanfitz'
|
|||
ws = None
|
||||
loaded_skills = {}
|
||||
last_modified_skill = 0
|
||||
skills_directories = []
|
||||
skill_reload_thread = None
|
||||
skills_manager_timer = None
|
||||
id_counter = 0
|
||||
|
@ -95,7 +94,7 @@ def skills_manager(message):
|
|||
skills_manager runs on a Timer every hour and checks for updated
|
||||
skills.
|
||||
"""
|
||||
global skills_manager_timer, ws
|
||||
global skills_manager_timer
|
||||
|
||||
if connected():
|
||||
if skills_manager_timer is None:
|
||||
|
@ -114,6 +113,7 @@ def _skills_manager_dispatch():
|
|||
"""
|
||||
Thread function to trigger skill_manager over message bus.
|
||||
"""
|
||||
global ws
|
||||
ws.emit(Message("skill_manager", {}))
|
||||
|
||||
|
||||
|
@ -128,8 +128,7 @@ def _starting_up():
|
|||
- adapt intent service
|
||||
- padatious intent service
|
||||
"""
|
||||
global ws, loaded_skills, last_modified_skill, skills_directories, \
|
||||
skill_reload_thread
|
||||
global ws, skill_reload_thread
|
||||
|
||||
check_connection()
|
||||
|
||||
|
@ -147,7 +146,7 @@ def _starting_up():
|
|||
IntentService(ws)
|
||||
|
||||
# Create a thread that monitors the loaded skills, looking for updates
|
||||
skill_reload_thread = Thread(target=_watch_skills)
|
||||
skill_reload_thread = WatchSkills()
|
||||
skill_reload_thread.daemon = True
|
||||
skill_reload_thread.start()
|
||||
|
||||
|
@ -201,65 +200,76 @@ def _get_last_modified_date(path):
|
|||
return last_date
|
||||
|
||||
|
||||
def _watch_skills():
|
||||
class WatchSkills(Thread):
|
||||
"""
|
||||
Thread function to reload skills when a change is detected.
|
||||
"""
|
||||
global ws, loaded_skills, last_modified_skill, \
|
||||
id_counter
|
||||
def __init__(self):
|
||||
super(WatchSkills, self).__init__()
|
||||
self._stop_event = Event()
|
||||
|
||||
# Scan the file folder that contains Skills. If a Skill is updated,
|
||||
# unload the existing version from memory and reload from the disk.
|
||||
while True:
|
||||
if exists(SKILLS_DIR):
|
||||
# checking skills dir and getting all skills there
|
||||
list = filter(lambda x: os.path.isdir(
|
||||
os.path.join(SKILLS_DIR, x)), os.listdir(SKILLS_DIR))
|
||||
def run(self):
|
||||
global ws, loaded_skills, last_modified_skill, \
|
||||
id_counter
|
||||
|
||||
for skill_folder in list:
|
||||
if skill_folder not in loaded_skills:
|
||||
id_counter += 1
|
||||
loaded_skills[skill_folder] = {"id": id_counter}
|
||||
skill = loaded_skills.get(skill_folder)
|
||||
skill["path"] = os.path.join(SKILLS_DIR, skill_folder)
|
||||
# checking if is a skill
|
||||
if not MainModule + ".py" in os.listdir(skill["path"]):
|
||||
continue
|
||||
# getting the newest modified date of skill
|
||||
skill["last_modified"] = _get_last_modified_date(skill["path"])
|
||||
modified = skill.get("last_modified", 0)
|
||||
# checking if skill is loaded and wasn't modified
|
||||
if skill.get(
|
||||
"loaded") and modified <= last_modified_skill:
|
||||
continue
|
||||
# checking if skill was modified
|
||||
elif skill.get("instance") and modified > last_modified_skill:
|
||||
# checking if skill should be reloaded
|
||||
if not skill["instance"].reload_skill:
|
||||
# Scan the file folder that contains Skills. If a Skill is updated,
|
||||
# unload the existing version from memory and reload from the disk.
|
||||
while not self._stop_event.is_set():
|
||||
if exists(SKILLS_DIR):
|
||||
# checking skills dir and getting all skills there
|
||||
list = filter(lambda x: os.path.isdir(
|
||||
os.path.join(SKILLS_DIR, x)), os.listdir(SKILLS_DIR))
|
||||
|
||||
for skill_folder in list:
|
||||
if skill_folder not in loaded_skills:
|
||||
id_counter += 1
|
||||
loaded_skills[skill_folder] = {"id": id_counter}
|
||||
skill = loaded_skills.get(skill_folder)
|
||||
skill["path"] = os.path.join(SKILLS_DIR, skill_folder)
|
||||
# checking if is a skill
|
||||
if not MainModule + ".py" in os.listdir(skill["path"]):
|
||||
continue
|
||||
logger.debug("Reloading Skill: " + skill_folder)
|
||||
# removing listeners and stopping threads
|
||||
skill["instance"].shutdown()
|
||||
# getting the newest modified date of skill
|
||||
last_mod = _get_last_modified_date(skill["path"])
|
||||
skill["last_modified"] = last_mod
|
||||
modified = skill.get("last_modified", 0)
|
||||
# checking if skill is loaded and wasn't modified
|
||||
if skill.get(
|
||||
"loaded") and modified <= last_modified_skill:
|
||||
continue
|
||||
# checking if skill was modified
|
||||
elif (skill.get("instance") and
|
||||
modified > last_modified_skill):
|
||||
# checking if skill should be reloaded
|
||||
if not skill["instance"].reload_skill:
|
||||
continue
|
||||
logger.debug("Reloading Skill: " + skill_folder)
|
||||
# removing listeners and stopping threads
|
||||
skill["instance"].shutdown()
|
||||
|
||||
# - 2 since there are two local references that are known
|
||||
refs = sys.getrefcount(skill["instance"]) - 2
|
||||
if refs > 0:
|
||||
logger.warn("After shutdown of {} there are still "
|
||||
"{} references remaining. The skill "
|
||||
"won't be cleaned from memory."
|
||||
.format(skill['instance'].name, refs))
|
||||
del skill["instance"]
|
||||
skill["loaded"] = True
|
||||
skill["instance"] = load_skill(
|
||||
create_skill_descriptor(skill["path"]), ws, skill["id"])
|
||||
# get the last modified skill
|
||||
modified_dates = map(lambda x: x.get("last_modified"),
|
||||
loaded_skills.values())
|
||||
if len(modified_dates) > 0:
|
||||
last_modified_skill = max(modified_dates)
|
||||
# -2 since two local references that are known
|
||||
refs = sys.getrefcount(skill["instance"]) - 2
|
||||
if refs > 0:
|
||||
logger.warn("After shutdown of {} there are still "
|
||||
"{} references remaining. The skill "
|
||||
"won't be cleaned from memory."
|
||||
.format(skill['instance'].name, refs))
|
||||
del skill["instance"]
|
||||
skill["loaded"] = True
|
||||
skill["instance"] = load_skill(
|
||||
create_skill_descriptor(skill["path"]),
|
||||
ws, skill["id"])
|
||||
# get the last modified skill
|
||||
modified_dates = map(lambda x: x.get("last_modified"),
|
||||
loaded_skills.values())
|
||||
if len(modified_dates) > 0:
|
||||
last_modified_skill = max(modified_dates)
|
||||
|
||||
# Pause briefly before beginning next scan
|
||||
time.sleep(2)
|
||||
# Pause briefly before beginning next scan
|
||||
time.sleep(2)
|
||||
|
||||
def stop(self):
|
||||
self._stop_event.set()
|
||||
|
||||
|
||||
def handle_converse_request(message):
|
||||
|
@ -330,11 +340,17 @@ if __name__ == "__main__":
|
|||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
skills_manager_timer.cancel()
|
||||
# Do a clean shutdown of all skills and terminate all running threads
|
||||
for skill in loaded_skills:
|
||||
skill.shutdown()
|
||||
try:
|
||||
loaded_skills[skill]['instance'].shutdown()
|
||||
except:
|
||||
pass
|
||||
if skills_manager_timer:
|
||||
skills_manager_timer.cancel()
|
||||
if skill_reload_thread:
|
||||
skill_reload_thread.cancel()
|
||||
skill_reload_thread.stop()
|
||||
skill_reload_thread.join()
|
||||
|
||||
finally:
|
||||
sys.exit()
|
||||
|
|
Loading…
Reference in New Issue