deferral 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +7 -0
- data/README.md +94 -0
- data/Rakefile +12 -0
- data/deferral.gemspec +27 -0
- data/lib/deferral.rb +48 -0
- data/lib/deferral/kernel_ext.rb +7 -0
- data/lib/deferral/stack_frame.rb +31 -0
- data/lib/deferral/toplevel.rb +9 -0
- data/lib/deferral/version.rb +3 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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)
|
data/Rakefile
ADDED
data/deferral.gemspec
ADDED
@@ -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
|
data/lib/deferral.rb
ADDED
@@ -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,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
|
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: []
|