EventMachine DeferrableChildProcess error handling - ruby

I'm having problems making EventMachine::DeferrableChildProcess.open actually do any sort of error handling. Hopefully I'm doing it wrong. Here's an example:
require "eventmachine"
EM.run do
cp = EM::DeferrableChildProcess.open("ls /trololo")
cp.callback { |data| puts "Received some data: '#{data}'" }
cp.errback { |err| puts "Failed: #{err.inspect}" }
end
I would expect the outcome of this piece of code (assuming you don't actually have a /trolol directory on your system) to be: "Failed: <SomeErrorObject>". Instead, I get "Received some data: ''". Adding insult to injury, the error message caused by ls ("ls: cannot access /trololo: No such file or directory") is somehow printed to stderr.
Am I doing something wrong, or is there basically no error handling in EM::DeferrableChildProcess.open? I got similar results with EM.popen.

Ok, looking at EventMachine's implementation, I can clearly see that DeferrableChildProcess simply assumes success - the errback will never be called. I guess the intention is that you in your own callback have to do get_status to figure out what's going on. I didn't care for that API, so I wrote an error-handling DeferrableChildProcess:
class DeferrableChildProcess < EventMachine::Connection
include EventMachine::Deferrable
def initialize
super
#data = []
end
def self.open cmd
EventMachine.popen(cmd, DeferrableChildProcess)
end
def receive_data data
#data << data
end
def unbind
status = get_status
if status.exitstatus != 0
fail(status)
else
succeed(#data.join, status)
end
end
end

Related

Best practice of error handling on controller and interactor

# users_show_controller.rb
class Controllers::Users::Show
include Hanami::Action
params do
required(:id).filled(:str?)
end
def call(params)
result = users_show_interactor(id: params[:id])
halt 404 if result.failure?
#user = result.user
end
end
# users_show_interactor.rb
class Users::Show::Interactor
include Hanami::Interactor
expose :user
def call(:id)
#user = UserRepository.find_by(:id)
end
end
I have a controller and a interactor like above.
And I'm considering the better way to distinguish ClientError from ServerError, on the controller.
I think It is nice if I could handle an error like below.
handle_exeption StandardError => :some_handler
But, hanami-interactor wraps errors raised inside themselves and so, controller receive errors through result object from interactor.
I don't think that re-raising an error on the controller is good way.
result = some_interactor.call(params)
raise result.error if result.failure
How about implementing the error handler like this?
I know the if statement will increase easily and so this way is not smart.
def call(params)
result = some_interactor.call(params)
handle_error(result.error) if result.faulure?
end
private
def handle_error(error)
return handle_client_error(error) if error.is_a?(ClientError)
return server_error(error) if error.is_a?(ServerError)
end
Not actually hanami-oriented way, but please have a look at dry-monads with do notation. The basic idea is that you can write the interactor-like processing code in the following way
def some_action
value_1 = yield step_1
value_2 = yield step_2(value_1)
return yield(step_3(value_2))
end
def step_1
if condition
Success(some_value)
else
Failure(:some_error_code)
end
end
def step_2
if condition
Success(some_value)
else
Failure(:some_error_code_2)
end
end
Then in the controller you can match the failures using dry-matcher:
matcher.(result) do |m|
m.success do |v|
# ok
end
m.failure :some_error_code do |v|
halt 400
end
m.failure :some_error_2 do |v|
halt 422
end
end
The matcher may be defined in the prepend code for all controllers, so it's easy to remove the code duplication.
Hanami way is validating input parameters before each request handler. So, ClientError must be identified always before actions logic.
halt 400 unless params.valid? #halt ClientError
#your code
result = users_show_interactor(id: params[:id])
halt 422 if result.failure? #ServerError
halt 404 unless result.user
#user = result.user
I normally go about by raising scoped errors in the interactor, then the controller only has to rescue the errors raised by the interactor and return the appropriate status response.
Interactor:
module Users
class Delete
include Tnt::Interactor
class UserNotFoundError < ApplicationError; end
def call(report_id)
deleted = UserRepository.new.delete(report_id)
fail_with!(UserNotFoundError) unless deleted
end
end
end
Controller:
module Api::Controllers::Users
class Destroy
include Api::Action
include Api::Halt
params do
required(:id).filled(:str?, :uuid?)
end
def call(params)
halt 422 unless params.valid?
Users::Delete.new.call(params[:id])
rescue Users::Delete::UserNotFoundError => e
halt_with_status_and_error(404, e)
end
end
end
fail_with! and halt_with_status_and_error are helper methods common to my interactors and controllers, respectively.
# module Api::Halt
def halt_with_status_and_error(status, error = ApplicationError)
halt status, JSON.generate(
errors: [{ key: error.key, message: error.message }],
)
end
# module Tnt::Interactor
def fail_with!(exception)
#__result.fail!
raise exception
end

How to use check something before an error is raised

I have the following ruby code
class Gateway
...
def post
begin
...
raise ClientError if state == :open
rescue ClientError => e
Log.add("error")
raise
end
end
end
On RSpec, how can I check that when ClientError is raised Log.add is called?
I have tried different things but I always get the error raised.
Thanks
You can probably do something like this (the initialize step might need to look bit different, depending on how you need to set the state to :open):
describe 'Gateway#post' do
let(:gateway) { Gateway.new(state: :open) }
before { allow(Log).to receive(:add) }
it 'raises an excpetion' do
expect { gateway.post }.to raise_error(ClientError)
expect(Log).to have_received(:add).with('error')
end
end
Something like this should work:
describe '#post' do
context 'with state :open' do
let(:gateway) { Gateway.new(state: :open) }
it 'logs the error' do
expect(Log).to receive(:add).with('error')
gateway.post rescue nil
end
it 're-raises the error' do
expect { gateway.post }.to raise_error(ClientError)
end
end
end
In the first example, rescue nil ensures that your spec is not failing because of the raised error (it silently rescues it). The second example checks that the error is being re-raised.

What is Camping::Server.start invoking in /bin/camping?

I'm studying how the Camping web framework works right now, and I don't understand what the Camping::Server.start at line 10 in /bin/camping is doing.
I expected this to call the start method in /lib/camping/server.rb at line 131, and so I put a simple puts 'hello' statement at the beginning of that method, expecting that statement to be invoked when I ran /bin/camping. However, I never saw my puts statement get called, so I can only assume that it's not that start method getting called.
I feel like I'm missing something obvious here. Here is the link to the camping github page and the relevant sections of code:
Github: https://github.com/camping/camping
From /bin/camping:
#!/usr/bin/env ruby
$:.unshift File.dirname(__FILE__) + "/../lib"
require 'camping'
require 'camping/server'
begin
Camping::Server.start
rescue OptionParser::ParseError => ex
puts "did it error"
STDERR.puts "!! #{ex.message}"
puts "** use `#{File.basename($0)} --help` for more details..."
exit 1
end
From /lib/server.rb:
def start
if options[:server] == "console"
puts "** Starting console"
#reloader.reload!
r = #reloader
eval("self", TOPLEVEL_BINDING).meta_def(:reload!) { r.reload!; nil }
ARGV.clear
IRB.start
exit
else
name = server.name[/\w+$/]
puts "** Starting #{name} on #{options[:Host]}:#{options[:Port]}"
super
end
end
My puts 'hello' on Camping::Server.start wasn't getting called because I didn't understand how static methods were defined in ruby.
start was being called statically, and I realize now that the start method I was looking at in the snippet wasn't a static method, which meant that another start method was getting called. I looked into Camping::Server and realized that it inherited from Rack::Server, which has the following method:
def self.start(options = nil)
new(options).start
end
That was the method getting called, not the one on /lib/camping/server.rb. I had been looking at the wrong method.

Ruby EventMachine testing

My first question concerning Ruby.
I'm trying to test EventMachine interaction inside the Reactor loop - I guess it could be classified as "functional" testing.
Say I have two classes - a server and a client. And I want to test both sides - I need to be sure about their interaction.
Server:
require 'singleton'
class EchoServer < EM::Connection
include EM::Protocols::LineProtocol
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data data
send_data ">>>you sent: #{data}"
close_connection if data =~ /quit/i
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
Client:
class EchoClient < EM::Connection
include EM::Protocols::LineProtocol
def post_init
send_data "Hello"
end
def receive_data(data)
#message = data
p data
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
So, I've tried different approaches and came up with nothing.
The fundamental question is - could I somehow test my code with RSpec, using should_recive?
EventMachine parameter should be a class or a module, so I can't send instantiated/mocked code inside. Right?
Something like this?
describe 'simple rspec test' do
it 'should pass the test' do
EventMachine.run {
EventMachine::start_server "127.0.0.1", 8081, EchoServer
puts 'running echo server on 8081'
EchoServer.should_receive(:receive_data)
EventMachine.connect '127.0.0.1', 8081, EchoClient
EventMachine.add_timer 1 do
puts 'Second passed. Stop loop.'
EventMachine.stop_event_loop
end
}
end
end
And, if not, how would you do it with EM::SpecHelper? I have this code using it, and can't figure out what I'm doing wrong.
describe 'when server is run and client sends data' do
include EM::SpecHelper
default_timeout 2
def start_server
EM.start_server('0.0.0.0', 12345) { |ws|
yield ws if block_given?
}
end
def start_client
client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
yield client if block_given?
return client
end
describe "examples from the spec" do
it "should accept a single-frame text message" do
em {
start_server
start_client { |client|
client.onopen {
client.send_data("\x04\x05Hello")
}
}
}
end
end
end
Tried a lot of variations of these tests and I just can't figure it out. I'm sure I'm missing something here...
Thanks for your help.
The simplest solution that I can think of is to change this:
EchoServer.should_receive(:receive_data)
To this:
EchoServer.any_instance.should_receive(:receive_data)
Since EM is expecting a class to start a server, the above any_instance trick will expect any instance of that class to receive that method.
The EMSpecHelper example (while being official/standard) is quite convoluted, I'd rather stick with the first rspec and use any_instance, just for simplicity's sake.

If nothing sent to STDOUT in last x minutes, how to raise an error

I wonder if this is possible, because if it is, it would help me implement what I need for a program I am making:
Is there a way to attach some kind of listener to STDOUT from within a Ruby program, so that if nothing is written (via puts) to STDOUT for a certain time interval, an error is raised?
Writing to STDOUT should otherwise work as expected.
Perhaps something like this:
def new_puts(what)
#time_th.kill if(#time_th)
puts what
#time_th = Thread.new() {
sleep(2)
raise "here"
}
#time_th.abort_on_exception = true
end
new_puts("test")
new_puts("test2")
sleep(10)
new_puts("test3") #too late
or with callback methods:
def callback
puts "Timeout!"
end
def new_puts(what)
#time_th.kill if(#time_th)
puts what
#time_th = Thread.new() {
sleep(2)
self.method(:callback).call
}
end
new_puts("test")
new_puts("test2")
sleep(10)
new_puts("test3") #too late

Resources