Repository: MycroftAI/mycroft-core

Issue:
No audio on ubuntu trusty desktop install
I am attempting to run mycroft on my desktop and it is not working correctly. My expectation is that I can issue commands in the CLI and hear responses. For instance if I type "say Hello" then I should hear hello spoken.

After a lot of debugging it seems to be a problem with how mimic is being invoked. It *seems* like the wav files are not being generated (or being deleted later) or the pho files are missing. 

I am running ubuntu trusty, python version 2.7. I have attempted running mycroft master branch (release/v0.9.0-19-g7e660bc) and also the release-v0.8.22 branch and nothing seems to work.

I have gone into the code and put various debug statements into the tts mimic handler to see what commands it executes. When I run the commands manually they properly create .wav files and .pho files in /tmp/mycroft/cache/tts. What is weird though is these files are disappearing, and I suspect it is happening after they are generated. If I chattr +i the files they can not be deleted, and I see stack traces showing the cache clean function is being called. Is it possible these files are being deleted from the cache too soon?

What I have proven though is everything is "working" -- if I generate a wav/pho file and log the file name written to disk, I can chattr both files. This means mycroft can't delete the files. So.. when I issue "say Hello" its the same hash (ie /tmp/mycroft/cache/tts/04af779804e578a145250c722cbfddef.pho) and i actually hear Hello spoken. However this required a lot of manual trickery and log watching. 

Unfortunately due to the async nature of mycroft I do not have the time to invest debugging it fully. I lost the flow after it started putting talk messages into queues. 


Discussion:
It could be possible. The cache is pruned if the remaining disk space on the tmp partition is too low. Too low in this case is < 5% free space.

Could that be it?
That is questionable logic. I have 99% used on root (where /tmp is) but root is a 225G mount and 1% is 2.6G free, more than enough space. What is the function called? 

I will go look later and disable that behavior, unless there is a configuration parameter.
Yes, I can see the issue here.

Called from here:
https://github.com/MycroftAI/mycroft-core/blob/dev/mycroft/tts/__init__.py#L241

The function is located here:
https://github.com/MycroftAI/mycroft-core/blob/dev/mycroft/util/__init__.py#L158
appreciate it, this is what was causing my issue. I cleaned up my hard drive to drop above 5% free and everything works now. 

In terms of the bad logic, treating 5% free as a cleanup threshold seems wrong (5% of 1TB drive is a lot of space). Second, the real underlying issue is how a cleanup routine can delete a file from cache before it is being used. Perhaps it makes sense to update the curate_cache function to check timestamps of files and not delete them if they are under an age threshold.

There could be better logging when errors occur. This might be the most frustrating thing as a developer watching the system silently fail without a single log line indicating an error.

Thanks
I will take all three things in consideration and give it a go tomorrow.

Resolving patch:
diff --git a/mycroft/tts/__init__.py b/mycroft/tts/__init__.py
--- a/mycroft/tts/__init__.py
+++ b/mycroft/tts/__init__.py
@@ -155,12 +155,23 @@ def __init__(self, lang, voice, validator):
 
     def begin_audio(self):
         """Helper function for child classes to call in execute()"""
+        # Create signals informing start of speech
         self.ws.emit(Message("recognizer_loop:audio_output_start"))
         create_signal("isSpeaking")
 
     def end_audio(self):
-        """Helper function for child classes to call in execute()"""
+        """
+            Helper function for child classes to call in execute().
+
+            Sends the recognizer_loop:audio_output_end message, indicating
+            that speaking is done for the moment. It also checks if cache
+            directory needs cleaning to free up disk space.
+        """
+
         self.ws.emit(Message("recognizer_loop:audio_output_end"))
+        # Clean the cache as needed
+        cache_dir = mycroft.util.get_cache_directory("tts")
+        mycroft.util.curate_cache(cache_dir, min_free_percent=100)
 
         # This check will clear the "signal"
         check_for_signal("isSpeaking")
@@ -236,10 +247,8 @@ def save_phonemes(self, key, phonemes):
                 key:        Hash key for the sentence
                 phonemes:   phoneme string to save
         """
-        # Clean out the cache as needed
-        cache_dir = mycroft.util.get_cache_directory("tts")
-        mycroft.util.curate_cache(cache_dir)
 
+        cache_dir = mycroft.util.get_cache_directory("tts")
         pho_file = os.path.join(cache_dir, key + ".pho")
         try:
             with open(pho_file, "w") as cachefile:
diff --git a/mycroft/util/__init__.py b/mycroft/util/__init__.py
--- a/mycroft/util/__init__.py
+++ b/mycroft/util/__init__.py
@@ -155,30 +155,36 @@ def connected(host="8.8.8.8", port=53, timeout=3):
             return False
 
 
-def curate_cache(dir, min_free_percent=5.0):
+def curate_cache(directory, min_free_percent=5.0, min_free_disk=50):
     """Clear out the directory if needed
 
     This assumes all the files in the directory can be deleted as freely
 
     Args:
-        dir (str): directory path that holds cached files
-        min_free_percent (float): percentage (0.0-100.0) of drive to keep free
+        directory (str): directory path that holds cached files
+        min_free_percent (float): percentage (0.0-100.0) of drive to keep free,
+                                  default is 5% if not specified.
+        min_free_disk (float): minimum allowed disk space in MB, default
+                               value is 50 MB if not specified.
     """
 
     # Simpleminded implementation -- keep a certain percentage of the
     # disk available.
     # TODO: Would be easy to add more options, like whitelisted files, etc.
-    space = psutil.disk_usage(dir)
+    space = psutil.disk_usage(directory)
 
+    # convert from MB to bytes
+    min_free_disk *= 1024 * 1024
     # space.percent = space.used/space.total*100.0
     percent_free = 100.0 - space.percent
-    if percent_free < min_free_percent:
+    if percent_free < min_free_percent and space.free < min_free_disk:
+        LOG.info('Low diskspace detected, cleaning cache')
         # calculate how many bytes we need to delete
         bytes_needed = (min_free_percent - percent_free) / 100.0 * space.total
         bytes_needed = int(bytes_needed + 1.0)
 
         # get all entries in the directory w/ stats
-        entries = (os.path.join(dir, fn) for fn in os.listdir(dir))
+        entries = (os.path.join(directory, fn) for fn in os.listdir(directory))
         entries = ((os.stat(path), path) for path in entries)
 
         # leave only regular files, insert modification date


Test patch: