diff options
author | Nicholas Clark <nick@ccl4.org> | 2009-09-28 17:39:01 +0100 |
---|---|---|
committer | Nicholas Clark <nick@ccl4.org> | 2009-09-29 11:12:38 +0100 |
commit | 6eb7b80a1af171aa2ba8c32e69d3cb08bb9359c8 (patch) | |
tree | d77c361946e8ebd30819aed1e7d258e1eab3325f /dist/threads-shared | |
parent | 72388ea1ff94f31ed9f6362cae7518adaaff4ee7 (diff) | |
download | perl-6eb7b80a1af171aa2ba8c32e69d3cb08bb9359c8.tar.gz |
Move threads::shared from ext/ to dist/
Diffstat (limited to 'dist/threads-shared')
-rwxr-xr-x | dist/threads-shared/Makefile.PL | 111 | ||||
-rw-r--r-- | dist/threads-shared/hints/linux.pl | 3 | ||||
-rw-r--r-- | dist/threads-shared/shared.pm | 616 | ||||
-rw-r--r-- | dist/threads-shared/shared.xs | 1624 | ||||
-rw-r--r-- | dist/threads-shared/t/0nothread.t | 78 | ||||
-rw-r--r-- | dist/threads-shared/t/av_refs.t | 95 | ||||
-rw-r--r-- | dist/threads-shared/t/av_simple.t | 135 | ||||
-rw-r--r-- | dist/threads-shared/t/blessed.t | 139 | ||||
-rw-r--r-- | dist/threads-shared/t/clone.t | 175 | ||||
-rw-r--r-- | dist/threads-shared/t/cond.t | 283 | ||||
-rw-r--r-- | dist/threads-shared/t/disabled.t | 52 | ||||
-rw-r--r-- | dist/threads-shared/t/hv_refs.t | 111 | ||||
-rw-r--r-- | dist/threads-shared/t/hv_simple.t | 78 | ||||
-rw-r--r-- | dist/threads-shared/t/no_share.t | 62 | ||||
-rw-r--r-- | dist/threads-shared/t/object.t | 179 | ||||
-rw-r--r-- | dist/threads-shared/t/shared_attr.t | 79 | ||||
-rw-r--r-- | dist/threads-shared/t/stress.t | 149 | ||||
-rw-r--r-- | dist/threads-shared/t/sv_refs.t | 101 | ||||
-rw-r--r-- | dist/threads-shared/t/sv_simple.t | 64 | ||||
-rw-r--r-- | dist/threads-shared/t/utf8.t | 96 | ||||
-rw-r--r-- | dist/threads-shared/t/wait.t | 341 | ||||
-rw-r--r-- | dist/threads-shared/t/waithires.t | 279 |
22 files changed, 4850 insertions, 0 deletions
diff --git a/dist/threads-shared/Makefile.PL b/dist/threads-shared/Makefile.PL new file mode 100755 index 0000000000..05c738397f --- /dev/null +++ b/dist/threads-shared/Makefile.PL @@ -0,0 +1,111 @@ +# Module makefile for threads::shared (using ExtUtils::MakeMaker) + +require 5.008; + +use strict; +use warnings; + +use ExtUtils::MakeMaker; + + +# Used to check for a 'C' compiler +sub check_cc +{ + require File::Spec; + + my $cmd = $_[0]; + if (-x $cmd or MM->maybe_command($cmd)) { + return (1); # CC command found + } + for my $dir (File::Spec->path(), '.') { + my $abs = File::Spec->catfile($dir, $cmd); + if (-x $abs or MM->maybe_command($abs)) { + return (1); # CC command found + } + } + return; +} + +sub have_cc +{ + eval { require Config_m; }; # ExtUtils::FakeConfig (+ ActivePerl) + if ($@) { + eval { require Config; }; # Everyone else + } + my @chunks = split(/ /, $Config::Config{cc}); + # $Config{cc} may contain args; try to find out the program part + while (@chunks) { + if (check_cc("@chunks")) { + return (1); # CC command found + } + pop(@chunks); + } + return; +} + + +# Build options for different environments +my @conditional_params; +if (not grep { $_ eq 'PERL_CORE=1' } @ARGV) { + # CPAN + + # Verify that a 'C' compiler is available + if (! have_cc()) { + die("OS unsupported: ERROR: No 'C' compiler found to build 'threads::shared'\n"); + } + + push(@conditional_params, 'DEFINE' => '-DHAS_PPPORT_H', + 'PREREQ_PM' => { + 'strict' => 0, + 'warnings' => 0, + 'Config' => 0, + 'Carp' => 0, + 'XSLoader' => 0, + 'Scalar::Util' => 0, + 'threads' => 1.73, + + 'Test' => 0, + 'Test::More' => 0, + 'ExtUtils::testlib' => 0, + }); +} + + +# Create Makefile +WriteMakefile( + 'NAME' => 'threads::shared', + 'AUTHOR' => 'Artur Bergman, Jerry D. Hedden <jdhedden AT cpan DOT org>', + 'VERSION_FROM' => 'shared.pm', + 'ABSTRACT_FROM' => 'shared.pm', + 'PM' => { + 'shared.pm' => '$(INST_LIBDIR)/shared.pm', + }, + 'INSTALLDIRS' => 'perl', + + ((ExtUtils::MakeMaker->VERSION() lt '6.25') ? + ('PL_FILES' => { }) : ()), + ((ExtUtils::MakeMaker->VERSION() gt '6.30') ? + ('LICENSE' => 'perl') : ()), + + @conditional_params +); + +# Additional 'make' targets +sub MY::postamble +{ + return <<'_EXTRAS_'; +fixfiles: + @dos2unix `cat MANIFEST` + @$(CHMOD) 644 `cat MANIFEST` + @$(CHMOD) 755 examples/*.pl + +ppport: + @( cd /tmp; perl -e 'use Devel::PPPort; Devel::PPPort::WriteFile("ppport.h");' ) + @if ! cmp -s ppport.h /tmp/ppport.h; then \ + ( tkdiff ppport.h /tmp/ppport.h & ); \ + perl /tmp/ppport.h; \ + fi +_EXTRAS_ +} + +# EOF diff --git a/dist/threads-shared/hints/linux.pl b/dist/threads-shared/hints/linux.pl new file mode 100644 index 0000000000..020f56d2f7 --- /dev/null +++ b/dist/threads-shared/hints/linux.pl @@ -0,0 +1,3 @@ +# https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=101767 +# explicit linking is required to ensure the use of versioned symbols +$self->{LIBS} = ['-lpthread'] if $Config{libs} =~ /-lpthread/; diff --git a/dist/threads-shared/shared.pm b/dist/threads-shared/shared.pm new file mode 100644 index 0000000000..b1b4552431 --- /dev/null +++ b/dist/threads-shared/shared.pm @@ -0,0 +1,616 @@ +package threads::shared; + +use 5.008; + +use strict; +use warnings; + +use Scalar::Util qw(reftype refaddr blessed); + +our $VERSION = '1.31'; +my $XS_VERSION = $VERSION; +$VERSION = eval $VERSION; + +# Declare that we have been loaded +$threads::shared::threads_shared = 1; + +# Load the XS code, if applicable +if ($threads::threads) { + require XSLoader; + XSLoader::load('threads::shared', $XS_VERSION); + + *is_shared = \&_id; + +} else { + # String eval is generally evil, but we don't want these subs to + # exist at all if 'threads' is not loaded successfully. + # Vivifying them conditionally this way saves on average about 4K + # of memory per thread. + eval <<'_MARKER_'; + sub share (\[$@%]) { return $_[0] } + sub is_shared (\[$@%]) { undef } + sub cond_wait (\[$@%];\[$@%]) { undef } + sub cond_timedwait (\[$@%]$;\[$@%]) { undef } + sub cond_signal (\[$@%]) { undef } + sub cond_broadcast (\[$@%]) { undef } +_MARKER_ +} + + +### Export ### + +sub import +{ + # Exported subroutines + my @EXPORT = qw(share is_shared cond_wait cond_timedwait + cond_signal cond_broadcast shared_clone); + if ($threads::threads) { + push(@EXPORT, 'bless'); + } + + # Export subroutine names + my $caller = caller(); + foreach my $sym (@EXPORT) { + no strict 'refs'; + *{$caller.'::'.$sym} = \&{$sym}; + } +} + + +# Predeclarations for internal functions +my ($make_shared); + + +### Methods, etc. ### + +sub threads::shared::tie::SPLICE +{ + require Carp; + Carp::croak('Splice not implemented for shared arrays'); +} + + +# Create a thread-shared clone of a complex data structure or object +sub shared_clone +{ + if (@_ != 1) { + require Carp; + Carp::croak('Usage: shared_clone(REF)'); + } + + return $make_shared->(shift, {}); +} + + +### Internal Functions ### + +# Used by shared_clone() to recursively clone +# a complex data structure or object +$make_shared = sub { + my ($item, $cloned) = @_; + + # Just return the item if: + # 1. Not a ref; + # 2. Already shared; or + # 3. Not running 'threads'. + return $item if (! ref($item) || is_shared($item) || ! $threads::threads); + + # Check for previously cloned references + # (this takes care of circular refs as well) + my $addr = refaddr($item); + if (exists($cloned->{$addr})) { + # Return the already existing clone + return $cloned->{$addr}; + } + + # Make copies of array, hash and scalar refs and refs of refs + my $copy; + my $ref_type = reftype($item); + + # Copy an array ref + if ($ref_type eq 'ARRAY') { + # Make empty shared array ref + $copy = &share([]); + # Add to clone checking hash + $cloned->{$addr} = $copy; + # Recursively copy and add contents + push(@$copy, map { $make_shared->($_, $cloned) } @$item); + } + + # Copy a hash ref + elsif ($ref_type eq 'HASH') { + # Make empty shared hash ref + $copy = &share({}); + # Add to clone checking hash + $cloned->{$addr} = $copy; + # Recursively copy and add contents + foreach my $key (keys(%{$item})) { + $copy->{$key} = $make_shared->($item->{$key}, $cloned); + } + } + + # Copy a scalar ref + elsif ($ref_type eq 'SCALAR') { + $copy = \do{ my $scalar = $$item; }; + share($copy); + # Add to clone checking hash + $cloned->{$addr} = $copy; + } + + # Copy of a ref of a ref + elsif ($ref_type eq 'REF') { + # Special handling for $x = \$x + if ($addr == refaddr($$item)) { + $copy = \$copy; + share($copy); + $cloned->{$addr} = $copy; + } else { + my $tmp; + $copy = \$tmp; + share($copy); + # Add to clone checking hash + $cloned->{$addr} = $copy; + # Recursively copy and add contents + $tmp = $make_shared->($$item, $cloned); + } + + } else { + require Carp; + Carp::croak("Unsupported ref type: ", $ref_type); + } + + # If input item is an object, then bless the copy into the same class + if (my $class = blessed($item)) { + bless($copy, $class); + } + + # Clone READONLY flag + if ($ref_type eq 'SCALAR') { + if (Internals::SvREADONLY($$item)) { + Internals::SvREADONLY($$copy, 1) if ($] >= 5.008003); + } + } + if (Internals::SvREADONLY($item)) { + Internals::SvREADONLY($copy, 1) if ($] >= 5.008003); + } + + return $copy; +}; + +1; + +__END__ + +=head1 NAME + +threads::shared - Perl extension for sharing data structures between threads + +=head1 VERSION + +This document describes threads::shared version 1.31 + +=head1 SYNOPSIS + + use threads; + use threads::shared; + + my $var :shared; + my %hsh :shared; + my @ary :shared; + + my ($scalar, @array, %hash); + share($scalar); + share(@array); + share(%hash); + + $var = $scalar_value; + $var = $shared_ref_value; + $var = shared_clone($non_shared_ref_value); + $var = shared_clone({'foo' => [qw/foo bar baz/]}); + + $hsh{'foo'} = $scalar_value; + $hsh{'bar'} = $shared_ref_value; + $hsh{'baz'} = shared_clone($non_shared_ref_value); + $hsh{'quz'} = shared_clone([1..3]); + + $ary[0] = $scalar_value; + $ary[1] = $shared_ref_value; + $ary[2] = shared_clone($non_shared_ref_value); + $ary[3] = shared_clone([ {}, [] ]); + + { lock(%hash); ... } + + cond_wait($scalar); + cond_timedwait($scalar, time() + 30); + cond_broadcast(@array); + cond_signal(%hash); + + my $lockvar :shared; + # condition var != lock var + cond_wait($var, $lockvar); + cond_timedwait($var, time()+30, $lockvar); + +=head1 DESCRIPTION + +By default, variables are private to each thread, and each newly created +thread gets a private copy of each existing variable. This module allows you +to share variables across different threads (and pseudo-forks on Win32). It +is used together with the L<threads> module. + +This module supports the sharing of the following data types only: scalars +and scalar refs, arrays and array refs, and hashes and hash refs. + +=head1 EXPORT + +The following functions are exported by this module: C<share>, +C<shared_clone>, C<is_shared>, C<cond_wait>, C<cond_timedwait>, C<cond_signal> +and C<cond_broadcast> + +Note that if this module is imported when L<threads> has not yet been loaded, +then these functions all become no-ops. This makes it possible to write +modules that will work in both threaded and non-threaded environments. + +=head1 FUNCTIONS + +=over 4 + +=item share VARIABLE + +C<share> takes a variable and marks it as shared: + + my ($scalar, @array, %hash); + share($scalar); + share(@array); + share(%hash); + +C<share> will return the shared rvalue, but always as a reference. + +Variables can also be marked as shared at compile time by using the +C<:shared> attribute: + + my ($var, %hash, @array) :shared; + +Shared variables can only store scalars, refs of shared variables, or +refs of shared data (discussed in next section): + + my ($var, %hash, @array) :shared; + my $bork; + + # Storing scalars + $var = 1; + $hash{'foo'} = 'bar'; + $array[0] = 1.5; + + # Storing shared refs + $var = \%hash; + $hash{'ary'} = \@array; + $array[1] = \$var; + + # The following are errors: + # $var = \$bork; # ref of non-shared variable + # $hash{'bork'} = []; # non-shared array ref + # push(@array, { 'x' => 1 }); # non-shared hash ref + +=item shared_clone REF + +C<shared_clone> takes a reference, and returns a shared version of its +argument, performing a deep copy on any non-shared elements. Any shared +elements in the argument are used as is (i.e., they are not cloned). + + my $cpy = shared_clone({'foo' => [qw/foo bar baz/]}); + +Object status (i.e., the class an object is blessed into) is also cloned. + + my $obj = {'foo' => [qw/foo bar baz/]}; + bless($obj, 'Foo'); + my $cpy = shared_clone($obj); + print(ref($cpy), "\n"); # Outputs 'Foo' + +For cloning empty array or hash refs, the following may also be used: + + $var = &share([]); # Same as $var = shared_clone([]); + $var = &share({}); # Same as $var = shared_clone({}); + +=item is_shared VARIABLE + +C<is_shared> checks if the specified variable is shared or not. If shared, +returns the variable's internal ID (similar to +L<refaddr()|Scalar::Util/"refaddr EXPR">). Otherwise, returns C<undef>. + + if (is_shared($var)) { + print("\$var is shared\n"); + } else { + print("\$var is not shared\n"); + } + +When used on an element of an array or hash, C<is_shared> checks if the +specified element belongs to a shared array or hash. (It does not check +the contents of that element.) + + my %hash :shared; + if (is_shared(%hash)) { + print("\%hash is shared\n"); + } + + $hash{'elem'} = 1; + if (is_shared($hash{'elem'})) { + print("\$hash{'elem'} is in a shared hash\n"); + } + +=item lock VARIABLE + +C<lock> places a B<advisory> lock on a variable until the lock goes out of +scope. If the variable is locked by another thread, the C<lock> call will +block until it's available. Multiple calls to C<lock> by the same thread from +within dynamically nested scopes are safe -- the variable will remain locked +until the outermost lock on the variable goes out of scope. + +C<lock> follows references exactly I<one> level: + + my %hash :shared; + my $ref = \%hash; + lock($ref); # This is equivalent to lock(%hash) + +Note that you cannot explicitly unlock a variable; you can only wait for the +lock to go out of scope. This is most easily accomplished by locking the +variable inside a block. + + my $var :shared; + { + lock($var); + # $var is locked from here to the end of the block + ... + } + # $var is now unlocked + +As locks are advisory, they do not prevent data access or modification by +another thread that does not itself attempt to obtain a lock on the variable. + +You cannot lock the individual elements of a container variable: + + my %hash :shared; + $hash{'foo'} = 'bar'; + #lock($hash{'foo'}); # Error + lock(%hash); # Works + +If you need more fine-grained control over shared variable access, see +L<Thread::Semaphore>. + +=item cond_wait VARIABLE + +=item cond_wait CONDVAR, LOCKVAR + +The C<cond_wait> function takes a B<locked> variable as a parameter, unlocks +the variable, and blocks until another thread does a C<cond_signal> or +C<cond_broadcast> for that same locked variable. The variable that +C<cond_wait> blocked on is relocked after the C<cond_wait> is satisfied. If +there are multiple threads C<cond_wait>ing on the same variable, all but one +will re-block waiting to reacquire the lock on the variable. (So if you're only +using C<cond_wait> for synchronisation, give up the lock as soon as possible). +The two actions of unlocking the variable and entering the blocked wait state +are atomic, the two actions of exiting from the blocked wait state and +re-locking the variable are not. + +In its second form, C<cond_wait> takes a shared, B<unlocked> variable followed +by a shared, B<locked> variable. The second variable is unlocked and thread +execution suspended until another thread signals the first variable. + +It is important to note that the variable can be notified even if no thread +C<cond_signal> or C<cond_broadcast> on the variable. It is therefore +important to check the value of the variable and go back to waiting if the +requirement is not fulfilled. For example, to pause until a shared counter +drops to zero: + + { lock($counter); cond_wait($counter) until $counter == 0; } + +=item cond_timedwait VARIABLE, ABS_TIMEOUT + +=item cond_timedwait CONDVAR, ABS_TIMEOUT, LOCKVAR + +In its two-argument form, C<cond_timedwait> takes a B<locked> variable and an +absolute timeout as parameters, unlocks the variable, and blocks until the +timeout is reached or another thread signals the variable. A false value is +returned if the timeout is reached, and a true value otherwise. In either +case, the variable is re-locked upon return. + +Like C<cond_wait>, this function may take a shared, B<locked> variable as an +additional parameter; in this case the first parameter is an B<unlocked> +condition variable protected by a distinct lock variable. + +Again like C<cond_wait>, waking up and reacquiring the lock are not atomic, +and you should always check your desired condition after this function +returns. Since the timeout is an absolute value, however, it does not have to +be recalculated with each pass: + + lock($var); + my $abs = time() + 15; + until ($ok = desired_condition($var)) { + last if !cond_timedwait($var, $abs); + } + # we got it if $ok, otherwise we timed out! + +=item cond_signal VARIABLE + +The C<cond_signal> function takes a B<locked> variable as a parameter and +unblocks one thread that's C<cond_wait>ing on that variable. If more than one +thread is blocked in a C<cond_wait> on that variable, only one (and which one +is indeterminate) will be unblocked. + +If there are no threads blocked in a C<cond_wait> on the variable, the signal +is discarded. By always locking before signaling, you can (with care), avoid +signaling before another thread has entered cond_wait(). + +C<cond_signal> will normally generate a warning if you attempt to use it on an +unlocked variable. On the rare occasions where doing this may be sensible, you +can suppress the warning with: + + { no warnings 'threads'; cond_signal($foo); } + +=item cond_broadcast VARIABLE + +The C<cond_broadcast> function works similarly to C<cond_signal>. +C<cond_broadcast>, though, will unblock B<all> the threads that are blocked in +a C<cond_wait> on the locked variable, rather than only one. + +=back + +=head1 OBJECTS + +L<threads::shared> exports a version of L<bless()|perlfunc/"bless REF"> that +works on shared objects such that I<blessings> propagate across threads. + + # Create a shared 'Foo' object + my $foo :shared = shared_clone({}); + bless($foo, 'Foo'); + + # Create a shared 'Bar' object + my $bar :shared = shared_clone({}); + bless($bar, 'Bar'); + + # Put 'bar' inside 'foo' + $foo->{'bar'} = $bar; + + # Rebless the objects via a thread + threads->create(sub { + # Rebless the outer object + bless($foo, 'Yin'); + + # Cannot directly rebless the inner object + #bless($foo->{'bar'}, 'Yang'); + + # Retrieve and rebless the inner object + my $obj = $foo->{'bar'}; + bless($obj, 'Yang'); + $foo->{'bar'} = $obj; + + })->join(); + + print(ref($foo), "\n"); # Prints 'Yin' + print(ref($foo->{'bar'}), "\n"); # Prints 'Yang' + print(ref($bar), "\n"); # Also prints 'Yang' + +=head1 NOTES + +L<threads::shared> is designed to disable itself silently if threads are not +available. This allows you to write modules and packages that can be used +in both threaded and non-threaded applications. + +If you want access to threads, you must C<use threads> before you +C<use threads::shared>. L<threads> will emit a warning if you use it after +L<threads::shared>. + +=head1 BUGS AND LIMITATIONS + +When C<share> is used on arrays, hashes, array refs or hash refs, any data +they contain will be lost. + + my @arr = qw(foo bar baz); + share(@arr); + # @arr is now empty (i.e., == ()); + + # Create a 'foo' object + my $foo = { 'data' => 99 }; + bless($foo, 'foo'); + + # Share the object + share($foo); # Contents are now wiped out + print("ERROR: \$foo is empty\n") + if (! exists($foo->{'data'})); + +Therefore, populate such variables B<after> declaring them as shared. (Scalar +and scalar refs are not affected by this problem.) + +It is often not wise to share an object unless the class itself has been +written to support sharing. For example, an object's destructor may get +called multiple times, once for each thread's scope exit. Another danger is +that the contents of hash-based objects will be lost due to the above +mentioned limitation. See F<examples/class.pl> (in the CPAN distribution of +this module) for how to create a class that supports object sharing. + +Does not support C<splice> on arrays! + +Taking references to the elements of shared arrays and hashes does not +autovivify the elements, and neither does slicing a shared array/hash over +non-existent indices/keys autovivify the elements. + +C<share()> allows you to C<< share($hashref->{key}) >> and +C<< share($arrayref->[idx]) >> without giving any error message. But the +C<< $hashref->{key} >> or C<< $arrayref->[idx] >> is B<not> shared, causing +the error "lock can only be used on shared values" to occur when you attempt +to C<< lock($hasref->{key}) >> or C<< lock($arrayref->[idx]) >> in another +thread. + +Using L<refaddr()|Scalar::Util/"refaddr EXPR">) is unreliable for testing +whether or not two shared references are equivalent (e.g., when testing for +circular references). Use L<is_shared()/"is_shared VARIABLE">, instead: + + use threads; + use threads::shared; + use Scalar::Util qw(refaddr); + + # If ref is shared, use threads::shared's internal ID. + # Otherwise, use refaddr(). + my $addr1 = is_shared($ref1) || refaddr($ref1); + my $addr2 = is_shared($ref2) || refaddr($ref2); + + if ($addr1 == $addr2) { + # The refs are equivalent + } + +L<each()|perlfunc/"each HASH"> does not work properly on shared references +embedded in shared structures. For example: + + my %foo :shared; + $foo{'bar'} = shared_clone({'a'=>'x', 'b'=>'y', 'c'=>'z'}); + + while (my ($key, $val) = each(%{$foo{'bar'}})) { + ... + } + +Either of the following will work instead: + + my $ref = $foo{'bar'}; + while (my ($key, $val) = each(%{$ref})) { + ... + } + + foreach my $key (keys(%{$foo{'bar'}})) { + my $val = $foo{'bar'}{$key}; + ... + } + +View existing bug reports at, and submit any new bugs, problems, patches, etc. +to: L<http://rt.cpan.org/Public/Dist/Display.html?Name=threads-shared> + +=head1 SEE ALSO + +L<threads::shared> Discussion Forum on CPAN: +L<http://www.cpanforum.com/dist/threads-shared> + +Annotated POD for L<threads::shared>: +L<http://annocpan.org/~JDHEDDEN/threads-shared-1.31/shared.pm> + +Source repository: +L<http://code.google.com/p/threads-shared/> + +L<threads>, L<perlthrtut> + +L<http://www.perl.com/pub/a/2002/06/11/threads.html> and +L<http://www.perl.com/pub/a/2002/09/04/threads.html> + +Perl threads mailing list: +L<http://lists.cpan.org/showlist.cgi?name=iThreads> + +=head1 AUTHOR + +Artur Bergman E<lt>sky AT crucially DOT netE<gt> + +Documentation borrowed from the old Thread.pm. + +CPAN version produced by Jerry D. Hedden E<lt>jdhedden AT cpan DOT orgE<gt>. + +=head1 LICENSE + +threads::shared is released under the same license as Perl. + +=cut diff --git a/dist/threads-shared/shared.xs b/dist/threads-shared/shared.xs new file mode 100644 index 0000000000..b9a324191b --- /dev/null +++ b/dist/threads-shared/shared.xs @@ -0,0 +1,1624 @@ +/* shared.xs + * + * Copyright (c) 2001-2002, 2006 Larry Wall + * + * You may distribute under the terms of either the GNU General Public + * License or the Artistic License, as specified in the README file. + * + * "Hand any two wizards a piece of rope and they would instinctively pull in + * opposite directions." + * --Sourcery + * + * Contributed by Artur Bergman <sky AT crucially DOT net> + * Pulled in the (an)other direction by Nick Ing-Simmons + * <nick AT ing-simmons DOT net> + * CPAN version produced by Jerry D. Hedden <jdhedden AT cpan DOT org> + */ + +/* + * Shared variables are implemented by a scheme similar to tieing. + * Each thread has a proxy SV with attached magic -- "private SVs" -- + * which all point to a single SV in a separate shared interpreter + * (PL_sharedsv_space) -- "shared SVs". + * + * The shared SV holds the variable's true values, and its state is + * copied between the shared and private SVs with the usual + * mg_get()/mg_set() arrangement. + * + * Aggregates (AVs and HVs) are implemented using tie magic, except that + * the vtable used is one defined in this file rather than the standard one. + * This means that where a tie function like FETCH is normally invoked by + * the tie magic's mg_get() function, we completely bypass the calling of a + * perl-level function, and directly call C-level code to handle it. On + * the other hand, calls to functions like PUSH are done directly by code + * in av.c, etc., which we can't bypass. So the best we can do is to provide + * XS versions of these functions. We also have to attach a tie object, + * blessed into the class threads::shared::tie, to keep the method-calling + * code happy. + * + * Access to aggregate elements is done the usual tied way by returning a + * proxy PVLV element with attached element magic. + * + * Pointers to the shared SV are squirrelled away in the mg->mg_ptr field + * of magic (with mg_len == 0), and in the IV2PTR(SvIV(sv)) field of tied + * object SVs. These pointers have to be hidden like this because they + * cross interpreter boundaries, and we don't want sv_clear() and friends + * following them. + * + * The three basic shared types look like the following: + * + * ----------------- + * + * Shared scalar (my $s : shared): + * + * SV = PVMG(0x7ba238) at 0x7387a8 + * FLAGS = (PADMY,GMG,SMG) + * MAGIC = 0x824d88 + * MG_TYPE = PERL_MAGIC_shared_scalar(n) + * MG_PTR = 0x810358 <<<< pointer to the shared SV + * + * ----------------- + * + * Shared aggregate (my @a : shared; my %h : shared): + * + * SV = PVAV(0x7175d0) at 0x738708 + * FLAGS = (PADMY,RMG) + * MAGIC = 0x824e48 + * MG_TYPE = PERL_MAGIC_tied(P) + * MG_OBJ = 0x7136e0 <<<< ref to the tied object + * SV = RV(0x7136f0) at 0x7136e0 + * RV = 0x738640 + * SV = PVMG(0x7ba238) at 0x738640 <<<< the tied object + * FLAGS = (OBJECT,IOK,pIOK) + * IV = 8455000 <<<< pointer to the shared AV + * STASH = 0x80abf0 "threads::shared::tie" + * MG_PTR = 0x810358 "" <<<< another pointer to the shared AV + * ARRAY = 0x0 + * + * ----------------- + * + * Aggregate element (my @a : shared; $a[0]) + * + * SV = PVLV(0x77f628) at 0x713550 + * FLAGS = (GMG,SMG,RMG,pIOK) + * MAGIC = 0x72bd58 + * MG_TYPE = PERL_MAGIC_shared_scalar(n) + * MG_PTR = 0x8103c0 "" <<<< pointer to the shared element + * MAGIC = 0x72bd18 + * MG_TYPE = PERL_MAGIC_tiedelem(p) + * MG_OBJ = 0x7136e0 <<<< ref to the tied object + * SV = RV(0x7136f0) at 0x7136e0 + * RV = 0x738660 + * SV = PVMG(0x7ba278) at 0x738660 <<<< the tied object + * FLAGS = (OBJECT,IOK,pIOK) + * IV = 8455064 <<<< pointer to the shared AV + * STASH = 0x80ac30 "threads::shared::tie" + * TYPE = t + * + * Note that PERL_MAGIC_tiedelem(p) magic doesn't have a pointer to a + * shared SV in mg_ptr; instead this is used to store the hash key, + * if any, like normal tied elements. Note also that element SVs may have + * pointers to both the shared aggregate and the shared element. + * + * + * Userland locks: + * + * If a shared variable is used as a perl-level lock or condition + * variable, then PERL_MAGIC_ext magic is attached to the associated + * *shared* SV, whose mg_ptr field points to a malloc'ed structure + * containing the necessary mutexes and condition variables. + * + * Nomenclature: + * + * In this file, any variable name prefixed with 's' (e.g., ssv, stmp or sobj) + * usually represents a shared SV which corresponds to a private SV named + * without the prefix (e.g., sv, tmp or obj). + */ + +#define PERL_NO_GET_CONTEXT +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" +#ifdef HAS_PPPORT_H +# define NEED_sv_2pv_flags +# define NEED_vnewSVpvf +# define NEED_warner +# define NEED_newSVpvn_flags +# include "ppport.h" +# include "shared.h" +#endif + +#ifdef USE_ITHREADS + +/* Magic signature(s) for mg_private to make PERL_MAGIC_ext magic safer */ +#define UL_MAGIC_SIG 0x554C /* UL = user lock */ + +/* + * The shared things need an intepreter to live in ... + */ +PerlInterpreter *PL_sharedsv_space; /* The shared sv space */ +/* To access shared space we fake aTHX in this scope and thread's context */ + +/* Bug #24255: We include ENTER+SAVETMPS/FREETMPS+LEAVE with + * SHARED_CONTEXT/CALLER_CONTEXT macros, so that any mortals, etc. created + * while in the shared interpreter context don't languish */ + +#define SHARED_CONTEXT \ + STMT_START { \ + PERL_SET_CONTEXT((aTHX = PL_sharedsv_space)); \ + ENTER; \ + SAVETMPS; \ + } STMT_END + +/* So we need a way to switch back to the caller's context... */ +/* So we declare _another_ copy of the aTHX variable ... */ +#define dTHXc PerlInterpreter *caller_perl = aTHX + +/* ... and use it to switch back */ +#define CALLER_CONTEXT \ + STMT_START { \ + FREETMPS; \ + LEAVE; \ + PERL_SET_CONTEXT((aTHX = caller_perl)); \ + } STMT_END + +/* + * Only one thread at a time is allowed to mess with shared space. + */ + +typedef struct { + perl_mutex mutex; + PerlInterpreter *owner; + I32 locks; + perl_cond cond; +#ifdef DEBUG_LOCKS + char * file; + int line; +#endif +} recursive_lock_t; + +recursive_lock_t PL_sharedsv_lock; /* Mutex protecting the shared sv space */ + +void +recursive_lock_init(pTHX_ recursive_lock_t *lock) +{ + Zero(lock,1,recursive_lock_t); + MUTEX_INIT(&lock->mutex); + COND_INIT(&lock->cond); +} + +void +recursive_lock_destroy(pTHX_ recursive_lock_t *lock) +{ + MUTEX_DESTROY(&lock->mutex); + COND_DESTROY(&lock->cond); +} + +void +recursive_lock_release(pTHX_ recursive_lock_t *lock) +{ + MUTEX_LOCK(&lock->mutex); + if (lock->owner == aTHX) { + if (--lock->locks == 0) { + lock->owner = NULL; + COND_SIGNAL(&lock->cond); + } + } + MUTEX_UNLOCK(&lock->mutex); +} + +void +recursive_lock_acquire(pTHX_ recursive_lock_t *lock, char *file, int line) +{ + assert(aTHX); + MUTEX_LOCK(&lock->mutex); + if (lock->owner == aTHX) { + lock->locks++; + } else { + while (lock->owner) { +#ifdef DEBUG_LOCKS + Perl_warn(aTHX_ " %p waiting - owned by %p %s:%d\n", + aTHX, lock->owner, lock->file, lock->line); +#endif + COND_WAIT(&lock->cond,&lock->mutex); + } + lock->locks = 1; + lock->owner = aTHX; +#ifdef DEBUG_LOCKS + lock->file = file; + lock->line = line; +#endif + } + MUTEX_UNLOCK(&lock->mutex); + SAVEDESTRUCTOR_X(recursive_lock_release,lock); +} + +#define ENTER_LOCK \ + STMT_START { \ + ENTER; \ + recursive_lock_acquire(aTHX_ &PL_sharedsv_lock, __FILE__, __LINE__);\ + } STMT_END + +/* The unlocking is done automatically at scope exit */ +#define LEAVE_LOCK LEAVE + + +/* A common idiom is to acquire access and switch in ... */ +#define SHARED_EDIT \ + STMT_START { \ + ENTER_LOCK; \ + SHARED_CONTEXT; \ + } STMT_END + +/* ... then switch out and release access. */ +#define SHARED_RELEASE \ + STMT_START { \ + CALLER_CONTEXT; \ + LEAVE_LOCK; \ + } STMT_END + + +/* User-level locks: + This structure is attached (using ext magic) to any shared SV that + is used by user-level locking or condition code +*/ + +typedef struct { + recursive_lock_t lock; /* For user-levl locks */ + perl_cond user_cond; /* For user-level conditions */ +} user_lock; + +/* Magic used for attaching user_lock structs to shared SVs + + The vtable used has just one entry - when the SV goes away + we free the memory for the above. + */ + +int +sharedsv_userlock_free(pTHX_ SV *sv, MAGIC *mg) +{ + user_lock *ul = (user_lock *) mg->mg_ptr; + assert(aTHX == PL_sharedsv_space); + if (ul) { + recursive_lock_destroy(aTHX_ &ul->lock); + COND_DESTROY(&ul->user_cond); + PerlMemShared_free(ul); + mg->mg_ptr = NULL; + } + return (0); +} + +MGVTBL sharedsv_userlock_vtbl = { + 0, /* get */ + 0, /* set */ + 0, /* len */ + 0, /* clear */ + sharedsv_userlock_free, /* free */ + 0, /* copy */ + 0, /* dup */ +#ifdef MGf_LOCAL + 0, /* local */ +#endif +}; + +/* + * Access to shared things is heavily based on MAGIC + * - in mg.h/mg.c/sv.c sense + */ + +/* In any thread that has access to a shared thing there is a "proxy" + for it in its own space which has 'MAGIC' associated which accesses + the shared thing. + */ + +extern MGVTBL sharedsv_scalar_vtbl; /* Scalars have this vtable */ +extern MGVTBL sharedsv_array_vtbl; /* Hashes and arrays have this + - like 'tie' */ +extern MGVTBL sharedsv_elem_vtbl; /* Elements of hashes and arrays have + this _AS WELL AS_ the scalar magic: + The sharedsv_elem_vtbl associates the element with the array/hash and + the sharedsv_scalar_vtbl associates it with the value + */ + + +/* Get shared aggregate SV pointed to by threads::shared::tie magic object */ + +STATIC SV * +S_sharedsv_from_obj(pTHX_ SV *sv) +{ + return ((SvROK(sv)) ? INT2PTR(SV *, SvIV(SvRV(sv))) : NULL); +} + + +/* Return the user_lock structure (if any) associated with a shared SV. + * If create is true, create one if it doesn't exist + */ +STATIC user_lock * +S_get_userlock(pTHX_ SV* ssv, bool create) +{ + MAGIC *mg; + user_lock *ul = NULL; + + assert(ssv); + /* XXX Redesign the storage of user locks so we don't need a global + * lock to access them ???? DAPM */ + ENTER_LOCK; + + /* Version of mg_find that also checks the private signature */ + for (mg = SvMAGIC(ssv); mg; mg = mg->mg_moremagic) { + if ((mg->mg_type == PERL_MAGIC_ext) && + (mg->mg_private == UL_MAGIC_SIG)) + { + break; + } + } + + if (mg) { + ul = (user_lock*)(mg->mg_ptr); + } else if (create) { + dTHXc; + SHARED_CONTEXT; + ul = (user_lock *) PerlMemShared_malloc(sizeof(user_lock)); + Zero(ul, 1, user_lock); + /* Attach to shared SV using ext magic */ + mg = sv_magicext(ssv, NULL, PERL_MAGIC_ext, &sharedsv_userlock_vtbl, + (char *)ul, 0); + mg->mg_private = UL_MAGIC_SIG; /* Set private signature */ + recursive_lock_init(aTHX_ &ul->lock); + COND_INIT(&ul->user_cond); + CALLER_CONTEXT; + } + LEAVE_LOCK; + return (ul); +} + + +/* Given a private side SV tries to find if the SV has a shared backend, + * by looking for the magic. + */ +SV * +Perl_sharedsv_find(pTHX_ SV *sv) +{ + MAGIC *mg; + if (SvTYPE(sv) >= SVt_PVMG) { + switch(SvTYPE(sv)) { + case SVt_PVAV: + case SVt_PVHV: + if ((mg = mg_find(sv, PERL_MAGIC_tied)) + && mg->mg_virtual == &sharedsv_array_vtbl) { + return ((SV *)mg->mg_ptr); + } + break; + default: + /* This should work for elements as well as they + * have scalar magic as well as their element magic + */ + if ((mg = mg_find(sv, PERL_MAGIC_shared_scalar)) + && mg->mg_virtual == &sharedsv_scalar_vtbl) { + return ((SV *)mg->mg_ptr); + } + break; + } + } + /* Just for tidyness of API also handle tie objects */ + if (SvROK(sv) && sv_derived_from(sv, "threads::shared::tie")) { + return (S_sharedsv_from_obj(aTHX_ sv)); + } + return (NULL); +} + + +/* Associate a private SV with a shared SV by pointing the appropriate + * magics at it. + * Assumes lock is held. + */ +void +Perl_sharedsv_associate(pTHX_ SV *sv, SV *ssv) +{ + MAGIC *mg = 0; + + /* If we are asked for any private ops we need a thread */ + assert ( aTHX != PL_sharedsv_space ); + + /* To avoid need for recursive locks require caller to hold lock */ + assert ( PL_sharedsv_lock.owner == aTHX ); + + switch(SvTYPE(sv)) { + case SVt_PVAV: + case SVt_PVHV: + if (!(mg = mg_find(sv, PERL_MAGIC_tied)) + || mg->mg_virtual != &sharedsv_array_vtbl + || (SV*) mg->mg_ptr != ssv) + { + SV *obj = newSV(0); + sv_setref_iv(obj, "threads::shared::tie", PTR2IV(ssv)); + if (mg) { + sv_unmagic(sv, PERL_MAGIC_tied); + } + mg = sv_magicext(sv, obj, PERL_MAGIC_tied, &sharedsv_array_vtbl, + (char *)ssv, 0); + mg->mg_flags |= (MGf_COPY|MGf_DUP); + SvREFCNT_inc_void(ssv); + SvREFCNT_dec(obj); + } + break; + + default: + if ((SvTYPE(sv) < SVt_PVMG) + || !(mg = mg_find(sv, PERL_MAGIC_shared_scalar)) + || mg->mg_virtual != &sharedsv_scalar_vtbl + || (SV*) mg->mg_ptr != ssv) + { + if (mg) { + sv_unmagic(sv, PERL_MAGIC_shared_scalar); + } + mg = sv_magicext(sv, Nullsv, PERL_MAGIC_shared_scalar, + &sharedsv_scalar_vtbl, (char *)ssv, 0); + mg->mg_flags |= (MGf_DUP +#ifdef MGf_LOCAL + |MGf_LOCAL +#endif + ); + SvREFCNT_inc_void(ssv); + } + break; + } + + assert ( Perl_sharedsv_find(aTHX_ sv) == ssv ); +} + + +/* Given a private SV, create and return an associated shared SV. + * Assumes lock is held. + */ +STATIC SV * +S_sharedsv_new_shared(pTHX_ SV *sv) +{ + dTHXc; + SV *ssv; + + assert(PL_sharedsv_lock.owner == aTHX); + assert(aTHX != PL_sharedsv_space); + + SHARED_CONTEXT; + ssv = newSV(0); + SvREFCNT(ssv) = 0; /* Will be upped to 1 by Perl_sharedsv_associate */ + sv_upgrade(ssv, SvTYPE(sv)); + CALLER_CONTEXT; + Perl_sharedsv_associate(aTHX_ sv, ssv); + return (ssv); +} + + +/* Given a shared SV, create and return an associated private SV. + * Assumes lock is held. + */ +STATIC SV * +S_sharedsv_new_private(pTHX_ SV *ssv) +{ + SV *sv; + + assert(PL_sharedsv_lock.owner == aTHX); + assert(aTHX != PL_sharedsv_space); + + sv = newSV(0); + sv_upgrade(sv, SvTYPE(ssv)); + Perl_sharedsv_associate(aTHX_ sv, ssv); + return (sv); +} + + +/* A threadsafe version of SvREFCNT_dec(ssv) */ + +STATIC void +S_sharedsv_dec(pTHX_ SV* ssv) +{ + if (! ssv) + return; + ENTER_LOCK; + if (SvREFCNT(ssv) > 1) { + /* No side effects, so can do it lightweight */ + SvREFCNT_dec(ssv); + } else { + dTHXc; + SHARED_CONTEXT; + SvREFCNT_dec(ssv); + CALLER_CONTEXT; + } + LEAVE_LOCK; +} + + +/* Implements Perl-level share() and :shared */ + +void +Perl_sharedsv_share(pTHX_ SV *sv) +{ + switch(SvTYPE(sv)) { + case SVt_PVGV: + Perl_croak(aTHX_ "Cannot share globs yet"); + break; + + case SVt_PVCV: + Perl_croak(aTHX_ "Cannot share subs yet"); + break; + + default: + ENTER_LOCK; + (void) S_sharedsv_new_shared(aTHX_ sv); + LEAVE_LOCK; + SvSETMAGIC(sv); + break; + } +} + + +#ifdef WIN32 +/* Number of milliseconds from 1/1/1601 to 1/1/1970 */ +#define EPOCH_BIAS 11644473600000. + +/* Returns relative time in milliseconds. (Adapted from Time::HiRes.) */ +STATIC DWORD +S_abs_2_rel_milli(double abs) +{ + double rel; + + /* Get current time (in units of 100 nanoseconds since 1/1/1601) */ + union { + FILETIME ft; + __int64 i64; /* 'signed' to keep compilers happy */ + } now; + + GetSystemTimeAsFileTime(&now.ft); + + /* Relative time in milliseconds */ + rel = (abs * 1000.) - (((double)now.i64 / 10000.) - EPOCH_BIAS); + if (rel <= 0.0) { + return (0); + } + return (DWORD)rel; +} + +#else +# if defined(OS2) +# define ABS2RELMILLI(abs) \ + do { \ + abs -= (double)time(NULL); \ + if (abs > 0) { abs *= 1000; } \ + else { abs = 0; } \ + } while (0) +# endif /* OS2 */ +#endif /* WIN32 */ + +/* Do OS-specific condition timed wait */ + +bool +Perl_sharedsv_cond_timedwait(perl_cond *cond, perl_mutex *mut, double abs) +{ +#if defined(NETWARE) || defined(FAKE_THREADS) || defined(I_MACH_CTHREADS) + Perl_croak_nocontext("cond_timedwait not supported on this platform"); +#else +# ifdef WIN32 + int got_it = 0; + + cond->waiters++; + MUTEX_UNLOCK(mut); + /* See comments in win32/win32thread.h COND_WAIT vis-a-vis race */ + switch (WaitForSingleObject(cond->sem, S_abs_2_rel_milli(abs))) { + case WAIT_OBJECT_0: got_it = 1; break; + case WAIT_TIMEOUT: break; + default: + /* WAIT_FAILED? WAIT_ABANDONED? others? */ + Perl_croak_nocontext("panic: cond_timedwait (%ld)",GetLastError()); + break; + } + MUTEX_LOCK(mut); + cond->waiters--; + return (got_it); +# else +# ifdef OS2 + int rc, got_it = 0; + STRLEN n_a; + + ABS2RELMILLI(abs); + + if ((rc = DosResetEventSem(*cond,&n_a)) && (rc != ERROR_ALREADY_RESET)) + Perl_rc = rc, croak_with_os2error("panic: cond_timedwait-reset"); + MUTEX_UNLOCK(mut); + if (CheckOSError(DosWaitEventSem(*cond,abs)) + && (rc != ERROR_INTERRUPT)) + croak_with_os2error("panic: cond_timedwait"); + if (rc == ERROR_INTERRUPT) errno = EINTR; + MUTEX_LOCK(mut); + return (got_it); +# else /* Hope you're I_PTHREAD! */ + struct timespec ts; + int got_it = 0; + + ts.tv_sec = (long)abs; + abs -= (NV)ts.tv_sec; + ts.tv_nsec = (long)(abs * 1000000000.0); + + switch (pthread_cond_timedwait(cond, mut, &ts)) { + case 0: got_it = 1; break; + case ETIMEDOUT: break; +#ifdef OEMVS + case -1: + if (errno == ETIMEDOUT || errno == EAGAIN) + break; +#endif + default: + Perl_croak_nocontext("panic: cond_timedwait"); + break; + } + return (got_it); +# endif /* OS2 */ +# endif /* WIN32 */ +#endif /* NETWARE || FAKE_THREADS || I_MACH_CTHREADS */ +} + + +/* Given a shared RV, copy it's value to a private RV, also copying the + * object status of the referent. + * If the private side is already an appropriate RV->SV combination, keep + * it if possible. + */ +STATIC void +S_get_RV(pTHX_ SV *sv, SV *ssv) { + SV *sobj = SvRV(ssv); + SV *obj; + if (! (SvROK(sv) && + ((obj = SvRV(sv))) && + (Perl_sharedsv_find(aTHX_ obj) == sobj) && + (SvTYPE(obj) == SvTYPE(sobj)))) + { + /* Can't reuse obj */ + if (SvROK(sv)) { + SvREFCNT_dec(SvRV(sv)); + } else { + assert(SvTYPE(sv) >= SVt_RV); + sv_setsv_nomg(sv, &PL_sv_undef); + SvROK_on(sv); + } + obj = S_sharedsv_new_private(aTHX_ SvRV(ssv)); + SvRV_set(sv, obj); + } + + if (SvOBJECT(obj)) { + /* Remove any old blessing */ + SvREFCNT_dec(SvSTASH(obj)); + SvOBJECT_off(obj); + } + if (SvOBJECT(sobj)) { + /* Add any new old blessing */ + STRLEN len; + char* stash_ptr = SvPV((SV*) SvSTASH(sobj), len); + HV* stash = gv_stashpvn(stash_ptr, len, TRUE); + SvOBJECT_on(obj); + SvSTASH_set(obj, (HV*)SvREFCNT_inc(stash)); + } +} + + +/* ------------ PERL_MAGIC_shared_scalar(n) functions -------------- */ + +/* Get magic for PERL_MAGIC_shared_scalar(n) */ + +int +sharedsv_scalar_mg_get(pTHX_ SV *sv, MAGIC *mg) +{ + SV *ssv = (SV *) mg->mg_ptr; + assert(ssv); + + ENTER_LOCK; + if (SvROK(ssv)) { + S_get_RV(aTHX_ sv, ssv); + /* Look ahead for refs of refs */ + if (SvROK(SvRV(ssv))) { + SvROK_on(SvRV(sv)); + S_get_RV(aTHX_ SvRV(sv), SvRV(ssv)); + } + } else { + sv_setsv_nomg(sv, ssv); + } + LEAVE_LOCK; + return (0); +} + +/* Copy the contents of a private SV to a shared SV. + * Used by various mg_set()-type functions. + * Assumes lock is held. + */ +void +sharedsv_scalar_store(pTHX_ SV *sv, SV *ssv) +{ + dTHXc; + bool allowed = TRUE; + + assert(PL_sharedsv_lock.owner == aTHX); + if (SvROK(sv)) { + SV *obj = SvRV(sv); + SV *sobj = Perl_sharedsv_find(aTHX_ obj); + if (sobj) { + SHARED_CONTEXT; + (void)SvUPGRADE(ssv, SVt_RV); + sv_setsv_nomg(ssv, &PL_sv_undef); + + SvRV_set(ssv, SvREFCNT_inc(sobj)); + SvROK_on(ssv); + if (SvOBJECT(sobj)) { + /* Remove any old blessing */ + SvREFCNT_dec(SvSTASH(sobj)); + SvOBJECT_off(sobj); + } + if (SvOBJECT(obj)) { + SV* fake_stash = newSVpv(HvNAME_get(SvSTASH(obj)),0); + SvOBJECT_on(sobj); + SvSTASH_set(sobj, (HV*)fake_stash); + } + CALLER_CONTEXT; + } else { + allowed = FALSE; + } + } else { + SvTEMP_off(sv); + SHARED_CONTEXT; + sv_setsv_nomg(ssv, sv); + if (SvOBJECT(ssv)) { + /* Remove any old blessing */ + SvREFCNT_dec(SvSTASH(ssv)); + SvOBJECT_off(ssv); + } + if (SvOBJECT(sv)) { + SV* fake_stash = newSVpv(HvNAME_get(SvSTASH(sv)),0); + SvOBJECT_on(ssv); + SvSTASH_set(ssv, (HV*)fake_stash); + } + CALLER_CONTEXT; + } + if (!allowed) { + Perl_croak(aTHX_ "Invalid value for shared scalar"); + } +} + +/* Set magic for PERL_MAGIC_shared_scalar(n) */ + +int +sharedsv_scalar_mg_set(pTHX_ SV *sv, MAGIC *mg) +{ + SV *ssv = (SV*)(mg->mg_ptr); + assert(ssv); + ENTER_LOCK; + if (SvTYPE(ssv) < SvTYPE(sv)) { + dTHXc; + SHARED_CONTEXT; + sv_upgrade(ssv, SvTYPE(sv)); + CALLER_CONTEXT; + } + sharedsv_scalar_store(aTHX_ sv, ssv); + LEAVE_LOCK; + return (0); +} + +/* Free magic for PERL_MAGIC_shared_scalar(n) */ + +int +sharedsv_scalar_mg_free(pTHX_ SV *sv, MAGIC *mg) +{ + S_sharedsv_dec(aTHX_ (SV*)mg->mg_ptr); + return (0); +} + +/* + * Called during cloning of PERL_MAGIC_shared_scalar(n) magic in new thread + */ +int +sharedsv_scalar_mg_dup(pTHX_ MAGIC *mg, CLONE_PARAMS *param) +{ + SvREFCNT_inc_void(mg->mg_ptr); + return (0); +} + +#ifdef MGf_LOCAL +/* + * Called during local $shared + */ +int +sharedsv_scalar_mg_local(pTHX_ SV* nsv, MAGIC *mg) +{ + MAGIC *nmg; + SV *ssv = (SV *) mg->mg_ptr; + if (ssv) { + ENTER_LOCK; + SvREFCNT_inc_void(ssv); + LEAVE_LOCK; + } + nmg = sv_magicext(nsv, mg->mg_obj, mg->mg_type, mg->mg_virtual, + mg->mg_ptr, mg->mg_len); + nmg->mg_flags = mg->mg_flags; + nmg->mg_private = mg->mg_private; + + return (0); +} +#endif + +MGVTBL sharedsv_scalar_vtbl = { + sharedsv_scalar_mg_get, /* get */ + sharedsv_scalar_mg_set, /* set */ + 0, /* len */ + 0, /* clear */ + sharedsv_scalar_mg_free, /* free */ + 0, /* copy */ + sharedsv_scalar_mg_dup, /* dup */ +#ifdef MGf_LOCAL + sharedsv_scalar_mg_local, /* local */ +#endif +}; + +/* ------------ PERL_MAGIC_tiedelem(p) functions -------------- */ + +/* Get magic for PERL_MAGIC_tiedelem(p) */ + +int +sharedsv_elem_mg_FETCH(pTHX_ SV *sv, MAGIC *mg) +{ + dTHXc; + SV *saggregate = S_sharedsv_from_obj(aTHX_ mg->mg_obj); + SV** svp; + + ENTER_LOCK; + if (SvTYPE(saggregate) == SVt_PVAV) { + assert ( mg->mg_ptr == 0 ); + SHARED_CONTEXT; + svp = av_fetch((AV*) saggregate, mg->mg_len, 0); + } else { + char *key = mg->mg_ptr; + I32 len = mg->mg_len; + assert ( mg->mg_ptr != 0 ); + if (mg->mg_len == HEf_SVKEY) { + STRLEN slen; + key = SvPV((SV *)mg->mg_ptr, slen); + len = slen; + if (SvUTF8((SV *)mg->mg_ptr)) { + len = -len; + } + } + SHARED_CONTEXT; + svp = hv_fetch((HV*) saggregate, key, len, 0); + } + CALLER_CONTEXT; + if (svp) { + /* Exists in the array */ + if (SvROK(*svp)) { + S_get_RV(aTHX_ sv, *svp); + /* Look ahead for refs of refs */ + if (SvROK(SvRV(*svp))) { + SvROK_on(SvRV(sv)); + S_get_RV(aTHX_ SvRV(sv), SvRV(*svp)); + } + } else { + /* $ary->[elem] or $ary->{elem} is a scalar */ + Perl_sharedsv_associate(aTHX_ sv, *svp); + sv_setsv(sv, *svp); + } + } else { + /* Not in the array */ + sv_setsv(sv, &PL_sv_undef); + } + LEAVE_LOCK; + return (0); +} + +/* Set magic for PERL_MAGIC_tiedelem(p) */ + +int +sharedsv_elem_mg_STORE(pTHX_ SV *sv, MAGIC *mg) +{ + dTHXc; + SV *saggregate = S_sharedsv_from_obj(aTHX_ mg->mg_obj); + SV **svp; + /* Theory - SV itself is magically shared - and we have ordered the + magic such that by the time we get here it has been stored + to its shared counterpart + */ + ENTER_LOCK; + assert(saggregate); + if (SvTYPE(saggregate) == SVt_PVAV) { + assert ( mg->mg_ptr == 0 ); + SHARED_CONTEXT; + svp = av_fetch((AV*) saggregate, mg->mg_len, 1); + } else { + char *key = mg->mg_ptr; + I32 len = mg->mg_len; + assert ( mg->mg_ptr != 0 ); + if (mg->mg_len == HEf_SVKEY) { + STRLEN slen; + key = SvPV((SV *)mg->mg_ptr, slen); + len = slen; + if (SvUTF8((SV *)mg->mg_ptr)) { + len = -len; + } + } + SHARED_CONTEXT; + svp = hv_fetch((HV*) saggregate, key, len, 1); + } + CALLER_CONTEXT; + Perl_sharedsv_associate(aTHX_ sv, *svp); + sharedsv_scalar_store(aTHX_ sv, *svp); + LEAVE_LOCK; + return (0); +} + +/* Clear magic for PERL_MAGIC_tiedelem(p) */ + +int +sharedsv_elem_mg_DELETE(pTHX_ SV *sv, MAGIC *mg) +{ + dTHXc; + MAGIC *shmg; + SV *saggregate = S_sharedsv_from_obj(aTHX_ mg->mg_obj); + ENTER_LOCK; + sharedsv_elem_mg_FETCH(aTHX_ sv, mg); + if ((shmg = mg_find(sv, PERL_MAGIC_shared_scalar))) + sharedsv_scalar_mg_get(aTHX_ sv, shmg); + if (SvTYPE(saggregate) == SVt_PVAV) { + SHARED_CONTEXT; + av_delete((AV*) saggregate, mg->mg_len, G_DISCARD); + } else { + char *key = mg->mg_ptr; + I32 len = mg->mg_len; + assert ( mg->mg_ptr != 0 ); + if (mg->mg_len == HEf_SVKEY) { + STRLEN slen; + key = SvPV((SV *)mg->mg_ptr, slen); + len = slen; + if (SvUTF8((SV *)mg->mg_ptr)) { + len = -len; + } + } + SHARED_CONTEXT; + hv_delete((HV*) saggregate, key, len, G_DISCARD); + } + CALLER_CONTEXT; + LEAVE_LOCK; + return (0); +} + +/* Called during cloning of PERL_MAGIC_tiedelem(p) magic in new + * thread */ + +int +sharedsv_elem_mg_dup(pTHX_ MAGIC *mg, CLONE_PARAMS *param) +{ + SvREFCNT_inc_void(S_sharedsv_from_obj(aTHX_ mg->mg_obj)); + assert(mg->mg_flags & MGf_DUP); + return (0); +} + +MGVTBL sharedsv_elem_vtbl = { + sharedsv_elem_mg_FETCH, /* get */ + sharedsv_elem_mg_STORE, /* set */ + 0, /* len */ + sharedsv_elem_mg_DELETE, /* clear */ + 0, /* free */ + 0, /* copy */ + sharedsv_elem_mg_dup, /* dup */ +#ifdef MGf_LOCAL + 0, /* local */ +#endif +}; + +/* ------------ PERL_MAGIC_tied(P) functions -------------- */ + +/* Len magic for PERL_MAGIC_tied(P) */ + +U32 +sharedsv_array_mg_FETCHSIZE(pTHX_ SV *sv, MAGIC *mg) +{ + dTHXc; + SV *ssv = (SV *) mg->mg_ptr; + U32 val; + SHARED_EDIT; + if (SvTYPE(ssv) == SVt_PVAV) { + val = av_len((AV*) ssv); + } else { + /* Not actually defined by tie API but ... */ + val = HvKEYS((HV*) ssv); + } + SHARED_RELEASE; + return (val); +} + +/* Clear magic for PERL_MAGIC_tied(P) */ + +int +sharedsv_array_mg_CLEAR(pTHX_ SV *sv, MAGIC *mg) +{ + dTHXc; + SV *ssv = (SV *) mg->mg_ptr; + SHARED_EDIT; + if (SvTYPE(ssv) == SVt_PVAV) { + av_clear((AV*) ssv); + } else { + hv_clear((HV*) ssv); + } + SHARED_RELEASE; + return (0); +} + +/* Free magic for PERL_MAGIC_tied(P) */ + +int +sharedsv_array_mg_free(pTHX_ SV *sv, MAGIC *mg) +{ + S_sharedsv_dec(aTHX_ (SV*)mg->mg_ptr); + return (0); +} + +/* + * Copy magic for PERL_MAGIC_tied(P) + * This is called when perl is about to access an element of + * the array - + */ +#if PERL_VERSION >= 11 +int +sharedsv_array_mg_copy(pTHX_ SV *sv, MAGIC* mg, + SV *nsv, const char *name, I32 namlen) +#else +int +sharedsv_array_mg_copy(pTHX_ SV *sv, MAGIC* mg, + SV *nsv, const char *name, int namlen) +#endif +{ + MAGIC *nmg = sv_magicext(nsv,mg->mg_obj, + toLOWER(mg->mg_type),&sharedsv_elem_vtbl, + name, namlen); + nmg->mg_flags |= MGf_DUP; + return (1); +} + +/* Called during cloning of PERL_MAGIC_tied(P) magic in new thread */ + +int +sharedsv_array_mg_dup(pTHX_ MAGIC *mg, CLONE_PARAMS *param) +{ + SvREFCNT_inc_void((SV*)mg->mg_ptr); + assert(mg->mg_flags & MGf_DUP); + return (0); +} + +MGVTBL sharedsv_array_vtbl = { + 0, /* get */ + 0, /* set */ + sharedsv_array_mg_FETCHSIZE,/* len */ + sharedsv_array_mg_CLEAR, /* clear */ + sharedsv_array_mg_free, /* free */ + sharedsv_array_mg_copy, /* copy */ + sharedsv_array_mg_dup, /* dup */ +#ifdef MGf_LOCAL + 0, /* local */ +#endif +}; + + +/* Recursively unlocks a shared sv. */ + +void +Perl_sharedsv_unlock(pTHX_ SV *ssv) +{ + user_lock *ul = S_get_userlock(aTHX_ ssv, 0); + assert(ul); + recursive_lock_release(aTHX_ &ul->lock); +} + + +/* Recursive locks on a sharedsv. + * Locks are dynamically scoped at the level of the first lock. + */ +void +Perl_sharedsv_lock(pTHX_ SV *ssv) +{ + user_lock *ul; + if (! ssv) + return; + ul = S_get_userlock(aTHX_ ssv, 1); + recursive_lock_acquire(aTHX_ &ul->lock, __FILE__, __LINE__); +} + +/* Handles calls from lock() builtin via PL_lockhook */ + +void +Perl_sharedsv_locksv(pTHX_ SV *sv) +{ + SV *ssv; + + if (SvROK(sv)) + sv = SvRV(sv); + ssv = Perl_sharedsv_find(aTHX_ sv); + if (!ssv) + croak("lock can only be used on shared values"); + Perl_sharedsv_lock(aTHX_ ssv); +} + + +/* Can a shared object be destroyed? + * True if not a shared, + * or if detroying last proxy on a shared object + */ +#ifdef PL_destroyhook +bool +Perl_shared_object_destroy(pTHX_ SV *sv) +{ + SV *ssv; + + if (SvROK(sv)) + sv = SvRV(sv); + ssv = Perl_sharedsv_find(aTHX_ sv); + return (!ssv || (SvREFCNT(ssv) <= 1)); +} +#endif + + +/* Saves a space for keeping SVs wider than an interpreter. */ + +void +Perl_sharedsv_init(pTHX) +{ + dTHXc; + /* This pair leaves us in shared context ... */ + PL_sharedsv_space = perl_alloc(); + perl_construct(PL_sharedsv_space); + CALLER_CONTEXT; + recursive_lock_init(aTHX_ &PL_sharedsv_lock); + PL_lockhook = &Perl_sharedsv_locksv; + PL_sharehook = &Perl_sharedsv_share; +#ifdef PL_destroyhook + PL_destroyhook = &Perl_shared_object_destroy; +#endif +} + +#endif /* USE_ITHREADS */ + +MODULE = threads::shared PACKAGE = threads::shared::tie + +PROTOTYPES: DISABLE + +#ifdef USE_ITHREADS + +void +PUSH(SV *obj, ...) + CODE: + dTHXc; + SV *sobj = S_sharedsv_from_obj(aTHX_ obj); + int i; + for (i = 1; i < items; i++) { + SV* tmp = newSVsv(ST(i)); + SV *stmp; + ENTER_LOCK; + stmp = S_sharedsv_new_shared(aTHX_ tmp); + sharedsv_scalar_store(aTHX_ tmp, stmp); + SHARED_CONTEXT; + av_push((AV*) sobj, stmp); + SvREFCNT_inc_void(stmp); + SHARED_RELEASE; + SvREFCNT_dec(tmp); + } + + +void +UNSHIFT(SV *obj, ...) + CODE: + dTHXc; + SV *sobj = S_sharedsv_from_obj(aTHX_ obj); + int i; + ENTER_LOCK; + SHARED_CONTEXT; + av_unshift((AV*)sobj, items - 1); + CALLER_CONTEXT; + for (i = 1; i < items; i++) { + SV *tmp = newSVsv(ST(i)); + SV *stmp = S_sharedsv_new_shared(aTHX_ tmp); + sharedsv_scalar_store(aTHX_ tmp, stmp); + SHARED_CONTEXT; + av_store((AV*) sobj, i - 1, stmp); + SvREFCNT_inc_void(stmp); + CALLER_CONTEXT; + SvREFCNT_dec(tmp); + } + LEAVE_LOCK; + + +void +POP(SV *obj) + CODE: + dTHXc; + SV *sobj = S_sharedsv_from_obj(aTHX_ obj); + SV* ssv; + ENTER_LOCK; + SHARED_CONTEXT; + ssv = av_pop((AV*)sobj); + CALLER_CONTEXT; + ST(0) = sv_newmortal(); + Perl_sharedsv_associate(aTHX_ ST(0), ssv); + SvREFCNT_dec(ssv); + LEAVE_LOCK; + /* XSRETURN(1); - implied */ + + +void +SHIFT(SV *obj) + CODE: + dTHXc; + SV *sobj = S_sharedsv_from_obj(aTHX_ obj); + SV* ssv; + ENTER_LOCK; + SHARED_CONTEXT; + ssv = av_shift((AV*)sobj); + CALLER_CONTEXT; + ST(0) = sv_newmortal(); + Perl_sharedsv_associate(aTHX_ ST(0), ssv); + SvREFCNT_dec(ssv); + LEAVE_LOCK; + /* XSRETURN(1); - implied */ + + +void +EXTEND(SV *obj, IV count) + CODE: + dTHXc; + SV *sobj = S_sharedsv_from_obj(aTHX_ obj); + SHARED_EDIT; + av_extend((AV*)sobj, count); + SHARED_RELEASE; + + +void +STORESIZE(SV *obj,IV count) + CODE: + dTHXc; + SV *sobj = S_sharedsv_from_obj(aTHX_ obj); + SHARED_EDIT; + av_fill((AV*) sobj, count); + SHARED_RELEASE; + + +void +EXISTS(SV *obj, SV *index) + CODE: + dTHXc; + SV *sobj = S_sharedsv_from_obj(aTHX_ obj); + bool exists; + if (SvTYPE(sobj) == SVt_PVAV) { + SHARED_EDIT; + exists = av_exists((AV*) sobj, SvIV(index)); + } else { + I32 len; + STRLEN slen; + char *key = SvPVutf8(index, slen); + len = slen; + if (SvUTF8(index)) { + len = -len; + } + SHARED_EDIT; + exists = hv_exists((HV*) sobj, key, len); + } + SHARED_RELEASE; + ST(0) = (exists) ? &PL_sv_yes : &PL_sv_no; + /* XSRETURN(1); - implied */ + + +void +FIRSTKEY(SV *obj) + CODE: + dTHXc; + SV *sobj = S_sharedsv_from_obj(aTHX_ obj); + char* key = NULL; + I32 len = 0; + HE* entry; + ENTER_LOCK; + SHARED_CONTEXT; + hv_iterinit((HV*) sobj); + entry = hv_iternext((HV*) sobj); + if (entry) { + I32 utf8 = HeKUTF8(entry); + key = hv_iterkey(entry,&len); + CALLER_CONTEXT; + ST(0) = sv_2mortal(newSVpvn_utf8(key, len, utf8)); + } else { + CALLER_CONTEXT; + ST(0) = &PL_sv_undef; + } + LEAVE_LOCK; + /* XSRETURN(1); - implied */ + + +void +NEXTKEY(SV *obj, SV *oldkey) + CODE: + dTHXc; + SV *sobj = S_sharedsv_from_obj(aTHX_ obj); + char* key = NULL; + I32 len = 0; + HE* entry; + + PERL_UNUSED_VAR(oldkey); + + ENTER_LOCK; + SHARED_CONTEXT; + entry = hv_iternext((HV*) sobj); + if (entry) { + I32 utf8 = HeKUTF8(entry); + key = hv_iterkey(entry,&len); + CALLER_CONTEXT; + ST(0) = sv_2mortal(newSVpvn_utf8(key, len, utf8)); + } else { + CALLER_CONTEXT; + ST(0) = &PL_sv_undef; + } + LEAVE_LOCK; + /* XSRETURN(1); - implied */ + + +MODULE = threads::shared PACKAGE = threads::shared + +PROTOTYPES: ENABLE + +void +_id(SV *myref) + PROTOTYPE: \[$@%] + PREINIT: + SV *ssv; + CODE: + myref = SvRV(myref); + if (SvMAGICAL(myref)) + mg_get(myref); + if (SvROK(myref)) + myref = SvRV(myref); + ssv = Perl_sharedsv_find(aTHX_ myref); + if (! ssv) + XSRETURN_UNDEF; + ST(0) = sv_2mortal(newSVuv(PTR2UV(ssv))); + /* XSRETURN(1); - implied */ + + +void +_refcnt(SV *myref) + PROTOTYPE: \[$@%] + PREINIT: + SV *ssv; + CODE: + myref = SvRV(myref); + if (SvROK(myref)) + myref = SvRV(myref); + ssv = Perl_sharedsv_find(aTHX_ myref); + if (! ssv) { + if (ckWARN(WARN_THREADS)) { + Perl_warner(aTHX_ packWARN(WARN_THREADS), + "%" SVf " is not shared", ST(0)); + } + XSRETURN_UNDEF; + } + ST(0) = sv_2mortal(newSViv(SvREFCNT(ssv))); + /* XSRETURN(1); - implied */ + + +void +share(SV *myref) + PROTOTYPE: \[$@%] + CODE: + if (! SvROK(myref)) + Perl_croak(aTHX_ "Argument to share needs to be passed as ref"); + myref = SvRV(myref); + if (SvROK(myref)) + myref = SvRV(myref); + Perl_sharedsv_share(aTHX_ myref); + ST(0) = sv_2mortal(newRV_inc(myref)); + /* XSRETURN(1); - implied */ + + +void +cond_wait(SV *ref_cond, SV *ref_lock = 0) + PROTOTYPE: \[$@%];\[$@%] + PREINIT: + SV *ssv; + perl_cond* user_condition; + int locks; + user_lock *ul; + CODE: + if (!SvROK(ref_cond)) + Perl_croak(aTHX_ "Argument to cond_wait needs to be passed as ref"); + ref_cond = SvRV(ref_cond); + if (SvROK(ref_cond)) + ref_cond = SvRV(ref_cond); + ssv = Perl_sharedsv_find(aTHX_ ref_cond); + if (! ssv) + Perl_croak(aTHX_ "cond_wait can only be used on shared values"); + ul = S_get_userlock(aTHX_ ssv, 1); + + user_condition = &ul->user_cond; + if (ref_lock && (ref_cond != ref_lock)) { + if (!SvROK(ref_lock)) + Perl_croak(aTHX_ "cond_wait lock needs to be passed as ref"); + ref_lock = SvRV(ref_lock); + if (SvROK(ref_lock)) ref_lock = SvRV(ref_lock); + ssv = Perl_sharedsv_find(aTHX_ ref_lock); + if (! ssv) + Perl_croak(aTHX_ "cond_wait lock must be a shared value"); + ul = S_get_userlock(aTHX_ ssv, 1); + } + if (ul->lock.owner != aTHX) + croak("You need a lock before you can cond_wait"); + + /* Stealing the members of the lock object worries me - NI-S */ + MUTEX_LOCK(&ul->lock.mutex); + ul->lock.owner = NULL; + locks = ul->lock.locks; + ul->lock.locks = 0; + + /* Since we are releasing the lock here, we need to tell other + * people that it is ok to go ahead and use it */ + COND_SIGNAL(&ul->lock.cond); + COND_WAIT(user_condition, &ul->lock.mutex); + while (ul->lock.owner != NULL) { + /* OK -- must reacquire the lock */ + COND_WAIT(&ul->lock.cond, &ul->lock.mutex); + } + ul->lock.owner = aTHX; + ul->lock.locks = locks; + MUTEX_UNLOCK(&ul->lock.mutex); + + +int +cond_timedwait(SV *ref_cond, double abs, SV *ref_lock = 0) + PROTOTYPE: \[$@%]$;\[$@%] + PREINIT: + SV *ssv; + perl_cond* user_condition; + int locks; + user_lock *ul; + CODE: + if (! SvROK(ref_cond)) + Perl_croak(aTHX_ "Argument to cond_timedwait needs to be passed as ref"); + ref_cond = SvRV(ref_cond); + if (SvROK(ref_cond)) + ref_cond = SvRV(ref_cond); + ssv = Perl_sharedsv_find(aTHX_ ref_cond); + if (! ssv) + Perl_croak(aTHX_ "cond_timedwait can only be used on shared values"); + ul = S_get_userlock(aTHX_ ssv, 1); + + user_condition = &ul->user_cond; + if (ref_lock && (ref_cond != ref_lock)) { + if (! SvROK(ref_lock)) + Perl_croak(aTHX_ "cond_timedwait lock needs to be passed as ref"); + ref_lock = SvRV(ref_lock); + if (SvROK(ref_lock)) ref_lock = SvRV(ref_lock); + ssv = Perl_sharedsv_find(aTHX_ ref_lock); + if (! ssv) + Perl_croak(aTHX_ "cond_timedwait lock must be a shared value"); + ul = S_get_userlock(aTHX_ ssv, 1); + } + if (ul->lock.owner != aTHX) + Perl_croak(aTHX_ "You need a lock before you can cond_wait"); + + MUTEX_LOCK(&ul->lock.mutex); + ul->lock.owner = NULL; + locks = ul->lock.locks; + ul->lock.locks = 0; + /* Since we are releasing the lock here, we need to tell other + * people that it is ok to go ahead and use it */ + COND_SIGNAL(&ul->lock.cond); + RETVAL = Perl_sharedsv_cond_timedwait(user_condition, &ul->lock.mutex, abs); + while (ul->lock.owner != NULL) { + /* OK -- must reacquire the lock... */ + COND_WAIT(&ul->lock.cond, &ul->lock.mutex); + } + ul->lock.owner = aTHX; + ul->lock.locks = locks; + MUTEX_UNLOCK(&ul->lock.mutex); + + if (RETVAL == 0) + XSRETURN_UNDEF; + OUTPUT: + RETVAL + + +void +cond_signal(SV *myref) + PROTOTYPE: \[$@%] + PREINIT: + SV *ssv; + user_lock *ul; + CODE: + if (! SvROK(myref)) + Perl_croak(aTHX_ "Argument to cond_signal needs to be passed as ref"); + myref = SvRV(myref); + if (SvROK(myref)) + myref = SvRV(myref); + ssv = Perl_sharedsv_find(aTHX_ myref); + if (! ssv) + Perl_croak(aTHX_ "cond_signal can only be used on shared values"); + ul = S_get_userlock(aTHX_ ssv, 1); + if (ckWARN(WARN_THREADS) && ul->lock.owner != aTHX) { + Perl_warner(aTHX_ packWARN(WARN_THREADS), + "cond_signal() called on unlocked variable"); + } + COND_SIGNAL(&ul->user_cond); + + +void +cond_broadcast(SV *myref) + PROTOTYPE: \[$@%] + PREINIT: + SV *ssv; + user_lock *ul; + CODE: + if (! SvROK(myref)) + Perl_croak(aTHX_ "Argument to cond_broadcast needs to be passed as ref"); + myref = SvRV(myref); + if (SvROK(myref)) + myref = SvRV(myref); + ssv = Perl_sharedsv_find(aTHX_ myref); + if (! ssv) + Perl_croak(aTHX_ "cond_broadcast can only be used on shared values"); + ul = S_get_userlock(aTHX_ ssv, 1); + if (ckWARN(WARN_THREADS) && ul->lock.owner != aTHX) { + Perl_warner(aTHX_ packWARN(WARN_THREADS), + "cond_broadcast() called on unlocked variable"); + } + COND_BROADCAST(&ul->user_cond); + + +void +bless(SV* myref, ...); + PROTOTYPE: $;$ + PREINIT: + HV* stash; + SV *ssv; + CODE: + if (items == 1) { + stash = CopSTASH(PL_curcop); + } else { + SV* classname = ST(1); + STRLEN len; + char *ptr; + + if (classname && + ! SvGMAGICAL(classname) && + ! SvAMAGIC(classname) && + SvROK(classname)) + { + Perl_croak(aTHX_ "Attempt to bless into a reference"); + } + ptr = SvPV(classname, len); + if (ckWARN(WARN_MISC) && len == 0) { + Perl_warner(aTHX_ packWARN(WARN_MISC), + "Explicit blessing to '' (assuming package main)"); + } + stash = gv_stashpvn(ptr, len, TRUE); + } + SvREFCNT_inc_void(myref); + (void)sv_bless(myref, stash); + ST(0) = sv_2mortal(myref); + ssv = Perl_sharedsv_find(aTHX_ myref); + if (ssv) { + dTHXc; + ENTER_LOCK; + SHARED_CONTEXT; + { + SV* fake_stash = newSVpv(HvNAME_get(stash), 0); + (void)sv_bless(ssv, (HV*)fake_stash); + } + CALLER_CONTEXT; + LEAVE_LOCK; + } + /* XSRETURN(1); - implied */ + +#endif /* USE_ITHREADS */ + +BOOT: +{ +#ifdef USE_ITHREADS + Perl_sharedsv_init(aTHX); +#endif /* USE_ITHREADS */ +} diff --git a/dist/threads-shared/t/0nothread.t b/dist/threads-shared/t/0nothread.t new file mode 100644 index 0000000000..7609fbee1e --- /dev/null +++ b/dist/threads-shared/t/0nothread.t @@ -0,0 +1,78 @@ +use strict; +use warnings; + +use Test::More (tests => 53); + +### Start of Testing ### + +my @array; +my %hash; + +sub hash +{ + my @val = @_; + is(keys %hash, 0, "hash empty"); + $hash{0} = $val[0]; + is(keys %hash,1, "Assign grows hash"); + is($hash{0},$val[0],"Value correct"); + $hash{2} = $val[2]; + is(keys %hash,2, "Assign grows hash"); + is($hash{0},$val[0],"Value correct"); + is($hash{2},$val[2],"Value correct"); + $hash{1} = $val[1]; + is(keys %hash,3,"Size correct"); + my @keys = keys %hash; + is(join(',',sort @keys),'0,1,2',"Keys correct"); + my @hval = @hash{0,1,2}; + is(join(',',@hval),join(',',@val),"Values correct"); + my $val = delete $hash{1}; + is($val,$val[1],"Delete value correct"); + is(keys %hash,2,"Size correct"); + while (my ($k,$v) = each %hash) { + is($v,$val[$k],"each works"); + } + %hash = (); + is(keys %hash,0,"Clear hash"); +} + +sub array +{ + my @val = @_; + is(@array, 0, "array empty"); + $array[0] = $val[0]; + is(@array,1, "Assign grows array"); + is($array[0],$val[0],"Value correct"); + unshift(@array,$val[2]); + is($array[0],$val[2],"Unshift worked"); + is($array[-1],$val[0],"-ve index"); + push(@array,$val[1]); + is($array[-1],$val[1],"Push worked"); + is(@array,3,"Size correct"); + is(shift(@array),$val[2],"Shift worked"); + is(@array,2,"Size correct"); + is(pop(@array),$val[1],"Pop worked"); + is(@array,1,"Size correct"); + @array = (); + is(@array,0,"Clear array"); +} + +ok((require threads::shared),"Require module"); + +if ($threads::shared::VERSION && ! $ENV{'PERL_CORE'}) { + diag('Testing threads::shared ' . $threads::shared::VERSION); +} + +array(24, [], 'Thing'); +hash(24, [], 'Thing'); + +threads::shared->import(); + +share(\@array); +array(24, 42, 'Thing'); + +share(\%hash); +hash(24, 42, 'Thing'); + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/av_refs.t b/dist/threads-shared/t/av_refs.t new file mode 100644 index 0000000000..8106e3253a --- /dev/null +++ b/dist/threads-shared/t/av_refs.t @@ -0,0 +1,95 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +sub ok { + my ($id, $ok, $name) = @_; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..14\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; +ok(1, 1, 'Loaded'); + +### Start of Testing ### + +my $sv; +share($sv); +$sv = "hi"; + +my @av; +share(@av); +push(@av, $sv); + +ok(2, $av[0] eq "hi", 'Array holds value'); + +push(@av, "foo"); +ok(3, $av[1] eq 'foo', 'Array holds 2nd value'); + +my $av = threads->create(sub { + my $av; + my @av2; + share($av); + share(@av2); + $av = \@av2; + push(@$av, "bar", \@av); + return ($av); +})->join(); + +ok(4,$av->[0] eq "bar", 'Thread added to array'); +ok(5,$av->[1]->[0] eq 'hi', 'Shared in shared'); + +threads->create(sub { $av[0] = "hihi" })->join(); +ok(6,$av->[1]->[0] eq 'hihi', 'Replaced shared in shared'); +ok(7, pop(@{$av->[1]}) eq "foo", 'Pop shared array'); +ok(8, scalar(@{$av->[1]}) == 1, 'Array size'); + +threads->create(sub { @$av = () })->join(); +threads->create(sub { ok(9, scalar @$av == 0, 'Array cleared in thread'); })->join(); + +threads->create(sub { + unshift(@$av, threads->create(sub { + my @array; + share(@array); + return (\@array); + })->join()); +})->join(); + +ok(10, ref($av->[0]) eq 'ARRAY', 'Array in array'); + +threads->create(sub { push @{$av->[0]}, \@av })->join(); +threads->create(sub { $av[0] = 'testtest'})->join(); +threads->create(sub { ok(11, $av->[0]->[0]->[0] eq 'testtest', 'Nested'); })->join(); + +ok(12, is_shared($sv), "Check for sharing"); +ok(13, is_shared(@av), "Check for sharing"); + +my $x :shared; +ok(14, is_shared($x), "Check for sharing"); + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/av_simple.t b/dist/threads-shared/t/av_simple.t new file mode 100644 index 0000000000..7fab9b2b76 --- /dev/null +++ b/dist/threads-shared/t/av_simple.t @@ -0,0 +1,135 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +sub ok { + my ($id, $ok, $name) = @_; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..44\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; +ok(1, 1, 'Loaded'); + +### Start of Testing ### + +my @foo; +share(@foo); +ok(2,1,"shared \@foo"); +$foo[0] = "hi"; +ok(3, $foo[0] eq 'hi', "Check assignment works"); +$foo[0] = "bar"; +ok(4, $foo[0] eq 'bar', "Check overwriting works"); +ok(5, !defined $foo[1], "Check undef value"); +$foo[2] = "test"; +ok(6, $foo[2] eq "test", "Check extending the array works"); +ok(7, !defined $foo[1], "Check undef value again"); +ok(8, scalar(@foo) == 3, "Check the length of the array"); +ok(9,$#foo == 2, "Check last element of array"); +threads->create(sub { $foo[0] = "thread1" })->join; +ok(10, $foo[0] eq "thread1", "Check that a value can be changed in another thread"); +push(@foo, "another value"); +ok(11, $foo[3] eq "another value", "Check that push works"); +push(@foo, 1,2,3); +ok(12, $foo[-1] == 3, "More push"); +ok(13, $foo[-2] == 2, "More push"); +ok(14, $foo[4] == 1, "More push"); +threads->create(sub { push @foo, "thread2" })->join(); +ok(15, $foo[7] eq "thread2", "Check push in another thread"); +unshift(@foo, "start"); +ok(16, $foo[0] eq "start", "Check unshift"); +unshift(@foo, 1,2,3); +ok(17, $foo[0] == 1, "Check multiple unshift"); +ok(18, $foo[1] == 2, "Check multiple unshift"); +ok(19, $foo[2] == 3, "Check multiple unshift"); +threads->create(sub { unshift @foo, "thread3" })->join(); +ok(20, $foo[0] eq "thread3", "Check unshift from another thread"); +my $var = pop(@foo); +ok(21, $var eq "thread2", "Check pop"); +threads->create(sub { my $foo = pop @foo; ok(22, $foo == 3, "Check pop works in a thread")})->join(); +$var = pop(@foo); +ok(23, $var == 2, "Check pop after thread"); +$var = shift(@foo); +ok(24, $var eq "thread3", "Check shift"); +threads->create(sub { my $foo = shift @foo; ok(25, $foo == 1, "Check shift works in a thread"); +})->join(); +$var = shift(@foo); +ok(26, $var == 2, "Check shift after thread"); +{ + my @foo2; + share @foo2; + my $empty = shift @foo2; + ok(27, !defined $empty, "Check shift on empty array"); + $empty = pop @foo2; + ok(28, !defined $empty, "Check pop on empty array"); +} +my $i = 0; +foreach my $var (@foo) { + $i++; +} +ok(29, scalar @foo == $i, "Check foreach"); +my $ref = \@foo; +ok(30, $ref->[0] == 3, "Check reference access"); +threads->create(sub { $ref->[0] = "thread4"})->join(); +ok(31, $ref->[0] eq "thread4", "Check that it works after another thread"); +undef($ref); +threads->create(sub { @foo = () })->join(); +ok(32, @foo == 0, "Check that array is empty"); +ok(33, exists($foo[0]) == 0, "Check that zero index doesn't index"); +@foo = ("sky"); +ok(34, exists($foo[0]) == 1, "Check that zero index exists now"); +ok(35, $foo[0] eq "sky", "And check that it also contains the right value"); +$#foo = 20; +$foo[20] = "sky"; +ok(36, delete($foo[20]) eq "sky", "Check delete works"); + +threads->create(sub { delete($foo[0])})->join(); +ok(37, !defined delete($foo[0]), "Check that delete works from a thread"); + +@foo = (1,2,3,4,5); + +{ + my ($t1,$t2) = @foo[2,3]; + ok(38, $t1 == 3, "Check slice"); + ok(39, $t2 == 4, "Check slice again"); + my @t1 = @foo[1...4]; + ok(40, $t1[0] == 2, "Check slice list"); + ok(41, $t1[2] == 4, "Check slice list 2"); + threads->create(sub { @foo[0,1] = ("hej","hop") })->join(); + ok(42,$foo[0] eq "hej", "Check slice assign"); +} +{ + eval { + my @t1 = splice(@foo,0,2,"hop", "hej"); + }; + ok(43, my $temp1 = $@ =~/Splice not implemented for shared arrays/, "Check that the warning message is correct for non splice"); +} + +ok(44, is_shared(@foo), "Check for sharing"); + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/blessed.t b/dist/threads-shared/t/blessed.t new file mode 100644 index 0000000000..2599423434 --- /dev/null +++ b/dist/threads-shared/t/blessed.t @@ -0,0 +1,139 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +sub ok { + my ($id, $ok, $name) = @_; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..37\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; +ok(1, 1, 'Loaded'); + +### Start of Testing ### + +my ($hobj, $aobj, $sobj) : shared; + +$hobj = &share({}); +$aobj = &share([]); +my $sref = \do{ my $x }; +share($sref); +$sobj = $sref; + +threads->create(sub { + # Bless objects + bless $hobj, 'foo'; + bless $aobj, 'bar'; + bless $sobj, 'baz'; + + # Add data to objects + $$aobj[0] = bless(&share({}), 'yin'); + $$aobj[1] = bless(&share([]), 'yang'); + $$aobj[2] = $sobj; + + $$hobj{'hash'} = bless(&share({}), 'yin'); + $$hobj{'array'} = bless(&share([]), 'yang'); + $$hobj{'scalar'} = $sobj; + + $$sobj = 3; + + # Test objects in child thread + ok(2, ref($hobj) eq 'foo', "hash blessing does work"); + ok(3, ref($aobj) eq 'bar', "array blessing does work"); + ok(4, ref($sobj) eq 'baz', "scalar blessing does work"); + ok(5, $$sobj eq '3', "scalar contents okay"); + + ok(6, ref($$aobj[0]) eq 'yin', "blessed hash in array"); + ok(7, ref($$aobj[1]) eq 'yang', "blessed array in array"); + ok(8, ref($$aobj[2]) eq 'baz', "blessed scalar in array"); + ok(9, ${$$aobj[2]} eq '3', "blessed scalar in array contents"); + + ok(10, ref($$hobj{'hash'}) eq 'yin', "blessed hash in hash"); + ok(11, ref($$hobj{'array'}) eq 'yang', "blessed array in hash"); + ok(12, ref($$hobj{'scalar'}) eq 'baz', "blessed scalar in hash"); + ok(13, ${$$hobj{'scalar'}} eq '3', "blessed scalar in hash contents"); + + })->join; + +# Test objects in parent thread +ok(14, ref($hobj) eq 'foo', "hash blessing does work"); +ok(15, ref($aobj) eq 'bar', "array blessing does work"); +ok(16, ref($sobj) eq 'baz', "scalar blessing does work"); +ok(17, $$sobj eq '3', "scalar contents okay"); + +ok(18, ref($$aobj[0]) eq 'yin', "blessed hash in array"); +ok(19, ref($$aobj[1]) eq 'yang', "blessed array in array"); +ok(20, ref($$aobj[2]) eq 'baz', "blessed scalar in array"); +ok(21, ${$$aobj[2]} eq '3', "blessed scalar in array contents"); + +ok(22, ref($$hobj{'hash'}) eq 'yin', "blessed hash in hash"); +ok(23, ref($$hobj{'array'}) eq 'yang', "blessed array in hash"); +ok(24, ref($$hobj{'scalar'}) eq 'baz', "blessed scalar in hash"); +ok(25, ${$$hobj{'scalar'}} eq '3', "blessed scalar in hash contents"); + +threads->create(sub { + # Rebless objects + bless $hobj, 'oof'; + bless $aobj, 'rab'; + bless $sobj, 'zab'; + + my $data = $$aobj[0]; + bless $data, 'niy'; + $$aobj[0] = $data; + $data = $$aobj[1]; + bless $data, 'gnay'; + $$aobj[1] = $data; + + $data = $$hobj{'hash'}; + bless $data, 'niy'; + $$hobj{'hash'} = $data; + $data = $$hobj{'array'}; + bless $data, 'gnay'; + $$hobj{'array'} = $data; + + $$sobj = 'test'; + })->join(); + +# Test reblessing +ok(26, ref($hobj) eq 'oof', "hash reblessing does work"); +ok(27, ref($aobj) eq 'rab', "array reblessing does work"); +ok(28, ref($sobj) eq 'zab', "scalar reblessing does work"); +ok(29, $$sobj eq 'test', "scalar contents okay"); + +ok(30, ref($$aobj[0]) eq 'niy', "reblessed hash in array"); +ok(31, ref($$aobj[1]) eq 'gnay', "reblessed array in array"); +ok(32, ref($$aobj[2]) eq 'zab', "reblessed scalar in array"); +ok(33, ${$$aobj[2]} eq 'test', "reblessed scalar in array contents"); + +ok(34, ref($$hobj{'hash'}) eq 'niy', "reblessed hash in hash"); +ok(35, ref($$hobj{'array'}) eq 'gnay', "reblessed array in hash"); +ok(36, ref($$hobj{'scalar'}) eq 'zab', "reblessed scalar in hash"); +ok(37, ${$$hobj{'scalar'}} eq 'test', "reblessed scalar in hash contents"); + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/clone.t b/dist/threads-shared/t/clone.t new file mode 100644 index 0000000000..fd31181126 --- /dev/null +++ b/dist/threads-shared/t/clone.t @@ -0,0 +1,175 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +sub ok { + my ($id, $ok, $name) = @_; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..34\n"); ### Number of tests that will be run ### +}; + +my $test = 1; + +use threads; +use threads::shared; +ok($test++, 1, 'Loaded'); + +### Start of Testing ### + +{ + my $x = shared_clone(14); + ok($test++, $x == 14, 'number'); + + $x = shared_clone('test'); + ok($test++, $x eq 'test', 'string'); +} + +{ + my %hsh = ('foo' => 2); + eval { + my $x = shared_clone(%hsh); + }; + ok($test++, $@ =~ /Usage:/, '1 arg'); + + threads->create(sub {})->join(); # Hide leaks, etc. +} + +{ + my $x = 'test'; + my $foo :shared = shared_clone($x); + ok($test++, $foo eq 'test', 'cloned string'); + + $foo = shared_clone(\$x); + ok($test++, $$foo eq 'test', 'cloned scalar ref'); + + threads->create(sub { + ok($test++, $$foo eq 'test', 'cloned scalar ref in thread'); + })->join(); + + $test++; +} + +{ + my $foo :shared; + $foo = shared_clone(\$foo); + ok($test++, ref($foo) eq 'REF', 'Circular ref typ'); + ok($test++, threads::shared::_id($foo) == threads::shared::_id($$foo), 'Circular ref'); + + threads->create(sub { + ok($test++, threads::shared::_id($foo) == threads::shared::_id($$foo), 'Circular ref in thread'); + + my ($x, $y, $z); + $x = \$y; $y = \$z; $z = \$x; + $foo = shared_clone($x); + })->join(); + + $test++; + + ok($test++, threads::shared::_id($$foo) == threads::shared::_id($$$$$foo), + 'Cloned circular refs from thread'); +} + +{ + my @ary = (qw/foo bar baz/); + my $ary = shared_clone(\@ary); + + ok($test++, $ary->[1] eq 'bar', 'Cloned array'); + $ary->[1] = 99; + ok($test++, $ary->[1] == 99, 'Clone mod'); + ok($test++, $ary[1] eq 'bar', 'Original array'); + + threads->create(sub { + ok($test++, $ary->[1] == 99, 'Clone mod in thread'); + + $ary[1] = 'bork'; + $ary->[1] = 'thread'; + })->join(); + + $test++; + + ok($test++, $ary->[1] eq 'thread', 'Clone mod from thread'); + ok($test++, $ary[1] eq 'bar', 'Original array'); +} + +{ + my $hsh :shared = shared_clone({'foo' => [qw/foo bar baz/]}); + ok($test++, is_shared($hsh), 'Shared hash ref'); + ok($test++, is_shared($hsh->{'foo'}), 'Shared hash ref elem'); + ok($test++, $$hsh{'foo'}[1] eq 'bar', 'Cloned structure'); +} + +{ + my $obj = \do { my $bork = 99; }; + bless($obj, 'Bork'); + Internals::SvREADONLY($$obj, 1) if ($] >= 5.008003); + + my $bork = shared_clone($obj); + ok($test++, $$bork == 99, 'cloned scalar ref object'); + ok($test++, ($] < 5.008003) || Internals::SvREADONLY($$bork), 'read-only'); + ok($test++, ref($bork) eq 'Bork', 'Object class'); + + threads->create(sub { + ok($test++, $$bork == 99, 'cloned scalar ref object in thread'); + ok($test++, ($] < 5.008003) || Internals::SvREADONLY($$bork), 'read-only'); + ok($test++, ref($bork) eq 'Bork', 'Object class'); + })->join(); + + $test += 3; +} + +{ + my $scalar = 'zip'; + + my $obj = { + 'ary' => [ 1, 'foo', [ 86 ], { 'bar' => [ 'baz' ] } ], + 'ref' => \$scalar, + }; + + $obj->{'self'} = $obj; + + bless($obj, 'Foo'); + + my $copy :shared; + + threads->create(sub { + $copy = shared_clone($obj); + + ok($test++, ${$copy->{'ref'}} eq 'zip', 'Obj ref in thread'); + ok($test++, threads::shared::_id($copy) == threads::shared::_id($copy->{'self'}), 'Circular ref in cloned obj'); + ok($test++, is_shared($copy->{'ary'}->[2]), 'Shared element in cloned obj'); + })->join(); + + $test += 3; + + ok($test++, ref($copy) eq 'Foo', 'Obj cloned by thread'); + ok($test++, ${$copy->{'ref'}} eq 'zip', 'Obj ref in thread'); + ok($test++, threads::shared::_id($copy) == threads::shared::_id($copy->{'self'}), 'Circular ref in cloned obj'); + ok($test++, $copy->{'ary'}->[3]->{'bar'}->[0] eq 'baz', 'Deeply cloned'); + ok($test++, ref($copy) eq 'Foo', 'Cloned object class'); +} + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/cond.t b/dist/threads-shared/t/cond.t new file mode 100644 index 0000000000..c2f02a42ed --- /dev/null +++ b/dist/threads-shared/t/cond.t @@ -0,0 +1,283 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +my $Base = 0; +sub ok { + my ($id, $ok, $name) = @_; + $id += $Base; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..32\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; +ok(1, 1, 'Loaded'); +$Base++; + +### Start of Testing ### + +# test locking +{ + my $lock : shared; + my $tr; + + # test that a subthread can't lock until parent thread has unlocked + + { + lock($lock); + ok(1, 1, "set first lock"); + $tr = async { + lock($lock); + ok(3, 1, "set lock in subthread"); + }; + threads->yield; + ok(2, 1, "still got lock"); + } + $tr->join; + + $Base += 3; + + # ditto with ref to thread + + { + my $lockref = \$lock; + lock($lockref); + ok(1,1,"set first lockref"); + $tr = async { + lock($lockref); + ok(3,1,"set lockref in subthread"); + }; + threads->yield; + ok(2,1,"still got lockref"); + } + $tr->join; + + $Base += 3; + + # make sure recursive locks unlock at the right place + { + lock($lock); + ok(1,1,"set first recursive lock"); + lock($lock); + threads->yield; + { + lock($lock); + threads->yield; + } + $tr = async { + lock($lock); + ok(3,1,"set recursive lock in subthread"); + }; + { + lock($lock); + threads->yield; + { + lock($lock); + threads->yield; + lock($lock); + threads->yield; + } + } + ok(2,1,"still got recursive lock"); + } + $tr->join; + + $Base += 3; + + # Make sure a lock factory gives out fresh locks each time + # for both attribute and run-time shares + + sub lock_factory1 { my $lock : shared; return \$lock; } + sub lock_factory2 { my $lock; share($lock); return \$lock; } + + my (@locks1, @locks2); + push @locks1, lock_factory1() for 1..2; + push @locks1, lock_factory2() for 1..2; + push @locks2, lock_factory1() for 1..2; + push @locks2, lock_factory2() for 1..2; + + ok(1,1,"lock factory: locking all locks"); + lock $locks1[0]; + lock $locks1[1]; + lock $locks1[2]; + lock $locks1[3]; + ok(2,1,"lock factory: locked all locks"); + $tr = async { + ok(3,1,"lock factory: child: locking all locks"); + lock $locks2[0]; + lock $locks2[1]; + lock $locks2[2]; + lock $locks2[3]; + ok(4,1,"lock factory: child: locked all locks"); + }; + $tr->join; + + $Base += 4; +} + + +# test cond_signal() +{ + my $lock : shared; + + sub foo { + lock($lock); + ok(1,1,"cond_signal: created first lock"); + my $tr2 = threads->create(\&bar); + cond_wait($lock); + $tr2->join(); + ok(5,1,"cond_signal: joined"); + } + + sub bar { + ok(2,1,"cond_signal: child before lock"); + lock($lock); + ok(3,1,"cond_signal: child locked"); + cond_signal($lock); + ok(4,1,"cond_signal: signalled"); + } + + my $tr = threads->create(\&foo); + $tr->join(); + + $Base += 5; + + # ditto, but with lockrefs + + my $lockref = \$lock; + sub foo2 { + lock($lockref); + ok(1,1,"cond_signal: ref: created first lock"); + my $tr2 = threads->create(\&bar2); + cond_wait($lockref); + $tr2->join(); + ok(5,1,"cond_signal: ref: joined"); + } + + sub bar2 { + ok(2,1,"cond_signal: ref: child before lock"); + lock($lockref); + ok(3,1,"cond_signal: ref: child locked"); + cond_signal($lockref); + ok(4,1,"cond_signal: ref: signalled"); + } + + $tr = threads->create(\&foo2); + $tr->join(); + + $Base += 5; +} + + +# test cond_broadcast() +{ + my $counter : shared = 0; + + # broad(N) forks off broad(N-1) and goes into a wait, in such a way + # that it's guaranteed to reach the wait before its child enters the + # locked region. When N reaches 0, the child instead does a + # cond_broadcast to wake all its ancestors. + + sub broad { + my $n = shift; + my $th; + { + lock($counter); + if ($n > 0) { + $counter++; + $th = threads->create(\&broad, $n-1); + cond_wait($counter); + $counter += 10; + } + else { + ok(1, $counter == 3, "cond_broadcast: all three waiting"); + cond_broadcast($counter); + } + } + $th->join if $th; + } + + threads->create(\&broad, 3)->join; + ok(2, $counter == 33, "cond_broadcast: all three threads woken"); + + $Base += 2; + + + # ditto, but with refs and shared() + + my $counter2 = 0; + share($counter2); + my $r = \$counter2; + + sub broad2 { + my $n = shift; + my $th; + { + lock($r); + if ($n > 0) { + $$r++; + $th = threads->create(\&broad2, $n-1); + cond_wait($r); + $$r += 10; + } + else { + ok(1, $$r == 3, "cond_broadcast: ref: all three waiting"); + cond_broadcast($r); + } + } + $th->join if $th; + } + + threads->create(\&broad2, 3)->join;; + ok(2, $$r == 33, "cond_broadcast: ref: all three threads woken"); + + $Base += 2; +} + + +# test warnings; +{ + my $warncount = 0; + local $SIG{__WARN__} = sub { $warncount++ }; + + my $lock : shared; + + cond_signal($lock); + ok(1, $warncount == 1, 'get warning on cond_signal'); + cond_broadcast($lock); + ok(2, $warncount == 2, 'get warning on cond_broadcast'); + no warnings 'threads'; + cond_signal($lock); + ok(3, $warncount == 2, 'get no warning on cond_signal'); + cond_broadcast($lock); + ok(4, $warncount == 2, 'get no warning on cond_broadcast'); + + $Base += 4; +} + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/disabled.t b/dist/threads-shared/t/disabled.t new file mode 100644 index 0000000000..46e781e494 --- /dev/null +++ b/dist/threads-shared/t/disabled.t @@ -0,0 +1,52 @@ +use strict; +use warnings; + +use Test; +plan tests => 31; + +use threads::shared; + +### Start of Testing ### + +# Make sure threads are really off +ok( !$INC{"threads.pm"} ); + +# Check each faked function. +foreach my $func (qw(share cond_wait cond_signal cond_broadcast)) { + ok( my $func_ref = __PACKAGE__->can($func) ? 1 : 0 ); + + eval qq{$func()}; + ok( $@, qr/^Not enough arguments / ); + + my %hash = (foo => 42, bar => 23); + eval qq{$func(\%hash)}; + ok( $@, '' ); + ok( $hash{foo}, 42 ); + ok( $hash{bar}, 23 ); +} + +# These all have no return value. +foreach my $func (qw(cond_wait cond_signal cond_broadcast)) { + my @array = qw(1 2 3 4); + ok( eval qq{$func(\@array)}, undef ); + ok( "@array", "1 2 3 4" ); +} + +# share() is supposed to return back it's argument as a ref. +{ + my @array = qw(1 2 3 4); + ok( share(@array), \@array ); + ok( ref &share({}), 'HASH' ); + ok( "@array", "1 2 3 4" ); +} + +# lock() should be a no-op. The return value is currently undefined. +{ + my @array = qw(1 2 3 4); + lock(@array); + ok( "@array", "1 2 3 4" ); +} + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/hv_refs.t b/dist/threads-shared/t/hv_refs.t new file mode 100644 index 0000000000..ecefdc6d5b --- /dev/null +++ b/dist/threads-shared/t/hv_refs.t @@ -0,0 +1,111 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +sub ok { + my ($id, $ok, $name) = @_; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..20\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; +ok(1, 1, 'Loaded'); + +### Start of Testing ### + +my $foo; +share($foo); +my %foo; +share(%foo); +$foo{"foo"} = \$foo; +ok(2, !defined ${$foo{foo}}, "Check deref"); +$foo = "test"; +ok(3, ${$foo{foo}} eq "test", "Check deref after assign"); +threads->create(sub{${$foo{foo}} = "test2";})->join(); +ok(4, $foo eq "test2", "Check after assign in another thread"); +my $bar = delete($foo{foo}); +ok(5, $$bar eq "test2", "check delete"); +threads->create( sub { + my $test; + share($test); + $test = "thread3"; + $foo{test} = \$test; + })->join(); +ok(6, ${$foo{test}} eq "thread3", "Check reference created in another thread"); +my $gg = $foo{test}; +$$gg = "test"; +ok(7, ${$foo{test}} eq "test", "Check reference"); +my $gg2 = delete($foo{test}); +ok(8, threads::shared::_id($$gg) == threads::shared::_id($$gg2), + sprintf("Check we get the same thing (%x vs %x)", + threads::shared::_id($$gg),threads::shared::_id($$gg2))); +ok(9, $$gg eq $$gg2, "And check the values are the same"); +ok(10, keys %foo == 0, "And make sure we realy have deleted the values"); +{ + my (%hash1, %hash2); + share(%hash1); + share(%hash2); + $hash1{hash} = \%hash2; + $hash2{"bar"} = "foo"; + ok(11, $hash1{hash}->{bar} eq "foo", "Check hash references work"); + threads->create(sub { $hash2{"bar2"} = "foo2"})->join(); + ok(12, $hash1{hash}->{bar2} eq "foo2", "Check hash references work"); + threads->create(sub { my (%hash3); share(%hash3); $hash2{hash} = \%hash3; $hash3{"thread"} = "yes"})->join(); + ok(13, $hash1{hash}->{hash}->{thread} eq "yes", "Check hash created in another thread"); +} + +{ + my $h = {a=>14}; + my $r = \$h->{a}; + share($r); + if ($] > 5.008) { + eval { lock($r); }; + ok(14, !$@, "lock on helems ref: $@"); + eval { lock($h->{a}); }; + ok(15, !$@, "lock on helems: $@"); + } else { + ok(14, 1, "skipped. < 5.8"); + ok(15, 1, "skipped. < 5.8"); + } +} +{ + my $object : shared = &share({}); + threads->create(sub { + bless $object, 'test1'; + })->join; + ok(16, ref($object) eq 'test1', "blessing does work"); + my %test = (object => $object); + ok(17, ref($test{object}) eq 'test1', "and some more work"); + bless $object, 'test2'; + ok(18, ref($test{object}) eq 'test2', "reblessing works!"); +} + +ok(19, is_shared($foo), "Check for sharing"); +ok(20, is_shared(%foo), "Check for sharing"); + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/hv_simple.t b/dist/threads-shared/t/hv_simple.t new file mode 100644 index 0000000000..574d8d5508 --- /dev/null +++ b/dist/threads-shared/t/hv_simple.t @@ -0,0 +1,78 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +sub ok { + my ($id, $ok, $name) = @_; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..16\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; +ok(1, 1, 'Loaded'); + +### Start of Testing ### + +my %hash; +share(%hash); +$hash{"foo"} = "bar"; +ok(2,$hash{"foo"} eq "bar","Check hash get"); +threads->create(sub { $hash{"bar"} = "thread1"})->join(); +threads->create(sub { ok(3,$hash{"bar"} eq "thread1", "Check thread get and write")})->join(); +{ + my $foo = delete($hash{"bar"}); + ok(4, $foo eq "thread1", "Check delete, want 'thread1' got '$foo'"); + $foo = delete($hash{"bar"}); + ok(5, !defined $foo, "Check delete on empty value"); +} +ok(6, keys %hash == 1, "Check keys"); +$hash{"1"} = 1; +$hash{"2"} = 2; +$hash{"3"} = 3; +ok(7, keys %hash == 4, "Check keys"); +ok(8, exists($hash{"1"}), "Exist on existing key"); +ok(9, !exists($hash{"4"}), "Exists on non existing key"); +my %seen; +foreach my $key ( keys %hash) { + $seen{$key}++; +} +ok(10, $seen{1} == 1, "Keys.."); +ok(11, $seen{2} == 1, "Keys.."); +ok(12, $seen{3} == 1, "Keys.."); +ok(13, $seen{"foo"} == 1, "Keys.."); + +# bugid #24407: the stringification of the numeric 1 got allocated to the +# wrong thread memory pool, which crashes on Windows. +ok(14, exists $hash{1}, "Check numeric key"); + +threads->create(sub { %hash = () })->join(); +ok(15, keys %hash == 0, "Check clear"); + +ok(16, is_shared(%hash), "Check for sharing"); + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/no_share.t b/dist/threads-shared/t/no_share.t new file mode 100644 index 0000000000..21703ae664 --- /dev/null +++ b/dist/threads-shared/t/no_share.t @@ -0,0 +1,62 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +sub ok { + my ($id, $ok, $name) = @_; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..6\n"); ### Number of tests that will be run ### +}; + +our $warnmsg; +BEGIN { + $SIG{__WARN__} = sub { $warnmsg = shift; }; +} + +use threads::shared; +use threads; +ok(1, 1, 'Loaded'); + +### Start of Testing ### + +ok(2, ($warnmsg =~ /Warning, threads::shared has already been loaded/)?1:0, + "threads has warned us"); + +my $test = "bar"; +share($test); +ok(3, $test eq "bar", "Test disabled share not interfering"); + +threads->create(sub { + ok(4, $test eq "bar", "Test disabled share after thread"); + $test = "baz"; + })->join(); +# Value should either remain unchanged or be value set by other thread +ok(5, $test eq "bar" || $test eq 'baz', "Test that value is an expected one"); + +ok(6, ! is_shared($test), "Check for sharing"); + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/object.t b/dist/threads-shared/t/object.t new file mode 100644 index 0000000000..4e3c189037 --- /dev/null +++ b/dist/threads-shared/t/object.t @@ -0,0 +1,179 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } + if ($] < 5.010) { + print("1..0 # SKIP Needs Perl 5.10.0 or later\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +BEGIN { + $| = 1; + print("1..28\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; + +my $TEST; +BEGIN { + share($TEST); + $TEST = 1; +} + +sub ok { + my ($ok, $name) = @_; + + lock($TEST); + my $id = $TEST++; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +ok(1, 'Loaded'); + +### Start of Testing ### + +{ package Jar; + my @jar :shared; + + sub new + { + bless(&threads::shared::share({}), shift); + } + + sub store + { + my ($self, $cookie) = @_; + push(@jar, $cookie); + return $jar[-1]; # Results in destruction of proxy object + } + + sub peek + { + return $jar[-1]; + } + + sub fetch + { + pop(@jar); + } +} + +{ package Cookie; + + sub new + { + my $self = bless(&threads::shared::share({}), shift); + $self->{'type'} = shift; + return $self; + } + + sub DESTROY + { + delete(shift->{'type'}); + } +} + +my $C1 = 'chocolate chip'; +my $C2 = 'oatmeal raisin'; +my $C3 = 'vanilla wafer'; + +my $cookie = Cookie->new($C1); +ok($cookie->{'type'} eq $C1, 'Have cookie'); + +my $jar = Jar->new(); +$jar->store($cookie); + +ok($cookie->{'type'} eq $C1, 'Still have cookie'); +ok($jar->peek()->{'type'} eq $C1, 'Still have cookie'); +ok($cookie->{'type'} eq $C1, 'Still have cookie'); + +threads->create(sub { + ok($cookie->{'type'} eq $C1, 'Have cookie in thread'); + ok($jar->peek()->{'type'} eq $C1, 'Still have cookie in thread'); + ok($cookie->{'type'} eq $C1, 'Still have cookie in thread'); + + $jar->store(Cookie->new($C2)); + ok($jar->peek()->{'type'} eq $C2, 'Added cookie in thread'); +})->join(); + +ok($cookie->{'type'} eq $C1, 'Still have original cookie after thread'); +ok($jar->peek()->{'type'} eq $C2, 'Still have added cookie after thread'); + +$cookie = $jar->fetch(); +ok($cookie->{'type'} eq $C2, 'Fetched cookie from jar'); +ok($jar->peek()->{'type'} eq $C1, 'Cookie still in jar'); + +$cookie = $jar->fetch(); +ok($cookie->{'type'} eq $C1, 'Fetched cookie from jar'); +undef($cookie); + +share($cookie); +$cookie = $jar->store(Cookie->new($C3)); +ok($jar->peek()->{'type'} eq $C3, 'New cookie in jar'); +ok($cookie->{'type'} eq $C3, 'Have cookie'); + +threads->create(sub { + ok($cookie->{'type'} eq $C3, 'Have cookie in thread'); + $cookie = Cookie->new($C1); + ok($cookie->{'type'} eq $C1, 'Change cookie in thread'); + ok($jar->peek()->{'type'} eq $C3, 'Still have cookie in jar'); +})->join(); + +ok($cookie->{'type'} eq $C1, 'Have changed cookie after thread'); +ok($jar->peek()->{'type'} eq $C3, 'Still have cookie in jar'); +undef($cookie); +ok($jar->peek()->{'type'} eq $C3, 'Still have cookie in jar'); +$cookie = $jar->fetch(); +ok($cookie->{'type'} eq $C3, 'Fetched cookie from jar'); + +{ package Foo; + + my $ID = 1; + threads::shared::share($ID); + + sub new + { + # Anonymous scalar with an internal ID + my $obj = \do{ my $scalar = $ID++; }; + threads::shared::share($obj); # Make it shared + return (bless($obj, 'Foo')); # Make it an object + } +} + +my $obj :shared; +$obj = Foo->new(); +ok($$obj == 1, "Main: Object ID $$obj"); + +threads->create( sub { + ok($$obj == 1, "Thread: Object ID $$obj"); + + $$obj = 10; + ok($$obj == 10, "Thread: Changed object ID $$obj"); + + $obj = Foo->new(); + ok($$obj == 2, "Thread: New object ID $$obj"); + } )->join(); + +ok($$obj == 2, "Main: New object ID $$obj # TODO - should be 2"); + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/shared_attr.t b/dist/threads-shared/t/shared_attr.t new file mode 100644 index 0000000000..9085e27f30 --- /dev/null +++ b/dist/threads-shared/t/shared_attr.t @@ -0,0 +1,79 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +sub ok { + my ($id, $ok, $name) = @_; + if (! defined($name)) { + $name = ''; + } + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..101\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; +ok(1, 1, 'Loaded'); + +### Start of Testing ### + +my $test_count; +share($test_count); +$test_count = 2; + +for(1..10) { + my $foo : shared = "foo"; + ok($test_count++, $foo eq "foo"); + threads->create(sub { $foo = "bar" })->join(); + ok($test_count++, $foo eq "bar"); + my @foo : shared = ("foo","bar"); + ok($test_count++, $foo[1] eq "bar"); + threads->create(sub { ok($test_count++, shift(@foo) eq "foo")})->join(); + ok($test_count++, $foo[0] eq "bar"); + my %foo : shared = ( foo => "bar" ); + ok($test_count++, $foo{foo} eq "bar"); + threads->create(sub { $foo{bar} = "foo" })->join(); + ok($test_count++, $foo{bar} eq "foo"); + + threads->create(sub { $foo{array} = \@foo})->join(); + threads->create(sub { push @{$foo{array}}, "baz"})->join(); + ok($test_count++, $foo[-1] eq "baz"); +} + +my $shared :shared = &share({}); +$$shared{'foo'} = 'bar'; + +for(1..10) { + my $str1 = "$shared"; + my $str2 = "$shared"; + ok($test_count++, $str1 eq $str2, 'stringify'); + $str1 = $$shared{'foo'}; + $str2 = $$shared{'foo'}; + ok($test_count++, $str1 eq $str2, 'contents'); +} + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/stress.t b/dist/threads-shared/t/stress.t new file mode 100644 index 0000000000..adfd1eda95 --- /dev/null +++ b/dist/threads-shared/t/stress.t @@ -0,0 +1,149 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } + if ($^O eq 'hpux' && $Config{osvers} <= 10.20) { + print("1..0 # SKIP Broken under HP-UX 10.20\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +BEGIN { + $| = 1; + print("1..1\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; + +### Start of Testing ### + +##### +# +# Launches a bunch of threads which are then +# restricted to finishing in numerical order +# +##### +{ + my $cnt = 50; + + my $TIMEOUT = 60; + + my $mutex = 1; + share($mutex); + + my $warning; + $SIG{__WARN__} = sub { $warning = shift; }; + + my @threads; + + for (reverse(1..$cnt)) { + $threads[$_] = threads->create(sub { + my $tnum = shift; + my $timeout = time() + $TIMEOUT; + threads->yield(); + + # Randomize the amount of work the thread does + my $sum; + for (0..(500000+int(rand(500000)))) { + $sum++ + } + + # Lock the mutex + lock($mutex); + + # Wait for my turn to finish + while ($mutex != $tnum) { + if (! cond_timedwait($mutex, $timeout)) { + if ($mutex == $tnum) { + return ('timed out - cond_broadcast not received'); + } else { + return ('timed out'); + } + } + } + + # Finish up + $mutex++; + cond_broadcast($mutex); + return ('okay'); + }, $_); + + # Handle thread creation failures + if ($warning) { + my $printit = 1; + if ($warning =~ /returned 11/) { + $warning = "Thread creation failed due to 'No more processes'\n"; + $printit = (! $ENV{'PERL_CORE'}); + } elsif ($warning =~ /returned 12/) { + $warning = "Thread creation failed due to 'No more memory'\n"; + $printit = (! $ENV{'PERL_CORE'}); + } + print(STDERR "# Warning: $warning") if ($printit); + lock($mutex); + $mutex = $_ + 1; + last; + } + } + + # Gather thread results + my ($okay, $failures, $timeouts, $unknown) = (0, 0, 0, 0, 0); + for (1..$cnt) { + if (! $threads[$_]) { + $failures++; + } else { + my $rc = $threads[$_]->join(); + if (! $rc) { + $failures++; + } elsif ($rc =~ /^timed out/) { + $timeouts++; + } elsif ($rc eq 'okay') { + $okay++; + } else { + $unknown++; + print(STDERR "# Unknown error: $rc\n"); + } + } + } + + if ($failures) { + my $only = $cnt - $failures; + print(STDERR "# Warning: Intended to use $cnt threads, but could only muster $only\n"); + $cnt -= $failures; + } + + if ($unknown || (($okay + $timeouts) != $cnt)) { + print("not ok 1\n"); + my $too_few = $cnt - ($okay + $timeouts + $unknown); + print(STDERR "# Test failed:\n"); + print(STDERR "#\t$too_few too few threads reported\n") if $too_few; + print(STDERR "#\t$unknown unknown errors\n") if $unknown; + print(STDERR "#\t$timeouts threads timed out\n") if $timeouts; + + } elsif ($timeouts) { + # Frequently fails under MSWin32 due to deadlocking bug in Windows + # hence test is TODO under MSWin32 + # http://rt.perl.org/rt3/Public/Bug/Display.html?id=41574 + # http://support.microsoft.com/kb/175332 + if ($^O eq 'MSWin32') { + print("not ok 1 # TODO - not reliable under MSWin32\n") + } else { + print("not ok 1\n"); + print(STDERR "# Test failed: $timeouts threads timed out\n"); + } + + } else { + print("ok 1\n"); + } +} + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/sv_refs.t b/dist/threads-shared/t/sv_refs.t new file mode 100644 index 0000000000..5cc6a22ecd --- /dev/null +++ b/dist/threads-shared/t/sv_refs.t @@ -0,0 +1,101 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +sub ok { + my ($id, $ok, $name) = @_; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..21\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; +ok(1, 1, 'Loaded'); + +### Start of Testing ### + +my $foo; +my $bar = "foo"; +share($foo); +eval { $foo = \$bar; }; +ok(2,my $temp1 = $@ =~/^Invalid\b.*shared scalar/, "Wrong error message"); + +share($bar); +$foo = \$bar; +ok(3, $temp1 = $foo =~/SCALAR/, "Check that is a ref"); +ok(4, $$foo eq "foo", "Check that it points to the correct value"); +$bar = "yeah"; +ok(5, $$foo eq "yeah", "Check that assignment works"); +$$foo = "yeah2"; +ok(6, $$foo eq "yeah2", "Check that deref assignment works"); +threads->create(sub {$bar = "yeah3"})->join(); +ok(7, $$foo eq "yeah3", "Check that other thread assignemtn works"); +threads->create(sub {$foo = "artur"})->join(); +ok(8, $foo eq "artur", "Check that uncopupling the ref works"); +my $baz; +share($baz); +$baz = "original"; +$bar = \$baz; +$foo = \$bar; +ok(9,$$$foo eq 'original', "Check reference chain"); +my($t1,$t2); +share($t1); +share($t2); +$t2 = "text"; +$t1 = \$t2; +threads->create(sub { $t1 = "bar" })->join(); +ok(10,$t1 eq 'bar',"Check that assign to a ROK works"); + +ok(11, is_shared($foo), "Check for sharing"); + +{ + # Circular references with 3 shared scalars + my $x : shared; + my $y : shared; + my $z : shared; + + $x = \$y; + $y = \$z; + $z = \$x; + ok(12, ref($x) eq 'REF', '$x ref type'); + ok(13, ref($y) eq 'REF', '$y ref type'); + ok(14, ref($z) eq 'REF', '$z ref type'); + + my @q :shared = ($x); + ok(15, ref($q[0]) eq 'REF', '$q[0] ref type'); + + my $w = $q[0]; + ok(16, ref($w) eq 'REF', '$w ref type'); + ok(17, ref($$w) eq 'REF', '$$w ref type'); + ok(18, ref($$$w) eq 'REF', '$$$w ref type'); + ok(19, ref($$$$w) eq 'REF', '$$$$w ref type'); + + ok(20, is_shared($x) == is_shared($w), '_id($x) == _id($w)'); + ok(21, is_shared($w) == is_shared($$$$w), '_id($w) == _id($$$$w)'); +} + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/sv_simple.t b/dist/threads-shared/t/sv_simple.t new file mode 100644 index 0000000000..9d264f7d31 --- /dev/null +++ b/dist/threads-shared/t/sv_simple.t @@ -0,0 +1,64 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +sub ok { + my ($id, $ok, $name) = @_; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..11\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; +ok(1, 1, 'Loaded'); + +### Start of Testing ### + +my $test = "bar"; +share($test); +ok(2,$test eq "bar","Test magic share fetch"); +$test = "foo"; +ok(3,$test eq "foo","Test magic share assign"); +my $c = threads::shared::_refcnt($test); +threads->create( + sub { + ok(4, $test eq "foo","Test magic share fetch after thread"); + $test = "baz"; + ok(5,threads::shared::_refcnt($test) > $c, "Check that threadcount is correct"); + })->join(); +ok(6,$test eq "baz","Test that value has changed in another thread"); +ok(7,threads::shared::_refcnt($test) == $c,"Check thrcnt is down properly"); +$test = "barbar"; +ok(8, length($test) == 6, "Check length code"); +threads->create(sub { $test = "barbarbar" })->join; +ok(9, length($test) == 9, "Check length code after different thread modified it"); +threads->create(sub { undef($test)})->join(); +ok(10, !defined($test), "Check undef value"); + +ok(11, is_shared($test), "Check for sharing"); + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/utf8.t b/dist/threads-shared/t/utf8.t new file mode 100644 index 0000000000..6e0e664d1c --- /dev/null +++ b/dist/threads-shared/t/utf8.t @@ -0,0 +1,96 @@ +use strict; +use warnings; + +BEGIN { + use Config; + if (! $Config{'useithreads'}) { + print("1..0 # SKIP Perl not compiled with 'useithreads'\n"); + exit(0); + } +} + +use ExtUtils::testlib; + +my $TEST = 1; + +sub is { + my ($got, $exp, $name) = @_; + + my $ok = ($got eq $exp); + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $TEST - $name\n"); + } else { + print("not ok $TEST - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + print("# Got: $got\n"); + print("# Expected: $exp\n"); + } + + $TEST++; + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..12\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; + +### Start of Testing ### + +binmode STDOUT, ":utf8"; + +my $plain = 'foo'; +my $utf8 = "\x{123}\x{84}\x{20F}\x{2C1}"; +my $code = \&is; + +my %a :shared; +$a{$plain} = $plain; +$a{$utf8} = $utf8; +$a{$code} = 'code'; + +is(exists($a{$plain}), 1, 'Found plain key in shared hash'); +is(exists($a{$utf8}), 1, 'Found UTF-8 key in shared hash'); +is(exists($a{$code}), 1, 'Found code ref key in shared hash'); + +while (my ($key, $value) = each (%a)) { + if ($key eq $plain) { + is($key, $plain, 'Plain key in shared hash'); + } elsif ($key eq $utf8) { + is($key, $utf8, 'UTF-8 key in shared hash'); + } elsif ($key eq "$code") { + is($key, "$code", 'Code ref key in shared hash'); + } else { + is($key, "???", 'Bad key'); + } +} + +my $a = &share({}); +$$a{$plain} = $plain; +$$a{$utf8} = $utf8; +$$a{$code} = 'code'; + +is(exists($$a{$plain}), 1, 'Found plain key in shared hash ref'); +is(exists($$a{$utf8}), 1, 'Found UTF-8 key in shared hash ref'); +is(exists($$a{$code}), 1, 'Found code ref key in shared hash ref'); + +while (my ($key, $value) = each (%$a)) { + if ($key eq $plain) { + is($key, $plain, 'Plain key in shared hash ref'); + } elsif ($key eq $utf8) { + is($key, $utf8, 'UTF-8 key in shared hash ref'); + } elsif ($key eq "$code") { + is($key, "$code", 'Code ref key in shared hash ref'); + } else { + is($key, "???", 'Bad key'); + } +} + +exit(0); + +# EOF diff --git a/dist/threads-shared/t/wait.t b/dist/threads-shared/t/wait.t new file mode 100644 index 0000000000..0f815d6f66 --- /dev/null +++ b/dist/threads-shared/t/wait.t @@ -0,0 +1,341 @@ +use strict; +use warnings; + +BEGIN { + # Import test.pl into its own package + { + package Test; + require($ENV{PERL_CORE} ? '../../t/test.pl' : './t/test.pl'); + } + + use Config; + if (! $Config{'useithreads'}) { + Test::skip_all(q/Perl not compiled with 'useithreads'/); + } +} + +use ExtUtils::testlib; + +sub ok { + my ($id, $ok, $name) = @_; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..91\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; + +Test::watchdog(300); # In case we get stuck + +my $TEST = 1; +ok($TEST++, 1, 'Loaded'); + +### Start of Testing ### + +# cond_wait and cond_timedwait extended tests adapted from cond.t + +# The two skips later on in these tests refer to this quote from the +# pod/perl583delta.pod: +# +# =head1 Platform Specific Problems +# +# The regression test ext/threads/shared/t/wait.t fails on early RedHat 9 +# and HP-UX 10.20 due to bugs in their threading implementations. +# RedHat users should see https://rhn.redhat.com/errata/RHBA-2003-136.html +# and consider upgrading their glibc. + + +# - TEST basics + +ok($TEST++, defined &cond_wait, "cond_wait() present"); +ok($TEST++, (prototype(\&cond_wait) eq '\[$@%];\[$@%]'), + q/cond_wait() prototype '\[$@%];\[$@%]'/); +ok($TEST++, defined &cond_timedwait, "cond_timedwait() present"); +ok($TEST++, (prototype(\&cond_timedwait) eq '\[$@%]$;\[$@%]'), + q/cond_timedwait() prototype '\[$@%]$;\[$@%]'/); + + +my @wait_how = ( + "simple", # cond var == lock var; implicit lock; e.g.: cond_wait($c) + "repeat", # cond var == lock var; explicit lock; e.g.: cond_wait($c, $c) + "twain" # cond var != lock var; explicit lock; e.g.: cond_wait($c, $l) +); + + +SYNC_SHARED: { + my $test_type :shared; # simple|repeat|twain + + my $cond :shared; + my $lock :shared; + + ok($TEST++, 1, "Shared synchronization tests preparation"); + + sub signaller + { + my $testno = $_[0]; + + ok($testno++, 1, "$test_type: child before lock"); + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testno++, 1, "$test_type: child obtained lock"); + + if ($test_type =~ 'twain') { + no warnings 'threads'; # lock var != cond var, so disable warnings + cond_signal($cond); + } else { + cond_signal($cond); + } + ok($testno++, 1, "$test_type: child signalled condition"); + + return($testno); + } + + # - TEST cond_wait + + sub cw + { + my ($testnum, $to) = @_; + + # Which lock to obtain? + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testnum++, 1, "$test_type: obtained initial lock"); + + my $thr = threads->create(\&signaller, $testnum); + for ($test_type) { + cond_wait($cond), last if /simple/; + cond_wait($cond, $cond), last if /repeat/; + cond_wait($cond, $lock), last if /twain/; + die "$test_type: unknown test\n"; + } + $testnum = $thr->join(); + ok($testnum++, 1, "$test_type: condition obtained"); + + return ($testnum); + } + + foreach (@wait_how) { + $test_type = "cond_wait [$_]"; + my $thr = threads->create(\&cw, $TEST); + $TEST = $thr->join(); + } + + # - TEST cond_timedwait success + + sub ctw_ok + { + my ($testnum, $to) = @_; + + # Which lock to obtain? + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testnum++, 1, "$test_type: obtained initial lock"); + + my $thr = threads->create(\&signaller, $testnum); + my $ok = 0; + for ($test_type) { + $ok = cond_timedwait($cond, time() + $to), last if /simple/; + $ok = cond_timedwait($cond, time() + $to, $cond), last if /repeat/; + $ok = cond_timedwait($cond, time() + $to, $lock), last if /twain/; + die "$test_type: unknown test\n"; + } + $testnum = $thr->join(); + ok($testnum++, $ok, "$test_type: condition obtained"); + + return ($testnum); + } + + foreach (@wait_how) { + $test_type = "cond_timedwait [$_]"; + my $thr = threads->create(\&ctw_ok, $TEST, 5); + $TEST = $thr->join(); + } + + # - TEST cond_timedwait timeout + + sub ctw_fail + { + my ($testnum, $to) = @_; + + if ($^O eq "hpux" && $Config{osvers} <= 10.20) { + # The lock obtaining would pass, but the wait will not. + ok($testnum++, 1, "$test_type: obtained initial lock"); + ok($testnum++, 0, "# SKIP see perl583delta"); + + } else { + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testnum++, 1, "$test_type: obtained initial lock"); + my $ok; + for ($test_type) { + $ok = cond_timedwait($cond, time() + $to), last if /simple/; + $ok = cond_timedwait($cond, time() + $to, $cond), last if /repeat/; + $ok = cond_timedwait($cond, time() + $to, $lock), last if /twain/; + die "$test_type: unknown test\n"; + } + ok($testnum++, ! defined($ok), "$test_type: timeout"); + } + + return ($testnum); + } + + foreach (@wait_how) { + $test_type = "cond_timedwait pause, timeout [$_]"; + my $thr = threads->create(\&ctw_fail, $TEST, 3); + $TEST = $thr->join(); + } + + foreach (@wait_how) { + $test_type = "cond_timedwait instant timeout [$_]"; + my $thr = threads->create(\&ctw_fail, $TEST, -60); + $TEST = $thr->join(); + } + +} # -- SYNCH_SHARED block + + +# same as above, but with references to lock and cond vars + +SYNCH_REFS: { + my $test_type :shared; # simple|repeat|twain + + my $true_cond :shared; + my $true_lock :shared; + + my $cond = \$true_cond; + my $lock = \$true_lock; + + ok($TEST++, 1, "Synchronization reference tests preparation"); + + sub signaller2 + { + my $testno = $_[0]; + + ok($testno++, 1, "$test_type: child before lock"); + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testno++, 1, "$test_type: child obtained lock"); + + if ($test_type =~ 'twain') { + no warnings 'threads'; # lock var != cond var, so disable warnings + cond_signal($cond); + } else { + cond_signal($cond); + } + ok($testno++, 1, "$test_type: child signalled condition"); + + return($testno); + } + + # - TEST cond_wait + + sub cw2 + { + my ($testnum, $to) = @_; + + # Which lock to obtain? + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testnum++, 1, "$test_type: obtained initial lock"); + + my $thr = threads->create(\&signaller2, $testnum); + for ($test_type) { + cond_wait($cond), last if /simple/; + cond_wait($cond, $cond), last if /repeat/; + cond_wait($cond, $lock), last if /twain/; + die "$test_type: unknown test\n"; + } + $testnum = $thr->join(); + ok($testnum++, 1, "$test_type: condition obtained"); + + return ($testnum); + } + + foreach (@wait_how) { + $test_type = "cond_wait [$_]"; + my $thr = threads->create(\&cw2, $TEST); + $TEST = $thr->join(); + } + + # - TEST cond_timedwait success + + sub ctw_ok2 + { + my ($testnum, $to) = @_; + + # Which lock to obtain? + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testnum++, 1, "$test_type: obtained initial lock"); + + my $thr = threads->create(\&signaller2, $testnum); + my $ok = 0; + for ($test_type) { + $ok = cond_timedwait($cond, time() + $to), last if /simple/; + $ok = cond_timedwait($cond, time() + $to, $cond), last if /repeat/; + $ok = cond_timedwait($cond, time() + $to, $lock), last if /twain/; + die "$test_type: unknown test\n"; + } + $testnum = $thr->join(); + ok($testnum++, $ok, "$test_type: condition obtained"); + + return ($testnum); + } + + foreach (@wait_how) { + $test_type = "cond_timedwait [$_]"; + my $thr = threads->create(\&ctw_ok2, $TEST, 5); + $TEST = $thr->join(); + } + + # - TEST cond_timedwait timeout + + sub ctw_fail2 + { + my ($testnum, $to) = @_; + + if ($^O eq "hpux" && $Config{osvers} <= 10.20) { + # The lock obtaining would pass, but the wait will not. + ok($testnum++, 1, "$test_type: obtained initial lock"); + ok($testnum++, 0, "# SKIP see perl583delta"); + + } else { + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testnum++, 1, "$test_type: obtained initial lock"); + my $ok; + for ($test_type) { + $ok = cond_timedwait($cond, time() + $to), last if /simple/; + $ok = cond_timedwait($cond, time() + $to, $cond), last if /repeat/; + $ok = cond_timedwait($cond, time() + $to, $lock), last if /twain/; + die "$test_type: unknown test\n"; + } + ok($testnum++, ! defined($ok), "$test_type: timeout"); + } + + return ($testnum); + } + + foreach (@wait_how) { + $test_type = "cond_timedwait pause, timeout [$_]"; + my $thr = threads->create(\&ctw_fail2, $TEST, 3); + $TEST = $thr->join(); + } + + foreach (@wait_how) { + $test_type = "cond_timedwait instant timeout [$_]"; + my $thr = threads->create(\&ctw_fail2, $TEST, -60); + $TEST = $thr->join(); + } + +} # -- SYNCH_REFS block + +# Done +exit(0); + +# EOF diff --git a/dist/threads-shared/t/waithires.t b/dist/threads-shared/t/waithires.t new file mode 100644 index 0000000000..e3a1086370 --- /dev/null +++ b/dist/threads-shared/t/waithires.t @@ -0,0 +1,279 @@ +use strict; +use warnings; + +BEGIN { + # Import test.pl into its own package + { + package Test; + require($ENV{PERL_CORE} ? '../../t/test.pl' : './t/test.pl'); + } + + use Config; + if (! $Config{'useithreads'}) { + Test::skip_all(q/Perl not compiled with 'useithreads'/); + } + + if (! eval 'use Time::HiRes "time"; 1') { + Test::skip_all('Time::HiRes not available'); + } +} + +use ExtUtils::testlib; + +sub ok { + my ($id, $ok, $name) = @_; + + # You have to do it this way or VMS will get confused. + if ($ok) { + print("ok $id - $name\n"); + } else { + print("not ok $id - $name\n"); + printf("# Failed test at line %d\n", (caller)[2]); + } + + return ($ok); +} + +BEGIN { + $| = 1; + print("1..57\n"); ### Number of tests that will be run ### +}; + +use threads; +use threads::shared; + +Test::watchdog(60); # In case we get stuck + +my $TEST = 1; +ok($TEST++, 1, 'Loaded'); + +### Start of Testing ### + +# subsecond cond_timedwait extended tests adapted from wait.t + +# The two skips later on in these tests refer to this quote from the +# pod/perl583delta.pod: +# +# =head1 Platform Specific Problems +# +# The regression test ext/threads/shared/t/wait.t fails on early RedHat 9 +# and HP-UX 10.20 due to bugs in their threading implementations. +# RedHat users should see https://rhn.redhat.com/errata/RHBA-2003-136.html +# and consider upgrading their glibc. + + +# - TEST basics + +my @wait_how = ( + "simple", # cond var == lock var; implicit lock; e.g.: cond_wait($c) + "repeat", # cond var == lock var; explicit lock; e.g.: cond_wait($c, $c) + "twain" # cond var != lock var; explicit lock; e.g.: cond_wait($c, $l) +); + + +SYNC_SHARED: { + my $test_type :shared; # simple|repeat|twain + + my $cond :shared; + my $lock :shared; + + ok($TEST++, 1, "Shared synchronization tests preparation"); + + # - TEST cond_timedwait success + + sub signaller + { + my $testno = $_[0]; + + ok($testno++, 1, "$test_type: child before lock"); + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testno++, 1, "$test_type: child obtained lock"); + + if ($test_type =~ 'twain') { + no warnings 'threads'; # lock var != cond var, so disable warnings + cond_signal($cond); + } else { + cond_signal($cond); + } + ok($testno++, 1, "$test_type: child signalled condition"); + + return($testno); + } + + sub ctw_ok + { + my ($testnum, $to) = @_; + + # Which lock to obtain? + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testnum++, 1, "$test_type: obtained initial lock"); + + my $thr = threads->create(\&signaller, $testnum); + my $ok = 0; + for ($test_type) { + $ok = cond_timedwait($cond, time() + $to), last if /simple/; + $ok = cond_timedwait($cond, time() + $to, $cond), last if /repeat/; + $ok = cond_timedwait($cond, time() + $to, $lock), last if /twain/; + die "$test_type: unknown test\n"; + } + $testnum = $thr->join(); + ok($testnum++, $ok, "$test_type: condition obtained"); + + return ($testnum); + } + + foreach (@wait_how) { + $test_type = "cond_timedwait [$_]"; + my $thr = threads->create(\&ctw_ok, $TEST, 0.1); + $TEST = $thr->join(); + } + + # - TEST cond_timedwait timeout + + sub ctw_fail + { + my ($testnum, $to) = @_; + + if ($^O eq "hpux" && $Config{osvers} <= 10.20) { + # The lock obtaining would pass, but the wait will not. + ok($testnum++, 1, "$test_type: obtained initial lock"); + ok($testnum++, 0, "# SKIP see perl583delta"); + + } else { + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testnum++, 1, "$test_type: obtained initial lock"); + my $ok; + for ($test_type) { + $ok = cond_timedwait($cond, time() + $to), last if /simple/; + $ok = cond_timedwait($cond, time() + $to, $cond), last if /repeat/; + $ok = cond_timedwait($cond, time() + $to, $lock), last if /twain/; + die "$test_type: unknown test\n"; + } + ok($testnum++, ! defined($ok), "$test_type: timeout"); + } + + return ($testnum); + } + + foreach (@wait_how) { + $test_type = "cond_timedwait pause, timeout [$_]"; + my $thr = threads->create(\&ctw_fail, $TEST, 0.3); + $TEST = $thr->join(); + } + + foreach (@wait_how) { + $test_type = "cond_timedwait instant timeout [$_]"; + my $thr = threads->create(\&ctw_fail, $TEST, -0.60); + $TEST = $thr->join(); + } + +} # -- SYNCH_SHARED block + + +# same as above, but with references to lock and cond vars + +SYNCH_REFS: { + my $test_type :shared; # simple|repeat|twain + + my $true_cond :shared; + my $true_lock :shared; + + my $cond = \$true_cond; + my $lock = \$true_lock; + + ok($TEST++, 1, "Synchronization reference tests preparation"); + + # - TEST cond_timedwait success + + sub signaller2 + { + my $testno = $_[0]; + + ok($testno++, 1, "$test_type: child before lock"); + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testno++, 1, "$test_type: child obtained lock"); + + if ($test_type =~ 'twain') { + no warnings 'threads'; # lock var != cond var, so disable warnings + cond_signal($cond); + } else { + cond_signal($cond); + } + ok($testno++, 1, "$test_type: child signalled condition"); + + return($testno); + } + + sub ctw_ok2 + { + my ($testnum, $to) = @_; + + # Which lock to obtain? + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testnum++, 1, "$test_type: obtained initial lock"); + + my $thr = threads->create(\&signaller2, $testnum); + my $ok = 0; + for ($test_type) { + $ok = cond_timedwait($cond, time() + $to), last if /simple/; + $ok = cond_timedwait($cond, time() + $to, $cond), last if /repeat/; + $ok = cond_timedwait($cond, time() + $to, $lock), last if /twain/; + die "$test_type: unknown test\n"; + } + $testnum = $thr->join(); + ok($testnum++, $ok, "$test_type: condition obtained"); + + return ($testnum); + } + + foreach (@wait_how) { + $test_type = "cond_timedwait [$_]"; + my $thr = threads->create(\&ctw_ok2, $TEST, 0.05); + $TEST = $thr->join(); + } + + # - TEST cond_timedwait timeout + + sub ctw_fail2 + { + my ($testnum, $to) = @_; + + if ($^O eq "hpux" && $Config{osvers} <= 10.20) { + # The lock obtaining would pass, but the wait will not. + ok($testnum++, 1, "$test_type: obtained initial lock"); + ok($testnum++, 0, "# SKIP see perl583delta"); + + } else { + $test_type =~ /twain/ ? lock($lock) : lock($cond); + ok($testnum++, 1, "$test_type: obtained initial lock"); + my $ok; + for ($test_type) { + $ok = cond_timedwait($cond, time() + $to), last if /simple/; + $ok = cond_timedwait($cond, time() + $to, $cond), last if /repeat/; + $ok = cond_timedwait($cond, time() + $to, $lock), last if /twain/; + die "$test_type: unknown test\n"; + } + ok($testnum++, ! defined($ok), "$test_type: timeout"); + } + + return ($testnum); + } + + foreach (@wait_how) { + $test_type = "cond_timedwait pause, timeout [$_]"; + my $thr = threads->create(\&ctw_fail2, $TEST, 0.3); + $TEST = $thr->join(); + } + + foreach (@wait_how) { + $test_type = "cond_timedwait instant timeout [$_]"; + my $thr = threads->create(\&ctw_fail2, $TEST, -0.60); + $TEST = $thr->join(); + } + +} # -- SYNCH_REFS block + +# Done +exit(0); + +# EOF |