Как проверить, заблокирован ли файл текущим потоком? - PullRequest
0 голосов
/ 26 октября 2018

Вот код Ruby:

File.open('a.txt', File::CREAT | File::RDWR) do |f|
  # Another thread deletes the a.txt file here
  f.flock(File::LOCK_EX | File::LOCK_NB)
  # How do I check that the file is really locked by my thread?
end

В многопоточной среде, когда многие из них пытаются заблокировать файл и затем удалить его одним потоком может удалитьэто прямо перед вызовом flock().В таком случае flock() по-прежнему считает, что файл на месте и возвращает true.

Я пытаюсь найти способ проверить, действительно ли файл заблокирован текущим потоком сразу послеflock() заканчивается.Как я могу это сделать?

1 Ответ

0 голосов
/ 20 декабря 2018

Если f.flock(File::LOCK_EX | File::LOCK_NB) возвращает не false значение, то f заблокировано.Он будет держать блокировку до тех пор, пока вы не закроете файл или явно не вызовете f.flock(File::LOCK_UN).Вам не нужно проверять, заблокирован ли он снова.Чтобы объяснить, что на самом деле там происходит, нам нужно сначала посмотреть на внутренние компоненты файловой системы и связанные системные вызовы:

 File Descriptor Table       Open File Table        i-node Table      Directory Index
╒════════════════════╕       ╒═════════════╕       ╒════════════╕     ╒═════════════╕
┃3 ..................┣━━━━━━▷┃ open file1  ┣━━┳━━━▷┃ /tmp/file1 ┃◃━━━━┫ file1       ┃
┃4 ..................┣━━━━━━▷┃ open file1  ┣━━┚ ┏━▷┃ /tmp/file2 ┃◃━━━━┫ file2       ┃
┃5 ..................┣━━━┳━━▷┃ open file2  ┣━━━━┚                   
┃6 ..................┣━━━┚

Ключевой момент на этой диаграмме состоит в том, что есть две разные и не связанные точки входа в Таблица i-узла : таблица открытых файлов и индекс каталога. Различные системные вызовы работают с разными точками входа:

  • open (file_path) => находит i-узелномер из индекса каталога и создает запись в таблице открытых файлов, на которую ссылается таблица дескрипторов файлов (одна таблица на процесс), затем увеличивает ref_counter в соответствующей записи таблицы i-узла.
  • close (file_descriptor) => closes (освобождает) связанную запись таблицы дескрипторов файлов и связанную запись из таблицы открытых файлов (если не существует других ссылающихся дескрипторов файлов), затем уменьшает значение ref_counter в связанной записи таблицы i-узла (если запись открытого файла не остается открытой)
  • unlink (file_path) => нет системного вызова Delete!Отключение таблицы i-node от индекса каталога путем удаления записи из индекса каталога.Счетчик уменьшений в соответствующей записи таблицы i-узла (не знает об Open File Table!)
  • flock (file_desriptor) => применить / удалить блокировку записей в Open File Table (не знает об индексе каталога!)
  • Удалена запись таблицы i-узла (практически удаляется файл). IFF ref_counter становится нулевым.Это может произойти после close () или после unlink ()

Ключевым моментом здесь является то, что unlink не обязательно удаляет файл (данные) немедленно!Он отменяет связь только с индексом каталога и таблицей i-узлаЭто означает, что даже после unlink файл все еще может быть открыт с активными блокировками!

Имея это в виду, представьте следующий сценарий с двумя потоками, пытаясь синхронизироваться пофайл с использованием open / flock / close и попыткой очистки с использованием unlink:

   THREAD 1                              THREAD 2
==================================================
       |                                    |
       |                                    |
(1) OPEN (file1, CREATE)                    |
       |                             (1) OPEN (file1, CREATE)
       |                                    |
(2) LOCK-EX (FD1->i-node-1)                 |
  [start work]                       (2) LOCK-EX (FD2->i-node-1) <---
       |                                    .                       |
       |                                    .                       |
(3)  work                                   .                       |
       |                             (3) waiting loop               |
       |                                    .                       |
   [end work]                               .                       |
(4) UNLINK (file1)                          . -----------------------
(5) CLOSE (FD1)--------unlocked------> [start work]
       |                                    |
       |                                    |
(6) OPEN (file1, CREATE)                    |
       |                                    |
       |                             (5)  work
(7) LOCK-EX (FD1->i-node-2)                 |
  [start work] !!! does not wait            |
       |                                    |
(8)  work                                   |
       |                                    |
  • (1) оба потока открывают (потенциально создают) один и тот же файл.В результате появляется ссылка из индекса каталога на таблицу i-узла.Каждый поток получает свой дескриптор файла.
  • (2) оба потока пытаются получить эксклюзивную блокировку, используя дескриптор файла, который они получают при открытом вызове
  • (3) первый поток получает блокировку, а второйпоток заблокирован (или пытается получить блокировку в цикле)
  • (4) первый поток завершает задачу и удаляет (отменяет связь) файл.На этом этапе ссылка из индекса каталога на i-узел удалена, и мы не увидим его в списке каталогов.НО, файл все еще там и открыт в двух потоках с активной блокировкой!Он просто потерял свое имя.
  • (5) первый поток закрывает дескриптор файла и в результате снимает блокировку.Таким образом, второй поток получает блокировку и начинает работать над задачей
  • (6), первый поток повторяется и пытается открыть файл с тем же именем.Но это тот же файл, что и раньше?Нет. Потому что на данный момент в каталоге индексов нет файла с данным именем.Таким образом, он создает новый файл вместо этого!новая запись таблицы i-узла.
  • (7) первый поток получает блокировку для нового файла!
  • (8), и мы получаем два потока с блокировкой для двух разных файлов и несинхронизированные

Проблема в приведенном выше сценарии заключается в том, что операции открытия / отмены связи работают с индексом каталога, а операции блокировки / закрытия - с дескрипторами файлов, которые не связаны друг с другом.

Чтобы решить эту проблему, нам нужно синхронизировать эти операции через некоторую центральную точку входа. Это можно реализовать, внедрив одноэлементную службу, которая будет обеспечивать эту синхронизацию, используя Mutex или примитивы из Concurrent Ruby .

Вот одна из возможных реализаций PoC:

class FS
  include Singleton

  def initialize
    @mutex = Mutex.new
    @files = {}
  end

  def open(path)
    path = File.absolute_path(path)
    file = nil
    @mutex.synchronize do
      file = File.open(path, File::CREAT | File::RDWR)
      ref_count = @files[path] || 0
      @files[path] = ref_count + 1
    end

    yield file
  ensure
    @mutex.synchronize do
      file.close
      ref_count = @files[path] - 1
      if ref_count.zero?
        FileUtils.rm(path, force: true)
        @files.delete(path)
      else
        @files[path] = ref_count
      end
    end
  end
end

А вот ваш переписанный пример из вопроса:

FS.instance.open('a.txt') do |f|
  if f.flock(File::LOCK_EX | File::LOCK_NB)
    # you can be sure that you have a lock
  end
  # 'a.txt' will finally be deleted
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...