Błąd “Errno::EMFILE: Too many open files” w testach jednostkowych

Dzisiaj pisząc kolejną porcję testów do Senpuu v5, po odpaleniu ich pod RCovem i Rubym 1.8.7 napotkałem na taki oto błąd:

Errno::EMFILE: Too many open files

Błąd ten związany jest z ilością otwartych jednocześnie plików (co ciekawe nie wystąpił podczas odpalania testów pod 1.9.2). Okazuje się, że tego typu wywołania:

file = File.new(File.join(Rails.root, "sciezka/do/pliku"), 'rb')

nie są nigdy zamykane, więc otwierając przykładowo 100 plików, żaden z nich nigdy nie zostanie zamknięty (przy zamykaniu aplikacji zostana). Z czasem ilość otwartych plików narasta i po pewnym czasie następuje zgłoszenie wyżej wspomnianego wyjątku. Rozwiązanie jest nader proste: zamykajmy otwierane pliki :)

W przypadku aplikacji jest to dość proste (robimy coś na pliku, kończymy i zamykamy). Jak jednak zapanować nad otwartymi plikami w testach? Przyznam szczerze, że jestem zbyt dużym leniem aby pamiętać o zamykaniu każdego pliku otworzonego w każdym teście. Z tego względu warto zbudować sobie prosty garbage collector który zajmie się tym za nas. Jak to zrobić pokaże na przykładzie podpięcia takiego kodu pod ActiveSupport::TestCase. Nic nie stoi na przeszkodzie aby tę metodę stosować także dla innych frameworków jak np. RSpec.

W metodze setup deklarujemy zmienną instancyjną, która przechowywać będzie nasze otwarte pliki:

  def setup
    super
    # Jakis inny kod ...
    @opened_files = []
  end

W metodzie teardown zamykamy wszystko:

  def teardown
    super
    # Jakis inny kod
    @opened_files.each { |f| f.close }
  end

I ostatnia z metod - metoda otwierająca pliki i zapamiętująca je w naszej zmiennej:

  def open_file(file = nil)
    return nil if filename.nil?
    file = File.new(File.join(Rails.root, file), 'rb')
    @opened_files << file
    file
  end

Od teraz, korzystając z metody open_file nie musimy się martwić otwartymi plikami. Zostaną automatycznie zamknięte podczas wywołania metody teardown.

Categories: Rails, Ruby

3 Comments

  1. A nie możesz w tym przypadku użyć metody File.open z dołączonym blokiem. Plik wtedy jest zamykany automatycznie po wykonaniu bloku.

  2. Mogę, tylko że te pliki są mi potrzebne praktycznie do końca testu (i to nie jeden) – miałem testy w których potrzebowałem mieć otwarte np 10 plików jednocześnie – pakowanie tego w bloki jest zdecydowanie bardziej pracochłonne niż stworzenie takiego prostego garbage collectora. Masz jednak rację, że w przypadku pojedyńczych operacji – Twoje rozwiązanie jest lepsze. Z drugiej jednak strony – w przypadku otwierania kilku, kilkunastu plików – ten błąd i tak nie wystąpi ;)

  3. Można by użyć finalizerów by zamknęły plik w momencie kiedy garbage colletor usuwać obiekt klasy będący nakładką na File. Nie było by to chyba trudno napisać.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Copyright © 2024 Closer to Code

Theme by Anders NorenUp ↑