Mark Glines
2005-12-12 22:10:14 UTC
Hi!
I've got a question which is halfway between perlxs and perlembed. I'm
hoping you guys can help me. :)
I wrote an XS wrapper for FUSE (http://fuse.sf.net) a couple years ago.
You "use Fuse;" in your perlscript, and call Fuse::main(). It then
maps filesystem calls from a C library (libfuse) into calls to Perl sub
refs, with call_sv(). For a long time, the whole thing has been
single-threaded, because it was unclear how to call perl from multiple
threads concurrently. (Back in the days of perl 5.6.1, I couldn't find
anything in the docs about this.)
Recently, I noticed a blurb in perlguts which said all I had to do was
do PERL_SET_CONTEXT() and everything would be happy. (This perlguts
entry seems unclear as to whether this will work for concurrent calls,
or just for the occasional call on its own.) So I tried it. And
everything does work, if I put a lock around the whole thing. It
crashes horribly if I call into it multiple times concurrently.
So, I did a little more research. It looks like I have to call
perl_clone(), but that crashes when I call into it concurrently, too.
And this time I have an additional problem: none of the arguments get
passed down to the callback sub!
I'm obviously doing something wrong, and I have no idea how to debug
this. I've tried boiling things down, I've got a test project which
just manages a single callback, with a single argument. Here's what
happens when I run it under valgrind (it shows a lost arg, followed by a
crash):
calling test_threads
interpreter cached (master)
Got to callback! Argument = 4658
perl_clone -> 0452c530
Got to callback! Argument =
perl_clone -> 04df9708
==4658== Thread 3:
==4658== Invalid read of size 4
==4658== at 0x80A02B5: Perl_pad_push (in /usr/bin/perl5.8.7)
==4658== by 0x80CD041: Perl_pp_entersub (in /usr/bin/perl5.8.7)
==4658== by 0x806152E: (within /usr/bin/perl5.8.7)
==4658== by 0x80648F2: Perl_call_sv (in /usr/bin/perl5.8.7)
==4658== by 0x404261C: test_callback (in
/home/paranoid/workspace/ithreads-test/Threadtest/blib/arch/auto/Threadtest/Threadtest.so)
==4658== by 0x404228C: do_something (in
/home/paranoid/workspace/ithreads-test/Threadtest/blib/arch/auto/Threadtest/Threadtest.so)
==4658== by 0x404A37F: start_thread (in /lib/tls/libpthread-2.3.5.so)
==4658== by 0x417ED1D: clone (in /lib/tls/libc-2.3.5.so)
==4658== Address 0x0 is not stack'd, malloc'd or (recently) free'd
==4658==
==4658== Process terminating with default action of signal 11 (SIGSEGV)
Unfortunately, this tree is still in several files. I'm pasting the XS
file and the test.pl script into this email; you can find the rest of
the tree at http://glines.org/bin/ithreads-test.tar.gz if needed.
Thanks!
Mark
------ begin Threadtest.xs ------
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#include "const-c.inc"
/* blatant linuxism for test purposes */
#include <linux/unistd.h>
#include <errno.h>
_syscall0(pid_t,gettid);
/* emulate FUSE; a simple C pthread thing to test concurrency */
#define NUM_THREADS 20
void *do_something(void *arg) {
int tid = gettid();
int rv = tid;
int (*func)(int) = arg;
rv = func(tid);
return NULL;
}
int test_threads(int (*funcptr)(int)) {
pthread_t threads[NUM_THREADS];
int i;
do_something(funcptr);
for(i = 0; i < NUM_THREADS; i++)
pthread_create(&threads[i], NULL, &do_something, funcptr);
do_something(funcptr);
for(i = 0; i < NUM_THREADS; i++)
pthread_join(threads[i], NULL);
}
/* this is /usr/bin/perl's PerlInterpreter, we clone this for new threads */
PerlInterpreter *master_interp = NULL;
/* thread-local storage key to clone PerlInterpreters as necessary*/
pthread_key_t test_interp_key;
/* set up our PerlInterpreter state */
static inline void setup_perl_context() {
if(master_interp) {
PerlInterpreter *me = pthread_getspecific(test_interp_key);
if(!me) {
PERL_SET_CONTEXT(master_interp);
me = perl_clone(master_interp, CLONEf_KEEP_PTR_TABLE);
pthread_setspecific(test_interp_key,me);
//PERL_SET_CONTEXT(me);
fprintf(stderr,"perl_clone -> %08lx\n",(long)me);
} else {
fprintf(stderr,"interpreter cached (%s)\n",
me == master_interp ? "master" : "slave");
}
}
}
/* free our PerlInterpreter when the thread exits */
static void destroy_perl_context(void *ptr) {
PerlInterpreter *ctx = ptr;
if(ctx && (ctx != master_interp)) {
perl_destruct(ctx);
perl_free(ctx);
fprintf(stderr,"perl_free\n");
}
}
/* storage for the callback sub-reference */
static SV *test_callback_SV;
int test_callback(int tid) {
int rv;
setup_perl_context();
{
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs(sv_2mortal(newSViv(tid)));
PUTBACK;
rv = call_sv(test_callback_SV,G_SCALAR);
SPAGAIN;
if(rv)
rv = POPi;
else
rv = 0;
FREETMPS;
LEAVE;
PUTBACK;
}
return rv;
}
MODULE = Threadtest PACKAGE = Threadtest
PROTOTYPES: DISABLE
INCLUDE: const-xs.inc
void
test_threads(...)
CODE:
test_callback_SV = ST(0);
/* save off the interpreter which we'll clone later on */
master_interp = PERL_GET_INTERP;
/* setup the TLS key, so new threads can figure themselves out */
pthread_key_create(&test_interp_key, destroy_perl_context);
/* the primary thread uses the primary perl interpreter */
pthread_setspecific(test_interp_key, master_interp);
/* this is where FUSE used to get called; we usually segfault here. */
test_threads(&test_callback);
/* cleanup */
pthread_key_delete(test_interp_key);
------ end Threadtest.xs ------
------ begin test.pl ------
#!/usr/bin/perl
push(@INC,'blib/arch');
push(@INC,'blib/lib');
require Threadtest;
sub cb {
my $arg = shift;
print(STDERR "Got to callback! Argument = $arg\n");
# this sleep makes the crash happen almost every time
select(undef,undef,undef,0.1);
return $arg;
}
print("calling test_threads\n");
Threadtest::test_threads(\&cb);
print("done\n");
------ end test.pl ------
I've got a question which is halfway between perlxs and perlembed. I'm
hoping you guys can help me. :)
I wrote an XS wrapper for FUSE (http://fuse.sf.net) a couple years ago.
You "use Fuse;" in your perlscript, and call Fuse::main(). It then
maps filesystem calls from a C library (libfuse) into calls to Perl sub
refs, with call_sv(). For a long time, the whole thing has been
single-threaded, because it was unclear how to call perl from multiple
threads concurrently. (Back in the days of perl 5.6.1, I couldn't find
anything in the docs about this.)
Recently, I noticed a blurb in perlguts which said all I had to do was
do PERL_SET_CONTEXT() and everything would be happy. (This perlguts
entry seems unclear as to whether this will work for concurrent calls,
or just for the occasional call on its own.) So I tried it. And
everything does work, if I put a lock around the whole thing. It
crashes horribly if I call into it multiple times concurrently.
So, I did a little more research. It looks like I have to call
perl_clone(), but that crashes when I call into it concurrently, too.
And this time I have an additional problem: none of the arguments get
passed down to the callback sub!
I'm obviously doing something wrong, and I have no idea how to debug
this. I've tried boiling things down, I've got a test project which
just manages a single callback, with a single argument. Here's what
happens when I run it under valgrind (it shows a lost arg, followed by a
crash):
calling test_threads
interpreter cached (master)
Got to callback! Argument = 4658
perl_clone -> 0452c530
Got to callback! Argument =
perl_clone -> 04df9708
==4658== Thread 3:
==4658== Invalid read of size 4
==4658== at 0x80A02B5: Perl_pad_push (in /usr/bin/perl5.8.7)
==4658== by 0x80CD041: Perl_pp_entersub (in /usr/bin/perl5.8.7)
==4658== by 0x806152E: (within /usr/bin/perl5.8.7)
==4658== by 0x80648F2: Perl_call_sv (in /usr/bin/perl5.8.7)
==4658== by 0x404261C: test_callback (in
/home/paranoid/workspace/ithreads-test/Threadtest/blib/arch/auto/Threadtest/Threadtest.so)
==4658== by 0x404228C: do_something (in
/home/paranoid/workspace/ithreads-test/Threadtest/blib/arch/auto/Threadtest/Threadtest.so)
==4658== by 0x404A37F: start_thread (in /lib/tls/libpthread-2.3.5.so)
==4658== by 0x417ED1D: clone (in /lib/tls/libc-2.3.5.so)
==4658== Address 0x0 is not stack'd, malloc'd or (recently) free'd
==4658==
==4658== Process terminating with default action of signal 11 (SIGSEGV)
Unfortunately, this tree is still in several files. I'm pasting the XS
file and the test.pl script into this email; you can find the rest of
the tree at http://glines.org/bin/ithreads-test.tar.gz if needed.
Thanks!
Mark
------ begin Threadtest.xs ------
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#include "const-c.inc"
/* blatant linuxism for test purposes */
#include <linux/unistd.h>
#include <errno.h>
_syscall0(pid_t,gettid);
/* emulate FUSE; a simple C pthread thing to test concurrency */
#define NUM_THREADS 20
void *do_something(void *arg) {
int tid = gettid();
int rv = tid;
int (*func)(int) = arg;
rv = func(tid);
return NULL;
}
int test_threads(int (*funcptr)(int)) {
pthread_t threads[NUM_THREADS];
int i;
do_something(funcptr);
for(i = 0; i < NUM_THREADS; i++)
pthread_create(&threads[i], NULL, &do_something, funcptr);
do_something(funcptr);
for(i = 0; i < NUM_THREADS; i++)
pthread_join(threads[i], NULL);
}
/* this is /usr/bin/perl's PerlInterpreter, we clone this for new threads */
PerlInterpreter *master_interp = NULL;
/* thread-local storage key to clone PerlInterpreters as necessary*/
pthread_key_t test_interp_key;
/* set up our PerlInterpreter state */
static inline void setup_perl_context() {
if(master_interp) {
PerlInterpreter *me = pthread_getspecific(test_interp_key);
if(!me) {
PERL_SET_CONTEXT(master_interp);
me = perl_clone(master_interp, CLONEf_KEEP_PTR_TABLE);
pthread_setspecific(test_interp_key,me);
//PERL_SET_CONTEXT(me);
fprintf(stderr,"perl_clone -> %08lx\n",(long)me);
} else {
fprintf(stderr,"interpreter cached (%s)\n",
me == master_interp ? "master" : "slave");
}
}
}
/* free our PerlInterpreter when the thread exits */
static void destroy_perl_context(void *ptr) {
PerlInterpreter *ctx = ptr;
if(ctx && (ctx != master_interp)) {
perl_destruct(ctx);
perl_free(ctx);
fprintf(stderr,"perl_free\n");
}
}
/* storage for the callback sub-reference */
static SV *test_callback_SV;
int test_callback(int tid) {
int rv;
setup_perl_context();
{
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs(sv_2mortal(newSViv(tid)));
PUTBACK;
rv = call_sv(test_callback_SV,G_SCALAR);
SPAGAIN;
if(rv)
rv = POPi;
else
rv = 0;
FREETMPS;
LEAVE;
PUTBACK;
}
return rv;
}
MODULE = Threadtest PACKAGE = Threadtest
PROTOTYPES: DISABLE
INCLUDE: const-xs.inc
void
test_threads(...)
CODE:
test_callback_SV = ST(0);
/* save off the interpreter which we'll clone later on */
master_interp = PERL_GET_INTERP;
/* setup the TLS key, so new threads can figure themselves out */
pthread_key_create(&test_interp_key, destroy_perl_context);
/* the primary thread uses the primary perl interpreter */
pthread_setspecific(test_interp_key, master_interp);
/* this is where FUSE used to get called; we usually segfault here. */
test_threads(&test_callback);
/* cleanup */
pthread_key_delete(test_interp_key);
------ end Threadtest.xs ------
------ begin test.pl ------
#!/usr/bin/perl
push(@INC,'blib/arch');
push(@INC,'blib/lib');
require Threadtest;
sub cb {
my $arg = shift;
print(STDERR "Got to callback! Argument = $arg\n");
# this sleep makes the crash happen almost every time
select(undef,undef,undef,0.1);
return $arg;
}
print("calling test_threads\n");
Threadtest::test_threads(\&cb);
print("done\n");
------ end test.pl ------