in reply to XS callback to mpv_set_wakeup_callback

Dear bliako, dear Dave, Thank you very much for your hints. I traced to my test script and - to be honest - I don't understand very much :-) But, I think Dave is right. The callp function is hited two times, the first time by perl, and the second time by a mpv/mpv core thread. The passed args are the same, so that I think, that the assignment from incompatible pointer type warnings are at the moment less important.. I will read the perlembed manual, but it is really a steep learning curve, hopefully not too steep... PS.: Here the output of gdb:
(gdb) run Starting program: /usr/bin/perl ./play2.t [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so +.1". [New Thread 0x7fffc9dca700 (LWP 7239)] callback called Thread 2 "mpv/mpv core" received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x7fffc9dca700 (LWP 7239)] 0x00007ffff6ab126d in callp (string=0x5555559756b0) at Simple.xs:30 30 dMY_CXT; (gdb) break Simple.xs:27 Haltepunkt 1 at 0x7ffff6ab1255: file Simple.xs, line 27. (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) x Please answer y or n. The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /usr/bin/perl ./play2.t [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so +.1". [New Thread 0x7fffc9dca700 (LWP 7241)] Thread 1 "perl" hit Breakpoint 1, callp (string=0x5555559756b0) at Sim +ple.xs:29 29 dTHX; (gdb) s __GI___pthread_getspecific (key=0) at pthread_getspecific.c:30 30 pthread_getspecific.c: Datei oder Verzeichnis nicht gefunden. (gdb) s 31 in pthread_getspecific.c (gdb) s 55 in pthread_getspecific.c (gdb) s 56 in pthread_getspecific.c (gdb) s 60 in pthread_getspecific.c (gdb) s 65 in pthread_getspecific.c (gdb) s callp (string=0x5555559756b0) at Simple.xs:30 30 dMY_CXT; (gdb) s 31 dSP; (gdb) s 33 ENTER; SAVETMPS; (gdb) s 34 PUSHMARK(SP); (gdb) s 36 PUTBACK; (gdb) s 38 perl_call_sv(MY_CXT.callback,G_DISCARD|G_NOARGS); (gdb) s callback called 39 SPAGAIN; (gdb) s 41 PUTBACK;FREETMPS;LEAVE; (gdb) s 43 } (gdb) s 0x00007ffff67708aa in mpv_set_wakeup_callback () from /usr/lib/x86_64- +linux-gnu/libmpv.so.1 (gdb) s Single stepping until exit from function mpv_set_wakeup_callback, which has no line number information. __GI___pthread_mutex_unlock (mutex=0x7fffc4018a08) at pthread_mutex_un +lock.c:345 345 pthread_mutex_unlock.c: Datei oder Verzeichnis nicht gefunden. (gdb) s __pthread_mutex_unlock_usercnt (decr=1, mutex=0x7fffc4018a08) at pthre +ad_mutex_unlock.c:344 344 in pthread_mutex_unlock.c (gdb) s 345 in pthread_mutex_unlock.c (gdb) s __pthread_mutex_unlock_usercnt (decr=1, mutex=0x7fffc4018a08) at pthre +ad_mutex_unlock.c:38 38 in pthread_mutex_unlock.c (gdb) s 39 in pthread_mutex_unlock.c (gdb) s 38 in pthread_mutex_unlock.c (gdb) s 39 in pthread_mutex_unlock.c (gdb) s 43 in pthread_mutex_unlock.c (gdb) s 48 in pthread_mutex_unlock.c (gdb) s 51 in pthread_mutex_unlock.c (gdb) s 54 in pthread_mutex_unlock.c (gdb) s 56 in pthread_mutex_unlock.c (gdb) s __GI___pthread_mutex_unlock (mutex=0x7fffc4018a08) at pthread_mutex_un +lock.c:346 346 in pthread_mutex_unlock.c (gdb) s XS_MPV__Simple__xs_set_wakeup_callback (my_perl=0x555555953260, cv=0x5 +55555aae830) at Simple.c:524 524 XSRETURN_EMPTY; (gdb) s 525 } (gdb) s 0x0000555555630201 in Perl_pp_entersub () (gdb) s Single stepping until exit from function Perl_pp_entersub, which has no line number information. 0x0000555555628026 in Perl_runops_standard () (gdb) s Single stepping until exit from function Perl_runops_standard, which has no line number information. [Switching to Thread 0x7fffc9dca700 (LWP 7241)] Thread 2 "mpv/mpv core" hit Breakpoint 1, callp (string=0x5555559756b0 +) at Simple.xs:29 29 dTHX; (gdb) s __GI___pthread_getspecific (key=0) at pthread_getspecific.c:30 30 pthread_getspecific.c: Datei oder Verzeichnis nicht gefunden. (gdb) s 31 in pthread_getspecific.c (gdb) s 55 in pthread_getspecific.c (gdb) s 56 in pthread_getspecific.c (gdb) s 65 in pthread_getspecific.c (gdb) s callp (string=0x5555559756b0) at Simple.xs:30 30 dMY_CXT; (gdb) s Thread 2 "mpv/mpv core" received signal SIGSEGV, Segmentation fault. 0x00007ffff6ab126d in callp (string=0x5555559756b0) at Simple.xs:30 30 dMY_CXT;

Replies are listed 'Best First'.
Re^2: XS callback to mpv_set_wakeup_callback
by dave_the_m (Monsignor) on Jan 04, 2019 at 20:35 UTC
    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.

      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);
      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.
      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.