Repository: pymedusa/Medusa

Issue:
[APP SUBMITTED]: KeyError: u'Ghost.Hunters.S06E09.Spirits.of.the.Night.1080i.HDTV.DD5.1.MPEG2-TrollHD'

### INFO
**Python Version**: `2.7.15 (default, Jun 17 2018, 22:57:51) [GCC 7.3.0]`
**Operating System**: `Linux-5.1.4-x86_64-Intel-R-_Xeon-R-_CPU___________X5650__@_2.67GHz-with-slackware-14.2`
**Locale**: `UTF-8`
**Branch**: [master](../tree/master)
**Database**: `44.14`
**Commit**: pymedusa/Medusa@d0c136d7a528a471b51676140bd35d24d97f65c6
**Link to Log**: https://gist.github.com/62ee3b054351935be5d412af664b29ae
### ERROR
<pre>
2019-12-28 20:47:13 ERROR    POSTPROCESSOR :: [d0c136d] Exception generated: u'Ghost.Hunters.S06E09.Spirits.of.the.Night.1080i.HDTV.DD5.1.MPEG2-TrollHD'
Traceback (most recent call last):
  File "/opt/Medusa-master/<a href="../blob/d0c136d7a528a471b51676140bd35d24d97f65c6/medusa/scheduler.py#L93">medusa/scheduler.py</a>", line 93, in run
    self.action.run(self.force)
  File "/opt/Medusa-master/<a href="../blob/d0c136d7a528a471b51676140bd35d24d97f65c6/medusa/process_tv.py#L65">medusa/process_tv.py</a>", line 65, in run
    return ProcessResult(path, process_method).process(force=force, **kwargs)
  File "/opt/Medusa-master/<a href="../blob/d0c136d7a528a471b51676140bd35d24d97f65c6/medusa/process_tv.py#L188">medusa/process_tv.py</a>", line 188, in process
    ignore_subs=ignore_subs)
  File "/opt/Medusa-master/<a href="../blob/d0c136d7a528a471b51676140bd35d24d97f65c6/medusa/process_tv.py#L378">medusa/process_tv.py</a>", line 378, in process_files
    self.process_media(path, self.video_files, force, is_priority, ignore_subs)
  File "/opt/Medusa-master/<a href="../blob/d0c136d7a528a471b51676140bd35d24d97f65c6/medusa/process_tv.py#L599">medusa/process_tv.py</a>", line 599, in process_media
    self.result = processor.process()
  File "/opt/Medusa-master/<a href="../blob/d0c136d7a528a471b51676140bd35d24d97f65c6/medusa/post_processor.py#L1015">medusa/post_processor.py</a>", line 1015, in process
    (series_obj, season, episodes, quality, version) = self._find_info()
  File "/opt/Medusa-master/<a href="../blob/d0c136d7a528a471b51676140bd35d24d97f65c6/medusa/post_processor.py#L607">medusa/post_processor.py</a>", line 607, in _find_info
    series_obj, season, episodes, quality, version, airdate = self._parse_info()
  File "/opt/Medusa-master/<a href="../blob/d0c136d7a528a471b51676140bd35d24d97f65c6/medusa/post_processor.py#L568">medusa/post_processor.py</a>", line 568, in _parse_info
    cur_show, cur_season, cur_episodes, cur_quality, cur_version = self._analyze_name(name)
  File "/opt/Medusa-master/<a href="../blob/d0c136d7a528a471b51676140bd35d24d97f65c6/medusa/post_processor.py#L680">medusa/post_processor.py</a>", line 680, in _analyze_name
    parse_result = NameParser().parse(name)
  File "/opt/Medusa-master/<a href="../blob/d0c136d7a528a471b51676140bd35d24d97f65c6/medusa/name_parser/parser.py#L395">medusa/name_parser/parser.py</a>", line 395, in parse
    name_parser_cache.add(name, result)
  File "/opt/Medusa-master/<a href="../blob/d0c136d7a528a471b51676140bd35d24d97f65c6/medusa/name_parser/parser.py#L604">medusa/name_parser/parser.py</a>", line 604, in add
    self.cache.popitem(last=False)
  File "/usr/lib64/python2.7/collections.py", line 178, in popitem
    value = self.pop(key)
  File "/usr/lib64/python2.7/collections.py", line 160, in pop
    raise KeyError(key)
KeyError: u'Ghost.Hunters.S06E09.Spirits.of.the.Night.1080i.HDTV.DD5.1.MPEG2-TrollHD'
</pre>
---
_STAFF NOTIFIED_: @pymedusa/support @pymedusa/moderators



Discussion:
Needs to be thread safe.

Resolving patch:
diff --git a/medusa/name_parser/parser.py b/medusa/name_parser/parser.py
--- a/medusa/name_parser/parser.py
+++ b/medusa/name_parser/parser.py
@@ -6,6 +6,7 @@
 import logging
 import time
 from collections import OrderedDict
+from threading import Lock
 
 import guessit
 
@@ -591,6 +592,7 @@ def __init__(self, max_size=1000):
         """Initialize the cache with a maximum size."""
         self.cache = OrderedDict()
         self.max_size = max_size
+        self.lock = Lock()
 
     def add(self, name, parse_result):
         """Add the result to the parser cache.
@@ -600,9 +602,10 @@ def add(self, name, parse_result):
         :param parse_result:
         :type parse_result: ParseResult
         """
-        while len(self.cache) >= self.max_size:
-            self.cache.popitem(last=False)
-        self.cache[name] = parse_result
+        with self.lock:
+            while len(self.cache) >= self.max_size:
+                self.cache.popitem(last=False)
+            self.cache[name] = parse_result
 
     def get(self, name):
         """Return the cached parsed result.
@@ -612,23 +615,23 @@ def get(self, name):
         :return:
         :rtype: ParseResult
         """
-        if name in self.cache:
-            log.debug('Using cached parse result for {name}', {'name': name})
-            return self.cache[name]
+        with self.lock:
+            if name in self.cache:
+                log.debug('Using cached parse result for {name}', {'name': name})
+                return self.cache[name]
 
     def remove(self, indexer, indexer_id):
         """Remove cache item given indexer and indexer_id."""
-        if not indexer or not indexer_id:
-            return
-        to_remove = [
-            cached_name
-            for cached_name, cached_parsed_result in iteritems(self.cache)
-            if cached_parsed_result.series.indexer == indexer
-            and cached_parsed_result.series.indexerid == indexer_id
-        ]
-        for item in to_remove:
-            del self.cache[item]
-            log.debug('Removed parsed cached result for release: {release}'.format(release=item))
+        with self.lock:
+            to_remove = [
+                cached_name
+                for cached_name, cached_parsed_result in iteritems(self.cache)
+                if cached_parsed_result.series.indexer == indexer
+                and cached_parsed_result.series.indexerid == indexer_id
+            ]
+            for item in to_remove:
+                del self.cache[item]
+                log.debug('Removed cached parse result for {name}', {'name': item})
 
 
 name_parser_cache = NameParserCache()


Test patch: