2017-11-10 16 views
0

コードをcatchedされている場合、どのように私はSTRG-C上のRubyプログラムを終了することができますSystemExit例外は、私がSTRG-C(Ctrl + Cキー)を使用して中断することはできません

orig_std_out = STDOUT.clone 
orig_std_err = STDERR.clone 
STDOUT.reopen('/dev/null', 'w') 
STDERR.reopen('/dev/null', 'w') 

name = cookbook_name(File.join(path, 'Metadata.rb')) 
error = 0 

begin 
    ::Chef::Knife.run(['cookbook', 'site', 'show', "#{name}"]) 
rescue SystemExit 
    error = 1 
end 
. 
. 
. 

私の理解では、これを私がExceptionを救助するならば、行動は合理的だろうが、この場合私は基本的に親の例外Exceptionを共有する兄弟を捕まえている。

私はすでに、例外InterruptSignalExceptionを明示的に救済しようとしました。

EDIT1:私の質問を明確にご希望の場合、私は私が試した次のコードを追加:どちらの場合も

begin 
    ::Chef::Knife.run(['cookbook', 'site', 'show', "#{name}"]) 
rescue SystemExit => e 
    msg1 = e.message 
    error = 1 
rescue Interrupt 
    msg2 = "interrupted" 
end 

を - SystemExitKnife.runによってスローされるとCtrl-Cによってスロー - e.messageリターン「終了"これは、Ctrl + CがSystemExitをスローするのに対して、Interruptをスローすることを期待しているだけでなく、エラーメッセージも同じであることを意味します。 私はルビーにはあまり馴染んでいないので、私はそこでルビーがどのように動作するかについて大きな誤解をしていると思います。

EDIT2:さらなるテストでは、Ctrl-C割り込みの一部がrescue Interruptによって救済されたことが明らかになりました。実行するのに約3-5秒かかるコマンド::Chef::Knife.run(['cookbook', 'site', 'show', "#{name}"])は、Ctrl-Cに応答する何らかの種類のサブプロセスを作成する可能性がありますが、いつもこれが中断されたときにのみSystemExitで終了し、rescue Interruptが機能しますサブプロセスは実行されていませんか?このような場合は、プログラム全体をどのように中断できるのでしょうか?

EDIT3:最初はKnife.runを呼び出すために呼び出されるすべてのメソッドを追加したいと思っていましたが、サブコマンドが実行されたと思いますが、これはLoCが多すぎるでしょう。シェフの宝石コードはhereです。

rescue Exception => e 
    raise if raise_exception || Chef::Config[:verbosity] == 2 
    humanize_exception(e) 
    exit 100 
end 

疑問につながる:したがって、次の抜粋は、私の意見では問題1である部分だけで、私はすでにサブコマンドによって救出されたのCtrl-Cをキャッチすることができますどのように?

+1

あなたの質問は私には分かりません。あなたは 'レスキューSystemExit'がSTRG + CがRubyを終了するのを防ぎ、あなたは回避策を探していることに気付きましたか?最初に 'レスキューSystemExit'が必要なのはなぜですか? – spickermann

+0

私のMacでは、プログラムを待たせるために 'gets'ステートメントを使ってCtrl-Cを実行すると、レスキューされた場合は' Interrupt'、レスキューされている場合は 'Exception'が生成され、そうでない場合は' gets ':Interrupt'が表示されます。 – BernardK

+0

私のLinuxシステムでは、Crtl-Cは 'Interrupt'を生成し、' Intercept'が 'Exception'から継承するので、' Exception'と 'Interrupt'の両方でキャッチされます。しかし、 'SystemExit'を救済することは、私の理解の中でCtrl-Cを捕らえるべきではありません。 @spickermann 'SystemExit'をキャッチする必要があります。なぜなら' :: Chef :: Knife.run'はエラーで 'SystemExit'で終了するからです。 –

答えて

1

私はgem install chefを行っています。今私はrun_with_pretty_exceptionsだけを交換する別の解決策を試みるが、requireがスクリプトに入れることを知らない。私はこれでした:

require 'chef' 
$:.unshift('Users/b/.rvm/gems/ruby-2.3.3/gems/chef-13-6-4/lib') 
require 'chef/knife' 

しかし、その後:

$ ruby chef_knife.rb 
WARNING: No knife configuration file found 
ERROR: Error connecting to https://supermarket.chef.io/api/v1/cookbooks/xyz, retry 1/5 
... 

ので、全体のインフラなしで、私は以下のソリューションをテストすることはできません。 Rubyでは、既存のクラスを再度開き、別の場所で定義されたメソッドを置き換えることができます。私はそれをチェックし、あなたを残している:

# necessary require of chef and knife ... 

class Chef::Knife # reopen the Knife class and replace this method 
    def run_with_pretty_exceptions(raise_exception = false) 
     unless respond_to?(:run) 
     ui.error "You need to add a #run method to your knife command before you can use it" 
     end 
     enforce_path_sanity 
     maybe_setup_fips 
     Chef::LocalMode.with_server_connectivity do 
     run 
     end 
    rescue Exception => e 
     raise if e.class == Interrupt # <---------- added ******************** 
     raise if raise_exception || Chef::Config[:verbosity] == 2 
     humanize_exception(e) 
     exit 100 
    end 
end 

name = cookbook_name(File.join(path, 'Metadata.rb')) 
error = 0 

begin 
    ::Chef::Knife.run(['cookbook', 'site', 'show', "#{name}"]) 
rescue SystemExit => e 
    puts "in rescue SystemExit e=#{e.inspect}" 
    error = 1 
rescue Interrupt 
    puts 'in rescue Interrupt' 
end 

raise if e.class == Interruptそれが1である場合Interrupt再発生させます。

は、通常、私はこのようになり、診断を表示するruby -wを実行します。

$ ruby -w ck.rb 
ck.rb:9: warning: method redefined; discarding old run_with_pretty_exceptions 
ck.rb:4: warning: previous definition of run_with_pretty_exceptions was here 

は残念ながら非常に多くの初期化されていない変数と円形があります。このオプションは、未手に負えない出力を生成することを、この宝石で警告を必要としています。

この解決法の欠点は、この変更に関する文書の追跡が必要であり、シェフのリリース変更の場合、誰かがrun_with_pretty_exceptionsのコードが変更されたかどうかを確認する必要があることです。

フィードバックをお願いします。


===== UPDATE =====

Chef::Knifeexit方法を規定することからなる以下侵入溶液があります。

exit 100、つまり受信者のないメッセージの場合、暗黙の受信者はselfです。これはself.exit 100に相当します。私たちの場合、selfinstance = subcommand_class.new(args)によって作成されたオブジェクトで、受信者はinstance.run_with_pretty_exceptionsです。

メッセージがオブジェクトに送信されると、メッセージ検索メカニズムがこのオブジェクトのクラスの検索を開始します。クラスにこの名前のメソッドがない場合、検索機構は含まれているモジュールを検索し、スーパークラスなどをObjectに達するまで検索します。デフォルトのスーパークラスはChef::Knifeです。ここではObject#exitが見つかり、実行します。それはChef::Knifeとして暗黙レシーバのインスタンスとexit 100に遭遇したとき

Chef::Knifeexit方法を定義した後、メッセージ検索機構は、最初にこのローカル方法を見つけ、それを実行します。以前に元のObject#exitにエイリアスを付けることによって、Rubyスクリプトの終了を開始するオリジナルのRubyメソッドを呼び出すことは可能です。このようにローカルのexitメソッドは、元のObject#exitを呼び出すか、他のアクションをとるかを決めることができます。

以下は、どのように動作するかを示す完全な例です。

# ***** Emulation of the gem ***** 

class Chef end 

class Chef::Knife 
    def self.run(x) 
     puts 'in original run' 
     self.new.run_with_pretty_exceptions 
    end 

    def run_with_pretty_exceptions 
     print 'Press Ctrl_C > ' 
     gets 
     rescue Exception => e 
      puts 
      puts "in run_with_pretty...'s Exception e=#{e.inspect} #{e.class}" 
      raise if false # if raise_exception || Chef::Config[:verbosity] == 2 
#   humanize_exception(e) 
      puts "now $!=#{$!.inspect}" 
      puts "about to exit,      self=#{self}" 
      exit 100 
    end 
end 

# ***** End of gem emulation ***** 

#---------------------------------------------------------------------- 

# ***** This is what you put into your script. ***** 

class Chef::Knife # reopen the Knife class and define one's own exit 
    alias_method :object_exit, :exit 

    def exit(p) 
     puts "in my own exit with parameter #{p}, self=#{self}" 
     puts "$!=#{$!.inspect}" 

     if Interrupt === $! 
      puts 'then about to raise Interrupt' 
      raise # re-raise Interrupt 
     else 
      puts 'else about to call Object#exit' 
      object_exit(p) 
     end 
    end 
end 

begin 
    ::Chef::Knife.run([]) 
rescue SystemExit => e 
    puts "in script's rescue SystemExit e=#{e.inspect}" 
rescue Interrupt 
    puts "in script's rescue Interrupt" 
end 

実行。 Ctrl-Cによる最初のテスト:

$ ruby -w simul_chef.rb 
in original run 
Press Ctrl_C > ^C 
in run_with_pretty...'s Exception e=Interrupt Interrupt 
now $!=Interrupt 
about to exit,      self=#<Chef::Knife:0x007fb2361c7038> 
in my own exit with parameter 100, self=#<Chef::Knife:0x007fb2361c7038> 
$!=Interrupt 
then about to raise Interrupt 
in script's rescue Interrupt 

ハード割り込みによる2番目のテスト。別のターミナルウィンドウで

$ ruby -w simul_chef.rb 
in original run 
Press Ctrl_C > 

:戻る最初の端末で

$ ps -ef 
    UID PID PPID C STIME TTY   TIME CMD 
    0  1  0 0 Fri01PM ??   0:52.65 /sbin/launchd 
... 
    0 363 282 0 Fri01PM ttys000 0:00.02 login -pfl b /bin/bash -c exec -la bash /bin/bash 
    501 364 363 0 Fri01PM ttys000 0:00.95 -bash 
    501 3175 364 0 9:51PM ttys000 0:00.06 ruby -w simul_chef.rb 
... 
$ kill 3175 

1つのターミナルウィンドウで

in run_with_pretty...'s Exception e=#<SignalException: SIGTERM> SignalException 
now $!=#<SignalException: SIGTERM> 
about to exit,      self=#<Chef::Knife:0x007fc5a79d70a0> 
in my own exit with parameter 100, self=#<Chef::Knife:0x007fc5a79d70a0> 
$!=#<SignalException: SIGTERM> 
else about to call Object#exit 
in script's rescue SystemExit e=#<SystemExit: exit> 

は、すべてのあなたを、あなたがもともと投稿コードを考慮すると最初に挿入する必要がありますが、必要な後にrequire

class Chef::Knife # reopen the Knife class and define one's own exit 
    alias_method :object_exit, :exit 

    def exit(p) 
     if Interrupt === $! 
      raise # re-raise Interrupt 
     else 
      object_exit(p) 
     end 
    end 
end 

オリジナルの宝石に触れる必要はありません。

+0

私はその特定のメソッドをオーバーライドすることは考えていませんでしたが、それはきちんとした解決策のように見えます。私は明日それを確かにテストします!この方法で一般的にクリーンなコーディングと見なされる宝石からメソッドをオーバーライドしていますか?欠落している設定については、おそらくしたくない 'knife configure'サブコマンドを使って、knife.rbを作成することができます。 –

+0

@BigX 'Knife'クラスのデザインと、Ctrl-Cを傍受して何かに触れることを望みます。宝石の方法を再定義することの不便は、更新後に長期的に不一致が生じることです。しかし、良いニュースは、ナイフに「出口」を定義するより良い解決策を見つけました。これはかなり無害でなければなりません。 UPDATEを参照してください。 – BernardK

+0

あなたと他の人にもフィードバックを出すために:私は基本的に「食料品」(階層型)であることを意味する(宝石)Foodcriticのルールを書いています。私にとっては、最初の解決策は機能しませんでした。なぜなら、いつも即座に 'Knife.run'を終了したからです。しかしながら、 'exit'メソッドをオーバーライドするアプローチは、完全にうまく動作します。 –

0

次のコードは、私はすべての後に中断することができる午前方法を示しています。

interrupted = false 
trap("INT") { interrupted = true} #sent INT to force exit in Knife.run and then exit 

begin 
    ::Chef::Knife.run(['cookbook', 'site', 'show', "#{name}"]) #exits on error and on interrupt with 100 
    if interrupted 
    exit 
    end 
rescue SystemExit => e 
    if interrupted 
    exit 
    end 
    error = 1 
end 

欠点は、まだ私はKnife.runを中断する正確にことはできませんよということですが、割り込みをトラップすることができるだけと後に確認してくださいそのコマンドは割り込みがトリガされたかどうかを示します。私は割り込みをトラップする方法を見つけ出しませんでした。同時に、それを「リレイズ」して、少なくともexitKnife.runの中から強制的に取り除くことができました。

関連する問題