`File.exists?/1` return format when :emfile error occurs

Hello Elixir Forum,

I have a block of pseudo-code saying this:

if !File.exists?(private_key) do
  generate_new_private_key(private_key)
  ...
end

The problem is, the implementation of File.exists?/1 is as follows:

  def exists?(path) do
    match?({:ok, _}, :file.read_file_info(IO.chardata_to_string(path)))
  end

Erlang’s :file.read_file_info/1 can for example return {:error, :emfile}, which is linux-speak for “Too many files open”. If that happens, File.exists?/1 returns false, and my code concludes there is no private key. If another process frees a file descriptor, my code will destroy my private key.

What is the proper way to handle these kinds of situations, besides implementing File.exists?/1 myself and having it return a format which handles this (nil, tuple return format, etc…)

I’m not sure on the best way to handle it, but you can use File.stat/2 to get an elixir-friendly version of :file.read_file_info. This lets you do something like:

case File.stat(key_file) do
  {:ok, _} -> handle_file_exists(key_file)
  {:error, :enoent} -> handle_file_doesnt_exist(key_file)
  {:error, :emfile} -> handle_too_many_descriptors(key_file)
  {:error, :enomem} -> handle_oom(key_file)
  {:error, reason} -> handle_generic(reason, keyfile)
end

The error reasons from File.stat/2 are the same as File.read/1, so if you are trying to either read it if it exists or create it if it doesn’t then you can skip stat and just use read instead.

There is probably a potential race condition where when you use File.stat there are enough free descriptors, but when you go to write the new key file the descriptors are all used up. Though I think if you are teetering on the edge of resource consumption probably a more holistic approach is in order.