Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backtrace propagation into the executor #810

Open
iNecas opened this issue May 25, 2019 · 6 comments
Open

Backtrace propagation into the executor #810

iNecas opened this issue May 25, 2019 · 6 comments
Labels
enhancement Adding features, adding tests, improving documentation. looking-for-contributor We are looking for a contributor to help with this issue.

Comments

@iNecas
Copy link
Contributor

iNecas commented May 25, 2019

When an error occurs within some code running inside concurrent-ruby executor, it ends up with something like this, while loosing the info about the true origin of the code:

concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/abstract.rb:25:in `pass'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/awaits.rb:15:in `on_envelope'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/abstract.rb:25:in `pass'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/sets_results.rb:14:in `on_envelope'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/abstract.rb:25:in `pass'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/buffer.rb:38:in `process_envelope'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/buffer.rb:31:in `process_envelopes?'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/buffer.rb:20:in `on_envelope'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/abstract.rb:25:in `pass'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/termination.rb:55:in `on_envelope'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/abstract.rb:25:in `pass'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/removes_child.rb:10:in `on_envelope'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/abstract.rb:25:in `pass'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/sets_results.rb:14:in `on_envelope'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/core.rb:162:in `process_envelope'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/core.rb:96:in `block in on_envelope'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/core.rb:119:in `block (2 levels) in schedule_execution'
concurrent-ruby-1.1.5/lib/concurrent/synchronization/mutex_lockable_object.rb:41:in `block in synchronize'
concurrent-ruby-1.1.5/lib/concurrent/synchronization/mutex_lockable_object.rb:41:in `synchronize'
concurrent-ruby-1.1.5/lib/concurrent/synchronization/mutex_lockable_object.rb:41:in `synchronize'
concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/core.rb:116:in `block in schedule_execution'
concurrent-ruby-1.1.5/lib/concurrent/executor/serialized_execution.rb:18:in `call'
concurrent-ruby-1.1.5/lib/concurrent/executor/serialized_execution.rb:96:in `work'
concurrent-ruby-1.1.5/lib/concurrent/executor/serialized_execution.rb:77:in `block in call_job'
concurrent-ruby-1.1.5/lib/concurrent/executor/ruby_thread_pool_executor.rb:348:in `run_task'
concurrent-ruby-1.1.5/lib/concurrent/executor/ruby_thread_pool_executor.rb:337:in `block (3 levels) in create_worker'
concurrent-ruby-1.1.5/lib/concurrent/executor/ruby_thread_pool_executor.rb:320:in `loop'
concurrent-ruby-1.1.5/lib/concurrent/executor/ruby_thread_pool_executor.rb:320:in `block (2 levels) in create_worker'
concurrent-ruby-1.1.5/lib/concurrent/executor/ruby_thread_pool_executor.rb:319:in `catch'
concurrent-ruby-1.1.5/lib/concurrent/executor/ruby_thread_pool_executor.rb:319:in `block in create_worker'

Example:

require 'concurrent'
require 'concurrent-edge'

class Counter < Concurrent::Actor::Context
  def initialize(initial_value)
    @count = initial_value
  end

  def on_message(message)
    if Integer === message
      @count += message
    else
      raise "error"
    end
  end
end

def run
  counter = Counter.spawn(:first, 5)
  counter.tell("1")
end
run
sleep 3

Result:

2019-05-25 06:27:07.466] ERROR -- /first: error (RuntimeError)
/home/inecas/tmp/actor.rb:13:in `on_message'
/home/inecas/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/context.rb:46:in `on_envelope'
/home/inecas/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/executes_context.rb:7:in `on_envelope'
/home/inecas/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/concurrent-ruby-edge-0.4.1/lib-edge/concurrent/actor/behaviour/abstract.rb:25:in `pass'
# the backtrace goes on with concurrent-ruby internal details

Desired would be something like

2019-05-25 06:27:07.466] ERROR -- /first: error (RuntimeError)
/home/inecas/tmp/actor.rb:13:in `on_message'
[ concurrent-ruby ]
/home/inecas/tmp/actor.rb:20:in `run'

This is very bad developer experience and makes it very hard to deal with the code that is using actors or other concurrent-ruby abstractions.

@ianks
Copy link
Contributor

ianks commented May 25, 2019

Depending on you environment, you can configure backtrace filtering. It's not reasonable to rewrite this code to optimize for short backtraces.

This is a good article about backtrace filtering in RSpec to help get you started.

@iNecas
Copy link
Contributor Author

iNecas commented May 25, 2019

Please note that this is not that much about filtering, as it is about propagating the backtrace when running some code via concurrent-ruby executor. The filtering can be handled separately (although if the backtrace propagation would be handled, I think it would improve the out-of-box usability)

@iNecas
Copy link
Contributor Author

iNecas commented May 25, 2019

In other words, the key thing in this desired outcome:

2019-05-25 06:27:07.466] ERROR -- /first: error (RuntimeError)
/home/inecas/tmp/actor.rb:13:in `on_message'
[ concurrent-ruby ]
/home/inecas/tmp/actor.rb:20:in `run'

is not [ concurrent-ruby ], but the /home/inecas/tmp/actor.rb:20:in run'`, that you don't get at the moment.

See Dynflow/dynflow#326 for real-world example of such a situation. Without that patch, it's next to impossible to find that dynflow/dispatcher/client_dispatcher.rb:148 has been initiated from /dynflow/world/invalidation.rb:54. With the backtrace propagation, you just get it screaming into your face:

dynflow/dispatcher/client_dispatcher.rb:148:in `dispatch_request'
[ concurrent-ruby ]
/dynflow/world/invalidation.rb:54:in `block in invalidate_execution_lock'

@pitr-ch pitr-ch added the enhancement Adding features, adding tests, improving documentation. label Aug 24, 2019
@pitr-ch pitr-ch added this to Interesting features in Hackathon Aug 24, 2019
@laasem
Copy link

laasem commented Jan 15, 2020

Any update here?

@pitr-ch pitr-ch added the looking-for-contributor We are looking for a contributor to help with this issue. label Jan 16, 2020
@pitr-ch
Copy link
Member

pitr-ch commented Jan 16, 2020

Nobody is working on it at the moment.

@laasem
Copy link

laasem commented Jan 16, 2020

We did this as a workaround for the moment - still testing it out though:

class ConcurrencyKlass
  def self.execute(&block)
    Concurrent::Promises.future {
      begin
        yield block
      rescue => e
        new_backtrace = [caller, e.backtrace].flatten
        e.set_backtrace(new_backtrace)
        CustomErrorHandler.send_error_to_error_alerting_system(e)
      end 
    }
  end
end

and then used as such

ConcurrencyKlass.execute { # Concurrent code here }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Adding features, adding tests, improving documentation. looking-for-contributor We are looking for a contributor to help with this issue.
Projects
Hackathon
Interesting features
Development

No branches or pull requests

4 participants