Skip to content

Commit ec95fdc

Browse files
Fix memory leak in PDF cleanup
1 parent 93a5135 commit ec95fdc

3 files changed

Lines changed: 43 additions & 10 deletions

File tree

lib/pdf.ex

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ defmodule Pdf do
137137
end
138138

139139
@deprecated "Use build/2 instead"
140-
@spec open((any() -> any())) :: any()
141140
def open(opts \\ [], func) do
142141
build(opts, func)
143142
end
@@ -492,7 +491,7 @@ defmodule Pdf do
492491
`:align` | :left , :center , :right | :left
493492
`:kerning` | `boolean` | false
494493
"""
495-
@spec text_wrap(pid, coords(), dimension(), binary | list, keyword) :: pid
494+
@spec text_wrap(pid, coords(), dimension(), binary | list, keyword) :: {pid, :complete | term()}
496495
def text_wrap(pid, coords, dimensions, text, opts) do
497496
result = GenServer.call(pid, {:text_wrap, coords, dimensions, text, opts})
498497
{pid, result}
@@ -657,12 +656,6 @@ defmodule Pdf do
657656
pid
658657
end
659658

660-
def terminate(_, %{objects: objects, fonts: fonts}) do
661-
GenServer.stop(objects)
662-
GenServer.stop(fonts)
663-
nil
664-
end
665-
666659
defmodule Server do
667660
use GenServer
668661

@@ -684,7 +677,7 @@ defmodule Pdf do
684677
end
685678

686679
def handle_call(:export, _from, document) do
687-
{:reply, Document.to_iolist(document) |> :binary.list_to_bin(), document}
680+
{:reply, :binary.list_to_bin(Document.to_iolist(document)), document}
688681
end
689682

690683
def handle_call({:add_page, size}, _from, document) do
@@ -836,5 +829,12 @@ defmodule Pdf do
836829
def handle_call({:set_info, key, value}, _from, document) do
837830
{:reply, :ok, Document.put_info(document, key, value)}
838831
end
832+
833+
@impl GenServer
834+
def terminate(_, %{objects: objects, fonts: fonts}) do
835+
GenServer.stop(objects)
836+
GenServer.stop(fonts)
837+
nil
838+
end
839839
end
840840
end

test/pdf/document_test.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
defmodule Pdf.DocumentTest do
22
use ExUnit.Case, async: true
3-
import Pdf.Utils
43

54
alias Pdf.{Document, ObjectCollection, Dictionary}
65

test/pdf_test.exs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,38 @@ defmodule PdfTest do
197197
end
198198
end)
199199
end
200+
201+
test "cleanup" do
202+
{:ok, pdf} = Pdf.new(size: :a4)
203+
{:links, links} = Process.info(pdf, :links)
204+
{:dictionary, dictionary} = Process.info(pdf, :dictionary)
205+
ancestors = Keyword.get(dictionary, :"$ancestors")
206+
# One of our links is an ancestor which will remain active after cleanup
207+
children = links -- ancestors
208+
# We expect an Object collection and a Fonts server to be linked
209+
assert length(children) == 2
210+
211+
assert :ok = Pdf.cleanup(pdf)
212+
213+
assert Process.alive?(pdf) == false
214+
refute Enum.any?(children, &Process.alive?/1)
215+
end
216+
217+
test "crash cleanup" do
218+
{:ok, pdf} = Pdf.new(size: :a4)
219+
# Unlink so we can crash the PDF process without raising an error in the test
220+
Process.unlink(pdf)
221+
{:links, links} = Process.info(pdf, :links)
222+
{:dictionary, dictionary} = Process.info(pdf, :dictionary)
223+
ancestors = Keyword.get(dictionary, :"$ancestors")
224+
# One of our links is an ancestor which will remain active after crash
225+
children = links -- ancestors
226+
# We expect an Object collection and a Fonts server to be linked
227+
assert length(children) == 2
228+
229+
Process.exit(pdf, :kill)
230+
231+
assert Process.alive?(pdf) == false
232+
refute Enum.any?(children, &Process.alive?/1)
233+
end
200234
end

0 commit comments

Comments
 (0)