-
Bug
-
Resolution: Unresolved
-
Normal
-
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]