errands 0.0.1 → 0.0.2

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.
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: []