Multithreading in Rails: while loop is blocking another method call - ruby-on-rails

I am using Puma server to achieve multithreading. Here is my controller:
class PhoneCallsController < ActionController::Base
include ActionController::Live
protect_from_forgery :except => :record_call
# GET: Return messages to JavaScript pop-up
def get_messages
random_num = rand(65536)
log "Start of get_messages, random id = #{random_num}"
sleep 60
#while session[:new_record_date]==session[:prev_record_date] do
# sleep 1
#end
session[:prev_record_date] = session[:new_record_date]
render :status => :ok, :json => ['my message']
log "End of get_messages, random id = #{random_num}"
end
# POST: Record a phone call. Called by PBX.
def record_call
log 'Start of record_call'
data = Hash.from_xml(request.raw_post)
pbx_data = data['PbxData']
pc = PhoneCall.new({
:command => pbx_data['cmd'].strip,
:recipient_ip => pbx_data['RecipientIP'].strip,
:phone_num => pbx_data['PhoneNum'].strip
})
pc.save!
session[:new_record_date] = pc.created_at
debug_text = "call received:\ncmd: #{pc.command}\nRecipientIP: #{pc.recipient_ip}\nPhoneNum: #{pc.phone_num}"
log debug_text
render :status => :ok, :text => debug_text
log 'End of record_call'
end
# Test page to simulate PBX calls
def call_test
end
# Test page to receive pop-up notifications
def messages_test
end
private
def log(msg)
Rails.logger.info msg
puts msg
end
end
So, there are two main methods: get_messages is called by JavaScript and its task is not to return a result until phone call arrives (connection should be hold).
Second method - record_call is called by PBX script when a call is being answered.
The problem is - when only
sleep 60
is mentioned in get_messages then record_call method can be called at any time and the whole allpication isn't being locked.
But if I replace this sleep 60 with
while session[:new_record_date]==session[:prev_record_date] do
sleep 1
end
(as it should be) - this while loop blocks entire application.
What am I doing wrong? Why sleep doesn't block the application, but when it is wrapped into while loop (suppose it is an infinite loop) the application is being locked?
I suppose that the multithreading in my case isn't a true multithreading and sleep is treated in a special way.

Related

Using Simple Scheduler Gem for Scheduling Tasks in a Rails App

I am trying to run a method that adds the response from an API call to Cache, I decided to use the simple_scheduler gem
Below are snippets of code that I am running
# update_cache_job.rb
class UpdateCacheJob < ActiveJob::Base
def perform
return QueuedJobs.new.update_cache
end
end
And
# simple_scheduler.yml
# Global configuration options. The `queue_ahead` and `tz` options can also be set on each task.
queue_ahead: 120 # Number of minutes to queue jobs into the future
queue_name: "default" # The Sidekiq queue name used by SimpleScheduler::FutureJob
tz: "nil" # The application time zone will be used by default if not set
# Runs once every 2 minutes
simple_task:
class: "UpdateCacheJob"
every: "2.minutes"
And the method I have scheduled to run every 2.minutes
class QueuedJobs
include VariableHelper
def initialize; end
def update_cache
#variables = obtain_software_development_table
# First refresh the project Reviews
puts 'Updating reviews...'
#records = Dashboard.new.obtain_projects_reviews.pluck(
obtain_project_reviews_student_variable,
obtain_project_reviews_id_variable,
'Project'
).map { |student, id, project| { 'Student': student, 'ID': id,
'Project': project } }
Rails.cache.write(
:reviews,
#records,
expires_in: 15.minutes
)
#grouped_reviews = Rails.cache.read(
:reviews
).group_by do |review|
review[:Student]&.first
end
puts 'reviews refreshed.'
# Then refresh the coding challenges submissions
puts "Updating challenges submissions.."
#all_required_submissions_columns = Dashboard.new.all_coding_challenges_submissions.all.map do |submission|
{
id: submission.id,
'Student': submission[obtain_coding_chall_subm_student_var],
'Challenge': submission[obtain_coding_chall_subm_challenge_var]
}
end
#all_grouped_submissions = #all_required_submissions_columns.group_by { |challenge| challenge[:Student]&.first }
Rails.cache.write(
:challenges_submissions,
#all_grouped_submissions,
expires_in: 15.minutes
)
puts "challenges submissions refreshed."
end
end
I have been able to reach these methods from the rails console but when ever I run rake simple_scheduler It just logs the first puts and sometimes it does nothing at all.
What do I need to do here?

Parse API and Show Output in Rails View

So, I wrote a program that sends a get request to HappyFox (a support ticket web app) and I get a JSON file, Tickets.json.
I also wrote methods that parse the JSON and return a hash with information that I want, i.e tickets with and without a response.
How do I integrate this with my Rails app? I want my HappyFox View (in rails) to show the output of those methods, and give the user the ability to refresh the info whenever they want.
Ruby Code:
require 'httparty'
def happy_fox_call()
auth = { :username => 'REDACTED',
:password => 'REDACTED' }
#tickets = HTTParty.get("http://avatarfleet.happyfox.com/api/1.1/json/tickets/?size=50&page=1",
:basic_auth => auth)
tickets = File.new("Tickets.json", "w")
tickets.puts #tickets
tickets.close
end
puts "Calling API, please wait..."
happy_fox_call()
puts "Complete!"
require 'json'
$data = File.read('/home/joe/API/Tickets.json')
$tickets = JSON.parse($data)
$users = $tickets["data"][3]["name"]
Count each status in ONE method
def count_each_status(*statuses)
status_counters = Hash.new(0)
$tickets["data"].each do |tix|
if statuses.include?(tix["status"]["name"])
#puts status_counters # this is cool! Run this
status_counters[tix["status"]["name"]] += 1
end
end
return status_counters
end
Count tickets with and without a response
def count_unresponded(tickets)
true_counter = 0
false_counter = 0
$tickets["data"].each do |tix|
if tix["unresponded"] == false
false_counter += 1
else true_counter += 1
end
end
puts "There are #{true_counter} tickets without a response"
puts "There are #{false_counter} ticket with a response"
end
Make a function that creates a count of tickets by user
def user_count(users)
user_count = Hash.new(0)
$tickets["data"].each do |users|
user_count[users["user"]["name"]] += 1
end
return user_count
end
puts count_each_status("Closed", "On Hold", "Open", "Unanswered",
"New", "Customer Review")
puts count_unresponded($data)
puts user_count($tickets)
Thank you in advance!
You could create a new module in your lib directory that handles the API call/JSON parsing and include that file in whatever controller you want to interact with it. From there it should be pretty intuitive to assign variables and dynamically display them as you wish.
https://www.benfranklinlabs.com/where-to-put-rails-modules/

Infinitely listen to jabber events?

I am writing a chat application, using Jabber, on Ruby on Rails. Sending messages was quite easy to implement, but receiving messages in a loop is quite a challenge.
I want to get callbacks from the server without browser refreshes. I tried to use ActionController::Live for this.
In my client side, my code is:
var source = new EventSource('/chat/events');
source.addEventListener('refresh', function(event)
{
AddMessage(event.data);
});
My controller code is:
def chat_events
# SSE expects the `text/event-stream` content type
response.headers['Content-Type'] = 'text/event-stream'
sse = Reloader::SSE.new(response.stream)
puts "Starting XMMP call"
begin
#xmmpClient = XmppClient.new
#xmmpClient.loginUsingNameAndPassword(****, ***)
#xmmpClient.listen_connections sse
rescue IOError
puts "Error"
# When the client disconnects, we'll get an IOError on write
ensure
puts "closing stream"
sse.close
end
end
And, my XMMP client is:
def listen_connections(writer = nil)
if not #logged_in
puts "Not logged"
raise "Not logged"
end
#robot.send(Jabber::Presence.new.set_show(nil))
loop do
puts "Listening callback"
#robot.add_message_callback do |message|
puts "Got message " + message.inspect
if not writer.nil?
writer.write({ :message => message.body }, :event => 'refresh')
end
end
sleep 2
end
end
What I got:
The chat_events method of the controller is called every second or so.
Because of this, several callbacks are executed at once.
When I got a message, I got it four or five times.
{"message":"fffdsfd"}
{"message":"fffdsfd"}
{"message":"fffdsfd"}
{"message":"fffdsfd"}
And the worst stuff - my server is not responding to other responses, although I use the Puma multi-threaded server.
What is the correct way to implement functionality like this?
I get the solution
puts "."
client.add_message_callback do |message|
if message.type != :error
arr = message.from.to_s.split('#')
puts arr[0]
puts message.body
end
end
while 1
end

Broadcasting message every second using websokets-rails gem

I'm building an app for receiving some info every second using websockets-rails gem.
Right now, it seems like all messages are send after method is fully executed.
My websockets controller:
class DbTestsController< WebsocketRails::BaseController
def run_tests_on_all
dbtsch = DbTestsScheduler.new
dbtsch.run(1, 10, message['shard'], :push) do |ops|
send_message 'db_test.run_tests_on_all', ops
Rails.logger.info(ops)
end
end
end
'run' method looks like
def run(ecfs, fafs, shard, operation)
st = tep_t = Time.now
while st + fafs.second > Time.now
Octopus.using(shard) do
send(operation)
end
if tep_t + ecfs.second <= Time.now
tep_t = tep_t + 1.second
yield(#ops) if block_given?
#ops = 0
end
end
end
In console I see Rails.logger.info(ops) outputs message every second, but send_message sends all 10 results at once when method execution is completed.
I think what you want to do is use a a gem like sync
Real-time partials with Rails. Sync lets you render partials for models that, with minimal code, update in realtime in the browser when changes occur on the server.
you can check out a example here

Speeding up Ruby code to make faster/more API calls

I have the following code:
list_entities = [{:phone => '0000000000', :name => 'Test', :"#i:type => '1'},{:phone => '1111111111', :name => 'Demo', :"#i:type => '1'}]
list_entities.each do |list_entity|
phone_contact = PhoneContact.create(list_entity.except(:"#i:type"))
add_record_response = api.add_record_to_list(phone_contact, "API Test")
if add_record_response[:add_record_to_list_response][:return][:list_records_inserted] != '0'
phone_contact.update(:loaded_at => Time.now)
end
end
This code is taking an array of hashes and creating a new phone_contact for each one. It then makes an api call (add_record_response) to do something with that phone_contact. If that api call is successful, it updates the loaded_at attribute for that specific phone_contact. Then it starts the loop over.
I am allowed something like 7200 api calls per hour with this service - However, I'm only able to make about 1 api call every 4 seconds right now.
Any thoughts on how I could speed this code block up to make faster api calls?
I would suggest using a thread pool. You can define a unit of work to be done and the number of threads you want to process the work on. This way you can get around the bottleneck of waiting for the server to response on each request. Maybe try something like (disclaimer: this was adapted from http://burgestrand.se/code/ruby-thread-pool/)
require 'thread'
class Pool
def initialize(size)
#size = size
#jobs = Queue.new
#pool = Array.new(#size) do |i|
Thread.new do
Thread.current[:id] = i
catch(:exit) do
loop do
job, args = #jobs.pop
job.call(*args)
end
end
end
end
end
def schedule(*args, &block)
#jobs << [block, args]
end
def shutdown
#size.times do
schedule { throw :exit }
end
#pool.map(&:join)
end
end
p = Pool.new(4)
list_entries.do |list_entry|
p.schedule do
phone_contact = PhoneContact.create(list_entity.except(:"#i:type"))
add_record_response = api.add_record_to_list(phone_contact, "API Test")
if add_record_response[:add_record_to_list_response][:return][:list_records_inserted] != '0'
phone_contact.update(:loaded_at => Time.now)
end
puts "Job #{i} finished by thread #{Thread.current[:id]}"
end
at_exit { p.shutdown }
end

Resources