lspace 0.1.pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/LICENSE.MIT ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Conrad Irwin <conrad.irwin@gmail.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ *in progress*
2
+
3
+ LSpace — Safe operation-local storage.
4
+
5
+ Global variables are awesome. Unfortunately they become a bit useless when you have
6
+ multiple threads because you often want different "global" state per-thread...
7
+
8
+ Thread-local variables are even more awesome! Unfortunately they become a bit useless when
9
+ you are doing multiple things on the same thread because you often want different
10
+ "thread-local" state per operation...
11
+
12
+ Operation-local variables are most awesome!
13
+
14
+ `LSpace`, named after the Discworld's [L-Space](http://en.wikipedia.org/wiki/Other_dimensions_of_the_Discworld#L-space)
15
+ gives you effective, safe operation-local variables.
16
+
17
+ It does this by following your operation as it jumps between thread-pools, or fires
18
+ callbacks on your event-loop; and makes sure to clean up after itself so that no state
19
+ accidentally leaks.
20
+
21
+ If you're using this on EventMachine, you should be ready to rock by requiring 'lspace/eventmachine'.
22
+ If you've got your own thread-pool, or are doing something fancy, you'll need to do some
23
+ manual work.
@@ -0,0 +1,37 @@
1
+ class Module
2
+ def in_lspace(*methods)
3
+ methods.each do |method|
4
+ method_without_lspace = "#{method}_without_lspace"
5
+ next if method_defined?(method_without_lspace) || private_method_defined?(method_without_lspace)
6
+
7
+ alias_method method_without_lspace, method
8
+
9
+ define_method(method) do |*args, &block|
10
+ args.map!{ |a| Proc === a ? a.in_lspace : a }
11
+ block = block && block.in_lspace
12
+ __send__(method_without_lspace, *args, &block)
13
+ end
14
+
15
+ private method if private_method_defined?(method_without_lspace)
16
+ protected method if protected_method_defined?(method_without_lspace)
17
+ end
18
+ end
19
+
20
+ def attr_lspace(*attrs)
21
+ attrs.each do |attr|
22
+ define_method(attr) do
23
+ LSpace[attr]
24
+ end
25
+
26
+ define_method("#{attr}=") do |value|
27
+ LSpace[attr] = value
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ class Proc
34
+ def in_lspace
35
+ LSpace.preserve(&self)
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ require 'eventmachine'
2
+ module EventMachine
3
+ module Deferrable
4
+ in_lspace :callback, :errback
5
+ end
6
+
7
+ class << Connection
8
+ alias_method :allocate_without_lspace, :allocate
9
+
10
+ def allocate
11
+ allocate_without_lspace.instance_eval do
12
+ extend EventMachine::LSpacePreserver
13
+ @lspace_context = LSpace.new
14
+ lspace_setup
15
+ self
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ class Connection
22
+ def lspace_setup; end
23
+ end
24
+
25
+ module LSpacePreserver
26
+ [:initialize, :post_init, :connection_completed, :receive_data, :ssl_verify_peer, :ssl_handshake_completed].each do |method|
27
+ define_method(method) { |*a, &b| LSpace.enter(@lspace_context) { super(*a, &b) } }
28
+ end
29
+
30
+ [:unbind].each do |method|
31
+ define_method(method) { LSpace.enter(@lspace_context) { super() } }
32
+ end
33
+ end
34
+ end
35
+
36
+ class << EventMachine
37
+ in_lspace :add_timer, :next_tick, :error_handler, :defer, :run, :run_block, :schedule, :fork_reactor
38
+ in_lspace :add_shutdown_hook if method_defined?(:add_shutdown_hook)
39
+ end
@@ -0,0 +1,7 @@
1
+ class << Thread
2
+ in_lspace :new, :start, :fork
3
+ end
4
+
5
+ module Kernel
6
+ in_lspace :fork
7
+ end
data/lib/lspace.rb ADDED
@@ -0,0 +1,214 @@
1
+ require File.expand_path('../lspace/core_ext', __FILE__)
2
+
3
+ module LSpace
4
+
5
+ class << self
6
+ # Get the most specific value for the key.
7
+ #
8
+ # If nested LSpaces are active, returns the value set in the innermost scope.
9
+ # If this key is not present in any of the nested LSpaces, nil is returned.
10
+ #
11
+ # @example
12
+ # LSpace.new :user_id => 5 do
13
+ # LSpace.new :user_id => 6 do
14
+ # LSpace[:user_id] == 6
15
+ # end
16
+ # end
17
+ # @param [Object] key
18
+ # @return [Object]
19
+ def [](key)
20
+ active.each do |c|
21
+ return c[key] if c.has_key?(key)
22
+ end
23
+ nil
24
+ end
25
+
26
+ # Sets the value for the key in the currently active LSpace.
27
+ #
28
+ # This does not have any effect on outer LSpaces.
29
+ #
30
+ # If your LSpace is shared between threads, you should think very hard before
31
+ # changing a value, it's often better to create a new LSpace if you want to
32
+ # override a value temporarily.
33
+ #
34
+ # @example
35
+ # LSpace.new :user_id => 5 do
36
+ # LSpace.new do
37
+ # LSpace[:user_id] = 6
38
+ # LSpace[:user_id] == 6
39
+ # end
40
+ # LSpace[:user_id] == 5
41
+ # end
42
+ #
43
+ # @param [Object] key
44
+ # @param [Object] value
45
+ # @return [Object] value
46
+ def []=(key, value)
47
+ current[key] = value
48
+ end
49
+
50
+ # Create a new LSpace.
51
+ #
52
+ # If a block is passed, then the block is called in the new LSpace,
53
+ # otherwise you can manually pass the LSpace to {LSpace.enter} later.
54
+ #
55
+ # The returned LSpace will inherit from the currently active LSpace:
56
+ #
57
+ # @example
58
+ # LSpace.new :job_id => 7 do
59
+ # LSpace[:job_id] == 7
60
+ # end
61
+ #
62
+ # @example
63
+ # class Job
64
+ # def initialize
65
+ # @lspace = LSpace.new
66
+ # end
67
+ #
68
+ # def run!
69
+ # LSpace.enter(@lspace){ run }
70
+ # end
71
+ # end
72
+ #
73
+ # @param [Hash] new Values to set in the new LSpace
74
+ # @param [Proc] block The block to run
75
+ # @return [Hash] The new LSpace (unless a block is given)
76
+ # @return [Object] The return value of the block (if a block is given)
77
+ def new(new={}, &block)
78
+ new[:outer_lspace] = current
79
+ if block_given?
80
+ enter(new, &block)
81
+ else
82
+ new
83
+ end
84
+ end
85
+
86
+ # Enter an LSpace
87
+ #
88
+ # This sets a new LSpace to be current for the duration of the block,
89
+ # it also runs any around filters for the new space. (Around filters that
90
+ # were present in the previous space are not run again).
91
+ #
92
+ # @example
93
+ # class Job
94
+ # def initialize
95
+ # @lspace = LSpace.new
96
+ # end
97
+ #
98
+ # def run!
99
+ # LSpace.enter(@lspace){ run }
100
+ # end
101
+ # end
102
+ #
103
+ # @param [Hash] new The LSpace to enter
104
+ # @param [Proc] block The block to run
105
+ def enter(new, &block)
106
+ previous = current
107
+ self.current = new
108
+
109
+ new_frames = active.take_while{ |space| space != previous }
110
+ filters = new_frames.map{ |space| space[:around_filter] }.compact
111
+
112
+ filters.inject(block) do |block, filter|
113
+ lambda{ filter.call(&block) }
114
+ end.call
115
+ ensure
116
+ self.current = previous
117
+ end
118
+
119
+ # Preserve the current LSpace when this block is called
120
+ #
121
+ # @example
122
+ # LSpace.new :user_id => 1 do
123
+ # $todo = LSpace.preserve do |args|
124
+ # LSpace[:user_id]
125
+ # end
126
+ # end
127
+ # $todo.call == 1
128
+ #
129
+ # @see [Proc#in_lspace]
130
+ # @param [Proc] original The block to wrap
131
+ # @return [Proc] A modified block that will be executed in the current LSpace.
132
+ def preserve(&original)
133
+ current = self.current
134
+
135
+ proc do |*args, &block|
136
+ LSpace.enter(current) do
137
+ original.call(*args, &block)
138
+ end
139
+ end
140
+ end
141
+
142
+ # Add an around filter for the current LSpace
143
+ #
144
+ # The filter will be called every time this LSpace is entered on a new call stack, which
145
+ # makes it suitable for maintaining state in libraries that are not LSpace aware (like
146
+ # log4r) or implementing unified fallback error handling.
147
+ #
148
+ # Bear in mind that when you add an around_filter to the current LSpace it will not be
149
+ # running. For this reason, you should try and set up around filters before using the
150
+ # LSpace properly.
151
+ #
152
+ # @example
153
+ # class Job
154
+ # def initialize
155
+ # LSpace.new do
156
+ #
157
+ # LSpace.around_filter do |&block|
158
+ # begin
159
+ # block.call
160
+ # rescue => e
161
+ # puts "Job #{LSpace[:job_id]} failed with: #{e}"
162
+ # end
163
+ # end
164
+ #
165
+ # @lspace = LSpace.current
166
+ # end
167
+ # end
168
+ #
169
+ # def run!
170
+ # LSpace.enter(@lspace){ run }
171
+ # end
172
+ # end
173
+ #
174
+ # @param [Proc] new_filter A Proc that takes a &block argument.
175
+ def around_filter(&new_filter)
176
+ if old_filter = current[:around_filter]
177
+ current[:around_filter] = lambda{ |&block| old_filter.call{ new_filter.call(&block) } }
178
+ else
179
+ current[:around_filter] = new_filter
180
+ end
181
+ end
182
+
183
+ # Get the currently active LSpace
184
+ #
185
+ # @see LSpace.enter
186
+ # @param [Hash] new The new LSpace
187
+ def current
188
+ Thread.current[:lspace] ||= {}
189
+ end
190
+
191
+ private
192
+
193
+ # Set the current LSpace
194
+ #
195
+ # @see LSpace.enter
196
+ # @param [Hash] new The new LSpace
197
+ def current=(new)
198
+ Thread.current[:lspace] = new
199
+ end
200
+
201
+ # All active LSpaces from most-specific to most-generic
202
+ #
203
+ # @return [Array<Hash>]
204
+ def active
205
+ c = self.current
206
+ a = []
207
+ while c
208
+ a << c
209
+ c = c[:outer_lspace]
210
+ end
211
+ a
212
+ end
213
+ end
214
+ end
data/lspace.gemspec ADDED
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "lspace"
3
+ s.version = "0.1.pre.1"
4
+ s.platform = Gem::Platform::RUBY
5
+ s.author = "Conrad Irwin"
6
+ s.email = "conrad.irwin@gmail.com"
7
+ s.homepage = "http://github.com/ConradIrwin/lspace"
8
+ s.summary = "Provides local global storage"
9
+ s.description = "Provides the convenience of global variables, without the safety concerns."
10
+ s.files = `git ls-files`.split("\n")
11
+ s.require_path = "lib"
12
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe LSpace do
4
+ it "should act like a hash" do
5
+ LSpace[:foo] = 1
6
+ LSpace[:foo].should == 1
7
+ end
8
+
9
+ it "should isolate changes to nested spaces" do
10
+ LSpace[:foo] = 2
11
+
12
+ LSpace.new do
13
+ LSpace[:foo] = 1
14
+ LSpace[:foo].should == 1
15
+ end
16
+
17
+ LSpace[:foo].should == 2
18
+ end
19
+
20
+ it "should fallback to outer spaces" do
21
+ LSpace[:bar] = 1
22
+ LSpace.new do
23
+ LSpace[:bar].should == 1
24
+ end
25
+ end
26
+
27
+ it "should be isolated between different threads" do
28
+ LSpace[:foo] = 1
29
+ Thread.new{ LSpace[:foo].should == nil }.join
30
+ end
31
+
32
+ it "should be isolated between different fibers" do
33
+ LSpace[:foo] = 1
34
+ Fiber.new{ LSpace[:foo].should == nil }.resume
35
+ end
36
+
37
+ it "should allow preserving spaces" do
38
+ p = LSpace.new(:foo => 1){ proc{ LSpace[:foo] }.in_lspace }
39
+ p.call.should == 1
40
+ end
41
+
42
+ it "should allow resuming spaces in different threads" do
43
+ p = LSpace.new(:foo => 1){ proc{ LSpace[:foo] }.in_lspace }
44
+ Thread.new{ p.call.should == 1 }.join
45
+ end
46
+
47
+ it "should allow resuming spaces in different fibers" do
48
+ p = LSpace.new(:foo => 1){ LSpace.preserve{ LSpace[:foo] } }
49
+ Fiber.new{ p.call.should == 1 }.resume
50
+ end
51
+
52
+ it "should clean up lspace after resuming" do
53
+ p = LSpace.new(:foo => 1){ proc{ LSpace[:foo] }.in_lspace }
54
+ p.call.should == 1
55
+ LSpace[:foo].should == nil
56
+ end
57
+
58
+ it "should resume the entire nested lspace" do
59
+ p = LSpace.new(:foo => 1) {
60
+ LSpace.new(:bar => 2) {
61
+ LSpace.new(:baz => 3) {
62
+ lambda &LSpace.preserve{ LSpace[:foo] + LSpace[:bar] + LSpace[:baz] }
63
+ }
64
+ }
65
+ }
66
+
67
+ p.call.should == 6
68
+ end
69
+
70
+ it "should return to enclosing lspace after re-entering new lspace" do
71
+ LSpace.new(:baz => 1) do
72
+ p = LSpace.new(:baz => 2){ proc{ LSpace[:baz] }.in_lspace }
73
+ p.call.should == 2
74
+ LSpace[:baz].should == 1
75
+ end
76
+ end
77
+
78
+ it "should clean up lspaces properly even if an exception is raised" do
79
+ LSpace.new(:baz => 1) do
80
+ begin
81
+ LSpace.new(:baz => 1) do
82
+ raise "OOPS"
83
+ end
84
+ rescue => e
85
+ LSpace[:baz].should == 1
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../lib/lspace'
2
+ RSpec.configure do |c|
3
+ c.around(:each) do |example|
4
+ LSpace.new do
5
+ example.run
6
+ end
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lspace
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.pre.1
5
+ prerelease: 4
6
+ platform: ruby
7
+ authors:
8
+ - Conrad Irwin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-22 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Provides the convenience of global variables, without the safety concerns.
15
+ email: conrad.irwin@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE.MIT
21
+ - README.md
22
+ - lib/lspace.rb
23
+ - lib/lspace/core_ext.rb
24
+ - lib/lspace/eventmachine.rb
25
+ - lib/lspace/thread.rb
26
+ - lspace.gemspec
27
+ - spec/lspace_spec.rb
28
+ - spec/spec_helper.rb
29
+ homepage: http://github.com/ConradIrwin/lspace
30
+ licenses: []
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>'
45
+ - !ruby/object:Gem::Version
46
+ version: 1.3.1
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 1.8.24
50
+ signing_key:
51
+ specification_version: 3
52
+ summary: Provides local global storage
53
+ test_files: []
54
+ has_rdoc: