How does my Rails app download a file from S3? - ruby-on-rails

I have an app where users will be uploading files directly to S3. This is working. Now, I need a background worker (presently delayed_job) to retrieve the file and stash it in 'tmp/files' for processing.
How can this be done?
Edits: The app is presently running in EC2.

Background workers will run independently from your web app.
Try Resque for a commonly used Rails background worker solution. The idea is that you start Resque up independently from your web app, and it does its jobs independently of the application.
Have this worker sent basic HTTP requests to S3. Here's an API reference card to get you started. The idea is that you use some sort of Ruby REST client to send these requests, and parse the response you get from S3. Rest-client is a gem you can use to do this.
Optionally, you can also have the worker use the S3 gem which could be a bit easier.
With this approach, you'd have your worker run a script that does something like
picture = S3Object.find 'headshot.jpg', 'photos'

Use Resque.
add
gem 'resque'
gem 'resque-status'
With Resque you need Redis (to store information about workers) either use Redis-to-go or install Redis locally on your EC2 machine.
So after Resque is installed edit config/initializers/resque.rb
rails_root = ENV['RAILS_ROOT'] || File.dirname(__FILE__) + '/../..'
rails_env = ENV['RAILS_ENV'] || 'production'
resque_config = YAML.load_file(rails_root + '/config/resque.yml')
Resque.redis = resque_config[rails_env]
# This is if you are using Redis to go:
# ENV["REDISTOGO_URL"] ||= "redis://REDISTOGOSTUFFGOESHERE"
# uri = URI.parse(ENV["REDISTOGO_URL"])
# Resque.redis = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password, :thread_safe => true)
Resque::Plugins::Status::Hash.expire_in = (24 * 60 * 60) # 24hrs in seconds
Dir["#{Rails.root}/app/workers/*.rb"].each { |file| require file }
Here we are using local Redis, so resque.yml looks like this:
development: localhost:6379
test: localhost:6379
fi: localhost:6379
production: localhost:6379
You will need something like God to start/manage workers
So install it then add "resque-production.god" to config/ folder of your app
You will be able to start your workers via this: god -c config/resque-production.god
the config/resque-production.god file willl have something like:
rails_env = ENV['RAILS_ENV'] || "production"
rails_root = ENV['RAILS_ROOT'] || File.dirname(__FILE__) + '/..'
num_workers = 1
num_workers.times do |num|
God.watch do |w|
w.dir = "#{rails_root}"
w.name = "resque-#{num}"
w.group = 'resque'
w.interval = 30.seconds
w.env = {"QUEUE"=>"*", "RAILS_ENV"=>"production"}
w.start = "rake -f #{rails_root}/Rakefile environment resque:work --trace"
w.log = "#{rails_root}/log/resque.log"
w.err_log = "#{rails_root}/log/resque_error.log"
# restart if memory gets too high
w.transition(:up, :restart) do |on|
on.condition(:memory_usage) do |c|
c.above = 350.megabytes
c.times = 2
end
end
# determine the state on startup
w.transition(:init, { true => :up, false => :start }) do |on|
on.condition(:process_running) do |c|
c.running = true
end
end
# determine when process has finished starting
w.transition([:start, :restart], :up) do |on|
on.condition(:process_running) do |c|
c.running = true
c.interval = 5.seconds
end
# failsafe
on.condition(:tries) do |c|
c.times = 5
c.transition = :start
c.interval = 5.seconds
end
end
# start if process is not running
w.transition(:up, :start) do |on|
on.condition(:process_running) do |c|
c.running = false
end
end
end
end
Finally workers. They go into app/workers/ folder (here is app/workers/processor.rb)
class Processor
include Resque::Plugins::Status
#queue = :collect_queue
def perform
article_id = options["article_id"]
article = Article.find(article_id)
article.download_remote_file(article.file_url)
end
end
It is triggered by the callback in the Article model (app/models/article.rb)
class Article < ActiveRecord::Base
after_create :process
def download_remote_file(url)
# OpenURI extends Kernel.open to handle URLs as files
io = open(url)
# overrides Paperclip::Upfile#original_filename;
# we are creating a singleton method on specific object ('io')
def io.original_filename
base_uri.path.split('/').last
end
io.original_filename.blank? ? nil : io
end
def process
Processor.create(:article_id => self.id)
end
end

Related

I have just deployed the discourse application and getting error during Heroku db:migrate. May be redis is not able to connect

I deployed discourse applicattion in rails on heroku.
deployed succesfully
Getting error when i do heroku run rake db:migrate db:seed_fu
alse getting an other error on heroku run rake db:create
config/discourse_defaults.conf
# message bus redis server address
message_bus_redis_host = redis://h:p27179a7ac96b0d215c36e5d5a4bda0c0565e1e6d96cdf043b9f5481d68dd1541#ec2-3-219-59-76.compute-1.amazonaws.com:27969
# message bus redis server port
message_bus_redis_port = 27969
# message bus redis slave server address
message_bus_redis_slave_host =
# message bus redis slave server port
message_bus_redis_slave_port = 27969
config/intializer/001-redis.rb
if Rails.env.development? && ENV['DISCOURSE_FLUSH_REDIS']
puts "Flushing redis (development mode)"
$redis.flushall
end
application.rb
# frozen_string_literal: true
# note, we require 2.5.2 and up cause 2.5.1 had some mail bugs we no longer
# monkey patch, so this avoids people booting with this problem version
begin
if !RUBY_VERSION.match?(/^2\.(([67])|(5\.[2-9]))/)
STDERR.puts "Discourse requires Ruby 2.5.2 or up"
exit 1
end
rescue
# no String#match?
STDERR.puts "Discourse requires Ruby 2.5.2 or up"
exit 1
end
require File.expand_path('../boot', __FILE__)
require 'active_record/railtie'
require 'action_controller/railtie'
require 'action_view/railtie'
require 'action_mailer/railtie'
require 'sprockets/railtie'
# Plugin related stuff
require_relative '../lib/discourse_event'
require_relative '../lib/discourse_plugin'
require_relative '../lib/discourse_plugin_registry'
require_relative '../lib/plugin_gem'
# Global config
require_relative '../app/models/global_setting'
GlobalSetting.configure!
unless Rails.env.test? && ENV['LOAD_PLUGINS'] != "1"
require_relative '../lib/custom_setting_providers'
end
GlobalSetting.load_defaults
if ENV['SKIP_DB_AND_REDIS'] == '1'
GlobalSetting.skip_db = true
GlobalSetting.skip_redis = true
end
require 'pry-rails' if Rails.env.development?
if defined?(Bundler)
bundler_groups = [:default]
if !Rails.env.production?
bundler_groups = bundler_groups.concat(Rails.groups(
assets: %w(development test profile)
))
end
Bundler.require(*bundler_groups)
end
module Discourse
class Application < Rails::Application
def config.database_configuration
if Rails.env.production?
GlobalSetting.database_config
else
super
end
end
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# this pattern is somewhat odd but the reloader gets very
# confused here if we load the deps without `lib` it thinks
# discourse.rb is under the discourse folder incorrectly
require_dependency 'lib/discourse'
require_dependency 'lib/es6_module_transpiler/rails'
require_dependency 'lib/js_locale_helper'
# tiny file needed by site settings
require_dependency 'lib/highlight_js/highlight_js'
# mocha hates us, active_support/testing/mochaing.rb line 2 is requiring the wrong
# require, patched in source, on upgrade remove this
if Rails.env.test? || Rails.env.development?
require "mocha/version"
require "mocha/deprecation"
if Mocha::VERSION == "0.13.3" && Rails::VERSION::STRING == "3.2.12"
Mocha::Deprecation.mode = :disabled
end
end
# Disable so this is only run manually
# we may want to change this later on
# issue is image_optim crashes on missing dependencies
config.assets.image_optim = false
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += Dir["#{config.root}/app/serializers"]
config.autoload_paths += Dir["#{config.root}/lib/validators/"]
config.autoload_paths += Dir["#{config.root}/app"]
if Rails.env.development? && !Sidekiq.server?
config.autoload_paths += Dir["#{config.root}/lib"]
end
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
config.assets.paths += %W(#{config.root}/config/locales #{config.root}/public/javascripts)
if Rails.env == "development" || Rails.env == "test"
config.assets.paths << "#{config.root}/test/javascripts"
config.assets.paths << "#{config.root}/test/stylesheets"
config.assets.paths << "#{config.root}/node_modules"
end
# Allows us to skip minifincation on some files
config.assets.skip_minification = []
# explicitly precompile any images in plugins ( /assets/images ) path
config.assets.precompile += [lambda do |filename, path|
path =~ /assets\/images/ && !%w(.js .css).include?(File.extname(filename))
end]
config.assets.precompile += %w{
vendor.js
admin.js
preload-store.js
browser-update.js
break_string.js
ember_jquery.js
pretty-text-bundle.js
wizard-application.js
wizard-vendor.js
plugin.js
plugin-third-party.js
markdown-it-bundle.js
service-worker.js
google-tag-manager.js
google-universal-analytics.js
preload-application-data.js
print-page.js
omniauth-complete.js
activate-account.js
auto-redirect.js
wizard-start.js
onpopstate-handler.js
embed-application.js
}
# Precompile all available locales
unless GlobalSetting.try(:omit_base_locales)
Dir.glob("#{config.root}/app/assets/javascripts/locales/*.js.erb").each do |file|
config.assets.precompile << "locales/#{file.match(/([a-z_A-Z]+\.js)\.erb$/)[1]}"
end
end
# out of the box sprockets 3 grabs loose files that are hanging in assets,
# the exclusion list does not include hbs so you double compile all this stuff
initializer :fix_sprockets_loose_file_searcher, after: :set_default_precompile do |app|
app.config.assets.precompile.delete(Sprockets::Railtie::LOOSE_APP_ASSETS)
start_path = ::Rails.root.join("app/assets").to_s
exclude = ['.es6', '.hbs', '.js', '.css', '']
app.config.assets.precompile << lambda do |logical_path, filename|
filename.start_with?(start_path) &&
!exclude.include?(File.extname(logical_path))
end
end
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
config.time_zone = 'UTC'
# auto-load locales in plugins
# NOTE: we load both client & server locales since some might be used by PrettyText
config.i18n.load_path += Dir["#{Rails.root}/plugins/*/config/locales/*.yml"]
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = 'utf-8'
config.assets.initialize_on_precompile = false
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [
:password,
:pop3_polling_password,
:api_key,
:s3_secret_access_key,
:twitter_consumer_secret,
:facebook_app_secret,
:github_client_secret,
:second_factor_token,
]
# Enable the asset pipeline
config.assets.enabled = true
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.2.4'
# see: http://stackoverflow.com/questions/11894180/how-does-one-correctly-add-custom-sql-dml-in-migrations/11894420#11894420
config.active_record.schema_format = :sql
# per https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet
config.pbkdf2_iterations = 64000
config.pbkdf2_algorithm = "sha256"
# rack lock is nothing but trouble, get rid of it
# for some reason still seeing it in Rails 4
config.middleware.delete Rack::Lock
# wrong place in middleware stack AND request tracker handles it
config.middleware.delete Rack::Runtime
# ETags are pointless, we are dynamically compressing
# so nginx strips etags, may revisit when mainline nginx
# supports etags (post 1.7)
config.middleware.delete Rack::ETag
unless Rails.env.development?
require 'middleware/enforce_hostname'
config.middleware.insert_after Rack::MethodOverride, Middleware::EnforceHostname
end
require 'content_security_policy/middleware'
config.middleware.swap ActionDispatch::ContentSecurityPolicy::Middleware, ContentSecurityPolicy::Middleware
require 'middleware/discourse_public_exceptions'
config.exceptions_app = Middleware::DiscoursePublicExceptions.new(Rails.public_path)
# Our templates shouldn't start with 'discourse/templates'
config.handlebars.templates_root = 'discourse/templates'
config.handlebars.raw_template_namespace = "Discourse.RAW_TEMPLATES"
require 'discourse_redis'
require 'logster/redis_store'
require 'freedom_patches/redis'
# Use redis for our cache
config.cache_store = DiscourseRedis.new_redis_store
$redis = DiscourseRedis.new
Logster.store = Logster::RedisStore.new(DiscourseRedis.new)
# we configure rack cache on demand in an initializer
# our setup does not use rack cache and instead defers to nginx
config.action_dispatch.rack_cache = nil
# ember stuff only used for asset precompliation, production variant plays up
config.ember.variant = :development
config.ember.ember_location = "#{Rails.root}/vendor/assets/javascripts/production/ember.js"
config.ember.handlebars_location = "#{Rails.root}/vendor/assets/javascripts/handlebars.js"
require 'auth'
if GlobalSetting.relative_url_root.present?
config.relative_url_root = GlobalSetting.relative_url_root
end
if Rails.env == "test"
if ENV['LOAD_PLUGINS'] == "1"
Discourse.activate_plugins!
end
else
Discourse.activate_plugins!
end
require_dependency 'stylesheet/manager'
require_dependency 'svg_sprite/svg_sprite'
config.after_initialize do
# require common dependencies that are often required by plugins
# in the past observers would load them as side-effects
# correct behavior is for plugins to require stuff they need,
# however it would be a risky and breaking change not to require here
require_dependency 'category'
require_dependency 'post'
require_dependency 'topic'
require_dependency 'user'
require_dependency 'post_action'
require_dependency 'post_revision'
require_dependency 'notification'
require_dependency 'topic_user'
require_dependency 'topic_view'
require_dependency 'topic_list'
require_dependency 'group'
require_dependency 'user_field'
require_dependency 'post_action_type'
# Ensure that Discourse event triggers for web hooks are loaded
require_dependency 'web_hook'
# So open id logs somewhere sane
OpenID::Util.logger = Rails.logger
# Load plugins
Discourse.plugins.each(&:notify_after_initialize)
# we got to clear the pool in case plugins connect
ActiveRecord::Base.connection_handler.clear_active_connections!
# This nasty hack is required for not precompiling QUnit assets
# in test mode. see: https://github.com/rails/sprockets-rails/issues/299#issuecomment-167701012
ActiveSupport.on_load(:action_view) do
default_checker = ActionView::Base.precompiled_asset_checker
ActionView::Base.precompiled_asset_checker = -> logical_path do
default_checker[logical_path] ||
%w{qunit.js qunit.css test_helper.css test_helper.js wizard/test/test_helper.js}.include?(logical_path)
end
end
end
if ENV['RBTRACE'] == "1"
require 'rbtrace'
end
config.generators do |g|
g.test_framework :rspec, fixture: false
end
# we have a monkey_patch we need to require early... prior to connection
# init
require 'freedom_patches/reaper'
end
end
app/model/global_settingg.rb
# frozen_string_literal: true
class GlobalSetting
def self.register(key, default)
define_singleton_method(key) do
provider.lookup(key, default)
end
end
VALID_SECRET_KEY ||= /^[0-9a-f]{128}$/
# this is named SECRET_TOKEN as opposed to SECRET_KEY_BASE
# for legacy reasons
REDIS_SECRET_KEY ||= 'SECRET_TOKEN'
REDIS_VALIDATE_SECONDS ||= 30
# In Rails secret_key_base is used to encrypt the cookie store
# the cookie store contains session data
# Discourse also uses this secret key to digest user auth tokens
# This method will
# - use existing token if already set in ENV or discourse.conf
# - generate a token on the fly if needed and cache in redis
# - enforce rules about token format falling back to redis if needed
def self.safe_secret_key_base
if #safe_secret_key_base && #token_in_redis && (#token_last_validated + REDIS_VALIDATE_SECONDS) < Time.now
#token_last_validated = Time.now
token = $redis.without_namespace.get(REDIS_SECRET_KEY)
if token.nil?
$redis.without_namespace.set(REDIS_SECRET_KEY, #safe_secret_key_base)
end
end
#safe_secret_key_base ||= begin
token = secret_key_base
if token.blank? || token !~ VALID_SECRET_KEY
#token_in_redis = true
#token_last_validated = Time.now
token = $redis.without_namespace.get(REDIS_SECRET_KEY)
unless token && token =~ VALID_SECRET_KEY
token = SecureRandom.hex(64)
$redis.without_namespace.set(REDIS_SECRET_KEY, token)
end
end
if !secret_key_base.blank? && token != secret_key_base
STDERR.puts "WARNING: DISCOURSE_SECRET_KEY_BASE is invalid, it was re-generated"
end
token
end
rescue Redis::CommandError => e
#safe_secret_key_base = SecureRandom.hex(64) if e.message =~ /READONLY/
end
def self.load_defaults
default_provider = FileProvider.from(File.expand_path('../../../config/discourse_defaults.conf', __FILE__))
default_provider.keys.concat(#provider.keys).uniq.each do |key|
default = default_provider.lookup(key, nil)
instance_variable_set("##{key}_cache", nil)
define_singleton_method(key) do
val = instance_variable_get("##{key}_cache")
unless val.nil?
val == :missing ? nil : val
else
val = provider.lookup(key, default)
if val.nil?
val = :missing
end
instance_variable_set("##{key}_cache", val)
val == :missing ? nil : val
end
end
end
end
def self.skip_db=(v)
#skip_db = v
end
def self.skip_db?
#skip_db
end
def self.skip_redis=(v)
#skip_redis = v
end
def self.skip_redis?
#skip_redis
end
def self.use_s3?
(#use_s3 ||=
begin
s3_bucket &&
s3_region && (
s3_use_iam_profile || (s3_access_key_id && s3_secret_access_key)
) ? :true : :false
end) == :true
end
def self.s3_bucket_name
#s3_bucket_name ||= s3_bucket.downcase.split("/")[0]
end
# for testing
def self.reset_s3_cache!
#use_s3 = nil
end
def self.database_config
hash = { "adapter" => "postgresql" }
%w{
pool
connect_timeout
timeout
socket
host
backup_host
port
backup_port
username
password
replica_host
replica_port
}.each do |s|
if val = self.public_send("db_#{s}")
hash[s] = val
end
end
hash["adapter"] = "postgresql_fallback" if hash["replica_host"]
hostnames = [ hostname ]
hostnames << backup_hostname if backup_hostname.present?
hostnames << URI.parse(cdn_url).host if cdn_url.present?
hash["host_names"] = hostnames
hash["database"] = db_name
hash["prepared_statements"] = !!self.db_prepared_statements
{ "production" => hash }
end
# For testing purposes
def self.reset_redis_config!
#config = nil
#message_bus_config = nil
end
def self.redis_config
#config ||=
begin
c = {}
c[:host] = redis_host if redis_host
c[:port] = redis_port if redis_port
if redis_slave_host && redis_slave_port
c[:slave_host] = redis_slave_host
c[:slave_port] = redis_slave_port
c[:connector] = DiscourseRedis::Connector
end
c[:password] = redis_password if redis_password.present?
c[:db] = redis_db if redis_db != 0
c[:db] = 1 if Rails.env == "test"
c[:id] = nil if redis_skip_client_commands
c.freeze
end
end
def self.message_bus_redis_config
return redis_config unless message_bus_redis_enabled
#message_bus_config ||=
begin
c = {}
c[:host] = message_bus_redis_host if message_bus_redis_host
c[:port] = message_bus_redis_port if message_bus_redis_port
if message_bus_redis_slave_host && message_bus_redis_slave_port
c[:slave_host] = message_bus_redis_slave_host
c[:slave_port] = message_bus_redis_slave_port
c[:connector] = DiscourseRedis::Connector
end
c[:password] = message_bus_redis_password if message_bus_redis_password.present?
c[:db] = message_bus_redis_db if message_bus_redis_db != 0
c[:db] = 1 if Rails.env == "test"
c[:id] = nil if message_bus_redis_skip_client_commands
c.freeze
end
end
def self.add_default(name, default)
unless self.respond_to? name
define_singleton_method(name) do
default
end
end
end
class BaseProvider
def self.coerce(setting)
return setting == "true" if setting == "true" || setting == "false"
return $1.to_i if setting.to_s.strip =~ /^([0-9]+)$/
setting
end
def resolve(current, default)
BaseProvider.coerce(
if current.present?
current
else
default.present? ? default : nil
end
)
end
end
class FileProvider < BaseProvider
attr_reader :data
def self.from(file)
if File.exists?(file)
parse(file)
end
end
def initialize(file)
#file = file
#data = {}
end
def read
ERB.new(File.read(#file)).result().split("\n").each do |line|
if line =~ /^\s*([a-z_]+[a-z0-9_]*)\s*=\s*(\"([^\"]*)\"|\'([^\']*)\'|[^#]*)/
#data[$1.strip.to_sym] = ($4 || $3 || $2).strip
end
end
end
def lookup(key, default)
var = #data[key]
resolve(var, var.nil? ? default : "")
end
def keys
#data.keys
end
def self.parse(file)
provider = self.new(file)
provider.read
provider
end
private_class_method :parse
end
class EnvProvider < BaseProvider
def lookup(key, default)
var = ENV["DISCOURSE_" + key.to_s.upcase]
resolve(var , var.nil? ? default : nil)
end
def keys
ENV.keys.select { |k| k =~ /^DISCOURSE_/ }.map { |k| k[10..-1].downcase.to_sym }
end
end
class BlankProvider < BaseProvider
def lookup(key, default)
if key == :redis_port
return ENV["DISCOURSE_REDIS_PORT"] if ENV["DISCOURSE_REDIS_PORT"]
end
default
end
def keys
[]
end
end
class << self
attr_accessor :provider
end
def self.configure!
if Rails.env == "test"
#provider = BlankProvider.new
else
#provider =
FileProvider.from(File.expand_path('../../../config/discourse.conf', __FILE__)) ||
EnvProvider.new
end
end
end
error
Failed to report error: Name or service not known 2 Name or service not known subscribe failed, reconnecting in 1 second. Call stack ["/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis/connection/hiredis.rb:19:in `connect'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis/connection/hiredis.rb:19:in `connect'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis/client.rb:334:in `establish_connection'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis/client.rb:99:in `block in connect'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis/client.rb:291:in `with_reconnect'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis/client.rb:98:in `connect'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis/client.rb:274:in `with_socket_timeout'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis/client.rb:131:in `call_loop'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis/subscribe.rb:43:in `subscription'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis/subscribe.rb:12:in `subscribe'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis.rb:2824:in `_subscription'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis.rb:2192:in `block in subscribe'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis.rb:45:in `block in synchronize'",
"/app/vendor/ruby-2.5.5/lib/ruby/2.5.0/monitor.rb:226:in `mon_synchronize'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis.rb:45:in `synchronize'",
"/app/vendor/bundle/ruby/2.5.0/gems/redis-4.0.1/lib/redis.rb:2191:in `subscribe'",
"/app/vendor/bundle/ruby/2.5.0/gems/message_bus-2.2.2/lib/message_bus/backends/redis.rb:287:in `global_subscribe'",
"/app/vendor/bundle/ruby/2.5.0/gems/message_bus-2.2.2/lib/message_bus.rb:721:in `global_subscribe_thread'",
"/app/vendor/bundle/ruby/2.5.0/gems/message_bus-2.2.2/lib/message_bus.rb:669:in `block in new_subscriber_thread'"]
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
rake aborted!
Name or service not known
Add your db and redis-host name and port no. in discourse_defaults.conf
Settinggs are available in Hroku_addons. In my case it is as following :
redis_host = ***-***-***.compute-1.amazonaws.com
redis_port = *****

Can not connect to Rails database from Sneakers worker (RabbitMQ RPC call)

TLDR;
Why Sneakers worker can't connect to the database or can't query it?
(General advices on "do's" and "dont's" are also welcome in comments)
Full question:
I am able to execute RPC call that returns a simple string, but I can't execute RPC call that is querying the database on the server side. I read the docs, tried many SO posts and blog tutorials, but I am still missing some piece.
I have two services. First service (Client) is using Bunny gem and is making an RPC call to second service (RPCServer) which is listening on workers using Sneakers gem. Both services are Rails apps.
RabbitMQ is serving in a docker container:
docker run -p 5672:5672 -p 15672:15672 rabbitmq:3-management
Postgres database is installed on a local machine.
Client service (mostly from Rabbitbunny docs ):
# app/services/client.rb
class Client
attr_accessor :call_id, :lock, :condition, :reply_queue, :exchange, :params, :response, :server_queue_name, :channel, :reply_queue_name
def initialize(rpc_route:, params:)
#channel = channel
#exchange = channel.fanout("Client.Server.exchange.#{params[:controller]}")
#server_queue_name = "Server.Client.queue.#{rpc_route}"
#reply_queue_name = "Client.Server.queue.#{params[:controller]}"
#params = params
setup_reply_queue
end
def setup_reply_queue
#lock = Mutex.new
#condition = ConditionVariable.new
that = self
#reply_queue = channel.queue(reply_queue_name, durable: true)
reply_queue.subscribe do |_delivery_info, properties, payload|
if properties[:correlation_id] == that.call_id
that.response = payload
that.lock.synchronize { that.condition.signal }
end
end
end
def call
#call_id = "NAIVE_RAND_#{rand}#{rand}#{rand}"
exchange.publish(params.to_json,
routing_key: server_queue_name,
correlation_id: call_id,
reply_to: reply_queue.name)
lock.synchronize { condition.wait(lock) }
connection.close
response
end
def channel
#channel ||= connection.create_channel
end
def connection
#connection ||= Bunny.new.tap { |c| c.start }
end
end
RPCServer service, using this gist (comments here are the "meat" of my question:
# app/workers/posts_worker.rb
require 'sneakers'
require 'sneakers/runner'
require 'byebug'
require 'oj'
class RpcServer
include Sneakers::Worker
from_queue 'Client.Server.queue.v1/filters/posts', durable: true, env: nil
def work_with_params(deserialized_msg, delivery_info, metadata)
post = {}
p "ActiveRecord::Base.connected?: #{ActiveRecord::Base.connected?}" # => true
##### This gets logged
Rails.logger.info "ActiveRecord::Base.connection_pool: #{ActiveRecord::Base.connection_pool}\n\n-------"
##### This never gets logged
Rails.logger.info "ActiveRecord::Base.connection_pool.with_connection: #{ActiveRecord::Base.connection_pool.with_connection}\n\n--------"
### interpreter never reaches this place when ActiveRecord methods like `with_connection`, `where`, `count` etc. are used
ActiveRecord::Base.connection_pool.with_connection do
post = Post.first.to_json
end
##### first commented `publish()` works fine and RPC works when no ActiveRecord is involved (this is, assuming above code using ActiveRecord is commented out)
##### second publish is not working
# publish("response from RPCServer", {
publish(post.to_json, {
to_queue: metadata[:reply_to],
correlation_id: metadata[:correlation_id],
content_type: metadata[:content_type]
})
ack!
end
end
Sneakers::Runner.new([RpcServer]).run
RPCServer sneakers configuration:
# config/initializers/sneakers.rb
Sneakers.configure({
amqp: "amqp://guest:guest#localhost:5672",
vhost: '/',
workers: 4,
log: 'log/sneakers.log',
pid_path: "tmp/pids/sneakers.pid",
timeout_job_after: 5,
prefetch: 10,
threads: 10,
durable: true,
ack: true,
heartbeat: 2,
exchange: "",
hooks: {
before_fork: -> {
Rails.logger.info('Worker: Disconnect from the database')
ActiveRecord::Base.connection_pool.disconnect!
Rails.logger.info("before_fork: ActiveRecord::Base.connected?: #{ActiveRecord::Base.connected?}") # => false
},
after_fork: -> {
ActiveRecord::Base.connection
Rails.logger.info("after_fork: ActiveRecord::Base.connected?: #{ActiveRecord::Base.connected?}") # => true
Rails.logger.info('Worker: Reconnect to the database')
},
timeout_job_after: 60
})
Sneakers.logger.level = Logger::INFO
RPCServer puma configuration:
# config/puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
port ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
preload_app!
### tried and did not work
# on_worker_boot do
# ActiveSupport.on_load(:active_record) do
# ActiveRecord::Base.establish_connection
# end
# end
before_fork do |server, worker|
# other settings
if defined?(ActiveRecord::Base)
ActiveRecord::Base.connection.disconnect!
end
end
after_worker_boot do |server, worker|
if defined?(ActiveRecord::Base)
ActiveRecord::Base.establish_connection
end
end
plugin :tmp_restart
for completeness, I also have an external Rakefile that is binding queues to exchanges (probably not important in this case)
namespace :rabbitmq do
desc "Setup routing"
task :setup do
conn = start_bunny
rpc_route service: :blog, from: 'v1/filters/posts_mappings', to: 'v1/filters/posts'
conn.close
end
def rpc_route(service:, from:, to:)
...
end
def start_bunny
...
end
end
I tried many sneakers configurations, and many orders of launching rabbitmq, resetting it, deleting queues, connections, etc. All of it is hard to list here and probably not the case.
Why I can't connect to the database or execute ActiveRecord methods? What Am I missing?
Ok I got it. The problem was last line of worker in RPCServer:
Sneakers::Runner.new([RpcServer]).run
It was running worker outside of Rails app. Commenting this out solved my problem of worker not being able to query database.

Resque: how can i use specific worker for only one specific queue

in my rails 5 app i use resque and resque-scheduler for sending message to my customer.for that i created different different queue for messages and created 3 worker for sending message using queue.
so, here my question is how can i use one specific worker for one specific queue.
i.e i have four queue like birthday_checker, reminder_message, appointment_checker, confirmation_message. For appointment_checker i set cron it will run every 56s. for that my all 3 worker busy to run appointment_checker queue. and other queue job going in pending. but here can i reserve one worker for this appointment_checker queue. so is it possible?
i tried find some related questions on stack but i can't find specific solu for that.
Here is my resque.god file code
rails_env = ENV['RAILS_ENV']
rails_root = File.dirname(__FILE__) + '/..'
num_workers = rails_env == 'production' ? 3 : 2
num_workers.times do |num|
God.watch do |w|
w.dir = "#{rails_root}"
w.name = "resque-#{num}"
w.group = 'resque'
w.interval = 30.seconds
w.env = {"QUEUE"=>"*", "RAILS_ENV"=>rails_env}
w.start = "bundle exec rake -f #{rails_root}/Rakefile environment resque:work"
w.log = "#{rails_root}/log/resque.log"
w.err_log = "#{rails_root}/log/resque_error.log"
# w.uid = 'git'
# w.gid = 'git'
# restart if memory gets too high
w.transition(:up, :restart) do |on|
on.condition(:memory_usage) do |c|
c.above = 350.megabytes
c.times = 2
end
end
# determine the state on startup
w.transition(:init, { true => :up, false => :start }) do |on|
on.condition(:process_running) do |c|
c.running = true
end
end
# determine when process has finished starting
w.transition([:start, :restart], :up) do |on|
on.condition(:process_running) do |c|
c.running = true
c.interval = 5.seconds
end
# failsafe
on.condition(:tries) do |c|
c.times = 5
c.transition = :start
c.interval = 5.seconds
end
end
# start if process is not running
w.transition(:up, :start) do |on|
on.condition(:process_running) do |c|
c.running = false
end
end
end
end
And this one is lib/tasks/resque_scheduler.rake file code
namespace :resque do
task :setup => :environment do
require 'resque'
ENV['QUEUE'] = '*'
end
task :setup_schedule => :setup do
require 'resque-scheduler'
Resque.schedule = YAML.load_file('config/resque_schedule.yml')
end
task :scheduler => :setup_schedule
end
I tried my best to ask my first question on stackoverflow so please ignore small mistake and give me some solution for above question.
Thanks!
The ENV['QUEUE'] = '*' is a wildcard for all queues. Each Queue will have the name of the class you are queuing with. Just replace the * with the name of the queue you want to run.

God configuration file to monitor existing processes?

I am monitoring my redis server using God (a Ruby gem). However, my existing server may already has an instance of redis up. How can I be sure it monitors the existing Redis server process that is already up?
This is my God file for redis:
rails_root = ENV['RAILS_ROOT']
redis_root = "/usr/local/bin"
# Redis
%w{6379}.each do |port|
God.watch do |w|
w.name = "redis"
w.interval = 30.seconds
w.start = "#{redis_root}/redis-server /etc/redis/redis.conf"
w.stop = "#{redis_root}/redis-cli shutdown"
w.restart = "#{w.stop} && #{w.start}"
w.start_grace = 10.seconds
w.restart_grace = 10.seconds
w.log = File.join(rails_root, 'log', 'redis.log')
w.keepalive(:memory_max => 5000.megabytes)
w.start_if do |start|
start.condition(:process_running) do |c|
c.interval = 5.seconds
c.running = false
end
end
end
end
To answer this question:
I put a
w.pid_file = "SOMETHING" in my God file, and made sure this PID file was also set in the configuration file for Redis.
You should also add:
w.pid_file = "Your_pid_file_name"
and then clean the pid file with
w.behaviour(:clean_pid_file)

Run resque in background

I have a working rails app with a resque queue system which works very well. However, I lack a good way of actually demonizing the resque workers.
I can start them just fine by going rake resque:work QUEUE="*" but I guess it's not the point that you should have your workers running in the foreground. For some reason nobody seems to adress this issue. On the official resque github page the claim you can do something like this:
PIDFILE=./resque.pid BACKGROUND=yes QUEUE="*" rake resque:work
well - it doesn't fork into the background here at least.
A +1 for resque-pool - it really rocks. We use it in combination with God to make sure that it is always available.
# Resque
God.watch do |w|
w.dir = RAILS_ROOT
w.name = "resque-pool"
w.interval = 30.seconds
w.start = "cd #{RAILS_ROOT} && sudo -u www-data sh -c 'umask 002 && resque-pool -d -E #{RAILS_ENV}'"
w.start_grace = 20.seconds
w.pid_file = "#{RAILS_ROOT}/tmp/pids/resque-pool.pid"
w.behavior(:clean_pid_file)
# restart if memory gets too high
#w.transition(:up, :restart) do |on|
# on.condition(:memory_usage) do |c|
# c.above = 350.megabytes
# c.times = 2
# end
#end
# determine the state on startup
w.transition(:init, { true => :up, false => :start }) do |on|
on.condition(:process_running) do |c|
c.running = true
end
end
# determine when process has finished starting
w.transition([:start, :restart], :up) do |on|
on.condition(:process_running) do |c|
c.running = true
c.interval = 5.seconds
end
# failsafe
on.condition(:tries) do |c|
c.times = 5
c.transition = :start
c.interval = 5.seconds
end
end
# start if process is not running
w.transition(:up, :start) do |on|
on.condition(:process_running) do |c|
c.running = false
end
end
end
This then gives you a really elegant way to reload code in your workers without interrupting jobs - simply kill -2 your resque-pool(s) when you deploy. Idle workers will die immediately, busy workers will die when they finish their current jobs, and God will restart resque-pool with workers using your new code.
These are our Resque tasks for Capistrano:
namespace :resque do
desc "Starts resque-pool daemon."
task :start, :roles => :app, :only => { :jobs => true } do
run "cd #{current_path};resque_pool -d -e #{rails_env} start"
end
desc "Sends INT to resque-pool daemon to close master, letting workers finish their jobs."
task :stop, :roles => :app, :only => { :jobs => true } do
pid = "#{current_path}/tmp/pids/resque-pool.pid"
sudo "kill -2 `cat #{pid}`"
end
desc "Restart resque workers - actually uses resque.stop and lets God restart in due course."
task :restart, :roles => :app, :only => { :jobs => true } do
stop # let God restart.
end
desc "List all resque processes."
task :ps, :roles => :app, :only => { :jobs => true } do
run 'ps -ef f | grep -E "[r]esque-(pool|[0-9])"'
end
desc "List all resque pool processes."
task :psm, :roles => :app, :only => { :jobs => true } do
run 'ps -ef f | grep -E "[r]esque-pool"'
end
end
You might need to reconnect any DB connections when resque-pool forks workers - check the docs.
I had the same problem and the following works for me.
PIDFILE=./resque.pid BACKGROUND=yes QUEUE="*" rake resque:work >> worker1.log &
You can also redirect STDERR to the same log file.
To demonize a process you can use nohup:
nohup cmd &
On resque's github there is a config for monit, that shows how to use nohup, it looks something like this:
nohup bundle exec rake resque:work QUEUE=queue_name PIDFILE=tmp/pids/resque_worker_QUEUE.pid & >> log/resque_worker_QUEUE.log 2>&1
Another option you should look into is using the resque pool gem to manage your workers.
You can run resque pool in background by using this command:
resque-pool --daemon --environment production
The BACKGROUND environment variable was added to Resque 1.20; make sure you're not using 1.19 or lower.
One good way is to use God to manage it. It launches a daemonized version of Resque and monitor it. Actually, you can choose between using Resque as a daemon and letting God daemonize Resque. I choose option 2.
A resque.god file example :
rails_env = ENV['RAILS_ENV'] || "production"
rails_root = ENV['RAILS_ROOT'] || "/path/to/my/app/current"
num_workers = rails_env == 'production' ? 5 : 2
num_workers.times do |num|
God.watch do |w|
w.dir = "#{rails_root}"
w.name = "resque-#{num}"
w.group = 'resque'
w.interval = 30.seconds
w.env = {"QUEUE"=>"critical,mailer,high,low", "RAILS_ENV"=>rails_env}
w.start = "bundle exec rake -f #{rails_root}/Rakefile resque:work"
w.stop_signal = 'QUIT'
w.stop_timeout = 20.seconds
w.uid = 'myappuser'
w.gid = 'myappuser'
w.behavior(:clean_pid_file)
# restart if memory gets too high
w.transition(:up, :restart) do |on|
on.condition(:memory_usage) do |c|
c.above = 350.megabytes
c.times = 2
c.notify = {:contacts => ['maxime'], :priority => 9, :category => 'myapp'}
end
end
# determine the state on startup
w.transition(:init, { true => :up, false => :start }) do |on|
on.condition(:process_running) do |c|
c.running = true
end
end
# determine when process has finished starting
w.transition([:start, :restart], :up) do |on|
on.condition(:process_running) do |c|
c.running = true
c.interval = 5.seconds
end
# failsafe
on.condition(:tries) do |c|
c.times = 5
c.transition = :start
c.interval = 5.seconds
end
end
# start if process is not running
w.transition(:up, :start) do |on|
on.condition(:process_running) do |c|
c.running = false
c.notify = {:contacts => ['maxime'], :priority => 1, :category => 'myapp'}
end
end
end
end
I also faced this issue, I start worker in cap task, but I got issue
BACKGROUND causes worker always in starting mode.
nohup process is killed right after finish, we must wait a couple seconds. But unable to append more command after '&'
At last, I must create a shell, let it sleep 5s after nohup... call.
My code
desc 'Start resque'
task :start, :roles => :app do
run("cd #{current_path} ; echo \"nohup bundle exec rake resque:work QUEUE=* RAILS_ENV=#{rails_env} PIDFILE=tmp/pids/resque_worker_1.pid &\nnohup bundle exec rake resque:work QUEUE=* RAILS_ENV=#{rails_env} PIDFILE=tmp/pids/resque_worker_2.pid &\nsleep 5s\" > startworker.sh ")
run("cd #{current_path} ; chmod +x startworker.sh")
run("cd #{current_path} ; ./startworker.sh")
run("cd #{current_path} ; rm startworker.sh")
end
I know this is a situation solution. but it works well in my project
You can manage your workers with this script. Commands available:
rake resque:start_workers
rake resque:stop_workers
rake resque:restart_workers
There is also included resque-scheduler. Comment this lines to disable it:
pid = spawn(env_vars, 'bundle exec rake resque:scheduler', ops_s)
Process.detach(pid)

Resources