{"record":{"id":"d0590c76eb9ad323","repo":"docusealco/docuseal","slug":"document-is-closed","errorCode":null,"errorMessage":"Document is closed.","messagePattern":"Document is closed\\.","errorType":"exception","errorClass":"Pdfium::PdfiumError","httpStatus":null,"severity":"error","filePath":"lib/pdfium.rb","lineNumber":693,"sourceCode":"      end\n\n      doc = new(doc_ptr, [file_access, get_block, io])\n\n      return doc unless block_given?\n\n      begin\n        yield doc\n      ensure\n        doc.close\n      end\n    end\n\n    def closed?\n      @closed\n    end\n\n    def ensure_not_closed!\n      raise PdfiumError, 'Document is closed.' if closed?\n    end\n\n    def get_page(page_index)\n      ensure_not_closed!\n\n      unless page_index.is_a?(Integer) && page_index >= 0 && page_index < page_count\n        raise PdfiumError, \"Page index #{page_index} out of range (0..#{page_count - 1})\"\n      end\n\n      @pages[page_index] ||= Page.new(self, page_index)\n    end\n\n    def bookmarks(parent = nil, seen = Set.new)\n      acc = []\n      bookmark = Pdfium.FPDFBookmark_GetFirstChild(@document_ptr, parent)\n\n      until bookmark.null?\n        break unless seen.add?(bookmark.address)","sourceCodeStart":675,"sourceCodeEnd":711,"githubUrl":"https://github.com/docusealco/docuseal/blob/004a22c1c88109c7ba0b567df011a8cb13894001/lib/pdfium.rb#L675-L711","documentation":"Guard raised by Document#ensure_not_closed! when any operation is attempted on a document after close. close nulls the document handle, closes every cached page, exits the form-fill environment and frees buffers, so later use would be a use-after-free at the FFI boundary; this guard converts that into a Ruby exception. The block forms of Document.create/open_file/open_bytes/open_io close the document in an ensure clause, so using the doc outside the block is the most common trigger.","triggerScenarios":"Capturing the yielded doc from Pdfium::Document.open_file(path) { |d| } and calling methods on it after the block; saving a document after its open block returned; storing the doc in an instance variable and touching it in a later request or job.","commonSituations":"Rails controllers that open with the block form but stash the doc for the view; background jobs returning Document objects instead of extracted data; retry code that reuses a doc already closed by ensure.","solutions":["Perform all work inside the open block, or use the non-block form and call doc.close yourself in an ensure","Check doc.closed? before reuse and re-open the document when true","Return extracted data (strings, arrays, rendered bytes) rather than Document/Page objects from the scope that owns their lifecycle"],"exampleFix":"# before\ndoc = nil\nPdfium::Document.open_file(path) { |d| doc = d }\ndoc.page_count # raises Document is closed.\n\n# after\nPdfium::Document.open_file(path) do |doc|\n  @count = doc.page_count\nend","handlingStrategy":"validation","validationCode":"if doc.closed?\n  doc = Pdfium::Document.open_file(path) # or raise/re-open from source bytes\nend","typeGuard":null,"tryCatchPattern":"begin\n  doc.get_page(0)\nrescue Pdfium::PdfiumError => e\n  raise unless e.message == 'Document is closed.'\n  reopen_and_retry\nend","preventionTips":["Prefer the block forms (open_file/open_bytes/open_io with a block); they close in ensure and make this class of bug impossible","Never leak Document objects across request/job boundaries; pass extracted data instead","Assert !doc.closed? in test helpers that share documents across examples"],"tags":["pdfium","lifecycle","use-after-free","ruby"],"backgroundTag":"use-after-close","analyzedSha":"004a22c1c88109c7ba0b567df011a8cb13894001","analyzedAt":"2026-08-21T13:38:23.343Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}