http://www.perlmonks.org?node_id=1228254


in reply to Re^3: XS callback to mpv_set_wakeup_callback
in thread XS callback to mpv_set_wakeup_callback

The explained segfault problem could be solved by cloning perl in the c callback handler as follows
Not really. The problem is that the interpreter you're trying to clone is owned by another thread. That thread might be actively executing, causing the state of that interpreter structure (and all the data hanging off it) to be changing at the same time that a second thread is trying to clone it.

It all depends on what the original perl thread is doing. If it just calls a 'start event loop' function in mpv which suspends the thread and lets mpv's own threads take over to do all the active work and callbacks, then you could just set my_perl to point to the original perl's interpeter and let the mpv thread update that. If multiple mpv threads can call callbacks simultaneously, then you'd need either to have a lock so that only one thread can use the interpreter at a time, or you'd need to create enough perl threads such that there is a big enough pool of interpreters to be borrowed by all active mpv threads. Of course with multiple interpreters you'd have the issue of only one interprter getting updated, not necessarily the one you want.

Or you could have some sort of queue system whereby mpv callbacks just put requests into queue which are popped and processed by one or more perl threads.

But without having some details of what the mpv library does and how, this is mainly speculation.

Dave.

  • Comment on Re^4: XS callback to mpv_set_wakeup_callback

Replies are listed 'Best First'.
Re^5: XS callback to mpv_set_wakeup_callback
by MaxPerl (Novice) on Jan 18, 2019 at 12:05 UTC
    Dear Monks, I had a conversation with the developer of mpv. He confirmed that the wakeup callback can be called from any internal mpv thread (which mpv may create and destroy as it pleases). Because Scripting languages often cannot be called safely from foreign threads, it seems not really possible to implement set_wakeup_callback in perl. Instead he suggested that the callback (&callp()) just writes into a wakeup pipe. A similar solution I found on some perl specific posts, too: see for example the last answer of NERDVANA here, the answer of Eric Garland here and I think the Perl SDL bindings discussed also a such solution, see the old discussion here). Unfortunately in the SDL bindings I couldn't find whether they really implemented a pipe. Instead they use a perl_clone way, too. So my question: Could somebody tell me, how I can use a pipe to perl or a shared variable in a C thread, which I didn't created. Or does somebody know a example of a XS module which takes this approach? Thanks for all your help, Max
Re^5: XS callback to mpv_set_wakeup_callback
by MaxPerl (Novice) on Jan 09, 2019 at 19:15 UTC
    Dear Dave,
    Thank you for your helpful answers. Indeed I think that the perl thread starts an event loop function in mpv which lets mpv's own threads take over to do all the active work and callbacks regarding playing medias. At the same time multiple mpv threads can absolutely call callbacks simultaneously, so locking is surely an important point. Where can I find informations on this topic in XS? Furthermore the perl thread isn't suspended, while mpv renders the video etc, but must be responsive for events for example from the GUI part of an application.
    The API is mostly described here: libmpv is a media player (the successor of mplayer). In General it is necessary to run mpv in an event loop. This is done with the mpv_wait_event function, whereby only one thread is allowed to call this on the same mpv_handle at a time. The mpv_set_wakeup_callback method is designed to make it possible to run mpv with existing event loops (especially from GUIs etc).
    The description describes it as follows: "It sets a custom function that should be called when there are new events. The callback is meant strictly for notification only, and is called from arbitrary core parts of the player, that make no considerations for reentrant API use or allowing the callee to spend a lot of time doing other things. It's also possible that the callback is called from a thread while a mpv API function is called (i.e. it can be reentrant). If you actually want to do processing in a callback, spawn a thread that does nothing but call mpv_wait_event() in a loop and dispatches the result to a callback."
    The mpv library starts one thread for event handling (I think this is the mpv core thread), since MPV sends events that must be processed quickly. In my understanding the main perl thread is only a thinn wrapper to the c functions and is especially important for the GUI stuff etc. The MPV threads seem to be created and administered independently from the perl Thread.
    The description of the MPV-API states also the following: "The client API is generally fully thread-safe, unless otherwise noted. Currently, there is no real advantage in using more than 1 thread to access the client API, since everything is serialized through a single lock in the playback core."
    Sorry, that I cannot give more informations. The topic threads is new for me (which is why I tried a pure perl workaround in MPV::Simple::Pipe).. For this very reason thank you very much for your understanding, patience and help,
    Max

    PS.: Here is a test application ("./einladung2.mp4" is a video file located in the same directory as the script). Everything seems to work fine, if I initialize the MPV handler before setting the properties. If I set the property before the initialization (as partly in the C example here) I get a segfault. Furthermore I would expect that there are more events (everytime I hit the Restart button, there should be an event occur. (But perhaps the thread writes to a different STDOUT?):
    use Tcl::Tk; use MPV::Simple; my $int =Tcl::Tk->new(); my $mw = $int->mainwindow(); # Bind MPV Event to the event handler $mw->bind('<<MPV>>' => \&on_mpv); my $f = $mw->Frame(-background => "green",-width => 640, -height => 48 +0)->pack(-expand =>1,-fill => "both"); my $id; $id = $f->id(); # MPV part $MPV::Simple::callback_data=$f; my $ctx = MPV::Simple->new(); $ctx->initialize(); $ctx->set_wakeup_callback(\&wakeup_callback, $f); $ctx->set_property_string("wid",$id); $ctx->set_property_string('input-default-bindings','yes'); $ctx->set_property_string('input-vo-keyboard','yes'); $ctx->set_property_string('osc','yes'); $ctx->command("loadfile", "./einladung2.mp4"); $mw->Button(-text => "Restart", -command => sub {$ctx->command("loadfi +le", "./einladung2.mp4")})->pack; $int->MainLoop(); # Wake up callback sub wakeup_callback { my $frame = shift; print "IN WAKEUP CALLBACK\n";$int->Eval("event +generate $frame <<MPV>> -when head"); } # Event handler sub on_mpv { while (1) { my $event = $ctx->wait_event(0) or die "SHit\n"; my $eid = $event->id; print "EVENT FIRED $MPV::Simple::event_names[eid]\n"; if ($eid == 0) { last; } } return }