zoidberg 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,82 @@
1
+ require 'zoidberg'
2
+
3
+ module Zoidberg
4
+ class Supervisor
5
+
6
+ include Zoidberg::Shell
7
+
8
+ # @return [Registry] current supervision registry
9
+ attr_reader :registry
10
+
11
+ # Create a new supervisor
12
+ #
13
+ # @return [self]
14
+ def initialize
15
+ @registry = Registry.new
16
+ end
17
+
18
+ # Fetch the supervised instance or pool
19
+ #
20
+ # @param k [String, Symbol] name of supervised item
21
+ # @return [Object] supervised object
22
+ def [](k)
23
+ registry[k]
24
+ end
25
+
26
+ # Supervise an instance
27
+ #
28
+ # @param name [String, Symbol] name of item to supervise
29
+ # @param klass [Class] class of instance
30
+ # @param args [Object] initialization arguments
31
+ # @yieldblock initialization block
32
+ # @return [Object] new instance
33
+ def supervise_as(name, klass, *args, &block)
34
+ klass = supervised_class(klass)
35
+ registry[name] = klass.new(*args, &block)
36
+ end
37
+
38
+ # Supervise a pool
39
+ #
40
+ # @param klass [Class] class of instance
41
+ # @param args [Hash] initialization arguments
42
+ # @option args [String] :as name of pool
43
+ # @option args [Integer] :size size of pool
44
+ # @option args [Array<Object>] :args initialization arguments
45
+ # @yieldblock initialization block
46
+ # @return [Object] new pool
47
+ def pool(klass, args={}, &block)
48
+ name = args[:as]
49
+ size = args[:size].to_i
50
+ args = args.fetch(:args, [])
51
+ klass = supervised_class(klass)
52
+ s_pool = Pool.new(klass, *args, &block)
53
+ s_pool._worker_count(size > 0 ? size : 1)
54
+ registry[name] = s_pool
55
+ end
56
+
57
+ # Destroy all supervised instances prior to destruction
58
+ def terminate
59
+ registry.values.each do |item|
60
+ item._zoidberg_destroy!
61
+ end
62
+ end
63
+
64
+ protected
65
+
66
+ # Make a supervised class
67
+ #
68
+ # @param klass [Class]
69
+ # @return [Class]
70
+ def supervised_class(klass)
71
+ unless(klass.include?(Zoidberg::Supervise))
72
+ n_klass = Class.new(klass) do
73
+ include Zoidberg::Supervise
74
+ end
75
+ n_klass.class_eval("def self.name; '#{klass.name}'; end")
76
+ klass = n_klass
77
+ end
78
+ klass
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,147 @@
1
+ require 'zoidberg'
2
+
3
+ module Zoidberg
4
+
5
+ # Run a task
6
+ class Task
7
+
8
+ # Supported task styles
9
+ SUPPORTED_STYLES = [:serial, :async]
10
+
11
+ # @return [Symbol] :fiber or :thread
12
+ attr_reader :style
13
+ # @return [Object] originator of task
14
+ attr_reader :origin
15
+ # @return [Proc] block to execute
16
+ attr_reader :content
17
+ attr_reader :content_arguments
18
+ # @return [Thread, Fiber] underlying task container
19
+ attr_reader :task
20
+
21
+ # Create a new task
22
+ #
23
+ # @param task_style [Symbol]
24
+ # @param origin [Object] origin object of block
25
+ # @yield block to execute
26
+ # @return [self]
27
+ def initialize(task_style, origin, block_args=[], &block)
28
+ unless(SUPPORTED_STYLES.include?(task_style))
29
+ raise ArgumentError.new("Allowed style values: #{SUPPORTED_STYLES.map(&:inspect).join(', ')} but received: #{task_style.inspect}")
30
+ end
31
+ @style = task_style
32
+ @origin = origin
33
+ @content = block
34
+ @content_arguments = block_args
35
+ @result = nil
36
+ send("run_#{style}")
37
+ end
38
+
39
+ # @return [Object] result of task
40
+ def value
41
+ if(task.alive?)
42
+ @result = style == :async ? task.join : task.resume
43
+ end
44
+ @result
45
+ end
46
+
47
+ # Force task to stop prior to completion if still in running state
48
+ #
49
+ # @return [NilClass]
50
+ def halt!
51
+ if(style == :async)
52
+ task.kill
53
+ else
54
+ @task = nil
55
+ end
56
+ end
57
+
58
+ # @return [TrueClass, FalseClass] task currently waiting to run
59
+ def waiting?
60
+ task && task.alive? && task.respond_to?(:stop?) ? task.stop? : true
61
+ end
62
+
63
+ # @return [TrueClass, FalseClass] task is running
64
+ def running?
65
+ if(task)
66
+ style == :async && task.alive? && !task.stop?
67
+ else
68
+ false
69
+ end
70
+ end
71
+
72
+ # @return [TrueClass, FalseClass] task is complete
73
+ def complete?
74
+ task.nil? || !task.alive?
75
+ end
76
+
77
+ # @return [TrueClass, FalseClass] task complete in error state
78
+ def error?
79
+ complete? && value.is_a?(Exception)
80
+ end
81
+
82
+ # @return [TrueClass, FalseClass] task complete in success state
83
+ def success?
84
+ complete? && !error?
85
+ end
86
+
87
+ # Reliquish running state and return optional value(s)
88
+ #
89
+ # @param args [Object] values to return
90
+ # @return [Object, Array<Object>]
91
+ def cease(*args)
92
+ if(style == :async)
93
+ task[:task_args] = args
94
+ task.stop
95
+ task[:task_args]
96
+ else
97
+ Fiber.yield(*args)
98
+ end
99
+ end
100
+
101
+ # Regain running state with optional value(s)
102
+ # @param args [Object] values to provide
103
+ # @return [Object, Array<Object>]
104
+ def proceed(*args)
105
+ if(style == :serial)
106
+ task.resume(*args)
107
+ else
108
+ task[:task_args] = args
109
+ task.run
110
+ task[:task_args]
111
+ end
112
+ end
113
+
114
+ protected
115
+
116
+ # Create new fiber based task
117
+ #
118
+ # @return [Fiber]
119
+ def run_serial
120
+ @task = Fiber.new do
121
+ begin
122
+ self.instance_exec(*content_arguments, &content)
123
+ rescue Exception => e
124
+ origin.send(:raise, e)
125
+ raise
126
+ end
127
+ end
128
+ end
129
+
130
+ # Create new thread based task
131
+ #
132
+ # @return [Thread]
133
+ def run_async
134
+ @task = Thread.new do
135
+ Thread.stop
136
+ begin
137
+ self.instance_exec(*content_arguments, &content)
138
+ rescue Exception => e
139
+ origin.send(:raise, e)
140
+ raise
141
+ end
142
+ end
143
+ end
144
+
145
+ end
146
+
147
+ end
@@ -0,0 +1,230 @@
1
+ require 'zoidberg'
2
+
3
+ module Zoidberg
4
+
5
+ # Simple timer class
6
+ class Timer
7
+
8
+ class Action
9
+
10
+ # @return [TrueClass, FalseClass]
11
+ attr_reader :recur
12
+ # @return [Proc] action to run
13
+ attr_reader :action
14
+ # @return [Numeric]
15
+ attr_reader :interval
16
+ # @return [Float]
17
+ attr_reader :last_run
18
+
19
+ # Create a new action
20
+ #
21
+ # @param args [Hash]
22
+ # @option args [Numeric] :interval
23
+ # @option args [TrueClass, FalseClass] :recur
24
+ # @return [self]
25
+ def initialize(args={}, &block)
26
+ unless(block)
27
+ raise ArgumentError.new 'Action is required. Block must be provided!'
28
+ end
29
+ @action = block
30
+ @recur = args.fetch(:recur, false)
31
+ @interval = args.fetch(:interval, 5)
32
+ @last_run = Time.now.to_f
33
+ @cancel = false
34
+ end
35
+
36
+ # Cancel the action
37
+ #
38
+ # @return [TrueClass]
39
+ def cancel
40
+ @recur = false
41
+ @cancel = true
42
+ end
43
+
44
+ # @return [TrueClass, FalseClass]
45
+ def cancelled?
46
+ @cancel
47
+ end
48
+
49
+ # Run the action
50
+ #
51
+ # @return [self]
52
+ def run!
53
+ @last_run = Time.now.to_f
54
+ action.call
55
+ @last_run = Time.now.to_f
56
+ self
57
+ end
58
+
59
+ end
60
+
61
+ include Zoidberg::SoftShell
62
+
63
+ # Custom exception used to wakeup timer
64
+ class Wakeup < StandardError; end
65
+
66
+ # @return [Mutex]
67
+ attr_reader :notify_locker
68
+ # @return [Array<Action>] items to run
69
+ attr_reader :to_run
70
+ # @return [TrueClass, FalseClass] timer is paused
71
+ attr_reader :paused
72
+
73
+ # Create a new timer
74
+ #
75
+ # @return [self]
76
+ def initialize
77
+ @to_run = []
78
+ @notify_locker = Mutex.new
79
+ @paused = false
80
+ @thread = Thread.new{ run! }
81
+ end
82
+
83
+ # Run recurring action at given interval
84
+ #
85
+ # @param interval [Numeric]
86
+ # @yield action to run
87
+ # @return [Action]
88
+ def every(interval, &block)
89
+ action = Action.new({
90
+ :interval => interval,
91
+ :recur => true
92
+ },
93
+ &block
94
+ )
95
+ to_run.push(action)
96
+ reset
97
+ action
98
+ end
99
+
100
+ # Run action after given interval
101
+ #
102
+ # @param interval [Numeric]
103
+ # @yield action to run
104
+ # @return [Action]
105
+ def after(interval, &block)
106
+ action = Action.new(
107
+ {:interval => interval},
108
+ &block
109
+ )
110
+ to_run.push(action)
111
+ reset
112
+ action
113
+ end
114
+
115
+ # Pause the timer to prevent any actions from being run
116
+ #
117
+ # @return [self]
118
+ def pause
119
+ unless(@paused)
120
+ @paused = true
121
+ reset
122
+ end
123
+ current_self
124
+ end
125
+
126
+ # Resume a paused timer
127
+ #
128
+ # @return [self]
129
+ def resume
130
+ if(@paused)
131
+ @paused = false
132
+ reset
133
+ end
134
+ current_self
135
+ end
136
+
137
+ # Remove all actions from the timer
138
+ #
139
+ # @return [self]
140
+ def cancel
141
+ to_run.clear
142
+ reset
143
+ current_self
144
+ end
145
+
146
+ # Reset the timer
147
+ #
148
+ # @param wakeup [TrueClass, FalseClass] wakeup the timer thread
149
+ # @return [self]
150
+ def reset(wakeup=true)
151
+ if(wakeup)
152
+ notify_locker.synchronize do
153
+ to_run.sort_by! do |item|
154
+ (item.interval + item.last_run) - Time.now.to_f
155
+ end
156
+ @thread.raise Wakeup.new
157
+ end
158
+ else
159
+ to_run.sort_by! do |item|
160
+ (item.interval + item.last_run) - Time.now.to_f
161
+ end
162
+ end
163
+ current_self
164
+ end
165
+
166
+ # @return [Numeric, NilClass] interval to next action
167
+ def next_interval
168
+ unless(to_run.empty? || paused)
169
+ item = to_run.first
170
+ result = (item.last_run + item.interval) - Time.now.to_f
171
+ result < 0 ? 0 : result
172
+ end
173
+ end
174
+
175
+ # Run any actions that are ready
176
+ #
177
+ # @return [self]
178
+ def run_ready
179
+ items = to_run.find_all do |item|
180
+ ((item.interval + item.last_run) - Time.now.to_f) <= 0
181
+ end
182
+ to_run.delete_if do |item|
183
+ items.include?(item)
184
+ end
185
+ items.map do |item|
186
+ begin
187
+ item.run! unless item.cancelled?
188
+ rescue DeadException
189
+ item.cancel
190
+ rescue => e
191
+ Zoidberg.logger.error "<#{self}> Timed action generated an error: #{e.class.name} - #{e}"
192
+ end
193
+ item if item.recur
194
+ end.compact.each do |item|
195
+ to_run << item
196
+ end
197
+ current_self
198
+ end
199
+
200
+ protected
201
+
202
+ # Run the timer loop
203
+ def run!
204
+ loop do
205
+ begin
206
+ interval = nil
207
+ # TODO: update with select for better subsecond support
208
+ notify_locker.synchronize do
209
+ interval = next_interval
210
+ end
211
+ sleep interval
212
+ notify_locker.synchronize do
213
+ run_ready
214
+ reset(false)
215
+ end
216
+ rescue Wakeup
217
+ Zoidberg.logger.debug "<#{self}> Received wakeup notification. Rechecking sleep interval!"
218
+ rescue DeadException
219
+ raise
220
+ rescue => e
221
+ Zoidberg.logger.error "<#{self}> Unexpected error in runner: #{e.class.name} - #{e}"
222
+ Zoidberg.logger.debug "<#{self}> #{e.class.name}: #{e}\n#{e.backtrace.join("\n")}"
223
+ current_self.raise e
224
+ end
225
+ end
226
+ end
227
+
228
+ end
229
+
230
+ end
@@ -1,3 +1,4 @@
1
1
  module Zoidberg
2
- VERSION = Gem::Version.new('0.0.1')
2
+ # Current library version
3
+ VERSION = Gem::Version.new('0.1.0')
3
4
  end
@@ -0,0 +1,51 @@
1
+ require 'zoidberg'
2
+
3
+ module Zoidberg
4
+ # Provide weak reference to object allowing for it to be garbage
5
+ # collected. This is a stripped down version of the ::WeakRef class
6
+ class WeakRef < BasicObject
7
+
8
+ # Exception type raised when referenced object no longer exists
9
+ class RecycledException < ::RuntimeError
10
+ # @return [String] ID of referenced object casted to string
11
+ attr_reader :recycled_object_id
12
+ # Create a new exception instance
13
+ #
14
+ # @param msg [String] exception message
15
+ # @param recycled_object_id [String] casted object ID
16
+ # @return [self]
17
+ def initialize(msg, recycled_object_id)
18
+ @recycled_object_id = recycled_object_id
19
+ super(msg)
20
+ end
21
+ end
22
+
23
+ @@__zoidberg_map = ::ObjectSpace::WeakMap.new
24
+
25
+ # Create a new weak reference
26
+ #
27
+ # @param orig [Object] referenced object
28
+ # @return [self]
29
+ def initialize(orig)
30
+ @_key = orig.object_id.to_s
31
+ @@__zoidberg_map[@_key] = orig
32
+ end
33
+
34
+ def method_missing(*args, &block) # :nodoc:
35
+ if(@@__zoidberg_map[@_key])
36
+ @@__zoidberg_map[@_key].__send__(*args, &block)
37
+ else
38
+ ::Kernel.raise RecycledException.new('Instance has been recycled by the system!', @_key)
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
46
+ # jruby compat [https://github.com/jruby/jruby/pull/2520]
47
+ if(Zoidberg::WeakRef.instance_methods.include?(:object_id))
48
+ class Zoidberg::WeakRef
49
+ undef_method :object_id
50
+ end
51
+ end
data/lib/zoidberg.rb CHANGED
@@ -1 +1,56 @@
1
+ require 'bogo'
2
+ require 'thread'
3
+ require 'securerandom'
1
4
  require 'zoidberg/version'
5
+
6
+ # Why not Zoidberg!?
7
+ module Zoidberg
8
+ autoload :DeadException, 'zoidberg/shell'
9
+ autoload :Future, 'zoidberg/future'
10
+ autoload :Logger, 'zoidberg/logger'
11
+ autoload :Pool, 'zoidberg/pool'
12
+ autoload :Proxy, 'zoidberg/proxy'
13
+ autoload :Registry, 'zoidberg/registry'
14
+ autoload :Shell, 'zoidberg/shell'
15
+ autoload :SoftShell, 'zoidberg/shell'
16
+ autoload :HardShell, 'zoidberg/shell'
17
+ autoload :Signal, 'zoidberg/signal'
18
+ autoload :Supervise, 'zoidberg/supervise'
19
+ autoload :Supervisor, 'zoidberg/supervisor'
20
+ autoload :Task, 'zoidberg/task'
21
+ autoload :Timer, 'zoidberg/timer'
22
+ autoload :WeakRef, 'zoidberg/weak_ref'
23
+
24
+ class << self
25
+
26
+ attr_accessor :default_shell
27
+
28
+ # @return [Zoidberg::Logger]
29
+ def logger
30
+ @zoidberg_logger
31
+ end
32
+
33
+ # Set new default logger
34
+ #
35
+ # @param log [Zoidberg::Logger]
36
+ # @return [zoidberg::Logger]
37
+ def logger=(log)
38
+ unless(log.is_a?(Zoidberg::Logger))
39
+ raise TypeError.new "Expecting type `Zoidberg::Logger` but received type `#{log.class}`"
40
+ end
41
+ @zoidberg_logger = log
42
+ end
43
+
44
+ # @return [String] UUID
45
+ def uuid
46
+ SecureRandom.uuid
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ # Always enable default logger
54
+ Zoidberg.logger = Zoidberg::Logger.new(STDERR)
55
+ # Set default shell to soft shell
56
+ Zoidberg.default_shell = Zoidberg::SoftShell
data/zoidberg.gemspec CHANGED
@@ -11,6 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.require_path = 'lib'
12
12
  s.license = 'Apache 2.0'
13
13
  s.add_runtime_dependency 'bogo'
14
+ s.add_runtime_dependency 'mono_logger'
14
15
  s.add_development_dependency 'pry'
15
16
  s.add_development_dependency 'minitest'
16
17
  s.files = Dir['lib/**/*'] + %w(zoidberg.gemspec README.md CHANGELOG.md CONTRIBUTING.md LICENSE)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zoidberg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Roberts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-19 00:00:00.000000000 Z
11
+ date: 2015-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bogo
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mono_logger
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: pry
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -63,7 +77,21 @@ files:
63
77
  - LICENSE
64
78
  - README.md
65
79
  - lib/zoidberg.rb
80
+ - lib/zoidberg/future.rb
81
+ - lib/zoidberg/logger.rb
82
+ - lib/zoidberg/pool.rb
83
+ - lib/zoidberg/proxy.rb
84
+ - lib/zoidberg/proxy/confined.rb
85
+ - lib/zoidberg/proxy/liberated.rb
86
+ - lib/zoidberg/registry.rb
87
+ - lib/zoidberg/shell.rb
88
+ - lib/zoidberg/signal.rb
89
+ - lib/zoidberg/supervise.rb
90
+ - lib/zoidberg/supervisor.rb
91
+ - lib/zoidberg/task.rb
92
+ - lib/zoidberg/timer.rb
66
93
  - lib/zoidberg/version.rb
94
+ - lib/zoidberg/weak_ref.rb
67
95
  - zoidberg.gemspec
68
96
  homepage: https://github.com/spox/zoidberg
69
97
  licenses:
@@ -90,3 +118,4 @@ signing_key:
90
118
  specification_version: 4
91
119
  summary: Why not?
92
120
  test_files: []
121
+ has_rdoc: