Я использовал File::Find
для обхода дерева каталогов и функцию GetAttributes
Win32::File
для просмотра атрибутов найденных в нем файлов. Это работало в однопоточной программе.
Потом я вынес обход каталога в отдельный поток, и он перестал работать. GetAttributes
сбой для каждого файла с сообщением «Система не может найти указанный файл» в качестве сообщения об ошибке в $^E
.
Я связал проблему с тем, что File::Find
использует chdir
, а GetAttributes
, видимо, не использует текущий каталог. Я мог бы обойти это, передав ему абсолютный путь, но тогда я мог бы столкнуться с ограничениями длины пути, и длинные пути определенно будут присутствовать там, где будет запускаться этот скрипт, поэтому мне действительно нужно использовать преимущества chdir
и относительных путей.
Чтобы продемонстрировать проблему, вот сценарий, который создает файл в текущем каталоге, другой файл в подкаталоге, chdir в подкаталог и ищет файл тремя способами: system("dir")
, open
и GetAttributes
.
Когда сценарий запускается без аргументов, dir
показывает подкаталог, open
находит файл в подкаталоге, а GetAttributes
успешно возвращает его атрибуты. При запуске с --thread
все тесты выполняются в подпотоке, и dir
и open
все еще работают, но GetAttributes
терпит неудачу. Затем он вызывает GetAttributes
для файла, находящегося в исходном каталоге (из которого мы удалили chdir), и находит его! Каким-то образом GetAttributes
использует исходный рабочий каталог процесса — или, может быть, рабочий каталог основного потока — в отличие от всех других операций с файлами.
Как я могу это исправить? Я могу гарантировать, что основной поток не будет делать никаких изменений, если это имеет значение.
use strict;
use warnings;
use threads;
use Data::Dumper;
use Win32::File qw/GetAttributes/;
sub doit
{
chdir("testdir") or die "chdir: $!\n";
system "dir";
my $attribs;
open F, '<', "file.txt" or die "open: $!\n";
print "open succeeded. File contents:\n-------\n", <F>, "\n--------\n";
close F;
my $x = GetAttributes("file.txt", $attribs);
print Dumper [$x, $attribs, $!, $^E];
if(!$x) {
# If we didn't find the file we were supposed to find, how about the
# bad one?
$x = GetAttributes("badfile.txt", $attribs);
if($x) {
print "GetAttributes found the bad file!\n";
if(open F, '<', "badfile.txt") {
print "opened the bad file\n";
close F;
} else {
print "But open didn't open it. Error: $! ($^E)\n";
}
}
}
}
# Setup
-d "testdir" or mkdir "testdir" or die "mkdir testdir: $!\n";
if(!-f "badfile.txt") {
open F, '>', "badfile.txt" or die "create badfile.txt: $!\n";
print F "bad\n";
close F;
}
if(!-f "testdir/file.txt") {
open F, '>', "testdir/file.txt" or die "create testdir/file.txt: $!\n";
print F "hello\n";
close F;
}
# Option 1: do it in the main thread - works fine
if(!(@ARGV && $ARGV[0] eq '--thread')) {
doit();
}
# Option 2: do it in a secondary thread - GetAttributes fails
if(@ARGV && $ARGV[0] eq '--thread') {
my $thr = threads->create(\&doit);
$thr->join();
}