zoidberg 0.0.1 → 0.1.0

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.
@@ -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: