deferral 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a4478021f73e80fbf08a0d2830543684653ea7513c836c7c03ae798b9fe96798
4
+ data.tar.gz: 78d3092455e200432ca094fef131875060330fe72d9ea4498b5c7e2485d43d9b
5
+ SHA512:
6
+ metadata.gz: 33bdfd6fcd268d83299f9f7c78fe377e14ee041d00a65d2b0c19b556dbe9cc9bece3370089546ce619b857c89b7bc894a1e73987ebe564c44b2c483a218d6c19
7
+ data.tar.gz: 6375625cb220f0aa1f138e8c776591e9e226ed752d3ef0bda2e9583ac33f3649386e18e9360b69df84fab346bf38cf72e591a3613f8a014b43891035f099c61b
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in deferral.gemspec
6
+ gemspec
@@ -0,0 +1,7 @@
1
+ Copyright 2017- Satoshi Tagomori
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,94 @@
1
+ # Deferral - introduce golang style "defer" in Ruby
2
+
3
+ ```ruby
4
+ # gem install deferral
5
+
6
+ require "deferral"
7
+
8
+ def my_method_name
9
+ # ...
10
+ file = File.open("my_file", "r")
11
+ Deferral.defer { file.close }
12
+
13
+ file.write "data..."
14
+ # ...
15
+ end # `file.close` is called at the end of method (even when exception thrown)
16
+
17
+ # or end of blocks
18
+ def my_method_name(list)
19
+ # ...
20
+ list.each do |item|
21
+ file = File.open(item, "r")
22
+ Deferral.defer { file.close }
23
+
24
+ file.write "data..."
25
+ # ...
26
+ end # `file.close` is called at the end of block
27
+ end
28
+
29
+ require "deferral/toplevel"
30
+ using Deferral::TopLevel
31
+ # it makes "defer" without module name available everywhere in this file
32
+
33
+ # or enable everywhere! (DANGER!)
34
+ require "deferral/kernel_ext"
35
+ ```
36
+
37
+ This gem provides a feature to release resources safely, using golang style `defer` method.
38
+
39
+ `Deferral.defer` method does:
40
+
41
+ * accept a block argument to execute at the end of caller scope
42
+ * execute specified blocks in reverse order when getting out from specified scope
43
+
44
+ Resource release blocks are called even when any exception occurs.
45
+
46
+ See also [with_resources gem](https://github.com/tagomoris/with_resources) for try-with-resources style resource allocator.
47
+
48
+ ### Disclosure
49
+
50
+ This library is a kind of PoC to introduce safe resource allocation in Ruby world. Take care about using this library in your production environment.
51
+
52
+ This library uses/enables TracePoint, and it may collapse optimizations of your Ruby runtime.
53
+
54
+ ## API
55
+
56
+ * `Deferral.defer(&block)`
57
+
58
+ This method registers a block to be called at the end of caller scope (end of method or block). Registered blocks will be called in reverse order (LIFO: last-in, first-out) when this method is called twice or more in a scope.
59
+
60
+ ### Introduce `defer` to top-level namespace
61
+
62
+ Top-level `defer` is available via 2 different ways. One is using Refinements, another is modifying `Kernel` in open-class way.
63
+
64
+ ```ruby
65
+ require "deferral/toplevel"
66
+ using Deferral::TopLevel
67
+
68
+ def my_method
69
+ f = AnyResource.new
70
+ defer { f.close }
71
+ # ...
72
+ end
73
+ ```
74
+
75
+ Refinements is a feature of Ruby to apply Module modification in just a file (by `using` statement).
76
+ `using Deferral::TopLevel` introduces top level `defer` in safer way than modifying `Kernel`.
77
+
78
+ ```ruby
79
+ require "deferral/kernel_ext"
80
+
81
+ # now, "defer" is available everywhere...
82
+ ```
83
+
84
+ Requiring `deferral/kernel_ext` modifies `Kernel` module globally to add `deferral`. It's not recommended in most cases.
85
+
86
+ * * * * *
87
+
88
+ ## Authors
89
+
90
+ * Satoshi Tagomori <tagomoris@gmail.com>
91
+
92
+ ## License
93
+
94
+ MIT (See License.txt)
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.test_files = FileList['test/**/*.rb']
8
+ test.warning = true
9
+ test.verbose = true
10
+ end
11
+
12
+ task :default => :test
@@ -0,0 +1,27 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "deferral/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "deferral"
8
+ spec.version = Deferral::VERSION
9
+ spec.authors = ["TAGOMORI Satoshi"]
10
+ spec.email = ["tagomoris@gmail.com"]
11
+
12
+ spec.summary = %q{Provide golang style defer method in Ruby}
13
+ spec.description = %q{Provide a method to release/collect resources in deferred way}
14
+ spec.homepage = "https://github.com/tagomoris/deferral"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.16"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "test-unit"
27
+ end
@@ -0,0 +1,48 @@
1
+ require "deferral/version"
2
+ require "deferral/stack_frame"
3
+ require "digest"
4
+
5
+ module Deferral
6
+ ### Can't add "suppressed" information, because TracePoint doesn't provide
7
+ ### an exception was rescued or not after it was raised.
8
+ # module ErrorExt
9
+ # def suppressed
10
+ # @suppressed ||= []
11
+ # end
12
+ # end
13
+
14
+ def self.defer(&block)
15
+ raise ArgumentError, "release block is not specified" unless block
16
+
17
+ store = (Thread.current[:deferral_store] ||= {})
18
+ if !store.empty? && !store[:stack].empty?
19
+ store[:stack].last.add(block)
20
+ return
21
+ end
22
+
23
+ stack = store[:stack] = [StackFrame.new(:root)] # root stack frame as first "caller" position of this method
24
+ first_return = true
25
+
26
+ trace = TracePoint.new(:call, :return, :b_call, :b_return) do |tp|
27
+ if tp.event == :return && first_return # return from this method
28
+ first_return = false
29
+ next
30
+ end
31
+
32
+ case tp.event
33
+ when :call, :b_call
34
+ stack << StackFrame.new(tp.event)
35
+ when :return, :b_return
36
+ frame = stack.pop
37
+ frame.release!
38
+ if frame.root?
39
+ trace.disable
40
+ end
41
+ else
42
+ raise "unexpected TracePoint event:#{tp.event}"
43
+ end
44
+ end
45
+ stack.last.add(block)
46
+ trace.enable
47
+ end
48
+ end
@@ -0,0 +1,7 @@
1
+ require_relative "../deferral"
2
+
3
+ module Kernel
4
+ def defer(&block)
5
+ Deferral.defer(&block)
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ module Deferral
2
+ class StackFrame
3
+ attr_reader :type, :id
4
+
5
+ def initialize(type)
6
+ @type = type
7
+ @releases = []
8
+ end
9
+
10
+ def root?
11
+ @type == :root
12
+ end
13
+
14
+ def add(release)
15
+ @releases << release
16
+ end
17
+
18
+ def release!
19
+ return if @releases.empty?
20
+ @releases.reverse.each do |r|
21
+ begin
22
+ r.call
23
+ rescue Exception => e
24
+ # ignore all exceptions ...
25
+ # no way to add "suppressed" exceptions to the exception already thrown
26
+ end
27
+ end
28
+ nil
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "../deferral"
2
+
3
+ module Deferral::TopLevel
4
+ refine Kernel do
5
+ def defer(&block)
6
+ Deferral.defer(&block)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Deferral
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deferral
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - TAGOMORI Satoshi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-03-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Provide a method to release/collect resources in deferred way
56
+ email:
57
+ - tagomoris@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - deferral.gemspec
68
+ - lib/deferral.rb
69
+ - lib/deferral/kernel_ext.rb
70
+ - lib/deferral/stack_frame.rb
71
+ - lib/deferral/toplevel.rb
72
+ - lib/deferral/version.rb
73
+ homepage: https://github.com/tagomoris/deferral
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.7.3
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Provide golang style defer method in Ruby
97
+ test_files: []