multitrap 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,85 +1,25 @@
1
- require 'multitrap/version'
2
1
  require 'thread'
3
2
 
4
3
  module Multitrap
5
- class Trap
6
- RESERVED_SIGNALS = {
7
- 'BUS' => 10,
8
- 'SEGV' => 11,
9
- 'ILL' => 4,
10
- 'FPE' => 8,
11
- 'VTALRM' => 26
12
- }
4
+ # The VERSION file must be in the root directory of the library.
5
+ VERSION_FILE = File.expand_path('../../VERSION', __FILE__)
13
6
 
14
- def self.trap(sig, prc, old_trap, &block)
15
- @multitrap ||= self.new(old_trap)
16
- @multitrap.add_trap(sig, prc, &block)
17
- end
7
+ VERSION = File.exist?(VERSION_FILE) ?
8
+ File.read(VERSION_FILE).chomp : '(could not find VERSION file)'
18
9
 
19
- def initialize(old_trap)
20
- @old_trap = old_trap
21
- @traps = {}
22
- @mutex = Mutex.new
23
- end
24
-
25
- def recursion?
26
- frame = caller.find do |f|
27
- f =~ %r{multitrap/lib/multitrap\.rb.+`block in add_trap'}
28
- end
29
-
30
- true if frame
31
- end
32
-
33
- def add_trap(sig, prc, &block)
34
- if RESERVED_SIGNALS.key?(sig)
35
- raise ArgumentError, "can't trap reserved signal SIG#{sig}"
36
- end
37
-
38
- unless Signal.list.key?(sig)
39
- raise ArgumentError, "unsupported signal SIG#{sig}"
40
- end
41
-
42
- prc ||= block
43
-
44
- if prc.nil?
45
- raise ArgumentError, "tried to create Proc object without a block"
46
- end
47
-
48
- @traps[sig] ||= []
49
-
50
- @mutex.synchronize do
51
- if recursion?
52
- @traps[sig].pop
53
- else
54
- @traps[sig].push(prc || block)
55
- end
56
- end
57
-
58
- @old_trap.call(sig) do
59
- @traps[sig].each do |trap_handler|
60
- trap_handler.call(Signal.list[sig])
61
- end
62
- end
63
-
64
- @traps
65
- end
10
+ def self.jruby?
11
+ RUBY_ENGINE == 'jruby'
66
12
  end
67
- end
68
13
 
69
- module Signal
70
- class << self
71
- alias_method :old_trap, :trap
72
- protected :old_trap
73
-
74
- def trap(sig, prc = nil, &block)
75
- Multitrap::Trap.trap(sig, prc, method(:old_trap), &block)
76
- end
14
+ def self.mri?
15
+ RUBY_ENGINE == 'ruby'
77
16
  end
78
- end
79
17
 
80
- module Kernel
81
- def trap(sig, prc = nil, &block)
82
- Signal.trap(sig, prc, &block)
18
+ def self.rbx?
19
+ RUBY_ENGINE == 'rbx'
83
20
  end
84
- module_function :trap
85
21
  end
22
+
23
+ require 'multitrap/trap'
24
+ require 'multitrap/patched_trap'
25
+ require 'multitrap/core_ext'
@@ -0,0 +1,11 @@
1
+ case RUBY_ENGINE
2
+ when 'ruby'
3
+ [Kernel, Kernel.singleton_class, Signal, Signal.singleton_class].each do |klass|
4
+ klass.__send__(:include, Multitrap::PatchedTrap)
5
+ end
6
+ when 'rbx'
7
+ Signal.__send__(:include, Multitrap::PatchedTrap)
8
+ Signal.singleton_class.__send__(:include, Multitrap::PatchedTrap)
9
+ when 'jruby'
10
+ Signal.singleton_class.__send__(:include, Multitrap::PatchedTrap)
11
+ end
@@ -0,0 +1,31 @@
1
+ module Multitrap
2
+ module PatchedTrap
3
+ def self.included(mod)
4
+ original_trap = Signal.method(:trap)
5
+
6
+ mod.instance_eval do
7
+ define_method(:trap) do |*args, &block|
8
+ if args.size < 1
9
+ if Multitrap.rbx?
10
+ raise ArgumentError, "method 'trap': given 0, expected 2"
11
+ elsif Multitrap.jruby?
12
+ raise ArgumentError, "wrong number of arguments (0 for 1)"
13
+ else
14
+ raise ArgumentError, "wrong number of arguments (0 for 1..2)"
15
+ end
16
+ end
17
+
18
+ if block.nil?
19
+ if Multitrap.rbx? && !args[1].respond_to?(:call)
20
+ raise ArgumentError, "Handler must respond to #call (was #{args[1].class})"
21
+ elsif args[1].nil?
22
+ raise ArgumentError, 'tried to create Proc object without a block'
23
+ end
24
+ end
25
+
26
+ Multitrap::Trap.trap(original_trap, *args, &block)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,104 @@
1
+ module Multitrap
2
+ class Trap
3
+ OWN_MRI_FRAME = %r{/lib/multitrap/trap.rb:[0-9]{1,3}:in `block in store_trap'}
4
+
5
+ OWN_RBX_FRAME = %r{/lib/multitrap/trap.rb:[0-9]{1,3}:in `store_trap'}
6
+
7
+ TRAVIS_FRAME = %r{lib/jruby\.jar!/jruby/kernel/signal\.rb}
8
+
9
+ RESERVED_SIGNALS = %w|BUS SEGV ILL FPE VTALRM|
10
+
11
+ KNOWN_SIGNALS = Signal.list.keys - RESERVED_SIGNALS
12
+
13
+ def self.trap(original_trap, *args, &block)
14
+ @@multitrap ||= new(original_trap)
15
+
16
+ signal = args[0]
17
+ command = args[1]
18
+
19
+ @@multitrap.store_trap(signal, command, &block)
20
+ end
21
+
22
+ def self.__clear_all_handlers!
23
+ @@multitrap ||= nil
24
+ @@multitrap && @@multitrap.__clear_all_handlers!
25
+ end
26
+
27
+ def initialize(original_trap)
28
+ @original_trap = original_trap
29
+ @trap_list = create_trap_list
30
+ end
31
+
32
+ def store_trap(signal, command, &block)
33
+ signal = signal.to_s
34
+ command ||= block
35
+
36
+ trap_is_nested = nested_trap?
37
+
38
+ if trap_is_nested
39
+ # JRuby doesn't support nested traps.
40
+ return if Multitrap.jruby?
41
+ @trap_list[signal].pop
42
+ end
43
+
44
+ @trap_list[signal] << command
45
+
46
+ prev_trap_handler = @original_trap.call(signal) do |signo|
47
+ @trap_list[signal].reverse_each do |trap_handler|
48
+ trap_handler.call(signo || Signal.list[signal])
49
+ end
50
+ end
51
+
52
+ if !trap_is_nested &&
53
+ @trap_list[signal].size == 1 &&
54
+ !default_handler?(prev_trap_handler) &&
55
+ !default_handler_path?(prev_trap_handler)
56
+ @trap_list[signal].unshift(prev_trap_handler)
57
+ end
58
+
59
+ @trap_list
60
+ end
61
+
62
+ def __clear_all_handlers!
63
+ @trap_list.keys.select {|s| KNOWN_SIGNALS.include?(s) }.each do |signal|
64
+ @original_trap.call(signal, 'DEFAULT')
65
+ end
66
+
67
+ @trap_list = create_trap_list
68
+ end
69
+
70
+ private
71
+
72
+ define_method(:nested_trap?) do
73
+ case RUBY_ENGINE
74
+ when 'ruby'
75
+ caller.any? { |stackframe| stackframe =~ OWN_MRI_FRAME }
76
+ when 'rbx'
77
+ caller.grep(OWN_RBX_FRAME).size > 1
78
+ when 'jruby'
79
+ if caller.any? { |stackframe| stackframe =~ TRAVIS_FRAME }
80
+ caller.grep(OWN_RBX_FRAME).size > 1
81
+ else
82
+ caller.grep(OWN_MRI_FRAME).size > 1
83
+ end
84
+ else
85
+ raise NotImplementedError, 'unsupported Ruby engine'
86
+ end
87
+ end
88
+
89
+ def create_trap_list
90
+ Hash.new { |h, k| h[k] = [] }
91
+ end
92
+
93
+ def default_handler?(prev)
94
+ ['DEFAULT', 'SYSTEM_DEFAULT', 'IGNORE'].any? { |h| h == prev }
95
+ end
96
+
97
+ def default_handler_path?(prev)
98
+ [%r{Proc:.+@kernel/loader\.rb:[0-9]{1,4}},
99
+ %r{Proc:.+@uri:classloader:/jruby/kernel/signal.rb:[0-9]{1,4}}].any? do |h|
100
+ h =~ prev.to_s
101
+ end
102
+ end
103
+ end
104
+ end
metadata CHANGED
@@ -1,115 +1,113 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: multitrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Kyrylo Silin
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2014-09-08 00:00:00.000000000 Z
12
+ date: 2015-08-09 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
- name: bundler
15
+ name: rake
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
- - - "~>"
19
+ - - ! '>='
18
20
  - !ruby/object:Gem::Version
19
- version: '1.6'
21
+ version: '0'
20
22
  type: :development
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
- - - "~>"
27
+ - - ! '>='
25
28
  - !ruby/object:Gem::Version
26
- version: '1.6'
29
+ version: '0'
27
30
  - !ruby/object:Gem::Dependency
28
- name: rake
31
+ name: rspec
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
- - - ">="
35
+ - - ! '>='
32
36
  - !ruby/object:Gem::Version
33
37
  version: '0'
34
38
  type: :development
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
- - - ">="
43
+ - - ! '>='
39
44
  - !ruby/object:Gem::Version
40
45
  version: '0'
41
46
  - !ruby/object:Gem::Dependency
42
- name: rspec
47
+ name: rspec-wait
43
48
  requirement: !ruby/object:Gem::Requirement
49
+ none: false
44
50
  requirements:
45
- - - ">="
51
+ - - ! '>='
46
52
  - !ruby/object:Gem::Version
47
53
  version: '0'
48
54
  type: :development
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
51
58
  requirements:
52
- - - ">="
59
+ - - ! '>='
53
60
  - !ruby/object:Gem::Version
54
61
  version: '0'
55
62
  - !ruby/object:Gem::Dependency
56
63
  name: pry
57
64
  requirement: !ruby/object:Gem::Requirement
65
+ none: false
58
66
  requirements:
59
- - - ">="
67
+ - - ! '>='
60
68
  - !ruby/object:Gem::Version
61
69
  version: '0'
62
70
  type: :development
63
71
  prerelease: false
64
72
  version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
65
74
  requirements:
66
- - - ">="
75
+ - - ! '>='
67
76
  - !ruby/object:Gem::Version
68
77
  version: '0'
69
78
  description:
70
- email:
71
- - silin@kyrylo.org
79
+ email: silin@kyrylo.org
72
80
  executables: []
73
81
  extensions: []
74
82
  extra_rdoc_files: []
75
83
  files:
76
- - ".gitignore"
77
- - ".rspec"
78
- - CHANGELOG.md
79
- - Gemfile
80
- - LICENSE.txt
81
- - README.md
82
- - Rakefile
84
+ - lib/multitrap/core_ext.rb
85
+ - lib/multitrap/patched_trap.rb
86
+ - lib/multitrap/trap.rb
83
87
  - lib/multitrap.rb
84
- - lib/multitrap/version.rb
85
- - multitrap.gemspec
86
- - spec/multitrap_spec.rb
87
- - spec/spec_helper.rb
88
- homepage: ''
88
+ homepage: https://github.com/kyrylo/multitrap
89
89
  licenses:
90
- - MIT
91
- metadata: {}
90
+ - Zlib
92
91
  post_install_message:
93
92
  rdoc_options: []
94
93
  require_paths:
95
94
  - lib
96
95
  required_ruby_version: !ruby/object:Gem::Requirement
96
+ none: false
97
97
  requirements:
98
- - - ">="
98
+ - - ! '>='
99
99
  - !ruby/object:Gem::Version
100
100
  version: '0'
101
101
  required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
102
103
  requirements:
103
- - - ">="
104
+ - - ! '>='
104
105
  - !ruby/object:Gem::Version
105
106
  version: '0'
106
107
  requirements: []
107
108
  rubyforge_project:
108
- rubygems_version: 2.2.2
109
+ rubygems_version: 1.8.23.2
109
110
  signing_key:
110
- specification_version: 4
111
+ specification_version: 3
111
112
  summary: Allows Signal.trap to have multiple callbacks
112
- test_files:
113
- - spec/multitrap_spec.rb
114
- - spec/spec_helper.rb
115
- has_rdoc:
113
+ test_files: []
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: f34b3f14dfb6b3271c98561b1a9dd6bde507947a
4
- data.tar.gz: df07859ab58e19de64a97b6ab417b408476e55ca
5
- SHA512:
6
- metadata.gz: 46b7369e7ce17076171ca335951f16f1b4d0f94d1ff5b6e641542d520ea512f7d7d71aa54de76c31f0c5228ac85f93e31f64319df4b1f76263bc1c250dbadb20
7
- data.tar.gz: f2f5d7b4abfb0ea065967e7910f01d5e9d7c09838243c26371285d642b3bf0fbb40720156a9d394d0d480708171b7ffd8e76ca79bb63dc5989a50fa72a60df2a
data/.gitignore DELETED
@@ -1,23 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
6
- Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
- coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
18
- *.bundle
19
- *.so
20
- *.o
21
- *.a
22
- mkmf.log
23
- *~
data/.rspec DELETED
@@ -1 +0,0 @@
1
- --color
@@ -1,6 +0,0 @@
1
- Multitrap changelog
2
- ===================
3
-
4
- ### v0.0.1 (September 7, 2014)
5
-
6
- * Initial release
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in multitrap.gemspec
4
- gemspec
@@ -1,22 +0,0 @@
1
- Copyright (c) 2014 Kyrylo Silin
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md DELETED
@@ -1,98 +0,0 @@
1
- # Multitrap
2
-
3
- The idea is to be able to attach multiple callbacks for `Signal.trap`.
4
-
5
- ```ruby
6
- require 'multitrap'
7
-
8
- trap('INT') do
9
- puts 111
10
- end
11
-
12
- trap('INT') do
13
- puts 222
14
- end
15
-
16
- Process.kill('INT', $$)
17
-
18
- # Outputs:
19
- # 111
20
- # 222
21
- ```
22
-
23
- Currently, the library doesn't achieve the goal. I want to make the gem
24
- unobtrusive: the user installs it and it "just works". However, it's not
25
- possible due to a limitation of MRI. If you earlier had defined traps and then
26
- required Multitrap, it would discard your previously defined callbacks.
27
-
28
- ```ruby
29
- trap('INT') do
30
- puts 111
31
- end
32
-
33
- require 'multitrap'
34
-
35
- trap('INT') do
36
- puts 222
37
- end
38
-
39
- trap('INT') do
40
- puts 333
41
- end
42
-
43
- Process.kill('INT', $$)
44
-
45
- # Outputs:
46
- # 222
47
- # 333
48
- ```
49
-
50
- However, it's possible to bypass this limitation. Just redefine your trap
51
- when Multitrap is loaded.
52
-
53
- ```ruby
54
- trap_proc = trap('INT') do
55
- puts 111
56
- end
57
-
58
- require 'multitrap'
59
-
60
- trap('INT', trap_proc)
61
-
62
- trap('INT') do
63
- puts 222
64
- end
65
-
66
- Process.kill('INT', $$)
67
-
68
- # Outputs:
69
- # 111
70
- # 222
71
- # 333
72
- ```
73
-
74
- ## Installation
75
-
76
- Add this line to your application's Gemfile:
77
-
78
- gem 'multitrap'
79
-
80
- And then execute:
81
-
82
- $ bundle
83
-
84
- Or install it yourself as:
85
-
86
- $ gem install multitrap
87
-
88
- ## Limitations
89
-
90
- ### Rubies
91
-
92
- * MRI 2.1
93
-
94
- ### Known bugs
95
-
96
- * recursive traps are broken (fixable)
97
- * overwrites traps defined before the loading of the library ([not fixable,
98
- requires patching MRI](https://bugs.ruby-lang.org/issues/10211))
data/Rakefile DELETED
@@ -1,2 +0,0 @@
1
- require "bundler/gem_tasks"
2
-
@@ -1,3 +0,0 @@
1
- module Multitrap
2
- VERSION = "0.0.1"
3
- end
@@ -1,24 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'multitrap/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "multitrap"
8
- spec.version = Multitrap::VERSION
9
- spec.authors = ["Kyrylo Silin"]
10
- spec.email = ["silin@kyrylo.org"]
11
- spec.summary = %q{Allows Signal.trap to have multiple callbacks}
12
- spec.homepage = ""
13
- spec.license = "MIT"
14
-
15
- spec.files = `git ls-files -z`.split("\x0")
16
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
- spec.require_paths = ["lib"]
19
-
20
- spec.add_development_dependency "bundler", "~> 1.6"
21
- spec.add_development_dependency "rake"
22
- spec.add_development_dependency "rspec"
23
- spec.add_development_dependency "pry"
24
- end
@@ -1,102 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Multitrap::Trap do
4
- describe "#trap" do
5
- describe "recursive" do
6
- it "unwinds the stack" do
7
- number = nil
8
-
9
- trap('INT') do
10
- trap('INT') do
11
- number = 42
12
- end
13
- end
14
-
15
- Process.kill('INT', $$)
16
- expect(number).to be_nil
17
-
18
- Process.kill('INT', $$)
19
- expect(number).to eq(42)
20
- end
21
- end
22
-
23
- it "adds multiple callbacks" do
24
- shared = []
25
-
26
- 3.times do |i|
27
- trap('INT') { shared << i }
28
- end
29
-
30
- Process.kill('INT', $$)
31
-
32
- expect(shared).to eq([0, 1, 2])
33
- end
34
-
35
- it "supports proc syntax" do
36
- shared = []
37
-
38
- 3.times do |i|
39
- trap('INT', proc { shared << i })
40
- end
41
-
42
- Process.kill('INT', $$)
43
-
44
- expect(shared).to eq([0, 1, 2])
45
- end
46
-
47
- it "ignores block if proc is given" do
48
- shared = []
49
-
50
- 3.times do |i|
51
- trap('INT', proc { shared << i }) do
52
- shared << i+100
53
- end
54
- end
55
-
56
- Process.kill('INT', $$)
57
-
58
- expect(shared).to eq([0, 1, 2])
59
- end
60
-
61
- it "binds to multiple signals" do
62
- shared_int = []
63
- shared_info = []
64
-
65
- 3.times do |i|
66
- trap('INT') { shared_int << i }
67
- end
68
-
69
- 3.times do |i|
70
- trap('INFO') { shared_info << i+100 }
71
- end
72
-
73
- Process.kill('INT', $$)
74
- Process.kill('INFO', $$)
75
-
76
- expect(shared_int).to eq([0, 1, 2])
77
- expect(shared_info).to eq([100, 101, 102])
78
- end
79
-
80
- it "yields signal's number" do
81
- number = nil
82
-
83
- trap('INT') { |signo| number = signo }
84
-
85
- Process.kill('INT', $$)
86
-
87
- expect(number).to eq(2)
88
- end
89
-
90
- it "raises ArgumentError if signal doesn't exist" do
91
- expect{
92
- trap('DONUTS') { }
93
- }.to raise_error(ArgumentError, /unsupported signal SIGDONUTS/)
94
- end
95
-
96
- it "raises ArgumentError if signal is reserved" do
97
- expect{
98
- trap('ILL') {}
99
- }.to raise_error(ArgumentError, /can't trap reserved signal SIGILL/)
100
- end
101
- end
102
- end
@@ -1,3 +0,0 @@
1
- require 'bundler'
2
- require 'pry'
3
- require 'multitrap'