stud 0.0.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.
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: