ActiveJob perform_later from inside Redis subscription block - ruby-on-rails
I want to use the ruby redis client to subscribe to a channel. When subscription was successful I want to start an ActiveJob (Sidekiq is used) job using perform_later. This Job will then do some work and produce a result which will be published to the channel Redis channel.
This produces the following error:
Processing by Api::LocationController#yyy as */*
Parameters: {"zip_code"=>"503400"}
[ActiveJob] Failed enqueuing XXXJob to Sidekiq(default): NoMethodError (undefined method `call_pipeline' for #<Redis::SubscribedClient:0x00007fd9222cdf78 #client=#<Redis::Client:0x00007fd921e1b890 #options={:url=>"redis://localhost?db=xxx", :scheme=>"redis", :host=>"localhost", :port=>6379, :path=>nil, :read_timeout=>0, :write_timeout=>5.0, :connect_timeout=>5.0, :timeout=>5.0, :username=>nil, :password=>nil, :db=>0, :driver=>Redis::Connection::Ruby, :id=>nil, :tcp_keepalive=>0, :reconnect_attempts=>1, :reconnect_delay=>0.0, :reconnect_delay_max=>0.5, :inherit_socket=>false, :logger=>nil, :sentinels=>nil, :role=>:master, :_parsed=>true}, #reconnect=true, #logger=nil, #connection=#<Redis::Connection::Ruby:0x00007fd921df08c0 #sock=#<Redis::Connection::TCPSocket:fd 26>>, #command_map={}, #pending_reads=-3, #connector=#<Redis::Client::Connector:0x00007fd921e1a2d8 #options={:url=>"redis://localhost?db=xxx", :scheme=>"redis", :host=>"localhost", :port=>6379, :path=>nil, :read_timeout=>5.0, :write_timeout=>5.0, :connect_timeout=>5.0, :timeout=>5.0, :username=>nil, :password=>nil, :db=>0, :driver=>Redis::Connection::Ruby, :id=>nil, :tcp_keepalive=>0, :reconnect_attempts=>1, :reconnect_delay=>0.0, :reconnect_delay_max=>0.5, :inherit_socket=>false, :logger=>nil, :sentinels=>nil, :role=>:master, :_parsed=>true}>, #pid=54943>>)
Completed 500 Internal Server Error in 26ms (Allocations: 2374)
NoMethodError (undefined method `call_pipeline' for #<Redis::SubscribedClient:0x00007fd9222cdf78 #client=#<Redis::Client:0x00007fd921e1b890 #options={:url=>"redis://localhost?db=xxx", :scheme=>"redis", :host=>"localhost", :port=>6379, :path=>nil, :read_timeout=>5.0, :write_timeout=>5.0, :connect_timeout=>5.0, :timeout=>5.0, :username=>nil, :password=>nil, :db=>0, :driver=>Redis::Connection::Ruby, :id=>nil, :tcp_keepalive=>0, :reconnect_attempts=>1, :reconnect_delay=>0.0, :reconnect_delay_max=>0.5, :inherit_socket=>false, :logger=>nil, :sentinels=>nil, :role=>:master, :_parsed=>true}, #reconnect=true, #logger=nil, #connection=#<Redis::Connection::Ruby:0x00007fd921df08c0 #sock=nil>, #command_map={}, #pending_reads=-3, #connector=#<Redis::Client::Connector:0x00007fd921e1a2d8 #options={:url=>"redis://localhost?db=xxx", :scheme=>"redis", :host=>"localhost", :port=>6379, :path=>nil, :read_timeout=>5.0, :write_timeout=>5.0, :connect_timeout=>5.0, :timeout=>5.0, :username=>nil, :password=>nil, :db=>0, :driver=>Redis::Connection::Ruby, :id=>nil, :tcp_keepalive=>0, :reconnect_attempts=>1, :reconnect_delay=>0.0, :reconnect_delay_max=>0.5, :inherit_socket=>false, :logger=>nil, :sentinels=>nil, :role=>:master, :_parsed=>true}>, #pid=54943>>):
app/controllers/api/location_controller.rb:10:in `block (3 levels) in yyy'
app/controllers/api/location_controller.rb:8:in `block in yyy'
app/controllers/api/location_controller.rb:5:in `yyy'
The yyy function is part of a controller that gets called on a GET request:
def yyy
REDIS_POOL.with { |r|
data = r.get("key")
if data.nil?
r.subscribe("channel") { |on|
on.subscribe {
XXXJob.perform_later(params)
}
on.message { |_, message|
puts message
render json: message
r.unsubscribe("channel")
}
}
else
render json: data
end
}
end
It seems to me like there's some context being lost from the Redis subscribe block which is important for the perform_later function. The same code works if I move the XXXJob.perform_later(params) call above the r.subscribe("channel") call and thus outside of the block. I however want to make sure that the job is only started after the subscription to the Redis channel was successful. Is there any way to achieve this?
It's difficult. It will not work because Sidekiq's default connection pool always reuses the thread's current connection, which is handling the subscribe call and that call is not reentrant. You need to manually create a separate connection pool and use it directly.
ANOTHER_POOL = ConnectionPool.new { Redis.new }
on.subscribe do
Sidekiq::Client.via(POOL) do
SomeWorker.perform_async(1,2,3)
SomeOtherWorker.perform_async(1,2,3)
end
end
Related
Twilio Webhooks in Rails - 401 error - no lines run in method?
I am trying to create a record in a model based on a incoming SMS. This is all in development now. I have the webhooks setup so that the incoming message triggers a webhook which then is processed by the controller. However, the method in the controller doesn't do anything and in the cmd line I get a 401 error. Here is the code from the webhook controller and the error message: class TwilioController < ApplicationController skip_before_action :verify_authenticity_token def sms_received #daily_journal = DailyJournal.new #daily_journal.user_id = current_user.id #daily_journal.message_sid = params["MessageSid"] #daily_journal.entry_text = params["Body"] #daily_journal.save end Started POST "/twilio/sms_received" for 54.81.71.35 at 2022-12-04 20:36:57 +0200 Cannot render console from 54.81.71.35! Allowed networks: 127.0.0.0/127.255.255.255, ::1 Processing by TwilioController#sms_received as */* Parameters: {"ToCountry"=>"US", "ToState"=>"TX", "SmsMessageSid"=>"SMa57a8f9422708b4c3336d33f9be17179", "NumMedia"=>"0", "ToCity"=>"ARLINGTON", "FromZip"=>"22191", "SmsSid"=>"SMa57a8f9422708b4c3336d33f9be17179", "FromState"=>"VA", "SmsStatus"=>"received", "FromCity"=>"ARLINGTON", "Body"=>"R", "FromCountry"=>"US", "To"=>"+18171234567", "MessagingServiceSid"=>"MG4b459339d046009b64e02ad50becab05", "ToZip"=>"76012", "NumSegments"=>"1", "ReferralNumMedia"=>"0", "MessageSid"=>"SMa57a8f9422708b4c3336d33f9be17179", "AccountSid"=>"deleted_but_there_was_something_here", "From"=>"+17031234567", "ApiVersion"=>"2010-04-01"} Completed 401 Unauthorized in 7ms (Allocations: 690)
Can't catch ActiveRecord::RecordNotFound with rescue
I'm new to Ruby, please bear with me if this is a stupid question, or if I'm not following the best practice. I'm finding an object in the DB using find(), and expect it to throw RecordNotFound in case the object of the id does not exist, like this. begin event = Event.find(event_id) rescue ActiveRecord::RecordNotFound => e Rails.logger.debug "Event does not exist, id: " + event_id return { # return "unauthorized" to avoid testing existence of event id # (some redacted codes) } end But somehow it is not caught (the log in the rescue block is not printed) and the entire program just return internal server error. Here's the stack trace: Completed 500 Internal Server Error in 22ms (ActiveRecord: 1.0ms) ActiveRecord::RecordNotFound (Couldn't find Event with 'id'=999): lib/sync/create_update_event_handler.rb:78:in `handleRequest' app/controllers/sync_controller.rb:36:in `block in sync' app/controllers/sync_controller.rb:31:in `each' app/controllers/sync_controller.rb:31:in `sync' Rendering /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout Rendering /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb Rendered /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb (6.4ms) Rendering /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb Rendered /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (2.3ms) Rendering /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb Rendered /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.9ms) Rendered /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (36.6ms) The only thing I can think of is there are two different ActiveRecord::RecordNotFound, and I'm catching the wrong one, but I don't know if it is the case or how I can verify it. What did I do wrong? ====================================== Update The problem is in the rescue block, I was concatenating event_id (an integer) to a string. The RecordNotFound exception was indeed caught, but when the type error was thrown in the rescue block, the wrong error message was printed.
You won't get an error if you do event = Event.find_by(id: event_id) In this case if the record can't be found by ID it will just event == nil be nil. In this case if the record can't be found by ID it will just event == nil be nil. The code you pasted works fine for me. If you don't see output in the log, check your environment and log level settings INFO, WARN, DEBUG etc. 500 error indicates some kind of controller action raising the error. see Set logging levels in Ruby on Rails To be sure your rescue block is executing try doing something besides log. If you're running a development server you can try : begin event = Event.find(event_id) rescue ActiveRecord::RecordNotFound => e msg = "Event does not exist, id: #{event_id.to_s}" Rails.logger.debug msg. puts msg binding.pry # if you have gem 'pry' in your development gems. File.open('test.log', 'w') {|f| f.write msg} #check if this appears in root of your app return { # return "unauthorized" to avoid testing existence of event id # (some redacted codes) } end UPDATE: I changed the string interpolation according to your answer. You can also call .to_s inside interpolation instead of closing quotes and appending.
Turned out the error message is wrong. The problem is that I was concentating the event_id (an integer) to a string. But somehow Rails prints out the RecordNotFound exception. The problem is fixed by replacing Rails.logger.debug "Event does not exist, id: " + event_id with Rails.logger.debug "Event does not exist, id: " + event_id.to_s Thanks #lacostenycoder for bringing my attention to the error message.
#event = Event.find(params[:id]). you should write instead params[:id] .That's the cause of an error.
Weird error when testing a Rails + AngularJS app with Rspec and PhantomJS
I have an app that lists tickets. It uses AngularJS. Here's the controller action: def index #tickets = apply_scopes(#tickets) response.headers['x-total-count'] = #tickets.total_count response.headers['x-per-page'] = Ticket.default_per_page end The Angular controller (Coffeescript): $scope.fetch = -> Ticket.query($scope.search).then (response) -> $scope.tickets = response.data $scope.totalCount = response.headers('x-total-count') $scope.perPage = response.headers('x-per-page') $scope.fetch() I'm using angular-rails-resource to fetch the records. Everything works smoothly if I test by hand. Here is the spec: let(:user) { create :user } scenario 'User lists tickets', js: true do login_as user, scope: :user ticket = create :ticket, user: user visit root_path click_on 'Support Requests' expect(page).to have_content(ticket.subject) end When I run this spec, I just get the regular Rspec failure message because the condition was not met, but it should have: expected to find text "ticket 000" in... I figured it had something to do with concurrency and Capybara not waiting for Angular to fetch and display the records. Then I went ahead and added a sleep 2 right above the expectation just to test that. When I do it, I get a different error: Capybara::Poltergeist::JavascriptError: One or more errors were raised in the Javascript code on the page. If you don't care about these errors, you can ignore them by setting js_errors: false in your Poltergeist configuration (see documentation for details). Possibly unhandled rejection: {"data":"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n<HTML>\n <HEAD><TITLE>Internal Server Error</TITLE></HEAD>\n <BODY>\n <H1>Internal Server Error</H1>\n undefined method `split' for 1:Fixnum\n <HR>\n <ADDRESS>\n WEBrick/1.3.1 (Ruby/2.3.3/2016-11-21) at\n 127.0.0.1:54674\n </ADDRESS>\n </BODY>\n</HTML>\n","status":500,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"jsonpCallbackParam":"callback","url":"/tickets","params":{},"headers":{"Accept":"application/json","Content-Type":"application/json"},"data":{}},"statusText":"Internal Server Error "} Possibly unhandled rejection: {"data":"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n<HTML>\n <HEAD><TITLE>Internal Server Error</TITLE></HEAD>\n <BODY>\n <H1>Internal Server Error</H1>\n undefined method `split' for 1:Fixnum\n <HR>\n <ADDRESS>\n WEBrick/1.3.1 (Ruby/2.3.3/2016-11-21) at\n 127.0.0.1:54674\n </ADDRESS>\n </BODY>\n</HTML>\n","status":500,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"jsonpCallbackParam":"callback","url":"/tickets","params":{},"headers":{"Accept":"application/json","Content-Type":"application/json"},"data":{}},"statusText":"Internal Server Error "} at http://127.0.0.1:54674/assets/application-713835b1641be632b29f7502c00a879e171bca5d6d06a5f264afcd819c123e76.js:14363 Here is my stack: rails (5.0.2) capybara (2.12.1) poltergeist (1.13.0) rspec-core (3.5.4) phantomjs 2.1.1 Additional info: If I output something right before ending the controller action, it gets outputted. The execution is going through the entire action; If I console.log something right before fetching tickets, it's outputted as well. However, the Promise is not being resolved.
I found out the issue was with my pagination headers (x-total-count and x-per-page). Converting them to String does the trick. The weird part is that it was working OK in development, but not in test environment. So, if anyone has this issue in the future, the solution in my case was: def index #tickets = apply_scopes(#tickets) response.headers['x-total-count'] = #tickets.total_count.to_s response.headers['x-per-page'] = Ticket.default_per_page.to_s end Notice .to_s being called when assigning the headers.
NoMethodError users_url with devise (ajax)
I use devise 2.2.2 with rails 3.2.11 I use devise with ajax requests I changed the following configuration in initializers/devise.rb config.navigational_formats = [:json, :html] config.http_authenticatable_on_xhr = false when I submit an empty sign in request, I expect to get a json response with errors hash, but i get a 500 instead (see below for the trace) (it works fine with sign up request) here are my routes (nothing special) devise_for :users the trace: Started POST "/users/sign_in.json" for 127.0.0.1 at 2013-01-27 13:33:45 +0100 Processing by Devise::SessionsController#create as JSON Parameters: {"user"=>{"email"=>"", "password"=>"[FILTERED]"}} Completed 401 Unauthorized in 1ms Processing by Devise::SessionsController#new as JSON Parameters: {"user"=>{"email"=>"", "password"=>"[FILTERED]"}} Completed 500 Internal Server Error in 40ms NoMethodError (undefined method `users_url' for #<Devise::SessionsController:0x007fe88ddd9550>):
You are probably overriding after_sign_in_path_for and have a code path in there that returns nil. This causes devise to fall back to its default behaviour and call users_url to get the path to redirect to. Why do I think this? Because you are having the same error I had (and lost some hair over) and also this bug report contains the github usernames of many other people who have been humbled by this particular issue.
Problem with Rails 3 and AMF , rails3-amf, RocketAMF
im trying to get AMF to work with Rails3. I have succesfully installed rails3-amf-0.1.0 gem and the RocketAMF-0.2.1 gem. In my app there is a controller with the following code: def getRandomCards #incoming = params[0] #cards = Cardvo.first respond_with(#cards) do |format| format.amf { render :amf => #cards.to_amf} end end through a call from Actionscript i would like to return some data in amf format. further more, as mentioned in the instructions for rails3-amf i did the following. in my production.rb under config/environment i added the line config.rails3amf.map_params :controller => 'CardvosController', :action => 'getRandomCards' an my amf gateway got config.rails3amf.gateway_path = "/gateway" The problem is: Any call from Actionscript / Flash raises the following (taken from the log ) Started POST "/gateway" for 192.178.168.1 at Fri Nov 19 15:13:28 +0100 2010 Processing by CardvosController#getRandomCards as AMF Parameters: {0=>100.0} [1m[36mSQL (0.4ms)[0m [1mSHOW TABLES[0m [1m[35mCardvo Load (0.2ms)[0m SELECT `cardvos`.* FROM `cardvos` LIMIT 1 Completed 200 OK in 13ms (Views: 0.9ms | ActiveRecord: 0.5ms) NoMethodError (undefined method `constructed?' for #<RocketAMF::Envelope:0x39ba868>): The Amf file is created but the method, which is in remoting.rb from RocketAMF could not be found. I think the error is thrown at request_parser.rb from Rails3AMF asking for constructed? # Wrap request and response env['rack.input'].rewind env['rails3amf.request'] = RocketAMF::Envelope.new.populate_from_stream(env['rack.input'].read) env['rails3amf.response'] = RocketAMF::Envelope.new # Pass up the chain to the request processor, or whatever is layered in between result = #app.call(env) # Calculate length and return response if env['rails3amf.response'].constructed? For me it seems it is looking at the wron class for the method. Where NoMethodError (undefined method `constructed?' for #RocketAMF::Envelope:0x39ba868): the essential part is RocketAMF::Envelope:0x39ba868 which should be RocketAMF:ANOTHER_CLASS:Envelope:0x39ba868 Am i right and where the heck is the error ? Any help would be appreciated! chris