summaryrefslogtreecommitdiff
path: root/dist/threads-shared
diff options
context:
space:
mode:
authorNicholas Clark <nick@ccl4.org>2009-09-28 17:39:01 +0100
committerNicholas Clark <nick@ccl4.org>2009-09-29 11:12:38 +0100
commit6eb7b80a1af171aa2ba8c32e69d3cb08bb9359c8 (patch)
treed77c361946e8ebd30819aed1e7d258e1eab3325f /dist/threads-shared
parent72388ea1ff94f31ed9f6362cae7518adaaff4ee7 (diff)
downloadperl-6eb7b80a1af171aa2ba8c32e69d3cb08bb9359c8.tar.gz
Move threads::shared from ext/ to dist/
Diffstat (limited to 'dist/threads-shared')
-rwxr-xr-xdist/threads-shared/Makefile.PL111
-rw-r--r--dist/threads-shared/hints/linux.pl3
-rw-r--r--dist/threads-shared/shared.pm616
-rw-r--r--dist/threads-shared/shared.xs1624
-rw-r--r--dist/threads-shared/t/0nothread.t78
-rw-r--r--dist/threads-shared/t/av_refs.t95
-rw-r--r--dist/threads-shared/t/av_simple.t135
-rw-r--r--dist/threads-shared/t/blessed.t139
-rw-r--r--dist/threads-shared/t/clone.t175
-rw-r--r--dist/threads-shared/t/cond.t283
-rw-r--r--dist/threads-shared/t/disabled.t52
-rw-r--r--dist/threads-shared/t/hv_refs.t111
-rw-r--r--dist/threads-shared/t/hv_simple.t78
-rw-r--r--dist/threads-shared/t/no_share.t62
-rw-r--r--dist/threads-shared/t/object.t179
-rw-r--r--dist/threads-shared/t/shared_attr.t79
-rw-r--r--dist/threads-shared/t/stress.t149
-rw-r--r--dist/threads-shared/t/sv_refs.t101
-rw-r--r--dist/threads-shared/t/sv_simple.t64
-rw-r--r--dist/threads-shared/t/utf8.t96
-rw-r--r--dist/threads-shared/t/wait.t341
-rw-r--r--dist/threads-shared/t/waithires.t279
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