python-vlc
python-vlc copied to clipboard
Segfault - how can I best debug this?
I get intermittent segfaults but I'm not certain what's causing them. There's an example bt below. I don't know whether this is a python-vlc problem or a me problem.
What's the best way of going about debugging this?
python: ../nptl/pthread_mutex_lock.c:428: __pthread_mutex_lock_full: Assertion `e != ESRCH || !robust' failed.
Thread 1 "python" received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007ffff7c48537 in __GI_abort () at abort.c:79
#2 0x00007ffff7c4840f in __assert_fail_base (fmt=0x7ffff7db1128 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n", assertion=0x7ffff7f9c617 "e != ESRCH || !robust", file=0x7ffff7f9c5fa "../nptl/pthread_mutex_lock.c", line=428, function=<optimized out>) at assert.c:92
#3 0x00007ffff7c57662 in __GI___assert_fail (assertion=assertion@entry=0x7ffff7f9c617 "e != ESRCH || !robust", file=file@entry=0x7ffff7f9c5fa "../nptl/pthread_mutex_lock.c", line=line@entry=428, function=function@entry=0x7ffff7f9c770 <__PRETTY_FUNCTION__.1> "__pthread_mutex_lock_full") at assert.c:101
#4 0x00007ffff7f905d4 in __pthread_mutex_lock_full (mutex=0x1c19e00) at ../nptl/pthread_mutex_lock.c:428
#5 0x00007ffff194e536 in input_resource_Terminate () at /lib/x86_64-linux-gnu/libvlccore.so.9
#6 0x00007ffff1a08ec3 in libvlc_media_player_stop () at /lib/x86_64-linux-gnu/libvlc.so.5
#7 0x00007ffff1b85d1d in () at /lib/x86_64-linux-gnu/libffi.so.7
#8 0x00007ffff1b85289 in () at /lib/x86_64-linux-gnu/libffi.so.7
#9 0x00007ffff7679360 in () at /usr/lib/python3.9/lib-dynload/_ctypes.cpython-39-x86_64-linux-gnu.so
#10 0x00007ffff766f8d8 in () at /usr/lib/python3.9/lib-dynload/_ctypes.cpython-39-x86_64-linux-gnu.so
#11 0x000000000051d89b in _PyObject_MakeTpCall ()
#12 0x00000000005170e4 in _PyEval_EvalFrameDefault ()
#13 0x0000000000528b63 in _PyFunction_Vectorcall ()
#14 0x0000000000511fb5 in _PyEval_EvalFrameDefault ()
#15 0x0000000000528b63 in _PyFunction_Vectorcall ()
#16 0x0000000000512192 in _PyEval_EvalFrameDefault ()
#17 0x00000000005106ed in ()
#18 0x0000000000528d21 in _PyFunction_Vectorcall ()
#19 0x0000000000512192 in _PyEval_EvalFrameDefault ()
#20 0x0000000000528b63 in _PyFunction_Vectorcall ()
#21 0x000000000053bdb1 in ()
[...]
Python 3.9.2 python-vlc==3.0.11115
This is tricky to debug, because it is (a) certainly unrelated to python-vlc
itself and (b) deeper inside the libvlc
library.
As an initial suggestion, double check the comments of the python-vlc
functions or methods used in your Python. Several of those are not thread-safe and if used, that may be the root cause.
If that does not clear anything up, debugging libvlc
may be the only way to get to the bottom of this.
It may be related to the bindings, if the ctypes definition is wrong and passes a wrong parameter type/size to the underlying lib. The first thing to debug this is to manage to reproducibly exhibit the problem in a minimal test code. With the stack trace only, we can only speculate. For instance, I see that the libvlc_media_player_stop is involved, so it may well be that you have a MediaPlayerStopped event handler registered, which may cause the issue?
I have no event handlers registered. I'll include the module that manages VLC below. The locks in that code are designed to protect the non-thread-safe parts of VLC - some of the functions (eg, get_position()
) are called from a timer asynchronously.
I've not been able to consistently reproduce the problem, but I'll try again later.
import os
import threading
import vlc
from config import Config
from datetime import datetime
from time import sleep
lock = threading.Lock()
class Music:
"""
Manage the playing of music tracks
"""
def __init__(self):
self.current_track_start_time = None
self.fading = 0
self.VLC = vlc.Instance()
self.player = None
self.track_path = None
self.max_volume = Config.VOLUME_VLC_DEFAULT
def fade(self):
"""
Fade the currently playing track.
The actual management of fading runs in its own thread so as not
to hold up the UI during the fade.
"""
with lock:
if not self.player:
return
if not self.player.get_position() > 0 and self.player.is_playing():
return
self.fading += 1
thread = threading.Thread(target=self._fade)
thread.start()
def _fade(self):
"""
Implementation of fading the current track in a separate thread.
"""
# Take a copy of current player to allow another track to be
# started without interfering here
p = self.player
fade_time = Config.FADE_TIME / 1000
steps = Config.FADE_STEPS
sleep_time = fade_time / steps
# We reduce volume by one mesure first, then by two measures,
# then three, and so on.
# The sum of the arithmetic sequence 1, 2, 3, ..n is
# (n**2 + n) / 2
total_measures_count = (steps**2 + steps) / 2
measures_to_reduce_by = 0
for i in range(1, steps + 1):
measures_to_reduce_by += i
volume_factor = 1 - (
measures_to_reduce_by / total_measures_count)
p.audio_set_volume(int(self.max_volume * volume_factor))
sleep(sleep_time)
self.stop(p)
self.fading -= 1
def get_playtime(self):
"Return elapsed play time"
with lock:
if not self.player:
return None
return self.player.get_time()
def get_position(self):
"Return current position"
with lock:
return self.player.get_position()
def play(self, path):
"""
Start playing the track at path.
Log and return if path not found.
"""
if not os.access(path, os.R_OK):
print(f"play({path}): path not found")
return
self.track_path = path
self.player = self.VLC.media_player_new(path)
self.player.audio_set_volume(self.max_volume)
self.player.play()
self.current_track_start_time = datetime.now()
def playing(self):
"""
Return True if currently playing a track, else False
vlc.is_playing() returns True if track was faded out.
get_position seems more reliable.
"""
with lock:
if self.player:
if self.player.get_position() > 0 and self.player.is_playing():
return True
# We take a copy of the player when fading, so we could be
# playing in a fade nowFalse
return self.fading > 0
def set_position(self, ms):
"Set current play time in milliseconds from start"
with lock:
return self.player.set_time(ms)
def set_volume(self, volume):
"Set maximum volume used for player"
with lock:
if not self.player:
return
self.max_volume = volume
self.player.audio_set_volume(volume)
def stop(self, player=None):
"Immediately stop playing"
with lock:
if not player:
if not self.player:
return
player = self.player
position = player.get_position()
player.stop()
player.release()
# Ensure we don't reference player after release
player = None
return position
Fading is done from a separate thread and that thread does call libvlc_media_player_stop
via self.stop(p)
and player.stop()
. Try running without fading to see if the segfault occurs then.
Also, try with the following changes to isolate the fading player entirely from the Music instance before fading starts.
def fade(self):
...
self.fading += 1
p = self.player # add
self.player = None # add
thread = threading.Thread(target=self._fade, args=(p,)) # change
...
def _fade(self, p):
...
# p = self.player remove
....
self.stop(p)
with lock: # add
self.fading -= 1
Thanks @mrJean1 It certainly works calling from a different thread, but I take your point that this could be the problem. Certainly when I've experienced the segfault, there has often (maybe always) been fading in progress. Maybe I'll try fading in a subprocess rather than a thread.
One other suggestion.
def stop(self, ...):
...
with lock:
if not player:
if not self.player:
return
player = self.player
self.player = None # add
...
# player = None # remove
return position