Skip to Content

Ruby - Working with temporary file

Posted on

Cuối tuần vừa rồi mình gặp 1 bug khá thú vị trên production nên muốn note lại, biết đâu lại hữu ích trong tương lai =))

Mô tả

Mình có 1 đoạn code mô phỏng (đơn giản) như bên dưới.

Các bước sẽ là:

  • Tạo tempfile
  • Write content vào file
  • Mở file ra để đọc lại
require 'tempfile'
require 'pry'

def write_to_file destination
  File.open(destination, "w") do |file|
    file_size_mb = 1000
    file_size_bytes = file_size_mb * 1024 * 1024

    file.write("a" * file_size_bytes)
  end
end


file_name = "test"
destination = Tempfile.new([file_name, ".pdf"]).path
write_to_file destination
File.open(destination)

binding.pry

Khi chạy đoạn code trên, thỉnh thoảng dòng File.open(destination) sẽ raise lỗi Errno::ENOENT: No such file or directory @ rb_sysopen, tức là không tìm thấy file.

Hoặc khi đặt debug vào bên dưới dòng File.open, ta gọi lại đoạn code File.open(destination) một lần nữa, ta sẽ gặp lỗi như trên.

Vấn đề là lỗi này chỉ thỉnh thoảng gặp phải, chứ không phải lúc nào cũng bị =)) Tần suất sẽ tăng lên nếu ta tăng file_size_mb lên.

Nguyên nhân

Khi khai báo: destination = Tempfile.new([file_name, ".pdf"]).path, Ruby sẽ:

  • Tạo ra 1 Tempfile object
  • Lấy path của object đó và gán vào biến destination

Vấn đề chính là ở đây.

Nếu code như trên, khi tạo ra 1 tempfile object, ta không gán reference cho object đó. Tức là không gán object đó vào 1 biến nào cả. Đây chỉ là 1 object tạm sinh ra để lấy được path thôi.

Khi nội dung write file càng tăng => Memory cần sử dụng sẽ tăng lên => Ruby Garbage Collector sẽ phải đi thu thập object “rác” => Những object không có reference sẽ được hiểu là không cần dùng nữa => Object tempfile bị thu hồi => File link với nó sẽ bị xóa => Ta không thể gọi File.open(destination) được nữa.

Giải pháp

Có 2 cách để xử lý vấn đề này.

  1. Khai báo biến khi tạo ra object tempfile
file = Tempfile.new('hello')
file.path

Khi đó, biến file sẽ được GC thu hồi khi hết chương trình, nên object tempfile sẽ không bị xóa giữa chừng nữa.

  1. Sử dụng Tempfile.create thay vì Tempfile.new, giống như recommend Devdocs
comments powered by Disqus