gsutil cp: одновременное выполнение приводит к повреждению локального файла

У меня есть Perl-скрипт, который вызывает gsutil cp для копирования выбранного из GCS в локальную папку:

$cmd = "[bin-path]/gsutil cp -n gs://[gcs-file-path] [local-folder]";
$output = `$cmd 2>&1`;

Сценарий вызывается через HTTP и, следовательно, может быть запущен несколько раз (например, двойным щелчком по ссылке). Когда это происходит, локальный файл может оказаться в два раза больше правильного размера и, следовательно, явно поврежден. Три вещи кажутся странными:

  1. Похоже, что gsutil не блокирует локальный файл во время записи в него, позволяя другому потоку (в данном случае другому экземпляру gsutil) записывать в тот же файл.

  2. Кажется, что «-n» не имеет никакого эффекта. Я ожидал, что это предотвратит попытку копирования вторым экземпляром gsutil.

  3. Проверка подписи MD5 дает сбой: обычно gsutil удаляет целевой файл, если есть несоответствие подписи, но это явно происходит не всегда.

Файлы, о которых идет речь, больше 2 МБ (обычно около 5 МБ), поэтому может возникнуть некоторое взаимодействие с функцией автоматического возобновления. Сценарий Perl вызывает gsutil только в том случае, если локальный файл еще не существует, но он не фиксирует двойной щелчок (из-за временной задержки для аутентификации передачи GCS).

версия gsutil: 3.42 для FreeBSD 8.2

Кто-нибудь испытывает подобную проблему? У кого-нибудь есть идеи?

Эдвард Ли


person EdwardLeigh    schedule 15.05.2014    source источник


Ответы (3)


1) Вы правы, я не вижу блокировки в исходниках.

2) Это может быть вызвано состоянием гонки - Процесс 1 проверяет, видит, что файла нет. Процесс 2 проверяет, видит, что файла нет. Процесс 1 начинает загрузку. Процесс 2 начинает загрузку. В документах говорится, что это операция HEAD перед фактическим процессом загрузки - это не атомарно с фактической загрузкой.

3) Никаких комментариев по этому поводу.

Вы можете решить эту проблему, если ваш скрипт поддерживает какую-то атомарную блокировку файла до начала передачи, т.е. ваша проверка будет выглядеть примерно так:

use Lock::File qw(lockfile);

if (my $lock = lockfile("$localfile.lock", { blocking => 0 } )) {
     ... perform transfer ...
     undef $lock;
}
else {
    die "Unable to retrieve $localfile, file is locked";
}
person Oesor    schedule 15.05.2014

1) gsutil в настоящее время не блокирует файлы.

2) -n не защищает от других экземпляров gsutil, запущенных одновременно с перекрывающимся пунктом назначения.

3) Хеш-дайджесты рассчитываются для байтов по мере их загрузки в целях оптимизации производительности. Это позволяет избежать длительных вычислений после завершения загрузки. Если проверка хэша прошла успешно, вы гарантируете, что байты были успешно записаны в какой-то момент. Но если что-то (даже другой экземпляр gsutil) изменит содержимое на месте во время работы процесса, дайджесты этого не обнаружат.

person Travis Hobrla    schedule 15.05.2014

Спасибо Oesor и Travis за ответы на все вопросы между ними. В качестве дополнения к предложенному Oesor решению я предлагаю эту альтернативу для систем, в которых отсутствует Lock::File:

use Fcntl ':flock'; # import LOCK_* constants

# if lock file exists ...
if (-e($lockFile))
{
  # abort if lock file still locked (or sleep and re-check)
  abort() if !unlink($lockFile);
  # otherwise delete local file and download again
  unlink($filePath);
}

# if file has not been downloaded already ...
if (!-e($filePath))
{
  $cmd = "[bin-path]/gsutil cp -n gs://[gcs-file-path] [local-dir]";

  abort() if !open(LOCKFILE, ">$lockFile");
  flock(LOCKFILE, LOCK_EX);
  my $output = `$cmd 2>&1`;
  flock(LOCKFILE, LOCK_UN);
  unlink($lockFile);
}
person EdwardLeigh    schedule 15.05.2014