GetAttributes использует неверный рабочий каталог в подпотоке
Я использовал File::Find
пройти через дерево каталогов и Win32::File
"s GetAttributes
функция, чтобы посмотреть на атрибуты файлов, найденных в нем. Это работало в однопоточной программе.
Затем я переместил обход каталога в отдельный поток, и он перестал работать. GetAttributes
ошибка в каждом файле с "Системой не удается найти указанный файл" в качестве сообщения об ошибке в $^E
,
Я проследил проблему до того, что File::Find
использования chdir
и, видимо, GetAttributes
не использует текущий каталог. Я мог бы обойти это, передав ему абсолютный путь, но затем я мог бы столкнуться с ограничениями длины пути, и длинные пути определенно будут присутствовать там, где будет запускаться этот скрипт, поэтому мне действительно нужно воспользоваться chdir
и относительные пути.
Чтобы продемонстрировать проблему, вот скрипт, который создает файл в текущем каталоге, другой файл в подкаталоге, chdir в подкаталог и ищет файл 3 способами: system("dir")
, open
, а также GetAttributes
,
Когда скрипт запускается без аргументов, dir
показывает подкаталог, open
находит файл в подкаталоге и GetAttributes
возвращает свои атрибуты успешно. Когда бегать с --thread
все тесты выполняются в подзадаче, а dir
а также open
все еще работает, но GetAttributes
выходит из строя. Тогда это вызывает GetAttributes
в файле, который находится в исходном каталоге (из которого мы удалили chdir), и он находит его! как-то GetAttributes
использует исходный рабочий каталог процесса - или, возможно, рабочий каталог основного потока - в отличие от всех других файловых операций.
Как я могу это исправить? Я могу гарантировать, что основной поток не будет выполнять chdir'ing, если это имеет значение.
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();
}
1 ответ
В конце концов я понял, что perl поддерживает какой-то вторичный cwd, который применяется только к встроенным операторам perl, в то время как GetAttributes
использует родной cwd. Я не знаю, почему это происходит или почему это происходит только во вторичном потоке; мое лучшее предположение, что perl пытается эмулировать правило unix одного cwd на процесс, и терпит неудачу, потому что Win32::*
модули не играют вместе.
Безотносительно причины, можно обойти это, заставляя родной cwd быть тем же самым как cwd perl всякий раз, когда вы собираетесь сделать Win32::*
операция, как это:
use Cwd;
use Win32::FindFile qw/SetCurrentDirectory/;
...
SetCurrentDirectory(getcwd());
спорно File::Find
должен сделать это при запуске на Win32.
Конечно, это только усугубляет проблему "слишком длинного пути", потому что теперь каждый каталог, который вы посещаете, будет являться целью абсолютного пути. SetCurrentDirectory
; попытаться обойти это с рядом меньших SetCurrentDirectory
звонки, и вы должны найти способ вернуться туда, откуда вы пришли, что трудно, даже если у вас нет fchdir
,