In perl.git, the branch blead has been updated <https://perl5.git.perl.org/perl.git/commitdiff/6cc7638e57c54706dc2d698d9b2f9f769c17ffb4?hp=a3371546646b4c0f722f2659beac614649d6cfd5>
- Log ----------------------------------------------------------------- commit 6cc7638e57c54706dc2d698d9b2f9f769c17ffb4 Merge: a337154664 ee5287f665 Author: Lukas Mai <l....@web.de> Date: Sat Nov 11 11:19:05 2017 +0100 Merge wrap_keyword_plugin() into blead ----------------------------------------------------------------------- Summary of changes: MANIFEST | 1 + dosish.h | 7 +-- embed.fnc | 1 + embed.h | 1 + embedvar.h | 2 + ext/XS-APItest/APItest.xs | 4 +- ext/XS-APItest/t/keyword_plugin_threads.t | 32 ++++++++++++++ perl.c | 1 + perl.h | 12 +++++ perlapi.h | 2 + perlvars.h | 6 +++ pod/perldelta.pod | 9 ++++ proto.h | 3 ++ toke.c | 73 +++++++++++++++++++++++++++++++ unixish.h | 15 ++++--- 15 files changed, 157 insertions(+), 12 deletions(-) create mode 100644 ext/XS-APItest/t/keyword_plugin_threads.t diff --git a/MANIFEST b/MANIFEST index 7fcd227fae..7df52ed8c9 100644 --- a/MANIFEST +++ b/MANIFEST @@ -4325,6 +4325,7 @@ ext/XS-APItest/t/hash.t XS::APItest: tests for hash related APIs ext/XS-APItest/t/join_with_space.t test op_convert_list ext/XS-APItest/t/keyword_multiline.t test keyword plugin parsing across lines ext/XS-APItest/t/keyword_plugin.t test keyword plugin mechanism +ext/XS-APItest/t/keyword_plugin_threads.t test keyword plugin loading from multiple threads ext/XS-APItest/t/labelconst.aux auxiliary file for label test ext/XS-APItest/t/labelconst.t test recursive descent label parsing ext/XS-APItest/t/labelconst_utf8.aux auxiliary file for label test in UTF-8 diff --git a/dosish.h b/dosish.h index 9fd43ea682..16ee9b7359 100644 --- a/dosish.h +++ b/dosish.h @@ -48,9 +48,10 @@ #endif #ifndef PERL_SYS_TERM_BODY -# define PERL_SYS_TERM_BODY() \ - HINTS_REFCNT_TERM; OP_CHECK_MUTEX_TERM; \ - OP_REFCNT_TERM; PERLIO_TERM; MALLOC_TERM; LOCALE_TERM; +# define PERL_SYS_TERM_BODY() \ + HINTS_REFCNT_TERM; KEYWORD_PLUGIN_MUTEX_TERM; \ + OP_CHECK_MUTEX_TERM; OP_REFCNT_TERM; PERLIO_TERM; \ + MALLOC_TERM; LOCALE_TERM; #endif #define dXSUB_SYS dNOOP diff --git a/embed.fnc b/embed.fnc index be12f88024..6269498ec7 100644 --- a/embed.fnc +++ b/embed.fnc @@ -1185,6 +1185,7 @@ Apd |void |cv_set_call_checker_flags|NN CV *cv \ |NN Perl_call_checker ckfun \ |NN SV *ckobj|U32 ckflags Apd |void |wrap_op_checker|Optype opcode|NN Perl_check_t new_checker|NN Perl_check_t *old_checker_p +AMpd |void |wrap_keyword_plugin|NN Perl_keyword_plugin_t new_plugin|NN Perl_keyword_plugin_t *old_plugin_p ApR |PERL_SI*|new_stackinfo|I32 stitems|I32 cxitems Ap |char* |scan_vstring |NN const char *s|NN const char *const e \ |NN SV *sv diff --git a/embed.h b/embed.h index ea05c91343..46c59b5b4e 100644 --- a/embed.h +++ b/embed.h @@ -771,6 +771,7 @@ #define whichsig_pv(a) Perl_whichsig_pv(aTHX_ a) #define whichsig_pvn(a,b) Perl_whichsig_pvn(aTHX_ a,b) #define whichsig_sv(a) Perl_whichsig_sv(aTHX_ a) +#define wrap_keyword_plugin(a,b) Perl_wrap_keyword_plugin(aTHX_ a,b) #define wrap_op_checker(a,b,c) Perl_wrap_op_checker(aTHX_ a,b,c) #if !(defined(HAS_MEMMEM)) #define ninstr Perl_ninstr diff --git a/embedvar.h b/embedvar.h index 898b71cd31..1a146c4d54 100644 --- a/embedvar.h +++ b/embedvar.h @@ -401,6 +401,8 @@ #define PL_Ghints_mutex (my_vars->Ghints_mutex) #define PL_keyword_plugin (my_vars->Gkeyword_plugin) #define PL_Gkeyword_plugin (my_vars->Gkeyword_plugin) +#define PL_keyword_plugin_mutex (my_vars->Gkeyword_plugin_mutex) +#define PL_Gkeyword_plugin_mutex (my_vars->Gkeyword_plugin_mutex) #define PL_locale_mutex (my_vars->Glocale_mutex) #define PL_Glocale_mutex (my_vars->Glocale_mutex) #define PL_malloc_mutex (my_vars->Gmalloc_mutex) diff --git a/ext/XS-APItest/APItest.xs b/ext/XS-APItest/APItest.xs index ffdc56c38e..8bf1545b63 100644 --- a/ext/XS-APItest/APItest.xs +++ b/ext/XS-APItest/APItest.xs @@ -1242,6 +1242,7 @@ static int my_keyword_plugin(pTHX_ *op_ptr = parse_join_with_space(); return KEYWORD_PLUGIN_EXPR; } else { + assert(next_keyword_plugin != my_keyword_plugin); return next_keyword_plugin(aTHX_ keyword_ptr, keyword_len, op_ptr); } } @@ -3893,8 +3894,7 @@ BOOT: hintkey_DEFSV_sv = newSVpvs_share("XS::APItest/DEFSV"); hintkey_with_vars_sv = newSVpvs_share("XS::APItest/with_vars"); hintkey_join_with_space_sv = newSVpvs_share("XS::APItest/join_with_space"); - next_keyword_plugin = PL_keyword_plugin; - PL_keyword_plugin = my_keyword_plugin; + wrap_keyword_plugin(my_keyword_plugin, &next_keyword_plugin); } void diff --git a/ext/XS-APItest/t/keyword_plugin_threads.t b/ext/XS-APItest/t/keyword_plugin_threads.t new file mode 100644 index 0000000000..db23ce7d58 --- /dev/null +++ b/ext/XS-APItest/t/keyword_plugin_threads.t @@ -0,0 +1,32 @@ +#!perl +use strict; +use warnings; + +require '../../t/test.pl'; + +use Config; +if (!$Config{useithreads}) { + skip_all("keyword_plugin thread test requires threads"); +} + +plan(1); + +fresh_perl_is( <<'----', <<'====', {}, "loading XS::APItest in threads works"); +use strict; +use warnings; + +use threads; + +require '../../t/test.pl'; +watchdog(5); + +for my $t (1 .. 3) { + threads->create(sub { + require XS::APItest; + })->join; +} + +print "all is well\n"; +---- +all is well +==== diff --git a/perl.c b/perl.c index 454cc756ba..bf48b31493 100644 --- a/perl.c +++ b/perl.c @@ -92,6 +92,7 @@ S_init_tls_and_interp(PerlInterpreter *my_perl) PERL_SET_THX(my_perl); OP_REFCNT_INIT; OP_CHECK_MUTEX_INIT; + KEYWORD_PLUGIN_MUTEX_INIT; HINTS_REFCNT_INIT; LOCALE_INIT; MUTEX_INIT(&PL_dollarzero_mutex); diff --git a/perl.h b/perl.h index 631c4f0e29..23f209c013 100644 --- a/perl.h +++ b/perl.h @@ -5420,6 +5420,18 @@ typedef struct am_table_short AMTS; #define PERLDB_LINE_OR_SAVESRC (PL_perldb & (PERLDBf_LINE | PERLDBf_SAVESRC)) +#ifdef USE_ITHREADS +# define KEYWORD_PLUGIN_MUTEX_INIT MUTEX_INIT(&PL_keyword_plugin_mutex) +# define KEYWORD_PLUGIN_MUTEX_LOCK MUTEX_LOCK(&PL_keyword_plugin_mutex) +# define KEYWORD_PLUGIN_MUTEX_UNLOCK MUTEX_UNLOCK(&PL_keyword_plugin_mutex) +# define KEYWORD_PLUGIN_MUTEX_TERM MUTEX_DESTROY(&PL_keyword_plugin_mutex) +#else +# define KEYWORD_PLUGIN_MUTEX_INIT NOOP +# define KEYWORD_PLUGIN_MUTEX_LOCK NOOP +# define KEYWORD_PLUGIN_MUTEX_UNLOCK NOOP +# define KEYWORD_PLUGIN_MUTEX_TERM NOOP +#endif + #ifdef USE_LOCALE /* These locale things are all subject to change */ diff --git a/perlapi.h b/perlapi.h index af0c2d593b..c461593dae 100644 --- a/perlapi.h +++ b/perlapi.h @@ -129,6 +129,8 @@ END_EXTERN_C #define PL_hints_mutex (*Perl_Ghints_mutex_ptr(NULL)) #undef PL_keyword_plugin #define PL_keyword_plugin (*Perl_Gkeyword_plugin_ptr(NULL)) +#undef PL_keyword_plugin_mutex +#define PL_keyword_plugin_mutex (*Perl_Gkeyword_plugin_mutex_ptr(NULL)) #undef PL_locale_mutex #define PL_locale_mutex (*Perl_Glocale_mutex_ptr(NULL)) #undef PL_malloc_mutex diff --git a/perlvars.h b/perlvars.h index b184b89419..472ae2dbb9 100644 --- a/perlvars.h +++ b/perlvars.h @@ -224,9 +224,15 @@ at a chain of handler functions, all of which have an opportunity to handle keywords, and only the last function in the chain (built into the Perl core) will normally return C<KEYWORD_PLUGIN_DECLINE>. +For thread safety, modules should not set this variable directly. +Instead, use the function L</wrap_keyword_plugin>. + =cut */ +#if defined(USE_ITHREADS) +PERLVAR(G, keyword_plugin_mutex, perl_mutex) /* Mutex for PL_keyword_plugin */ +#endif PERLVARI(G, keyword_plugin, Perl_keyword_plugin_t, Perl_keyword_plugin_standard) PERLVARI(G, op_sequence, HV *, NULL) /* dump.c */ diff --git a/pod/perldelta.pod b/pod/perldelta.pod index 1ba60a6628..023b55ad45 100644 --- a/pod/perldelta.pod +++ b/pod/perldelta.pod @@ -485,6 +485,15 @@ would be (4,6,1). If the string contains characters such as C<\x80>, whose representation changes under utf8, two sets of strings plus lengths are precomputed and stored. +=item * + +Direct access to L<C<PL_keyword_plugin>|perlapi/PL_keyword_plugin> is not +safe in the presence of multithreading. A new +L<C<wrap_keyword_plugin>|perlapi/wrap_keyword_plugin> function has been +added to allow XS modules to safely define custom keywords even when +loaded from a thread, analoguous to L<C<PL_check>|perlapi/PL_check> / +L<C<wrap_op_checker>|perlapi/wrap_op_checker>. + =back =head1 Selected Bug Fixes diff --git a/proto.h b/proto.h index bc24aee4fd..8c58a086bb 100644 --- a/proto.h +++ b/proto.h @@ -3764,6 +3764,9 @@ PERL_CALLCONV I32 Perl_whichsig_pvn(pTHX_ const char* sig, STRLEN len); PERL_CALLCONV I32 Perl_whichsig_sv(pTHX_ SV* sigsv); #define PERL_ARGS_ASSERT_WHICHSIG_SV \ assert(sigsv) +PERL_CALLCONV void Perl_wrap_keyword_plugin(pTHX_ Perl_keyword_plugin_t new_plugin, Perl_keyword_plugin_t *old_plugin_p); +#define PERL_ARGS_ASSERT_WRAP_KEYWORD_PLUGIN \ + assert(new_plugin); assert(old_plugin_p) PERL_CALLCONV void Perl_wrap_op_checker(pTHX_ Optype opcode, Perl_check_t new_checker, Perl_check_t *old_checker_p); #define PERL_ARGS_ASSERT_WRAP_OP_CHECKER \ assert(new_checker); assert(old_checker_p) diff --git a/toke.c b/toke.c index 18eda7a3c1..c8ca63adbb 100644 --- a/toke.c +++ b/toke.c @@ -12072,6 +12072,79 @@ Perl_keyword_plugin_standard(pTHX_ return KEYWORD_PLUGIN_DECLINE; } +/* +=for apidoc Amx|void|wrap_keyword_plugin|Perl_keyword_plugin_t new_plugin|Perl_keyword_plugin_t *old_plugin_p + +Puts a C function into the chain of keyword plugins. This is the +preferred way to manipulate the L</PL_keyword_plugin> variable. +C<new_plugin> is a pointer to the C function that is to be added to the +keyword plugin chain, and C<old_plugin_p> points to the storage location +where a pointer to the next function in the chain will be stored. The +value of C<new_plugin> is written into the L</PL_keyword_plugin> variable, +while the value previously stored there is written to C<*old_plugin_p>. + +L</PL_keyword_plugin> is global to an entire process, and a module wishing +to hook keyword parsing may find itself invoked more than once per +process, typically in different threads. To handle that situation, this +function is idempotent. The location C<*old_plugin_p> must initially +(once per process) contain a null pointer. A C variable of static +duration (declared at file scope, typically also marked C<static> to give +it internal linkage) will be implicitly initialised appropriately, if it +does not have an explicit initialiser. This function will only actually +modify the plugin chain if it finds C<*old_plugin_p> to be null. This +function is also thread safe on the small scale. It uses appropriate +locking to avoid race conditions in accessing L</PL_keyword_plugin>. + +When this function is called, the function referenced by C<new_plugin> +must be ready to be called, except for C<*old_plugin_p> being unfilled. +In a threading situation, C<new_plugin> may be called immediately, even +before this function has returned. C<*old_plugin_p> will always be +appropriately set before C<new_plugin> is called. If C<new_plugin> +decides not to do anything special with the identifier that it is given +(which is the usual case for most calls to a keyword plugin), it must +chain the plugin function referenced by C<*old_plugin_p>. + +Taken all together, XS code to install a keyword plugin should typically +look something like this: + + static Perl_keyword_plugin_t next_keyword_plugin; + static OP *my_keyword_plugin(pTHX_ + char *keyword_plugin, STRLEN keyword_len, OP **op_ptr) + { + if (memEQs(keyword_ptr, keyword_len, + "my_new_keyword")) { + ... + } else { + return next_keyword_plugin(aTHX_ + keyword_ptr, keyword_len, op_ptr); + } + } + BOOT: + wrap_keyword_plugin(my_keyword_plugin, + &next_keyword_plugin); + +Direct access to L</PL_keyword_plugin> should be avoided. + +=cut +*/ + +void +Perl_wrap_keyword_plugin(pTHX_ + Perl_keyword_plugin_t new_plugin, Perl_keyword_plugin_t *old_plugin_p) +{ + dVAR; + + PERL_UNUSED_CONTEXT; + PERL_ARGS_ASSERT_WRAP_KEYWORD_PLUGIN; + if (*old_plugin_p) return; + KEYWORD_PLUGIN_MUTEX_LOCK; + if (!*old_plugin_p) { + *old_plugin_p = PL_keyword_plugin; + PL_keyword_plugin = new_plugin; + } + KEYWORD_PLUGIN_MUTEX_UNLOCK; +} + #define parse_recdescent(g,p) S_parse_recdescent(aTHX_ g,p) static void S_parse_recdescent(pTHX_ int gramtype, I32 fakeeof) diff --git a/unixish.h b/unixish.h index 4cd8e43833..cd869cd240 100644 --- a/unixish.h +++ b/unixish.h @@ -138,9 +138,10 @@ int afstat(int fd, struct stat *statb); #if defined(__amigaos4__) # define PERL_SYS_INIT_BODY(c,v) \ MALLOC_CHECK_TAINT2(*c,*v) PERL_FPU_INIT; PERLIO_INIT; MALLOC_INIT; amigaos4_init_fork_array(); amigaos4_init_environ_sema(); -# define PERL_SYS_TERM_BODY() \ - HINTS_REFCNT_TERM; OP_CHECK_MUTEX_TERM; \ - OP_REFCNT_TERM; PERLIO_TERM; MALLOC_TERM; LOCALE_TERM; \ +# define PERL_SYS_TERM_BODY() \ + HINTS_REFCNT_TERM; KEYWORD_PLUGIN_MUTEX_TERM; \ + OP_CHECK_MUTEX_TERM; OP_REFCNT_TERM; PERLIO_TERM; \ + MALLOC_TERM; LOCALE_TERM; \ amigaos4_dispose_fork_array(); #endif @@ -150,10 +151,10 @@ int afstat(int fd, struct stat *statb); #endif #ifndef PERL_SYS_TERM_BODY -# define PERL_SYS_TERM_BODY() \ - HINTS_REFCNT_TERM; OP_CHECK_MUTEX_TERM; \ - OP_REFCNT_TERM; PERLIO_TERM; MALLOC_TERM; \ - LOCALE_TERM; +# define PERL_SYS_TERM_BODY() \ + HINTS_REFCNT_TERM; KEYWORD_PLUGIN_MUTEX_TERM; \ + OP_CHECK_MUTEX_TERM; OP_REFCNT_TERM; PERLIO_TERM; \ + MALLOC_TERM; LOCALE_TERM; #endif -- Perl5 Master Repository