Packages
livebook
0.14.0-rc.0
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook/session/file_guard.ex
defmodule Livebook.Session.FileGuard do
# Serves as a locking mechanism for notebook files.
#
# Every session process willing to persist notebook
# should turn to `FileGuard` to make sure the file
# is not already used by another session.
use GenServer
alias Livebook.FileSystem
@type state :: %{
files: %{term() => {FileSystem.File.t(), owner_pid :: pid(), reference()}}
}
@name __MODULE__
def start_link(_opts \\ []) do
GenServer.start_link(__MODULE__, [], name: @name)
end
def stop() do
GenServer.stop(@name)
end
@doc """
Locks the given file associating it with the given process.
If the owner process dies the file is automatically unlocked.
"""
@spec lock(FileSystem.File.t(), pid()) :: :ok | {:error, :already_in_use}
def lock(file, owner_pid) do
GenServer.call(@name, {:lock, file, owner_pid})
end
@doc """
Unlocks the given file.
"""
@spec unlock(FileSystem.File.t()) :: :ok
def unlock(file) do
GenServer.cast(@name, {:unlock, file})
end
# Callbacks
@impl true
def init(_opts) do
{:ok, %{files: %{}}}
end
@impl true
def handle_call({:lock, file, owner_pid}, _from, state) do
file_id = FileSystem.File.resource_identifier(file)
if Map.has_key?(state.files, file_id) or lock_globally(file, file_id, owner_pid) == false do
{:reply, {:error, :already_in_use}, state}
else
monitor_ref = Process.monitor(owner_pid)
state = put_in(state.files[file_id], {file, owner_pid, monitor_ref})
{:reply, :ok, state}
end
end
@impl true
def handle_cast({:unlock, file}, state) do
file_id = FileSystem.File.resource_identifier(file)
{maybe_file, state} = pop_in(state.files[file_id])
with {file, owner_pid, monitor_ref} <- maybe_file do
unlock_globally(file, file_id, owner_pid)
Process.demonitor(monitor_ref, [:flush])
end
{:noreply, state}
end
@impl true
def handle_info({:DOWN, ref, :process, _, _}, state) do
{file_id, {file, owner_pid, ^ref}} =
Enum.find(state.files, &match?({_file_id, {_file, _owner_pid, ^ref}}, &1))
unlock_globally(file, file_id, owner_pid)
{_, state} = pop_in(state.files[file_id])
{:noreply, state}
end
defp lock_globally(file, file_id, owner_pid) do
FileSystem.File.local?(file) or :global.set_lock({file_id, owner_pid})
end
defp unlock_globally(file, file_id, owner_pid) do
FileSystem.File.local?(file) or :global.del_lock({file_id, owner_pid})
end
end