bk5115545 bk5115545 - 4 months ago 11
Ruby Question

Long sleep in Ruby

I have a rather large Sinatra app (about 4k lines) and one portion of it involves generating reports for the user to download.

I want the reports to auto-delete after so many days even if the webserver is restarted. I don't have a daemon process on the server and it's Windows or else I would just throw up some cron.

How is this kind of thing normally done?

I was thinking about something like the below but it wouldn't persist after server restart.

report_filename = "asdasdsad.pdf"
generate report(report_filename)
Thread.new {
sleep((60*60*24) * 7) # wait 7 days
File.delete(report_filename)
}

mwp mwp
Answer

Normally you would use cron or some kind of job queue like delayed_job or Sidekiq. You can do cron jobs in Windows with the Task Scheduler or the command-line AT command. If that isn't an option for some other reason, you can do something like wp-cron, which uses incoming requests as a kind of timer to see if there are any jobs that need to be run. It would look like:

require 'sinatra'
require 'date'

class App < Sinatra::Base
  configure do
    set :report_dir, '/path/to/reports'
    set :keep_for, 7 # days
  end

  helpers do
    def generate
      # write .pdf to settings.report_dir and return path to pdf
    end

    def cleanup
      Dir.glob(File.expand_path('*.pdf', settings.report_dir)) do |pdf|
        stat = File.lstat(pdf)
        File.unlink(pdf) if stat.file? and Date.today - stat.ctime.to_date > settings.keep_for
      end
    rescue Errno::ENOENT
      # another thread is working
    end
  end

  post '/generate_report' do
    send_file generate
  end

  after do
    Thread.new { cleanup } if rand < 0.01 # 1% of requests trigger cleanup
  end
end

This is obviously a simplistic example to get you started. You might consider using a mutex as well if you're concerned about multiple cleanup threads running concurrently and interfering with each other, and catching the ENOENT becomes expensive.