Uploaded image for project: 'Picard'
  1. Picard
  2. PICARD-2878

Crash in coverart when scan triggers recursion beyond python limits (e.g. 1000)

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Normal Normal
    • 3.0
    • 2.8.5
    • None
    • Debian Bookworm (latest) 6.1.85-1 (2024-04-11) x86_64 GNU/Linux

      Picard (latest version in Debian Bookworm) crashes from "RecursionError: maximum recursion depth exceeded while calling a Python object" when scanning certain songs in the root directory of a large hierarchy of songs.

      The root cause was found, but fixing it might require some refactoring (I just started looking at the codebase tonight, so I barely know how it works). 

      The _init_ method (picard/coverart/_init_.py:191) processes a list recursively, and this causes a crash when stack depth reaches 1000.

      The list of images was large because the code was recursively searching subfolders for images, and the song was in the root of the hierarchy, so thousands of images were searched.  If the search ended early enough, it wouldn't crash, so it only happened on some songs.

      Scenario

      A single folder initially had many thousands of songs from various artists.  These were not originally in an organized folder hierarchy because the tools to do the organizing didn't exist when they were encoded over the decades.

      Using Picard, thousands of songs were moved into hierarchical folders.  Midway through this process, some songs in the root of the tree would trigger a crash repeatably.

      Code:

      picard/coverart/providers/local.py:106  Calls os.walk, which will create many results in this scenario, as the song is in the root of the hierarchy.

      picard/coverart/_init_.py:191  Is one of several lines where a method calls itself recursively to iterate through a list of coverart images.

      Potential solution:

      If recursion is used to process/find images, the size of the iterated list needs to be checked or limited to much less than 1000 elements (or whatever sys.getrecursionlimit() returns).

      Stack trace:
      Traceback (most recent call last):
        File "/usr/lib/picard/picard/webservice/_init_.py", line 572, in _process_reply
          self._handle_reply(reply, request)
        File "/usr/lib/picard/picard/webservice/_init_.py", line 532, in _handle_reply
          handler(reply.readAll(), reply, error)
        File "/usr/lib/picard/picard/coverart/providers/caa.py", line 670, in _caa_json_downloaded
          self.next_in_queue()
        File "/usr/lib/picard/picard/coverart/providers/provider.py", line 122, in next_in_queue
          self.coverart.next_in_queue()
        File "/usr/lib/picard/picard/coverart/_init_.py", line 160, in next_in_queue
          self.next_in_queue()
        File "/usr/lib/picard/picard/coverart/_init_.py", line 191, in next_in_queue
          self.next_in_queue()
        File "/usr/lib/picard/picard/coverart/_init_.py", line 191, in next_in_queue
          self.next_in_queue()
        File "/usr/lib/picard/picard/coverart/_init_.py", line 191, in next_in_queue
          self.next_in_queue()
        [Previous line repeated 973 more times]
        File "/usr/lib/picard/picard/coverart/_init_.py", line 182, in next_in_queue
          self._set_metadata(coverartimage, file.read())
        File "/usr/lib/picard/picard/coverart/_init_.py", line 71, in _set_metadata
          coverartimage.set_data(data)
        File "/usr/lib/picard/picard/coverart/image.py", line 281, in set_data
          self.datahash = DataHash(data, suffix=self.extension)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib/picard/picard/coverart/image.py", line 91, in _init_
          log.debug("Saving image data %s to %r" % (self._hash, self._filename))
        File "/usr/lib/python3.11/logging/_init_.py", line 1477, in debug
          self._log(DEBUG, msg, args, **kwargs)
        File "/usr/lib/python3.11/logging/_init_.py", line 1634, in _log
          self.handle(record)
        File "/usr/lib/python3.11/logging/_init_.py", line 1644, in handle
          self.callHandlers(record)
        File "/usr/lib/python3.11/logging/_init_.py", line 1706, in callHandlers
          hdlr.handle(record)
        File "/usr/lib/python3.11/logging/_init_.py", line 978, in handle
          self.emit(record)
        File "/usr/lib/picard/picard/log.py", line 83, in emit
          self.format(record),
          ^^^^^^^^^^^^^^^^^^^
        File "/usr/lib/python3.11/logging/_init_.py", line 953, in format
          return fmt.format(record)
                 ^^^^^^^^^^^^^^^^^^
        File "/usr/lib/python3.11/logging/_init_.py", line 688, in format
          if self.usesTime():
             ^^^^^^^^^^^^^^^
        File "/usr/lib/python3.11/logging/_init_.py", line 656, in usesTime
          return self._style.usesTime()
                 ^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib/python3.11/logging/_init_.py", line 433, in usesTime
          return self._fmt.find(self.asctime_search) >= 0
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      RecursionError: maximum recursion depth exceeded while calling a Python object

       

      Instrumentation used to debug:

      The added code below causes the stack exception on a different line of code, but it shows how the stack depth is growing as various coverart images are considered.

      picard/coverart/image.py:  in Class DataHash, _init_()

                  if self._hash not in _datafiles:
                      (fd, self._filename) = tempfile.mkstemp(prefix=prefix, suffix=suffix)
                      QObject.tagger.register_cleanup(self.delete_file)
                      with os.fdopen(fd, "wb") as imagefile:
                          imagefile.write(data)
                      _datafiles[self._hash] = self._filename
                      periodictouch.register_file(self._filename)
      {{                }}

                      }}{{# log.debug("Saving image data %s to %r" % (self._hash, self._filename))
                      # Ugly Instrumentation for quick bug tracking
                      import inspect
                      import sys
                      import traceback
                      sys.stdout.flush()
                      slen = len(inspect.stack())
                      print(f"STACK SIZE { slen }")
                      if slen>980:
                          traceback.print_stack()
                      
                      print("Saving image data %s to %r" % (self._hash, self._filename))
                      sys.stdout.flush()
                      sys.stderr.flush()
                      #end instrumentation
                  else:
                      self._filename = _datafiles[self._hash]

       

       

            Unassigned Unassigned
            foobarbaz lizak
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:

                Version Package
                3.0