fb6d68
From faa03ffb8ccbf754d38d041570fcf2ce8816f36b Mon Sep 17 00:00:00 2001
fb6d68
From: =?UTF-8?q?Petr=20=C5=A0abata?= <contyk@redhat.com>
fb6d68
Date: Wed, 2 Sep 2015 16:24:58 +0200
fb6d68
Subject: [PATCH] File::Glob: Dup glob state in CLONE()
fb6d68
MIME-Version: 1.0
fb6d68
Content-Type: text/plain; charset=UTF-8
fb6d68
Content-Transfer-Encoding: 8bit
fb6d68
fb6d68
File::Glob: Dup glob state in CLONE()
fb6d68
fb6d68
This solves [perl #119897] and [perl #117823], and restores the
fb6d68
behavior of glob() in conjunction with threads of 5.14 and older.
fb6d68
fb6d68
Since 5.16, code that used glob() inside a thread had been
fb6d68
unintentionally sharing state between threads, which lead to things
fb6d68
like this crashing and failing assertions:
fb6d68
fb6d68
./perl -Ilib -Mthreads -e 'scalar glob("*"); threads->create(sub { glob("*") })->join();'
fb6d68
fb6d68
Signed-off-by: Petr Ĺ abata <contyk@redhat.com>
fb6d68
---
fb6d68
 MANIFEST                  |  1 +
fb6d68
 ext/File-Glob/Glob.xs     | 33 ++++++++++++++++++++++
fb6d68
 ext/File-Glob/t/threads.t | 71 +++++++++++++++++++++++++++++++++++++++++++++++
fb6d68
 3 files changed, 105 insertions(+)
fb6d68
 create mode 100644 ext/File-Glob/t/threads.t
fb6d68
fb6d68
diff --git a/MANIFEST b/MANIFEST
fb6d68
index 181bb3f..9771022 100644
fb6d68
--- a/MANIFEST
fb6d68
+++ b/MANIFEST
fb6d68
@@ -3683,6 +3683,7 @@ ext/File-Glob/t/global.t	See if File::Glob works
fb6d68
 ext/File-Glob/TODO		File::Glob extension todo list
fb6d68
 ext/File-Glob/t/rt114984.t	See if File::Glob works
fb6d68
 ext/File-Glob/t/taint.t		See if File::Glob works
fb6d68
+ext/File-Glob/t/threads.t	See if File::Glob + threads works
fb6d68
 ext/GDBM_File/GDBM_File.pm	GDBM extension Perl module
fb6d68
 ext/GDBM_File/GDBM_File.xs	GDBM extension external subroutines
fb6d68
 ext/GDBM_File/hints/sco.pl	Hint for GDBM_File for named architecture
fb6d68
diff --git a/ext/File-Glob/Glob.xs b/ext/File-Glob/Glob.xs
fb6d68
index d74e7a4..6c69aa6 100644
fb6d68
--- a/ext/File-Glob/Glob.xs
fb6d68
+++ b/ext/File-Glob/Glob.xs
fb6d68
@@ -9,6 +9,9 @@
fb6d68
 #define MY_CXT_KEY "File::Glob::_guts" XS_VERSION
fb6d68
 
fb6d68
 typedef struct {
fb6d68
+#ifdef USE_ITHREADS
fb6d68
+    tTHX interp;
fb6d68
+#endif
fb6d68
     int		x_GLOB_ERROR;
fb6d68
     HV *	x_GLOB_ENTRIES;
fb6d68
 } my_cxt_t;
fb6d68
@@ -380,6 +383,33 @@ PPCODE:
fb6d68
     iterate(aTHX_ doglob_iter_wrapper);
fb6d68
     SPAGAIN;
fb6d68
 
fb6d68
+#ifdef USE_ITHREADS
fb6d68
+
fb6d68
+void
fb6d68
+CLONE(...)
fb6d68
+INIT:
fb6d68
+    HV *glob_entries_clone = NULL;
fb6d68
+CODE:
fb6d68
+    PERL_UNUSED_ARG(items);
fb6d68
+    {
fb6d68
+        dMY_CXT;
fb6d68
+        if ( MY_CXT.x_GLOB_ENTRIES ) {
fb6d68
+            CLONE_PARAMS param;
fb6d68
+            param.stashes    = NULL;
fb6d68
+            param.flags      = 0;
fb6d68
+            param.proto_perl = MY_CXT.interp;
fb6d68
+            
fb6d68
+            glob_entries_clone = MUTABLE_HV(sv_dup_inc((SV*)MY_CXT.x_GLOB_ENTRIES, &param));
fb6d68
+        }
fb6d68
+    }
fb6d68
+    {
fb6d68
+        MY_CXT_CLONE;
fb6d68
+        MY_CXT.x_GLOB_ENTRIES = glob_entries_clone;
fb6d68
+        MY_CXT.interp = aTHX;
fb6d68
+    }
fb6d68
+
fb6d68
+#endif
fb6d68
+
fb6d68
 BOOT:
fb6d68
 {
fb6d68
 #ifndef PERL_EXTERNAL_GLOB
fb6d68
@@ -394,6 +424,9 @@ BOOT:
fb6d68
     {
fb6d68
 	dMY_CXT;
fb6d68
 	MY_CXT.x_GLOB_ENTRIES = NULL;
fb6d68
+#ifdef USE_ITHREADS
fb6d68
+	MY_CXT.interp = aTHX;
fb6d68
+#endif
fb6d68
     }  
fb6d68
 }
fb6d68
 
fb6d68
diff --git a/ext/File-Glob/t/threads.t b/ext/File-Glob/t/threads.t
fb6d68
new file mode 100644
fb6d68
index 0000000..141450a
fb6d68
--- /dev/null
fb6d68
+++ b/ext/File-Glob/t/threads.t
fb6d68
@@ -0,0 +1,71 @@
fb6d68
+#!./perl
fb6d68
+
fb6d68
+BEGIN {
fb6d68
+    chdir 't' if -d 't';
fb6d68
+    @INC = '../lib';
fb6d68
+    require Config; import Config;
fb6d68
+    if ($Config{'extensions'} !~ /\bFile\/Glob\b/i) {
fb6d68
+        print "1..0\n";
fb6d68
+        exit 0;
fb6d68
+    }
fb6d68
+}
fb6d68
+use strict;
fb6d68
+use warnings;
fb6d68
+# Test::More needs threads pre-loaded
fb6d68
+use if $Config{useithreads}, 'threads';
fb6d68
+use Test::More;
fb6d68
+
fb6d68
+BEGIN {
fb6d68
+    if (! $Config{'useithreads'}) {
fb6d68
+        plan skip_all => "Perl not compiled with 'useithreads'";
fb6d68
+    }
fb6d68
+}
fb6d68
+
fb6d68
+use File::Temp qw(tempdir);
fb6d68
+use File::Spec qw();
fb6d68
+use File::Glob qw(csh_glob);
fb6d68
+
fb6d68
+my($dir) = tempdir(CLEANUP => 1)
fb6d68
+    or die "Could not create temporary directory";
fb6d68
+
fb6d68
+my @temp_files = qw(1_file 2_file 3_file);
fb6d68
+for my $file (@temp_files) {
fb6d68
+    open my $fh, ">", File::Spec->catfile($dir, $file)
fb6d68
+        or die "Could not create file $dir/$file: $!";
fb6d68
+    close $fh;
fb6d68
+}
fb6d68
+my $cwd = Cwd::cwd();
fb6d68
+chdir $dir
fb6d68
+    or die "Could not chdir to $dir: $!";
fb6d68
+
fb6d68
+sub do_glob { scalar csh_glob("*") }
fb6d68
+# Stablish some glob state
fb6d68
+my $first_file = do_glob();
fb6d68
+is($first_file, $temp_files[0]);
fb6d68
+
fb6d68
+my @files;
fb6d68
+push @files, threads->create(\&do_glob)->join() for 1..5;
fb6d68
+is_deeply(
fb6d68
+    \@files,
fb6d68
+    [($temp_files[1]) x 5],
fb6d68
+    "glob() state is cloned for new threads"
fb6d68
+);
fb6d68
+
fb6d68
+@files = threads->create({'context' => 'list'},
fb6d68
+    sub {
fb6d68
+        return do_glob(), threads->create(\&do_glob)->join()
fb6d68
+    })->join();
fb6d68
+
fb6d68
+is_deeply(
fb6d68
+    \@files,
fb6d68
+    [@temp_files[1,2]],
fb6d68
+    "..and for new threads inside threads"
fb6d68
+);
fb6d68
+
fb6d68
+my $second_file = do_glob();
fb6d68
+is($second_file, $temp_files[1], "state doesn't leak from threads");
fb6d68
+
fb6d68
+chdir $cwd
fb6d68
+    or die "Could not chdir back to $cwd: $!";
fb6d68
+
fb6d68
+done_testing;
fb6d68
-- 
fb6d68
2.4.3
fb6d68