Ruby - Working with temporary file
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ếndestination
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.
- 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.
- Sử dụng Tempfile.create thay vì Tempfile.new, giống như recommend Devdocs