Packages
livebook
0.19.7
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