2

I'm trying to create a thread at Rails startup which will run throughout the lifetime of the app. The strange thing is, I already had this working with another thread I was running. I copied that (working) code and used it as boilerplate for the new code for the new thread. But the thread won't fire up.

  • Code is in config/initializers (is this the correct place?).
  • File is named starting with 'z_...' to insure it runs last.
  • Rails 3.2.6

Here is the general structure of the code:

class Blah
  def self.the_thread
    The_Model.transaction do
    # do some database stuff
    # ...
  end

  TheThread = Thread.new do
    while true do
      the_thread
      sleep 5.seconds
    end
  end
end

Opening a rails console and examining Blah::TheThread reveals a dead thread that appears not to ever have run. There don't seem to be any errors in the declaration of the class, nor in the method because I can run the method when I open a rails console and it works just fine. Also, if I manually type into the rails console the exact code above which spawns the thread (TheThread = Thread new do ...), it works just fine (wakes up every 5 seconds, does its thing, sleeps again).

Again, the weird thing is, this exact boilerplate for spawning a simple thread in Rails worked for me before, in this exact same application.

If anyone has some possible insights into what I consider to be a strange problem, I'm all ears.

Thanks.

EDIT: New information - I just commented out the transaction call (and matching end) and it works fine. I don't know what the transaction could be interfering with though. I removed my other custom initializer script, and the only other ones in the directory are either default ones, or one that belongs to devise. I perused them, and didn't see any transactions going on.

More new info. I added 'TheThread.abort_on_exception = true' at the end of the thread code. When I startup rails I now get an exception 'ArgumentError: prepare called on a closed database: rollback transaction (ActiveRecord::StatementInvalid)'. The error is happening in the sqlite3 gem. I have sqlite3 gem version 1.3.6 and sqlite-ruby gem version 1.3.3. I have googled the error and found several posts complaining about the same error, but have not seen any solutions.

I no longer consider this to be a thread problem, but rather a database problem, but too late to change the title of this post.

THIS JUST IN: I put a 'sleep 15.seconds' right at the top of the thread code (just before it goes into the while loop) and that clears the problem up. But I'd still like to know what is causing the problem, and what a 'non-hacked' solution would be. For some reason the database isn't ready when this code runs. Is my initialization code simply in the wrong place?

user1992634
  • 764
  • 7
  • 16
  • this is probably an issue with your code. if you copy and pasted stuff, there is a good chance, that you used the same classnames and somehow did override the other threading code or stuff like that. – phoet Jan 19 '13 at 14:00
  • I've gone over every inch of the code (there isn't much - about a page). There is nothing in common (variable names, class names, etc.) except the basic structure. I even removed the other code as a test, but that didn't help the situation. – user1992634 Jan 19 '13 at 14:52
  • 1
    Im curious as to why you are doing this? Assuming it is because you want to have something running in a background process. If so why dont you look at delayed_job, resque, or sidekiq ? – ADAM Jan 20 '13 at 03:54
  • I didn't want to get into something complicated or heavy-duty for a very simple task that can be coded in half a page that just needs to be run once a day (the 'sleep 5 seconds' above is for testing). – user1992634 Jan 20 '13 at 04:22
  • Then use a cron job with a pure file. Threading is heavy-duty and asking for trouble here – Jim Deville Jan 20 '13 at 07:48
  • I want to run a spider that signs into a website, waits for a trigger, crawls the site, then waits for the next trigger while still signed in (looping with a sleep statement). This because the sign in process takes too long time in user time. For the OP, maybe the database connection is not fully established, hence the "prepare called on a closed database" and inserting the sleep statement makes it work. – Kevin Triplett Nov 30 '22 at 02:12

1 Answers1

0

I would really recommend looking at another way to implement this. Unless you really need 1 extra thread per instance when you go to production (imagine setting up 3-4 thin / mongrel / etc. servers - each will have the extra thread), there's better ways to go about it.

Option 1 - the simplest - is just to create a controller action that will execute your task, and then use wget and cron to trigger that action regularly.

20 * * * *  /usr/bin/wget --quiet -O - 'http://www.mydomain.com/my_controller/my_action'

The problem here is that you've got to deal with authentication / access control - unless you really don't care if some random user can trigger your job (which, frankly, is sometimes the case for very simple jobs).

Option 2 - use Clockwork/Sidekiq or BackgroundRb or some other way of running tasks in the background. I've done this in a production app, and it works very nicely. I chose Clockwork and Sidekiq, and my scheduled tasks look something like this:

app/workers/daily_worker.rb:

class DailyWorker
  include Sidekiq::Worker

  def perform
    # do stuff here
  end
end

clockwork.rb:

require 'clockwork'
require 'sidekiq'

Dir["app/workers/*"].each {|f| load f }

module Clockwork
  every 1.day, 'daily.jobs', :at => "00:30" do
    DailyWorker.perform_async
  end
end

This to me seemed like the best solution - and it only took me about 15 minutes to get it all up and running.

drosboro
  • 398
  • 1
  • 9