stud 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
File without changes
data/LICENSE ADDED
File without changes
File without changes
@@ -0,0 +1,23 @@
1
+ module Stud
2
+ # This implementation tries to keep clock more accurately.
3
+ # Prior implementations still permitted skew, where as this one
4
+ # will attempt to correct for skew.
5
+ #
6
+ # The execution patterns of this method should be that
7
+ # the start time of 'block.call' should always be at time T*interval
8
+ def interval(time, &block)
9
+ start = Time.now
10
+ while true
11
+ block.call
12
+ duration = Time.now - start
13
+ # Sleep only if the duration was less than the time interval
14
+ if duration < time
15
+ sleep(time - duration)
16
+ start += time
17
+ else
18
+ # Duration exceeded interval time, reset the clock and do not sleep.
19
+ start = Time.now
20
+ end
21
+ end # loop forever
22
+ end # def interval
23
+ end # module Stud
@@ -0,0 +1,215 @@
1
+ require "thread"
2
+
3
+ module Stud
4
+ # Public: A thread-safe, generic resource pool.
5
+ #
6
+ # This class is agnostic as to the resources in the pool. You can put
7
+ # database connections, sockets, threads, etc. It's up to you!
8
+ #
9
+ # Examples:
10
+ #
11
+ # pool = Pool.new
12
+ # pool.add(Sequel.connect("postgres://pg-readonly-1/prod"))
13
+ # pool.add(Sequel.connect("postgres://pg-readonly-2/prod"))
14
+ # pool.add(Sequel.connect("postgres://pg-readonly-3/prod"))
15
+ #
16
+ # pool.fetch # => Returns one of the Sequel::Database values from the pool
17
+ class Pool
18
+
19
+ class Error < StandardError; end
20
+
21
+ # An error indicating a given resource is busy.
22
+ class ResourceBusy < Error; end
23
+
24
+ # An error indicating a given resource is not found.
25
+ class NotFound < Error; end
26
+
27
+ # You performed an invalid action.
28
+ class InvalidAction < Error; end
29
+
30
+ # Default all methods to private. See the bottom of the class definition
31
+ # for public method declarations.
32
+ private
33
+
34
+ # Public: initialize a new pool.
35
+ #
36
+ # max_size - if specified, limits the number of resources allowed in the pool.
37
+ def initialize(max_size=nil)
38
+ # Available resources
39
+ @available = Hash.new
40
+ # Busy resources
41
+ @busy = Hash.new
42
+
43
+ # The pool lock
44
+ @lock = Mutex.new
45
+
46
+ # Locks for blocking {#fetch} calls if the pool is full.
47
+ @full_lock = Mutex.new
48
+ @full_cv = ConditionVariable.new
49
+
50
+ # Maximum size of this pool.
51
+ @max_size = max_size
52
+ end # def initialize
53
+
54
+ # Private: Is this pool size-limited?
55
+ #
56
+ # Returns true if this pool was created with a max_size. False, otherwise.
57
+ def sized?
58
+ return !@max_size.nil?
59
+ end # def sized?
60
+
61
+ # Private: Is this pool full?
62
+ #
63
+ # Returns true if the pool is sized and the count of resources is at maximum.
64
+ def full?
65
+ return sized? && (count == @max_size)
66
+ end # def full?
67
+
68
+ # Public: the count of resources in the pool
69
+ #
70
+ # Returns the count of resources in the pool.
71
+ def count
72
+ return (@busy.size + @available.size)
73
+ end # def count
74
+
75
+ # Public: Add a new resource to this pool.
76
+ #
77
+ # The resource, once added, is assumed to be available for use.
78
+ # That means once you add it, you must not use it unless you receive it from
79
+ # {Pool#fetch}
80
+ #
81
+ # resource - the object resource to add to the pool.
82
+ #
83
+ # Returns nothing
84
+ def add(resource)
85
+ @lock.synchronize do
86
+ @available[resource.object_id] = resource
87
+ end
88
+ return nil
89
+ end # def add
90
+
91
+ # Public: Fetch an available resource.
92
+ #
93
+ # If no resource is available, and the pool is not full, the
94
+ # default_value_block will be called and the return value of it used as the
95
+ # resource.
96
+ #
97
+ # If no resource is availabe, and the pool is full, this call will block
98
+ # until a resource is available.
99
+ #
100
+ # Returns a resource ready to be used.
101
+ def fetch(&default_value_block)
102
+ @lock.synchronize do
103
+ object_id, resource = @available.shift
104
+ if !resource.nil?
105
+ @busy[resource.object_id] = resource
106
+ return resource
107
+ end
108
+ end
109
+
110
+ @full_lock.synchronize do
111
+ if full?
112
+ # This should really use a logger.
113
+ puts "=> Pool is full and nothing available. Waiting for a release..."
114
+ @full_cv.wait(@full_lock)
115
+ return fetch(&default_value_block)
116
+ end
117
+ end
118
+
119
+ # TODO(sissel): If no block is given, we should block until a resource is
120
+ # available.
121
+
122
+ # If we get here, no resource is available and the pool is not full.
123
+ resource = default_value_block.call
124
+ # Only add the resource if the default_value_block returned one.
125
+ if !resource.nil?
126
+ add(resource)
127
+ return fetch
128
+ end
129
+ end # def fetch
130
+
131
+ # Public: Remove a resource from the pool.
132
+ #
133
+ # This is useful if the resource is no longer useful. For example, if it is
134
+ # a database connection and that connection has failed.
135
+ #
136
+ # This resource *MUST* be available and not busy.
137
+ #
138
+ # Raises Pool::NotFound if no such resource is found.
139
+ # Raises Pool::ResourceBusy if the resource is found but in use.
140
+ def remove(resource)
141
+ # Find the object by object_id
142
+ #p [:internal, :busy => @busy, :available => @available]
143
+ @lock.synchronize do
144
+ if available?(resource)
145
+ raise InvalidAction, "This resource must be busy for you to remove " \
146
+ "it (ie; it must be fetched from the pool)"
147
+ end
148
+ @busy.delete(resource.object_id)
149
+ end
150
+ end # def remove
151
+
152
+ # Private: Verify this resource is in the pool.
153
+ #
154
+ # You *MUST* call this method only when you are holding @lock.
155
+ #
156
+ # Returns :available if it is available, :busy if busy, false if not in the pool.
157
+ def include?(resource)
158
+ if @available.include?(resource.object_id)
159
+ return :available
160
+ elsif @busy.include?(resource.object_id)
161
+ return :busy
162
+ else
163
+ return false
164
+ end
165
+ end # def include?
166
+
167
+ # Private: Is this resource available?
168
+ # You *MUST* call this method only when you are holding @lock.
169
+ #
170
+ # Returns true if this resource is available in the pool.
171
+ # Raises NotFound if the resource given is not in the pool at all.
172
+ def available?(resource)
173
+ case include?(resource)
174
+ when :available; return true
175
+ when :busy; return false
176
+ else; raise NotFound, "No resource, #{resource.inspect}, found in pool"
177
+ end
178
+ end # def avilable?
179
+
180
+ # Private: Is this resource busy?
181
+ #
182
+ # You *MUST* call this method only when you are holding @lock.
183
+ #
184
+ # Returns true if this resource is busy.
185
+ # Raises NotFound if the resource given is not in the pool at all.
186
+ def busy?(resource)
187
+ return !available?(resource)
188
+ end # def busy?
189
+
190
+ # Public: Release this resource back to the pool.
191
+ #
192
+ # After you finish using a resource you received with {#fetch}, you must
193
+ # release it back to the pool using this method.
194
+ #
195
+ # Alternately, you can {#remove} it if you want to remove it from the pool
196
+ # instead of releasing it.
197
+ def release(resource)
198
+ @lock.synchronize do
199
+ if !include?(resource)
200
+ raise NotFound, "No resource, #{resource.inspect}, found in pool"
201
+ end
202
+
203
+ # Release is a no-op if this resource is already available.
204
+ #return if available?(resource)
205
+ @busy.delete(resource.object_id)
206
+ @available[resource.object_id] = resource
207
+
208
+ # Notify any threads waiting on a resource from the pool.
209
+ @full_lock.synchronize { @full_cv.signal }
210
+ end
211
+ end # def release
212
+
213
+ public(:add, :remove, :fetch, :release, :sized?, :count)
214
+ end # class Pool
215
+ end # module Stud
@@ -0,0 +1,26 @@
1
+ module Stud
2
+ class Supervisor
3
+ def initialize(*args, &block)
4
+ @args = args
5
+ @block = block
6
+
7
+ run
8
+ end # def initialize
9
+
10
+ def run
11
+ while true
12
+ task = Task.new(*@args, &@block)
13
+ begin
14
+ puts :result => task.wait
15
+ rescue => e
16
+ puts e
17
+ puts e.backtrace
18
+ end
19
+ end
20
+ end # def run
21
+ end # class Supervisor
22
+
23
+ def self.supervise(&block)
24
+ Supervisor.new(&block)
25
+ end # def supervise
26
+ end # module Stud
@@ -0,0 +1,32 @@
1
+ require "thread"
2
+
3
+ module Stud
4
+ class Task
5
+ def initialize(*args, &block)
6
+ # A queue to receive the result of the block
7
+ # TODO(sissel): Don't use a queue, just store it in an instance variable.
8
+ @queue = Queue.new
9
+
10
+ @thread = Thread.new(@queue, *args) do |queue, *args|
11
+ begin
12
+ result = block.call(*args)
13
+ queue << [:return, result]
14
+ rescue => e
15
+ queue << [:exception, e]
16
+ end
17
+ end # thread
18
+ end # def initialize
19
+
20
+ def wait
21
+ @thread.join
22
+ reason, result = @queue.pop
23
+
24
+ if reason == :exception
25
+ #raise StandardError.new(result)
26
+ raise result
27
+ else
28
+ return result
29
+ end
30
+ end # def wait
31
+ end # class Task
32
+ end # module Stud
@@ -0,0 +1,76 @@
1
+ module Stud
2
+ # Public: try a block of code until either it succeeds or we give up.
3
+ #
4
+ # enumerable - an Enumerable or omitted, #each is invoked and is tried that
5
+ # number of times. If this value is omitted or nil, we will try until
6
+ # success with no limit on the number of tries.
7
+ #
8
+ # Returns the return value of the block once the block succeeds.
9
+ # Raises the last seen exception if we run out of tries.
10
+ #
11
+ # Examples
12
+ #
13
+ # # Try 10 times to fetch http://google.com/
14
+ # response = try(10.times) { Net::HTTP.get_response("google.com", "/") }
15
+ #
16
+ # # Try many times, yielding the value of the enumeration to the block.
17
+ # # This allows you to try different inputs.
18
+ # response = try([0, 2, 4, 6]) { |val| 50 / val }
19
+ #
20
+ # Output:
21
+ # Failed (divided by 0). Retrying in 0.01 seconds...
22
+ # => 25
23
+ #
24
+ # # Try forever
25
+ # return_value = try { ... }
26
+ def try(enumerable=nil, &block)
27
+ if block.arity == 0
28
+ # If the block takes no arguments, give none
29
+ procedure = lambda { |val| block.call }
30
+ else
31
+ # Otherwise, pass the current 'enumerable' value to the block.
32
+ procedure = lambda { |val| block.call(val) }
33
+ end
34
+
35
+ last_exception = nil
36
+
37
+ # Retry after a sleep to be nice.
38
+ backoff = 0.01
39
+ backoff_max = 2.0
40
+
41
+ # Try forever if enumerable is nil.
42
+ if enumerable.nil?
43
+ enumerable = Enumerator.new do |y|
44
+ a = 0
45
+ while true
46
+ a += 1
47
+ y << a
48
+ end
49
+ end
50
+ end
51
+
52
+ # When 'enumerable' runs out of things, if we still haven't succeeded, we'll
53
+ # reraise
54
+ enumerable.each do |val|
55
+ begin
56
+ # If the 'procedure' (the block, really) succeeds, we'll break
57
+ # and return the return value of the block. Win!
58
+ return procedure.call(val)
59
+ rescue => e
60
+ puts "Failed (#{e}). Retrying in #{backoff} seconds..."
61
+ last_exception = e
62
+
63
+ # Exponential backoff
64
+ sleep(backoff)
65
+ backoff = [backoff * 2, backoff_max].min unless backoff == backoff_max
66
+ end
67
+ end
68
+
69
+ # generally make the exception appear from the 'try' method itself, not from
70
+ # any deeply nested enumeration/begin/etc
71
+ last_exception.set_backtrace(StandardError.new.backtrace)
72
+ raise last_exception
73
+ end # def try
74
+
75
+ extend self
76
+ end # module Stud
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stud
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jordan Sissel
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-10 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: small reusable bits of code I'm tired of writing over and over. A library
15
+ form of my software-patterns github repo.
16
+ email: jls@semicomplete.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/stud/task.rb
22
+ - lib/stud/pool.rb
23
+ - lib/stud/interval.rb
24
+ - lib/stud/try.rb
25
+ - lib/stud/supervise.rb
26
+ - LICENSE
27
+ - CHANGELIST
28
+ - README.md
29
+ homepage: https://github.com/jordansissel/ruby-stud
30
+ licenses: []
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 1.8.24
51
+ signing_key:
52
+ specification_version: 3
53
+ summary: stud - common code techniques
54
+ test_files: []
55
+ has_rdoc: