Resource management in Elixir - what happens if between the call to File.open and File.close an error occurs?

Sorry if this was asked before, but i can’t find an answer to this anywhere.

I’m currently learning elixir (i come from a mostly non-functional background go & javascript) there’s something bugging me, in elixir we just let things crash right? and have the supervising process deal with the error but what happens if we do something like

{:ok, f} = File.open("foo.json")
# do things with f
:ok = File.close(f)

What if between the call to File.open("foo.json") and the File.close(f)
an exception occurs this would mean that File.close will never be called does that mean that my process will start leaking resources ? or is the process spawned by File.open is linked to the process calling File.open and will be shutdown and the file handle released when the supervising process shuts down.

Hey @MSE99 welcome! When you open a resource in Elixir / Erlang, that resource is linked to your current process. If that process crashes, the link ensures that the resource is released as well.

Here is a great explanation by @ityonemo
Peeking under the hood of File IO in Elixir

The important piece here is that the runtime doesn’t prevent leaks because the processes opening the file cleans up, but it works because there are multiple processes. Monitors and links are methods a process can use to get notified if another process crashed. This can then be used to trigger cleanup even if the original process requesting the resource is gone.

It is actually not linked, but the process that opened the file is monitored so the resources can be released if it dies.

❯ iex
Erlang/OTP 25 [erts-13.0.1] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit]

Interactive Elixir (1.14.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> self()
#PID<0.110.0>
iex(2)> {:ok, file} = File.open("test.db")
{:ok, #PID<0.113.0>}
iex(3)> Process.info(file)
[
  current_function: {:file_io_server, :server_loop, 1},
  initial_call: {:erlang, :apply, 2},
  status: :waiting,
  message_queue_len: 0,
  links: [#PID<0.58.0>],  # <--------- no link to #PID<0.110.0> here
  dictionary: [],
  trap_exit: false,
  error_handler: :error_handler,
  priority: :normal,
  group_leader: #PID<0.46.0>,
  total_heap_size: 233,
  heap_size: 233,
  stack_size: 4,
  reductions: 102,
  garbage_collection: [
    max_heap_size: %{error_logger: true, kill: true, size: 0},
    min_bin_vheap_size: 46422,
    min_heap_size: 233,
    fullsweep_after: 65535,
    minor_gcs: 0
  ],
  suspending: []
]
Originally sent in Elixir Chat
MSE99

We open files with File.open right? And we close them using File.close - what if between the call to File.open and File.close an error occurs?

That File.close will never be called and the program will leak that file descriptor?

I know that File.open spawns a process, is that process linked to the process that spawned it?

kwando

from the docs of File.open:

This function returns:

  • {:ok, io_device} - the file has been opened in the requested mode.io_device is actually the PID of the process which handles the file. This process monitors the process that originally opened the file (the owner process). If the owner process terminates, the file is closed and the process itself terminates too. If any process to which the io_device is linked terminates, the file will be closed and the process itself will be terminated. An io_device returned from this call can be used as an argument to the IO module functions.

File.open also takes a callback. File will be closed automatically after the callback.

wolf4earth

Indeed

IMO:

  • if an exception is raised without being catched, then the process will be killed (by default), and all the related resources will be garbage-collected.
  • if an exception is raised, and you can catch it, then you still have the chance to call File.close.

(oops made a mistake)