EventMachine EM::Iterator being blocked with rabbitmq RPC - ruby

I am trying to set up RabbitMQ rpc. I want one queue to listen, and when it receives a message I want it to reply to an anonymous queue that is specified via the reply_to header with multiple messages.
I have the following thor task creates a queue and then uses EM:Iterator to send a number of messages back to the queue specified with the replyt_to routing key:
desc "start_consumer", "start the test consumer"
def start_consumer
conf = {
:host => "localhost",
:user => "guest",
:password => "guest",
:vhost => "/",
:logging => true,
:port => 5672
}
# n = 1
AMQP.start(conf) do |connection|
channel = AMQP::Channel.new(connection)
requests_queue = channel.queue("one")
requests_queue.purge
Signal.trap("INT") do
connection.close do
EM.stop{exit}
end
end
channel.prefetch(1)
requests_queue.subscribe(:ack => true) do |header, body|
url_search = MultiJson.decode(body)
EM::Iterator.new(0..5).each do |n, iter|
lead = get_lead(n, (n == 5))
puts "about to publish #{n} message is_last = #{lead.is_last} at #{Time.now}"
AMQP::Exchange.default.publish(
MultiJson.encode(lead),
:immediate => true,
:routing_key => header.reply_to,
:correlation_id => header.correlation_id
)
iter.next
end
end
puts " [x] Awaiting RPC requests"
end
end
The code beloow sends a message to the queue specified above and also creates a queue that will be used to listen for the messages sent by the EM::Iterator code. This queue's name is the routing key for the first queues reply_to header.
def publish(urlSearch, routing_key)
EM.run do
corr_id = rand(10_000_000).to_s
requests ||= Hash.new
connection = AMQP.connect(:host => "localhost")
callback_queue = AMQP::Channel.new(connection).queue("", :exclusive => false)
callback_queue.subscribe do |header, body|
lead = safe_json_decode(body)
puts "company = #{lead["company"]} is_last = #{lead["is_last"]} received at #{Time.now}"
if lead["is_last"]
puts "in exit"
connection.close do
EM.stop{exit}
end
end
end
callback_queue.append_callback(:declare) do
AMQP::Exchange.default.publish(MultiJson.encode(urlSearch), :routing_key => routing_key, :reply_to => callback_queue.name, :correlation_id => corr_id)
end
puts "initial message sent"
end
end
The above code works as I want with one annoying exception. Something is blocking the EM::Iterator code from being executed asynchronously. It is only after the EM::Iterator code has completed that the messages are sent. I want the messages to be sent asynchronously and handled by the anonymous queue after each iteration. At the moment, it is only after the EM::Iterator code has completed its last iteration that all the messages are sent.
Can anyone see what I am doing wrong or suggest a different approach? I tried EM::defer and had the same behaviour.

Spinning up a new thread was the answer to my problem:
Thread.new do
5.times do
lead = get_lead(n, (n == 5))
puts "message #{n} is_last = #{lead.is_last} at #{Time.now}";
AMQP::Exchange.default.publish(
MultiJson.encode(lead),
:routing_key => header.reply_to,
:correlation_id => header.correlation_id
)
n += 1
sleep(2)
end
end
Creating a new thread stops the EventMachine reactor being blocked and the messages are sent async.

Related

How to pause and resume consumer in bunny

Is there any method to temporarily pause a consumer and resume it at later time?
Here is an example of what i want to do:
require "bunny"
conn = Bunny.new
conn.start
ch1 = conn.create_channel
publisher = ch.direct('test', :auto_delete => false)
consumer1 = nil
Thread.new do
ch2 = conn.create_channel(nil, 8) #Using eight worker
queue1 = ch2.queue('', :exclusive => true)
queue1.bind(publisher, :routing_key => 'low_priority')
consumer1 = queue1.subscribe(:block => true) do |delivery_info, properties, payload|
#do some work
end
end
Thread.new do
ch3 = conn.create_channel
queue2 = ch3.queue('', :exclusive => true)
queue2.bind(publisher, :routing_key => 'high_priority')
consumer2 = queue2.subscribe(:block => true) do |delivery_info, properties, payload|
consumer1.pause #pause the other consumer
#do other things
consumer1.resume #resume the consumer
end
end
#rest of the code
I want to pause consumer1 when I'm doing the work at consumer2. Is there any valid way to do that?
If you want to create priority policies, bunny has implemented this:
http://rubybunny.info/articles/queues.html#consumer_priorities
q = ch.queue("a.queue")
q.subscribe(:manual_ack => true, :arguments => {"x-priority" => 5}) do |delivery_info, properties, payload|
# ...
end
q.subscribe(:manual_ack => true, :arguments => {"x-priority" => 2}) do |delivery_info, properties, payload|
# ...
end
And if you want to process it in parallel I suggest this gem:
https://github.com/grosser/parallel
Example:
results = Parallel.map(['a','b','c'], in_processes: 3) { |one_letter| ... }
Hope it helps.

Better way to gurantte initializing without error

My initialization failed randomly.
What the better way to implement the init ?
#client = Mongo::Client.new([ cfg["HOST"] ], :database => cfg["NAME"])
At least retry 10 times.
I found some solution like that
begin
coll.insert( { "counter" => i, "named" => "name#{i}" })
rescue Mongo::ConnectionFailure => ex
sleep(0.5)
coll.insert( { "counter" => i, "named" => "name#{i}" })
end
Is there any elegant way to do so ? because I need to do similar init in my project many places
You could extract your connection logic to another method and do
connections = 0
while connections < 10 # or however many
begin
try_connecting
break # only called when no error raised
rescue Mongo::ConnectionFailure => ex
connections += 1
sleep(0.5)
end
end
or maybe this:
def connect
try_connecting
rescue Mongo::ConnectionFailure => ex
fail = true
sleep(0.5)
ensure
return defined?(fail)
end
10.times { break unless connect }

Migrating Sinatra Webrick RACK:SSLEnforcer based HTTPS to Thin

I have been running Sinatra with Webrick and SSL using Rack::SSLenforcer in a development environment for a long while without any issues (based on https://github.com/tobmatth/rack-ssl-enforcer#readme ), i am trying to migrate to Thin in order to add websockets support but have issues getting my current app (without websockets) to run with Thin and SSL.
The basic code that i currently have on websockets is the following:
begin
pkey = OpenSSL::PKey::RSA.new(File.open("private_key.pem").read)
cert = OpenSSL::X509::Certificate.new(File.open("certificate.pem").read)
end
webrick_options = {
:Port => 8447,
:Logger => WEBrick::Log::new($stderr, WEBrick::Log::DEBUG),
:DocumentRoot => "/ruby/htdocs",
:SSLEnable => true,
:SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
:SSLCertificate => cert,
:SSLPrivateKey => pkey,
:SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ],
:app => MyWebRTCServer
}
Rack::Server.start webrick_options
Then in my app i have the following:
configure do
# require SSL - https://github.com/tobmatth/rack-ssl-enforcer#readme
use Rack::SslEnforcer
set :session_secret, 'asdfa2342923422f1adc05c837fa234230e3594b93824b00e930ab0fb94b'
use Rack::Session::Cookie, :key => '_rack_session',
:path => '/',
:expire_after => 2592000, # In seconds
:secret => session_secret
# load password file -
begin
##config = YAML.load_file(File.join(Dir.pwd, 'config', 'users.yml'))
rescue ArgumentError => e
puts "Could not parse YAML: #{e.message}"
end
# puts "config: " + ##config.to_s
use Rack::Auth::Basic, "Restricted Area" do |u, p|
$LOG.info "Use Rack::Auth::Basic"
if (!##config[:users][u])
puts "Bad username"
false
else
# initialize the BCrypt with the password
tPassword = BCrypt::Password.new(##config[:users][u][:password].to_s)
# puts "From BCrypt: " + tPassword
if (tPassword == p)
# puts "Validated password"
# check whether the user is already logged in or not
if (!##user_table_cache[u.to_sym])
# puts "User already logged in or session has not expired"
userHash = Hash.new
userHash[:name] = u
userHash[:privilege] = ##config[:users][u][:privilege]
# add the user hash to the cache
##user_table_cache[u.to_sym] = userHash
end
end
true
end
end
end
All of this works on webrick with Sinatra. I have tried the following on Thin (based on Can I enable SSL in Sinatra with Thin?)
class MyApp < Sinatra::Base
# ...
get '/' do
puts "got request"
end
end
MyApp.run! do |server|
ssl_options = {
:cert_chain_file => './certificate.pem',
:private_key_file => './private_key.pem',
:verify_peer => false
}
server.ssl = true
server.ssl_options = ssl_options
end
However, I get the following error, when i try to access it from the browser.
C:\Software\Ruby Projects\Utils\sandbox\thintest>thistest
== Sinatra/1.4.5 has taken the stage on 4567 for development with backup from Th
in
Thin web server (v1.6.3 codename Protein Powder)
Maximum connections set to 1024
Listening on localhost:4567, CTRL+C to stop
terminate called after throwing an instance of 'std::runtime_error'
what(): Encryption not available on this event-machine
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
any thoughts would be greatly appreciated.

Sinatra not sending response once I add an extra method call

post '/payment/transactions/amount' do # => creates a new charge
content_type :json, :charset => 'utf-8'
class Transaction
def initialize(endUserId, code, referenceCode, callback=false)
#endUserId = endUserId
#code = code
#referenceCode = referenceCode
$callback = callback
$transId = rand(1000)
end
def transId
return $transId
end
def callback
return $callback
end
def postCallback
sleep(5)
RestClient.post"#{$callback}", :message => 'CALLBACK SUCCESS', :content_type => :json
end
def to_json(*a)
{"amountTransaction" => {
"endUserId" => #endUserId,
"paymentAmount" =>{
"chargingInformation" => {"code" => #code, "description" => #description},
"totalAmountCharged"=> "0"
},
"referenceCode" => #referenceCode,
"serverReferenceCode" =>"#{(0...6).map{65.+(rand(25)).chr}.join}", # just an example, can be any string
"resourceURL" => "http://localhost:4567/payment/#{#endUserId}/transactions/amount/#{Trans.transId}",
"transactionOperationStatus" => "Processing"
}}.to_json(*a)
end
end
Trans = Transaction.new(params[:endUserId],params[:code], params[:referenceCode], params[:callback])
jsonTrans = Trans.to_json
receipts.addReceipt(Trans.transId,jsonTrans)
jsonTrans
# fire callback
if Trans.callback then
sleep(10)
Trans.postCallback
end
end
Problem for me is the code following #fire callback. If I omit this if...then , the jsonTrans JSON object is returned as expected. When I include the if...then to fire the callback, then the desired Trans.postcallback occurs after 5 seconds - but the preceding jsonTrans is not returned, it is simply ignored. Any ideas on what I'm doing wrong? To confirm, the desired behaviour is to return the jsonTrans in the HTTP response and then call the postCallback method after 5 seconds (assuming the if evaluates to true). Cheers!
EDIT: Solved by spawning a new thread:
Thread.new{
if Trans.callback then
sleep(5)
Trans.postCallback
end}
jsonTrans
The last statement is what the method returns(outputs) so it ignores your jsonTrans in the output.
The first idea I have would be to reorder it:
#...
Trans = Transaction.new(params[:endUserId],params[:code], params[:referenceCode], params[:callback])
jsonTrans = Trans.to_json
receipts.addReceipt(Trans.transId,jsonTrans)
# fire callback
if Trans.callback then
sleep(10)
Trans.postCallback
end
jsonTrans
end
This should work if you don't expect any output from that RestClient.post-call.
Thanks for the suggestion daddz - actually I managed to solve it by wrapping by spawning a new thread: Thread.new{
if Trans.callback then
sleep(5)
Trans.postCallback
end}
jsonTrans

Ruby Server #eliminate garbage build up

I have a working TCP/IP socket server that loads 3-flash files in succession. How can I unload previous files and eliminate the garbage build up?
2-Flash clients are active, 1-the loader, 2-the next Flash file being loaded, however "the Flash files don't unload." Maybe there's a "put - kill" method or something similar to addChild removeChild in as3. Any resource would help, since I'm not very familiar with Ruby.
Files involved
POLICY SERVER "server lets Flash files play"
policyserver_little.rb
RUBY TCP/IP SOCKET SERVER "server plays loader, that loads 3-Flash files"
server_little#.rb
FLASH LOADER "client"
loader_little.swf
FLASH MOVIES "numbers_odom.swf, numbers_fruits.swf, $mil.swf"
"msg1, msg2, msg3"
WHAT I SEE
def worker==>end
There's no method to unload.
RUBY SERVER server_little#.rb
require 'socket'
require 'rexml/document'
include Socket::Constants
def create_xml_msg(msg, parent)
el = nil
msg.each do |key, value|
if parent
el = parent.add_element(key)
else
el = REXML::Element.new(key)
end
if value.instance_of?(Hash)
create_xml_msg(value, el)
else
el.text = value.to_s
end
end
return el
end
def worker(client, client_sockaddr, worker_number)
$tid << Thread.new([client, client_sockaddr, worker_number]) do |cl|
Thread.current[:number] = cl[2]
puts("\nThread #{cl[2]} servicing #{Socket.unpack_sockaddr_in(cl[1]).join(':')}")
#2
seq_no = cl[2] * 10000000
loop do
begin
msg1 = {"msg" => {"head" => {"type" => "frctl", "seq_no" => seq_no, "version" => 1.0},
"body" => {"file" => "numbers_odom.swf", "start" => 5,
"end" => 3000, "rate" => 40, "duration" => 60}}}
msg2 = {"msg" => {"head" => {"type" => "frctl", "seq_no" => seq_no, "version" => 1.0},
"body" => {"file" => "numbers_fruits.swf", "start" => 5, "end" => 3000, "rate" => 40, "duration" => 60}}}
msg3 = {"msg" => {"head" => {"type" => "frctl", "seq_no" => seq_no, "version" => 1.0},
"body" => {"file" => "$mil.swf", "start" => 5,
"end" => 3000, "rate" => 40, "duration" => 60}}}
[ msg1, msg2, msg3 ].each do |m|
seq_no += 1
m["msg"]["head"]["seq_no"] = seq_no
xml_msg = create_xml_msg(m, nil)
xml_msg.write(cl[0], 0)
cl[0].putc 0
sleep 10
end
rescue
cl[0].close
puts "\nThread #{Thread.current[:number]} exiting..."
Thread.exit
end
end
end
end
$tid = [] # array of active worker thread ids
$wno = [] # array of active worker numbers
$worker_count = 0
$max_workers = 3
$wlist = Array(1..$max_workers) #array of all possible worker numbers
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 1999, '0.0.0.0' )
begin
socket.bind( sockaddr )
socket.listen( 5 )
rescue
print $!.class, " ", $!
sleep 3
retry
end
loop do
begin
$tid.each do |t|
if (t.status == false || t.status == "aborting" )
t.join
$wno.delete(t[:number])
$tid.delete(t)
$worker_count -= 1
puts("\nWorker count #{$worker_count}")
end
end
client, client_sockaddr = socket.accept_nonblock
if (client)
if ($worker_count >= $max_workers)
puts "\n too many clients...\n"
client.puts("<msg>error: too many clients; closing connection...</msg>")
client.close
else
$worker_count += 1
$wlist.each do |w| #find a hole in worker number list
if (!$wno.include?(w))
$wno << w #add new worker number to the active worker num array
worker(client, client_sockaddr, w)
break
end
end
puts("\nWorker count #{$worker_count}")
end
end
rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EINTR, Errno::EWOULDBLOCK
IO.select([socket], nil, nil, 1)
retry
end
end
REFERENCE
Ruby version 186-25
http://rubylearning.com/satishtalim/ruby_threads.html
A very good resource for socket programming in ruby:
Ruby Sockets: IBM
It was tough for me to get a feel for your code by reading through it, so I can't give you a direct answer. However I can tell you that if you want a good resource for sockets in ruby that pdf is it.
In general i think the only way to eliminate garbage is to fork off a process, let it do the garbage-y stuff then die. If that's your question. NB that jruby, macruby, and rubinius have more advanced GC's.
-r

Resources