From: Vincent Pit Date: Fri, 1 May 2009 18:54:03 +0000 (+0200) Subject: Enhance thread safety X-Git-Tag: v0.06~1 X-Git-Url: http://git.vpit.fr/?p=perl%2Fmodules%2FLexical-Types.git;a=commitdiff_plain;h=b56734d8bae611bf89f0c65a084de7079d119d96 Enhance thread safety --- diff --git a/Types.xs b/Types.xs index 60f499c..0644b00 100644 --- a/Types.xs +++ b/Types.xs @@ -31,9 +31,149 @@ # define HvNAMELEN_get(H) strlen(HvNAME_get(H)) #endif +#ifndef SvIS_FREED +# define SvIS_FREED(sv) ((sv)->sv_flags == SVTYPEMASK) +#endif + +/* ... Thread safety and multiplicity ...................................... */ + +#ifndef LT_MULTIPLICITY +# if defined(MULTIPLICITY) || defined(PERL_IMPLICIT_CONTEXT) +# define LT_MULTIPLICITY 1 +# else +# define LT_MULTIPLICITY 0 +# endif +#endif +#if LT_MULTIPLICITY && !defined(tTHX) +# define tTHX PerlInterpreter* +#endif + +#if LT_MULTIPLICITY && defined(USE_ITHREADS) && defined(dMY_CXT) && defined(MY_CXT) && defined(START_MY_CXT) && defined(MY_CXT_INIT) && (defined(MY_CXT_CLONE) || defined(dMY_CXT_SV)) +# define LT_THREADSAFE 1 +# ifndef MY_CXT_CLONE +# define MY_CXT_CLONE \ + dMY_CXT_SV; \ + my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1)); \ + Copy(INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)), my_cxtp, 1, my_cxt_t); \ + sv_setuv(my_cxt_sv, PTR2UV(my_cxtp)) +# endif +#else +# define LT_THREADSAFE 0 +#endif + /* --- Helpers ------------------------------------------------------------- */ -/* ... Hints ............................................................... */ +/* ... Thread-safe hints ................................................... */ + +#if LT_THREADSAFE + +#define PTABLE_NAME ptable_hints +#define PTABLE_VAL_FREE(V) if ((V) && !SvIS_FREED((SV *) (V))) SvREFCNT_dec(V) + +#define pPTBL pTHX +#define pPTBL_ pTHX_ +#define aPTBL aTHX +#define aPTBL_ aTHX_ + +#include "ptable.h" + +#define MY_CXT_KEY __PACKAGE__ "::_guts" XS_VERSION + +typedef struct { + ptable *tbl; + tTHX owner; +} my_cxt_t; + +START_MY_CXT + +STATIC void lt_ptable_hints_clone(pTHX_ ptable_ent *ent, void *ud_) { + my_cxt_t *ud = ud_; + SV *val = ent->val; + + if (ud->owner != aTHX) { + CLONE_PARAMS param; + AV *stashes = (SvTYPE(val) == SVt_PVHV && HvNAME_get(val)) ? newAV() : NULL; + param.stashes = stashes; + param.flags = 0; + param.proto_perl = ud->owner; + val = sv_dup(val, ¶m); + if (stashes) { + av_undef(stashes); + SvREFCNT_dec(stashes); + } + } + + ptable_hints_store(aPTBL_ ud->tbl, ent->key, val); + SvREFCNT_inc(val); +} + +STATIC void lt_thread_cleanup(pTHX_ void *); + +STATIC void lt_thread_cleanup(pTHX_ void *ud) { + int *level = ud; + SV *id; + + if (*level) { + *level = 0; + LEAVE; + SAVEDESTRUCTOR_X(lt_thread_cleanup, level); + ENTER; + } else { + dMY_CXT; + PerlMemShared_free(level); + ptable_hints_free(aPTBL_ MY_CXT.tbl); + } +} + +STATIC SV *lt_tag(pPTBL_ SV *value) { +#define lt_tag(V) lt_tag(aPTBL_ (V)) + dMY_CXT; + + value = SvOK(value) && SvROK(value) ? SvRV(value) : NULL; + /* We only need for the key to be an unique tag for looking up the value later. + * Allocated memory provides convenient unique identifiers, so that's why we + * use the value pointer as the key itself. */ + ptable_hints_store(aPTBL_ MY_CXT.tbl, value, value); + SvREFCNT_inc(value); + + return newSVuv(PTR2UV(value)); +} + +STATIC SV *lt_detag(pTHX_ const SV *hint) { +#define lt_detag(H) lt_detag(aTHX_ (H)) + void *tag; + SV *value; + + if (!hint || !SvOK(hint) || !SvIOK(hint)) + croak("Wrong hint"); + + tag = INT2PTR(void *, SvIVX(hint)); + { + dMY_CXT; + value = ptable_fetch(MY_CXT.tbl, tag); + } + + return value; +} + +#else + +STATIC SV *lt_tag(pTHX_ SV *value) { +#define lt_tag(V) lt_tag(aTHX_ (V)) + UV tag = 0; + + if (SvOK(value) && SvROK(value)) { + value = SvRV(value); + SvREFCNT_inc(value); + tag = PTR2UV(value); + } + + return newSVuv(tag); +} + +#define lt_detag(H) INT2PTR(SV *, SvUVX(H)) + +#endif /* LT_THREADSAFE */ STATIC U32 lt_hash = 0; @@ -74,8 +214,8 @@ typedef struct { OP *(*pp_padsv)(pTHX); } lt_op_info; -STATIC void lt_map_store(pPTABLE_ const OP *o, SV *orig_pkg, SV *type_pkg, SV *type_meth, OP *(*pp_padsv)(pTHX)) { -#define lt_map_store(O, OP, TP, TM, PP) lt_map_store(aPTABLE_ (O), (OP), (TP), (TM), (PP)) +STATIC void lt_map_store(pPTBL_ const OP *o, SV *orig_pkg, SV *type_pkg, SV *type_meth, OP *(*pp_padsv)(pTHX)) { +#define lt_map_store(O, OP, TP, TM, PP) lt_map_store(aPTBL_ (O), (OP), (TP), (TM), (PP)) lt_op_info *oi; #ifdef USE_ITHREADS @@ -84,7 +224,7 @@ STATIC void lt_map_store(pPTABLE_ const OP *o, SV *orig_pkg, SV *type_pkg, SV *t if (!(oi = ptable_fetch(lt_op_map, o))) { oi = PerlMemShared_malloc(sizeof *oi); - ptable_store(lt_op_map, o, oi); + ptable_store(aPTBL_ lt_op_map, o, oi); } oi->orig_pkg = orig_pkg; @@ -215,7 +355,7 @@ STATIC OP *lt_ck_padany(pTHX_ OP *o) { SV *orig_meth = lt_default_meth; SV *type_pkg = NULL; SV *type_meth = NULL; - SV *code = INT2PTR(SV *, SvUVX(hint)); + SV *code = lt_detag(hint); SvREADONLY_on(orig_pkg); @@ -294,11 +434,18 @@ STATIC U32 lt_initialized = 0; MODULE = Lexical::Types PACKAGE = Lexical::Types -PROTOTYPES: DISABLE +PROTOTYPES: ENABLE BOOT: { if (!lt_initialized++) { + HV *stash; +#if LT_THREADSAFE + MY_CXT_INIT; + MY_CXT.tbl = ptable_new(); + MY_CXT.owner = aTHX; +#endif + lt_op_map = ptable_new(); #ifdef USE_ITHREADS MUTEX_INIT(<_op_map_mutex); @@ -313,20 +460,47 @@ BOOT: PL_check[OP_PADANY] = MEMBER_TO_FPTR(lt_ck_padany); lt_old_ck_padsv = PL_check[OP_PADSV]; PL_check[OP_PADSV] = MEMBER_TO_FPTR(lt_ck_padsv); + + stash = gv_stashpvn(__PACKAGE__, __PACKAGE_LEN__, 1); + newCONSTSUB(stash, "LT_THREADSAFE", newSVuv(LT_THREADSAFE)); } } -SV *_tag(SV *ref) +#if LT_THREADSAFE + +void +CLONE(...) +PROTOTYPE: DISABLE PREINIT: - SV *ret; + ptable *t; + int *level; CODE: - if (SvOK(ref) && SvROK(ref)) { - SV *sv = SvRV(ref); - SvREFCNT_inc(sv); - ret = newSVuv(PTR2UV(sv)); - } else { - ret = newSVuv(0); + { + my_cxt_t ud; + dMY_CXT; + ud.tbl = t = ptable_new(); + ud.owner = MY_CXT.owner; + ptable_walk(MY_CXT.tbl, lt_ptable_hints_clone, &ud); + } + { + MY_CXT_CLONE; + MY_CXT.tbl = t; + MY_CXT.owner = aTHX; } - RETVAL = ret; + { + level = PerlMemShared_malloc(sizeof *level); + *level = 1; + LEAVE; + SAVEDESTRUCTOR_X(lt_thread_cleanup, level); + ENTER; + } + +#endif + +SV * +_tag(SV *value) +PROTOTYPE: $ +CODE: + RETVAL = lt_tag(value); OUTPUT: RETVAL diff --git a/lib/Lexical/Types.pm b/lib/Lexical/Types.pm index 0ac794a..2999a76 100644 --- a/lib/Lexical/Types.pm +++ b/lib/Lexical/Types.pm @@ -245,6 +245,12 @@ If you prefer to use constants rather than creating empty packages, you can repl sub new { ... } +=head1 CONSTANTS + +=head2 C + +True iff the module could have been built when thread-safety features. + =head1 CAVEATS The restrictions on the type (being either a defined package name or a constant) apply even if you use the C<'as'> option to redirect to another package, and are unlikely to find a workaround as this happens deep inside the lexer - far from the reach of an extension. diff --git a/ptable.h b/ptable.h index a279736..fc50652 100644 --- a/ptable.h +++ b/ptable.h @@ -6,47 +6,95 @@ * shared across threads. * Copyright goes to the original authors, bug reports to me. */ +/* This header is designed to be included several times with different + * definitions for PTABLE_NAME and PTABLE_VAL_FREE(). */ + +#undef pPTBLMS +#undef pPTBLMS_ +#undef aPTBLMS +#undef aPTBLMS_ + +/* Context for PerlMemShared_* functions */ + #ifdef PERL_IMPLICIT_SYS -# define pPTABLE pTHX -# define pPTABLE_ pTHX_ -# define aPTABLE aTHX -# define aPTABLE_ aTHX_ +# define pPTBLMS pTHX +# define pPTBLMS_ pTHX_ +# define aPTBLMS aTHX +# define aPTBLMS_ aTHX_ #else -# define pPTABLE -# define pPTABLE_ -# define aPTABLE -# define aPTABLE_ +# define pPTBLMS +# define pPTBLMS_ +# define aPTBLMS +# define aPTBLMS_ +#endif + +#ifndef pPTBL +# define pPTBL pPTBLMS +#endif +#ifndef pPTBL_ +# define pPTBL_ pPTBLMS_ +#endif +#ifndef aPTBL +# define aPTBL aPTBLMS +#endif +#ifndef aPTBL_ +# define aPTBL_ aPTBLMS_ +#endif + +#ifndef PTABLE_NAME +# define PTABLE_NAME ptable +#endif + +#ifndef PTABLE_VAL_FREE +# define PTABLE_VAL_FREE(V) +#endif + +#ifndef PTABLE_JOIN +# define PTABLE_PASTE(A, B) A ## B +# define PTABLE_JOIN(A, B) PTABLE_PASTE(A, B) #endif +#ifndef PTABLE_PREFIX +# define PTABLE_PREFIX(X) PTABLE_JOIN(PTABLE_NAME, X) +#endif + +#ifndef ptable_ent typedef struct ptable_ent { struct ptable_ent *next; const void * key; void * val; } ptable_ent; +#define ptable_ent ptable_ent +#endif /* !ptable_ent */ +#ifndef ptable typedef struct ptable { ptable_ent **ary; UV max; UV items; } ptable; +#define ptable ptable +#endif /* !ptable */ -#ifndef PTABLE_VAL_FREE -# define PTABLE_VAL_FREE(V) -#endif - -STATIC ptable *ptable_new(pPTABLE) { -#define ptable_new() ptable_new(aPTABLE) +#ifndef ptable_new +STATIC ptable *ptable_new(pPTBLMS) { +#define ptable_new() ptable_new(aPTBLMS) ptable *t = PerlMemShared_malloc(sizeof *t); t->max = 127; t->items = 0; t->ary = PerlMemShared_calloc(t->max + 1, sizeof *t->ary); return t; } +#endif /* !ptable_new */ -#define PTABLE_HASH(ptr) \ - ((PTR2UV(ptr) >> 3) ^ (PTR2UV(ptr) >> (3 + 7)) ^ (PTR2UV(ptr) >> (3 + 17))) +#ifndef PTABLE_HASH +# define PTABLE_HASH(ptr) \ + ((PTR2UV(ptr) >> 3) ^ (PTR2UV(ptr) >> (3 + 7)) ^ (PTR2UV(ptr) >> (3 + 17))) +#endif +#ifndef ptable_find STATIC ptable_ent *ptable_find(const ptable * const t, const void * const key) { +#define ptable_find ptable_find ptable_ent *ent; const UV hash = PTABLE_HASH(key); @@ -58,15 +106,20 @@ STATIC ptable_ent *ptable_find(const ptable * const t, const void * const key) { return NULL; } +#endif /* !ptable_find */ +#ifndef ptable_fetch STATIC void *ptable_fetch(const ptable * const t, const void * const key) { +#define ptable_fetch ptable_fetch const ptable_ent *const ent = ptable_find(t, key); return ent ? ent->val : NULL; } +#endif /* !ptable_fetch */ -STATIC void ptable_split(pPTABLE_ ptable * const t) { -#define ptable_split(T) ptable_split(aPTABLE_ (T)) +#ifndef ptable_split +STATIC void ptable_split(pPTBLMS_ ptable * const t) { +#define ptable_split(T) ptable_split(aPTBLMS_ (T)) ptable_ent **ary = t->ary; const UV oldsize = t->max + 1; UV newsize = oldsize * 2; @@ -93,9 +146,9 @@ STATIC void ptable_split(pPTABLE_ ptable * const t) { } } } +#endif /* !ptable_split */ -STATIC void ptable_store(pPTABLE_ ptable * const t, const void * const key, void * const val) { -#define ptable_store(T, K, V) ptable_store(aPTABLE_ (T), (K), (V)) +STATIC void PTABLE_PREFIX(_store)(pPTBL_ ptable * const t, const void * const key, void * const val) { ptable_ent *ent = ptable_find(t, key); if (ent) { @@ -115,10 +168,22 @@ STATIC void ptable_store(pPTABLE_ ptable * const t, const void * const key, void } } -#if 0 +#ifndef ptable_walk +STATIC void ptable_walk(pTHX_ ptable * const t, void (*cb)(pTHX_ ptable_ent *ent, void *userdata), void *userdata) { +#define ptable_walk(T, CB, UD) ptable_walk(aTHX_ (T), (CB), (UD)) + if (t && t->items) { + register ptable_ent ** const array = t->ary; + UV i = t->max; + do { + ptable_ent *entry; + for (entry = array[i]; entry; entry = entry->next) + cb(aTHX_ entry, userdata); + } while (i--); + } +} +#endif /* !ptable_walk */ -STATIC void ptable_clear(pPTABLE_ ptable * const t) { -#define ptable_clear(T) ptable_clear(aPTABLE_ (T)) +STATIC void PTABLE_PREFIX(_clear)(pPTBL_ ptable * const t) { if (t && t->items) { register ptable_ent ** const array = t->ary; UV i = t->max; @@ -130,7 +195,7 @@ STATIC void ptable_clear(pPTABLE_ ptable * const t) { void *val = oentry->val; entry = entry->next; PTABLE_VAL_FREE(val); - PerlMemShared_free(entry); + PerlMemShared_free(oentry); } array[i] = NULL; } while (i--); @@ -139,13 +204,13 @@ STATIC void ptable_clear(pPTABLE_ ptable * const t) { } } -STATIC void ptable_free(pPTABLE_ ptable * const t) { -#define ptable_free(T) ptable_free(aPTABLE_ (T)) +STATIC void PTABLE_PREFIX(_free)(pPTBL_ ptable * const t) { if (!t) return; - ptable_clear(t); + PTABLE_PREFIX(_clear)(aPTBL_ t); PerlMemShared_free(t->ary); PerlMemShared_free(t); } -#endif +#undef PTABLE_NAME +#undef PTABLE_VAL_FREE diff --git a/t/30-threads.t b/t/30-threads.t index c3db6c1..3f46124 100644 --- a/t/30-threads.t +++ b/t/30-threads.t @@ -15,9 +15,17 @@ BEGIN { use threads; -use Test::More tests => 10 * 2 * 2 * (1 + 2); +use Test::More; -defined and diag "Using threads $_" for $threads::VERSION; +BEGIN { + require Lexical::Types; + if (Lexical::Types::LT_THREADSAFE()) { + plan tests => 10 * 2 * 3 * (1 + 2); + defined and diag "Using threads $_" for $threads::VERSION; + } else { + plan skip_all => 'This Lexical::Types isn\'t thread safe'; + } +} { package Lexical::Types::Test::Tag; @@ -50,6 +58,16 @@ sub try { is $t2, $tid, "typed lexical correctly initialized in eval at run $_ in thread $tid"; EVALD diag $@ if $@; + +SKIP: + { + skip 'Hints aren\'t propagated into eval STRING below perl 5.10' => 3 + unless $] >= 5.010; + eval <<'EVALD'; + my Tag $t3; + is $t3, $tid, "typed lexical correctly initialized in eval (propagated) at run $_ in thread $tid" +EVALD + } } }