errands 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a781f76605abb3a1044de0fe07eeb8239e747f17
4
- data.tar.gz: 24ab0e0c763815f8250ba72ebf8a72c98eee9ec0
3
+ metadata.gz: 0b3ec45541ab232b30e76994f6f9e157e6ce0090
4
+ data.tar.gz: fab0fe7273f9ac5617597da2a114bbc4c1fceb05
5
5
  SHA512:
6
- metadata.gz: 0f2c8a6784b251e01f897004db3249c606ae129e421d7137481789732d9690a5a5384df3f559324ef01f7a2acba78a37b4b191eff432c147a7555adc95993c2b
7
- data.tar.gz: 8faf6c10f519130311a048d71d0b0c80a30d6e8a69bdca20cb8a4429b7cd13d0d5f265bebd5388622b433801841fbc2f2172873bb9f672cd3aa63217b4d6bbf9
6
+ metadata.gz: 8fc4027f696fc7b287b34f2fed5e9159ad7548c16ba9bfedd95e4a66854c854bde830b54dbc0c8ab2a0cc1b1317a3ed839e376974058b522a31046969ccccf14
7
+ data.tar.gz: 674b812a10089f7d5829fb2f1a53f633c648270a361f4a160f4fb51863cfc6d6164e6ba2e2d629db720a7a156ad726f2849032d7c51e12e160f5397cfb812931
@@ -0,0 +1,51 @@
1
+ module Errands
2
+
3
+ module AlternatePrivateAccess
4
+
5
+ def self.included(singleton)
6
+ class << singleton
7
+ attr_accessor :errands_store
8
+ end
9
+ end
10
+
11
+ def self.extended(klass)
12
+ class << klass
13
+ attr_accessor :errands_store
14
+ end
15
+ end
16
+
17
+ def set_store(store)
18
+ singleton_class.errands_store = store
19
+ end
20
+
21
+ private
22
+
23
+ def our_store!(h = nil)
24
+ (Thread.main[singleton_class.errands_store] = h || {}).tap do |store|
25
+ if t = store[:threads]
26
+ t.singleton_class.include AlternatePrivateAccess
27
+ t.singleton_class.errands_store = singleton_class.errands_store
28
+ end
29
+
30
+ if r = Thread.main[singleton_class.errands_store][:receptors]
31
+ r.singleton_class.include AlternatePrivateAccess
32
+ r.singleton_class.errands_store = singleton_class.errands_store
33
+
34
+ def r.default(key)
35
+ self[key] = Errands::Receptors::Receptor.new(key).tap do |v|
36
+ v.singleton_class.include Errands::AlternatePrivateAccess
37
+ v.singleton_class.errands_store = singleton_class.errands_store
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+
45
+ def our
46
+ singleton_class.errands_store && Thread.main[singleton_class.errands_store]
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -1,178 +1,363 @@
1
1
  module Errands
2
2
 
3
- module Runner
3
+ module ThreadAccessor
4
4
 
5
- module ThreadAccessors
5
+ def self.extended(klass)
6
+ klass.include PrivateAccess
7
+ end
6
8
 
7
- def thread_accessors(*accessors)
8
- accessors.each do |a|
9
- define_method a, -> { our[a] }
10
- end
9
+ def thread_accessor(*accessors)
10
+ accessors.each do |a|
11
+ define_method a, -> { our[a] }
12
+ define_method "#{a}=", ->(v) { our[a] = v }
13
+ end
14
+ end
15
+
16
+ module PrivateAccess
17
+
18
+ def err(h = {})
19
+ his_store! Thread.current, h
20
+ end
21
+
22
+ private
23
+
24
+ def our_store!(h = nil)
25
+ Thread.main[:errands] = h || {}
26
+ end
27
+
28
+ def his_store!(thread, h = nil)
29
+ thread[:errands] = h || {}
30
+ end
31
+
32
+ def my
33
+ Thread.current[:errands]
34
+ end
35
+
36
+ def his(thread)
37
+ thread[:errands]
38
+ end
39
+
40
+ def our
41
+ Thread.main[:errands]
11
42
  end
12
43
 
13
44
  end
14
45
 
15
- class ImplementationError < StandardError; end
46
+ end
47
+
48
+ module Started
49
+
50
+ def start(*_)
51
+ new(*_).tap &:start
52
+ end
16
53
 
17
- %w|job process|.each do |m|
18
- define_method m do |*_|
19
- raise method(__method__).owner::ImplementationError,
20
- "#{__method__} has to be implemented in client class"
54
+ def run(*_)
55
+ new(*_).tap do |e|
56
+ Process.daemon if (callee = __callee__) == :daemon
57
+ startups << define_method(:startups_alternate_run) { { callee => true } } if __callee__ != __method__
58
+ e.run
21
59
  end
22
60
  end
23
61
 
24
- def self.included(klass)
25
- klass.extend ThreadAccessors
62
+ alias_method :daemon, :run
63
+ alias_method :threaded_run, :run
64
+ alias_method :noop_run, :run
65
+
66
+ def started_workers(*_)
67
+ (@started_workers ||= [:worker]).concat _.map(&:to_sym).flatten
26
68
  end
27
69
 
28
- attr_accessor :running_mode
70
+ def startups
71
+ @startups ||= []
72
+ end
29
73
 
30
- def run(options = startup)
31
- start options
32
- our[:events] = []
74
+ private
75
+
76
+ def default_workers(*_)
77
+ started_workers(*_).tap { |s| s.delete :worker }
78
+ end
33
79
 
34
- loop do
35
- errands *our[:events].shift if our[:events].any?
80
+ end
81
+
82
+ class Receptors < Hash
83
+
84
+ class Receptor < Array
85
+
86
+ module Track
87
+
88
+ def track(v, r = nil)
89
+ v.__send__ "instance_variable_#{r ? :set : :get}", *["@receptor_track", r].compact
90
+ rescue => e
91
+ my.merge!(data: v, error: :tracking_error)
92
+ raise e
93
+ end
94
+
95
+ end
96
+
97
+ include ThreadAccessor::PrivateAccess
98
+ include Track
99
+
100
+ attr_reader :name
101
+
102
+ def initialize(name)
103
+ @name = name
104
+ end
105
+
106
+ def shift(*_)
107
+ my[:data] = super.tap { |value| my.merge! receptor_track: track(value), latency: empty? }
108
+ end
109
+
110
+ def <<(value)
111
+ return if value.nil?
112
+ track value, my[:receptor_track]
113
+ super.tap { our[:threads][@name] && our[:threads][@name].run }
36
114
  end
115
+
37
116
  end
38
117
 
39
- def start(options = startup)
40
- our.merge! options
41
- starter
118
+ def default(key)
119
+ self[key] = Receptor.new key
120
+ end
121
+
122
+ end
123
+
124
+ class Runners < Hash
125
+
126
+ include ThreadAccessor::PrivateAccess
127
+
128
+ def [](k)
129
+ v = super
130
+ v if v && v.alive?
42
131
  end
43
132
 
44
- def my
45
- Thread.current[:runner] ||= {}
133
+ def []=(k, v)
134
+ our[k] = super if v.is_a? Thread
46
135
  end
47
136
 
48
- def his(t, k, v = nil)
49
- t[:runner] ||= {}
50
- v ? t[:runner][k] = v : t[:runner][k]
137
+ def delete(k)
138
+ our.delete k
139
+ super
51
140
  end
52
141
 
53
- def our
54
- Thread.main[:runner] ||= {}
142
+ def stopping_order(all = false)
143
+ scope(:type, :starter).merge(scope(:type, :data_acquisition)).merge all ? self : {}
55
144
  end
56
145
 
57
- def starter
58
- running do
59
- loop do
60
- if secure_check :worker, :alive?
61
- sleep 1
62
- else
63
- secure_check :worker, :join
64
- worker
65
- end
66
- end
146
+ def key_sliced(*list)
147
+ select_keys = keys & list.flatten
148
+ typecast select { |k, v| select_keys.include? k }
149
+ end
67
150
 
68
- errands :stop, :starter
69
- end
151
+ def alive
152
+ typecast select { |k, v| self[k] }
153
+ end
154
+
155
+ def scope(s, value = true)
156
+ typecast select { |k, v| his(v)[s] == value }
157
+ end
158
+
159
+ private
160
+
161
+ def typecast(h)
162
+ self.class.new.merge! h
70
163
  end
71
164
 
165
+ end
166
+
167
+ module LousyCompat
168
+
72
169
  def worker
73
- our[:work_done] = false
74
-
75
- running do
76
- loop do
77
- begin
78
- (our[:work_done] = true) && break if work_done?
79
- process job
80
- sleep our[:frequency] if our[:frequency]
81
- rescue => e
82
- log_error e
83
- end
84
- end
170
+ working :worker, :process, :job
171
+ end
172
+
173
+ end
174
+
175
+ module Runner
176
+
177
+ def self.included(klass)
178
+ klass.extend(ThreadAccessor).extend(Started)
179
+ klass.thread_accessor :events, :receptors, :threads
180
+ end
181
+
182
+ attr_accessor :running_mode
183
+
184
+ def start(options = startups)
185
+ our_store! options.merge(threads: Runners.new, receptors: Receptors.new)
186
+ starter
187
+ end
188
+
189
+ def run(options = startups)
190
+ start options unless started?
191
+ our.merge! events: receptors[:events]
192
+ our[:threaded_run] || our[:noop_run] ? running { main_loop } : main_loop
193
+ end
194
+
195
+ def starter(*_)
196
+ if our[:starter]
197
+ self.class.started_workers *_
198
+ elsif !our[:noop_run]
199
+ starting self.class.started_workers
200
+ end
201
+ end
202
+
203
+ def starting(started)
204
+ puts ["Starting #{self.class} with :", our[:config], "workers : #{started}", "\n"].join("\n") unless our[:quiet]
85
205
 
86
- our[:work_done] && work_done
87
- errands :stop, :worker
206
+ running thread_name, loop: true, started: started, type: :starter do
207
+ Array(my[:started]).uniq.each { |s| threads[s] ||= send *(respond_to?(s, true) ? [s] : [:working, s]) }
208
+ sleep frequency || 1
88
209
  end
89
210
  end
90
211
 
91
- def stop(threads = nil)
92
- our_selection(threads || our[:threads]) do |n, t|
93
- if Thread.current == t
94
- errands :stop, n
95
- else
96
- t.exit
97
- t.join
98
- wait_for n, :status, false
212
+ def working(*_)
213
+ work_done, processing, data_acquisition = working_jargon *_
214
+ our[work_done] = false
215
+
216
+ running _.first, loop: true, type: :data_acquisition do
217
+ unless my[:stop] ||= our[work_done] = checked_send("#{work_done}?")
218
+ r = ready_receptor! processing
219
+ ((r << send(data_acquisition)) && !my[:latency]) || sleep(frequency.to_i)
99
220
  end
100
221
  end
222
+ end
101
223
 
102
- our[:stopped] = !status.values.any? { |t| t.alive? }
224
+ def exit_on_stop
225
+ stop
226
+ exit
103
227
  end
104
228
 
105
- def status
106
- our_selection our[:threads]
229
+ def stop(*_)
230
+ [false, true].each do |all|
231
+ list = threads.key_sliced(_.any? ? _ : stopped_threads)
232
+ list.alive.each { |n, t| his(t)[:stop] = true }
233
+ list.stopping_order(all).alive.each { |n, t| exiting(n, !all || t.stop?) }
234
+ end
235
+
236
+ stopped?
237
+ threads.key_sliced(_.any? ? _ : stopped_threads).alive.empty?
107
238
  end
108
239
 
109
240
  def status
110
- our_selection our[:threads]
241
+ {}.tap { |s| threads.each { |name, t| s[name] = t.status } }
111
242
  end
112
243
 
113
244
  def wait_for(key, meth = nil, result = true)
114
- loop do
115
- break if meth && our[key].respond_to?(meth) ?
116
- our[key].send(meth) == result :
117
- !!our[key] == result
118
- end
245
+ time = Time.now.to_f
246
+ loop {
247
+ break if @errands_wait_timeout && Time.now.to_f - time > @errands_wait_timeout
248
+ break if meth && our[key].respond_to?(meth, true) ?
249
+ ((our[key].send(meth) == result) rescue nil) :
250
+ !!our[key] == result }
251
+ end
252
+
253
+ def stopped?
254
+ our[:stopped] = threads.key_sliced(stopped_threads).alive.empty?
119
255
  end
120
256
 
121
- def work_done?
122
- false
257
+ def started?
258
+ !!our && !!threads && our[:started] = !stopped?
123
259
  end
124
260
 
125
261
  private
126
262
 
127
- def our_selection(selection)
128
- our.dup.select do |k, v|
129
- Array(selection).include?(k).tap { |bool|
130
- yield k, our[k] if bool && block_given?
131
- }
263
+ def frequency(name = nil)
264
+ our[:config] && our[:config][:frequencies] && our[:config][:frequencies][name || my[:name]]
265
+ end
266
+
267
+ def main_loop
268
+ rescued_loop do
269
+ (e = events.shift) ? errands(*e) : sleep(frequency(:main_loop) || 1)
132
270
  end
133
271
  end
134
272
 
135
- def errands(errand, *_)
136
- name = thread_name(1).to_s << "_#{errand}"
273
+ def ready_receptor!(processing)
274
+ receptors[processing].tap { threads[processing] ||= spring processing }
275
+ end
137
276
 
138
- running name.to_sym do
139
- send errand, *_
140
- our[:threads].delete my[:name]
277
+ def spring(processing)
278
+ running processing, loop: true, deletable: true do
279
+ data = receptors[my[:name]].shift || Thread.stop || receptors[my[:name]].shift
280
+ data && send(processing, data).tap do |r|
281
+ if my[:receptor_track] && my[:receptor_track][:receptor].name != my[:name]
282
+ my[:receptor_track][:receptor] << my[:receptor_track].merge(result: r).reject { |k, v| k == :receptor }
283
+ end
284
+ end
141
285
  end
142
286
  end
143
287
 
144
- def running(name = nil, &block)
288
+ def errands(errand, *_)
289
+ running("#{thread_name(1)}_#{errand}".to_sym, deletable: true) { send errand, *_ }
290
+ end
291
+
292
+ def running(name = thread_name, options = {}, &block)
145
293
  if @running_mode
146
294
  send @running_mode, &block
147
295
  else
148
- register_thread name || thread_name, Thread.new(&block)
296
+ threads[name] = Thread.new {
297
+ (my && my[:name] && (my[:named] = true)) || Thread.stop || (my[:named] = true)
298
+ r = my[:result] = my[:loop] ? rescued_loop(&block) : block.call
299
+ ["stop_#{name}", our[name] && "stop_#{his(our[name])[:type]}"].compact.each { |s| checked_send s }
300
+ my[:deletable] && threads.delete(name)
301
+ r
302
+ }.tap { |t|
303
+ his_store! t, { name: name, time: Time.now.to_f, stop: false, type: :any, receptor_track: my && my.delete(:receptor_track) }.merge(options)
304
+ t.run unless his(t)[:named]
305
+ }
149
306
  end
150
307
  end
151
308
 
309
+ def exiting(name, force = true)
310
+ force && Thread.current == our[name] ? errands(:exiting, name) : our[name] && (force || our[name].stop?) && our[name].exit
311
+ wait_for name, :alive?, false
312
+ end
313
+
314
+ def stopped_threads
315
+ our[:stopped_threads] || threads.keys.reject { |k| k.to_s =~ /^errands_.+_stop$/}
316
+ end
317
+
152
318
  def thread_name(caller_depth = 2)
153
319
  caller_locations(caller_depth, 1).first.base_label.dup.tap { |n|
154
320
  n << "_" << Time.now.to_f.to_s.sub('.', '_') if n.end_with? 's'
155
321
  }.to_sym
156
322
  end
157
323
 
158
- def register_thread(name, thread)
159
- ((our[:threads] ||= []) << name).uniq!
160
- his our[name] = thread, :name, name
324
+ def rescued_loop
325
+ loop { our["#{my[:name]}_iteration".to_sym] = begin
326
+ my[:stop] ? break : yield; Time.now
327
+ rescue => e
328
+ log_error e, my[:data], my
329
+ end }
330
+ end
331
+
332
+ def checked_send(meth, recipient = self, *_)
333
+ recipient.respond_to?(meth, true) && (recipient.send(meth, *_) rescue nil)
161
334
  end
162
335
 
163
- def secure_check(name, meth)
164
- our[name] && our[name].respond_to?(meth) && our[name].send(meth)
336
+ def working_jargon(started, processing = nil, data_acquisition = nil)
337
+ [ "#{started}_done".to_sym,
338
+ processing || "#{started}_process".to_sym,
339
+ data_acquisition || "#{started}_data_acquisition".to_sym ]
165
340
  end
166
341
 
167
- def log_error(e)
168
- puts e.message
342
+ def log_error(e, data, *_)
343
+ puts(e) || puts(e.message) || puts(data) || puts(e.backtrace) || puts(_) if our[:verbose]
169
344
  end
170
345
 
171
- def startup
172
- {}
346
+ def startups
347
+ self.class.startups
348
+ .dup
349
+ .tap { |s| s << :startup if respond_to?(:startup, true) }
350
+ .uniq
351
+ .inject({}) { |s, m| extended_merge(s, __send__(m)) }
173
352
  end
174
353
 
175
- def work_done; end
354
+ def extended_merge(from, to)
355
+ from.tap do |f|
356
+ to.keys.each do |k|
357
+ f[k] = f[k].is_a?(Hash) && to[k].is_a?(Hash) ? extended_merge(f[k], to[k]) : to[k]
358
+ end
359
+ end
360
+ end
176
361
 
177
362
  end
178
363
 
@@ -0,0 +1,122 @@
1
+ require 'errands/alternate_private_access'
2
+
3
+ module Errands
4
+
5
+ module TestHelpers
6
+
7
+ module OurStore
8
+
9
+ def initialize(*_)
10
+ s = if _.size == 1 && _.first.is_a?(Hash)
11
+ _.first.delete(:startup).tap { _.pop if _.first.empty? }
12
+ elsif _.last.is_a?(Hash)
13
+ _.pop[:startup]
14
+ end
15
+
16
+ our_store! (s.is_a?(Hash) ? s : send(s)) || {}
17
+
18
+ super
19
+ end
20
+
21
+ end
22
+
23
+ module ResetStartedWorkers
24
+
25
+ def reset_started_workers
26
+ @started_workers = nil
27
+ end
28
+
29
+ end
30
+
31
+ module StopAll
32
+
33
+ def stopped_threads
34
+ threads.keys.reject { |k| k.to_s =~ /^errands_.+_stop$/}
35
+ end
36
+
37
+ end
38
+
39
+ class Wrapper
40
+
41
+ class Vanilla
42
+
43
+ extend ThreadAccessor::PrivateAccess
44
+
45
+ class << self
46
+
47
+ def theirs
48
+ our
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ include Errands::AlternatePrivateAccess
56
+
57
+ def self.helper
58
+ @instance = new
59
+ end
60
+
61
+ def self.help(helped, options = {}, &block)
62
+ helper
63
+ #~ @instance.theirs_reset! unless options[:reset] == false
64
+ @instance.help helped, &block
65
+ end
66
+
67
+ def initialize
68
+ set_store :errands_test
69
+ our_store!
70
+ end
71
+
72
+ def theirs
73
+ Vanilla.theirs
74
+ end
75
+
76
+ def help(helped)
77
+ (our[:helped] = helped).tap do |i|
78
+ i.instance_variable_set '@errands_wait_timeout', 10
79
+ i.start unless i.started?
80
+ yield i if block_given?
81
+ end
82
+ end
83
+
84
+ def helped(i = nil)
85
+ i ? our[:helped] = i : our[:helped]
86
+ end
87
+
88
+ def push_event(e)
89
+ theirs && theirs[:events] && theirs[:events] << e
90
+ end
91
+
92
+ def theirs_reset!
93
+ theirs && theirs.clear
94
+ end
95
+
96
+ def self.after
97
+ threads_cleanup
98
+
99
+ if @instance
100
+ @instance.stop_helped if @instance.helped
101
+ @instance.theirs_reset!
102
+ end
103
+
104
+ @instance = nil
105
+ end
106
+
107
+ def self.threads_cleanup
108
+ while (t = Thread.list.select { |t| t[:errands] } - [Thread.main]).any?
109
+ t.each &:exit
110
+ end
111
+ end
112
+
113
+ def stop_helped
114
+ helped.stop
115
+ helped.wait_for :stopped
116
+ end
117
+
118
+ end
119
+
120
+ end
121
+
122
+ end
@@ -1,3 +1,3 @@
1
1
  module Errands
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: errands
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - lacravate
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-15 00:00:00.000000000 Z
11
+ date: 2018-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -61,7 +61,9 @@ extensions: []
61
61
  extra_rdoc_files: []
62
62
  files:
63
63
  - lib/errands.rb
64
+ - lib/errands/alternate_private_access.rb
64
65
  - lib/errands/runner.rb
66
+ - lib/errands/test_helpers/wrapper.rb
65
67
  - lib/errands/version.rb
66
68
  homepage: https://github.com/lacravate/errands
67
69
  licenses: []