Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Re^2: XS callback to mpv_set_wakeup_callback

by dave_the_m (Monsignor)
on Jan 04, 2019 at 20:35 UTC ( #1228048=note: print w/replies, xml ) Need Help??


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

I'd suggest setting a breakpoint in callp() on the dMY_CXT line - so it stops just after dTHX has been executed. Then print out the value of my_perl at that point. I suspect on the first call it will be same as that passed to other functions, e.g. the
XS_MPV__Simple__xs_set_wakeup_callback (my_perl=0x555555953260, ...)
from your example above. I also suspect that on the second call it will be NULL (or at least not the same as the first call). If so it will confirm my diagnosis.

You'll need to explain to me how these extra threads are created and what they do, and what happens to the main perl thread while the callback is taking place - is it suspended, or is it off busily doing its own thing?

Dave.

Replies are listed 'Best First'.
Re^3: XS callback to mpv_set_wakeup_callback
by MaxPerl (Novice) on Jan 04, 2019 at 22:45 UTC
    Dear Dave, Yes, the perl interpreters are not the same:
    # perl thread (gdb) p my_perl $1 = (PerlInterpreter *) 0xb (...) dSP; (gdb) p my_perl $7 = (PerlInterpreter *) 0x555555953260 (...) # mpv thread: (gdb) p my_perl $2 = (PerlInterpreter *) 0x1 (...) dSP; (gdb) p my_perl $3 = (PerlInterpreter *) 0x0
    Regarding the creation of the extra threads: To be honest, I don't know how they are created and what with the main perl thread happens. This is managed by libmpv and I don't know and understand the code very well.. The error seems to occur in mpv_initialize from mpv-player/client.c, which calls mp_wakeup_core from mpv-player/playloop.c that triggers an event from the saved dispatch queue. mpv/misc/dispatch.c describes it as follows: "A dispatch queue lets other threads run callbacks in a target thread. The target thread is the thread which calls mp_dispatch_queue_process()." This is here mp_dispatch_interrupt which was called in mp_wakeup_core. So I think the target thread is created in mpv_create with the following lines:
    pthread_t thread; if (pthread_create(&thread, NULL, core_thread, mpctx) != 0) { ctx->clients->have_terminator = true; // avoid blocking mpv_terminate_destroy(ctx); mp_destroy(mpctx); return NULL; }
    core_thread() consists of the following lines:
    static void *core_thread(void *p) { struct MPContext *mpctx = p; mpthread_set_name("mpv core"); while (!mpctx->initialized && mpctx->stop_play != PT_QUIT) mp_idle(mpctx); if (mpctx->initialized) mp_play_files(mpctx); // This actually waits until all clients are gone before actually // destroying mpctx. Actual destruction is done by whatever destro +ys // the last mpv_handle. mp_shutdown_clients(mpctx); return NULL; }
    I don't know whether this is helpful. Perhaps the task is a number too big for me because I have no clue regarding threads? Nevertheless thank you very much for all your help and patience..

    Max

    PS.: I also don't understand why one time the callback is called in the perl thread? Perhaps because of the following line in mpv_set_wakeup_callback?
    if (ctx->wakeup_cb) ctx->wakeup_cb(ctx->wakeup_cb_ctx);
Re^3: XS callback to mpv_set_wakeup_callback
by MaxPerl (Novice) on Jan 05, 2019 at 06:49 UTC
    Ok, I found https://www.perlmonks.org/?node_id=867652 and without studying the thread in depth, I tried the following and it seems to work so far:
    first ein deleteted #define PERL_NO_GET_CONTEXT in the beginning of my Simple.xs file. then I created a set_ctx function
    void set_ctx() { dTHX; mine = Perl_get_context; } (...) MODULE = MPV::Simple PACKAGE = MPV::Simple (...) void _set_context(ctx) MPV::Simple ctx CODE: set_ctx();
    and called it in the constructor in Simple.pm
    sub new { my ($class) = shift; my $obj = $class->xs_create(); bless $obj; $obj->_set_context(); return $obj; }
    Last I set Perl_Context in callp instead of dTHX:
    void callp( SV* data) { //dTHX; PERL_SET_CONTEXT(mine); { dMY_CXT; dSP; ENTER; SAVETMPS; PUSHMARK(SP); PUTBACK; perl_call_sv(MY_CXT.callback,G_DISCARD|G_NOARGS); SPAGAIN; PUTBACK;FREETMPS;LEAVE; } }
    Does that make any sense? Or is it ugly and bad? To be honest, I don't really understand the linked thread :-) And I am a little bit worried because of deleting PERL_NO_GET_CONTEXT.. I have to do further tests, whether the binding now works as expected.. Max

    UPDATE: Unfortunately this doesn't work, too. Sometimes it seems to work and the video is shown. But in other cases there is again the segfault or the thread is closed too early.
Re^3: XS callback to mpv_set_wakeup_callback
by MaxPerl (Novice) on Jan 08, 2019 at 22:12 UTC
    Hello everybody, I have given up to create the binding to mpv_set_wakeup_callback. Instead I created an own pure Perl Module, which makes it possible to run the mpv player in another fork process beside an existing event loop (see MPV::Simple::Pipe on github). It is not perfect, but the best that I can. Nevertheless I wanted to explain my status quo. Perhaps it is useful for some other person, who is confronted with the same problem and is more experienced to solve the problem. The explained segfault problem could be solved by cloning perl in the c callback handler as follows (@dave_the_m: I think this is the solution you explained, but I didn't understand first?):
    void callp( ) { int new_perl = 0; dTHX; if ( my_perl != NULL ) printf ("my_perl == %ul\n", my_perl); else { printf ("my_perl was NULL\n"); PERL_SET_CONTEXT(mine); perl_for_cb = perl_clone(mine, CLONEf_KEEP_PTR_TABLE); PERL_SET_CONTEXT(perl_for_cb); // The following seems not necessary //CLONE_PARAMS clone_param; clone_param.stashes = NULL; clone +_param.flags = 0; clone_param.proto_perl = perl_for_cb; new_perl = 1; } dSP; SV* callback = get_sv("MPV::Simple::callback",0); SV* data = get_sv("MPV::Simple::callback_data",0); ENTER; SAVETMPS; PUSHMARK(SP); EXTEND(SP,1); PUSHs(sv_2mortal(newSVsv(data))); PUTBACK; perl_call_sv(callback,G_DISCARD); SPAGAIN; PUTBACK;FREETMPS;LEAVE; // I don't know whether this is important if ( new_perl ) { perl_free(my_perl); PERL_SET_CONTEXT(mine); } }
    The callback data and the reference to the perl function was saved in a global perl variable in Simple.pm, before the xs function is called (this was similarly effectiv as the complex MY_CTX stuff). The XS function is:
    void _xs_set_wakeup_callback(MPV::Simple ctx, SV* callback) CODE: { void (*callp_ptr)(void*); callp_ptr = callp; mpv_set_wakeup_callback(ctx,callp_ptr,NULL); }
    Unfortunately in more complex examples there are sometimes new segfaults, sometimes it works smoothly. I think the problem is the complex structure of the thread for event handling in mpv. If someone has a further idea to implement this all in perl, it would be great. I am still interested in learning and understanding XS more... Thanks all, Max
      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.

        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
        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 }

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://1228048]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others lurking in the Monastery: (3)
As of 2019-05-22 04:33 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Do you enjoy 3D movies?



    Results (138 votes). Check out past polls.

    Notices?
    • (Sep 10, 2018 at 22:53 UTC) Welcome new users!