Skip to content

Commit bb53f2c

Browse files
authored
Clean up temp files after diff generation (#145)
TmpDir only cleans up on process exit, but LiveView processes are long-lived. Accumulated temp files cause page cache and kernel slab memory to grow, counted against the container's cgroup memory limit. Add TmpDir.cleanup/0 and call it after diff stream is consumed.
1 parent be4c221 commit bb53f2c

3 files changed

Lines changed: 79 additions & 33 deletions

File tree

lib/diff/tmp_dir.ex

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ defmodule Diff.TmpDir do
3232
dir
3333
end
3434

35+
def cleanup do
36+
pid = self()
37+
entries = :ets.lookup(@table, pid)
38+
39+
Enum.each(entries, fn {_pid, path} ->
40+
File.rm_rf(path)
41+
end)
42+
43+
:ets.delete(@table, pid)
44+
end
45+
3546
def await_cleanup(pid) do
3647
GenServer.call(__MODULE__, {:await_cleanup, pid}, 5000)
3748
end

lib/diff_web/live/diff_live_view.ex

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -144,40 +144,44 @@ defmodule DiffWeb.DiffLiveView do
144144
def handle_info({:generate_diff, package, from, to}, socket) do
145145
hex_impl = Application.get_env(:diff, :hex_impl, Diff.Hex)
146146

147-
case hex_impl.diff(package, from, to) do
148-
{:ok, stream} ->
149-
case process_stream_to_diffs(package, from, to, stream) do
150-
{:ok, metadata, diff_ids} ->
151-
initial_batch_size = 5
152-
{initial_diffs, _remaining} = Enum.split(diff_ids, initial_batch_size)
153-
154-
socket =
155-
socket
156-
|> assign(
157-
metadata: metadata,
158-
all_diff_ids: diff_ids,
159-
loaded_diffs: [],
160-
loaded_diff_content: %{},
161-
remaining_diffs: diff_ids,
162-
generating: false,
163-
has_more_diffs: length(diff_ids) > 0
164-
)
165-
166-
send(self(), {:load_diffs, initial_diffs})
167-
168-
{:noreply, socket}
169-
170-
{:error, reason} ->
171-
Logger.error("Failed to generate diff: #{inspect(reason)}")
172-
{:noreply, assign(socket, error: "Failed to generate diff", generating: false)}
173-
end
174-
175-
:error ->
176-
{:noreply, assign(socket, error: "Failed to generate diff", generating: false)}
147+
try do
148+
case hex_impl.diff(package, from, to) do
149+
{:ok, stream} ->
150+
case process_stream_to_diffs(package, from, to, stream) do
151+
{:ok, metadata, diff_ids} ->
152+
initial_batch_size = 5
153+
{initial_diffs, _remaining} = Enum.split(diff_ids, initial_batch_size)
154+
155+
socket =
156+
socket
157+
|> assign(
158+
metadata: metadata,
159+
all_diff_ids: diff_ids,
160+
loaded_diffs: [],
161+
loaded_diff_content: %{},
162+
remaining_diffs: diff_ids,
163+
generating: false,
164+
has_more_diffs: length(diff_ids) > 0
165+
)
166+
167+
send(self(), {:load_diffs, initial_diffs})
168+
169+
{:noreply, socket}
170+
171+
{:error, reason} ->
172+
Logger.error("Failed to generate diff: #{inspect(reason)}")
173+
{:noreply, assign(socket, error: "Failed to generate diff", generating: false)}
174+
end
175+
176+
:error ->
177+
{:noreply, assign(socket, error: "Failed to generate diff", generating: false)}
178+
end
179+
catch
180+
:throw, {:diff, :invalid_diff} ->
181+
{:noreply, assign(socket, error: "Invalid diff", generating: false)}
182+
after
183+
Diff.TmpDir.cleanup()
177184
end
178-
catch
179-
:throw, {:diff, :invalid_diff} ->
180-
{:noreply, assign(socket, error: "Invalid diff", generating: false)}
181185
end
182186

183187
def handle_info({:load_diffs_and_update, diff_ids}, socket) do

test/diff/tmp_dir_test.exs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,37 @@ defmodule Diff.TmpDirTest do
7272
end
7373
end
7474

75+
test "cleanup removes paths for calling process" do
76+
file = Diff.TmpDir.tmp_file("test")
77+
dir = Diff.TmpDir.tmp_dir("test")
78+
79+
assert File.exists?(file)
80+
assert File.dir?(dir)
81+
82+
Diff.TmpDir.cleanup()
83+
84+
refute File.exists?(file)
85+
refute File.exists?(dir)
86+
end
87+
88+
test "cleanup only removes paths for calling process" do
89+
test_pid = self()
90+
91+
Task.start(fn ->
92+
other_file = Diff.TmpDir.tmp_file("other")
93+
send(test_pid, {:other_path, other_file})
94+
Process.sleep(:infinity)
95+
end)
96+
97+
assert_receive {:other_path, other_file}
98+
99+
file = Diff.TmpDir.tmp_file("test")
100+
Diff.TmpDir.cleanup()
101+
102+
refute File.exists?(file)
103+
assert File.exists?(other_file)
104+
end
105+
75106
test "paths persist while process is alive" do
76107
file = Diff.TmpDir.tmp_file("test")
77108
dir = Diff.TmpDir.tmp_dir("test")

0 commit comments

Comments
 (0)