totally_lazy 0.0.4 → 0.0.5

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,442 @@
1
+ require 'rbconfig'
2
+ require_relative 'processor_count'
3
+
4
+ module Parallel
5
+ extend Parallel::ProcessorCount
6
+
7
+ class DeadWorker < StandardError
8
+ end
9
+
10
+ class Break < StandardError
11
+ end
12
+
13
+ class Kill < StandardError
14
+ end
15
+
16
+ Stop = Object.new
17
+
18
+ INTERRUPT_SIGNAL = :SIGINT
19
+
20
+ class ExceptionWrapper
21
+ attr_reader :exception
22
+ def initialize(exception)
23
+ dumpable = Marshal.dump(exception) rescue nil
24
+ unless dumpable
25
+ exception = RuntimeError.new("Undumpable Exception -- #{exception.inspect}")
26
+ end
27
+
28
+ @exception = exception
29
+ end
30
+ end
31
+
32
+ class Worker
33
+ attr_reader :pid, :read, :write
34
+ attr_accessor :thread
35
+ def initialize(read, write, pid)
36
+ @read, @write, @pid = read, write, pid
37
+ end
38
+
39
+ def close_pipes
40
+ read.close
41
+ write.close
42
+ end
43
+
44
+ def wait
45
+ Process.wait(pid)
46
+ rescue Interrupt
47
+ # process died
48
+ end
49
+
50
+ def work(data)
51
+ begin
52
+ Marshal.dump(data, write)
53
+ rescue Errno::EPIPE
54
+ raise DeadWorker
55
+ end
56
+
57
+ begin
58
+ Marshal.load(read)
59
+ rescue EOFError
60
+ raise DeadWorker
61
+ end
62
+ end
63
+ end
64
+
65
+ class ItemWrapper
66
+ def initialize(array, mutex)
67
+ @lambda = (array.respond_to?(:call) && array) || queue_wrapper(array)
68
+ @items = array.to_a unless @lambda # turn Range and other Enumerable-s into an Array
69
+ @mutex = mutex
70
+ @index = -1
71
+ end
72
+
73
+ def producer?
74
+ @lambda
75
+ end
76
+
77
+ def each_with_index(&block)
78
+ if producer?
79
+ loop do
80
+ item, index = self.next
81
+ break unless index
82
+ yield(item, index)
83
+ end
84
+ else
85
+ @items.each_with_index(&block)
86
+ end
87
+ end
88
+
89
+ def next
90
+ if producer?
91
+ # - index and item stay in sync
92
+ # - do not call lambda after it has returned Stop
93
+ item, index = @mutex.synchronize do
94
+ return if @stopped
95
+ item = @lambda.call
96
+ @stopped = (item == Parallel::Stop)
97
+ return if @stopped
98
+ [item, @index += 1]
99
+ end
100
+ else
101
+ index = @mutex.synchronize { @index += 1 }
102
+ return if index >= size
103
+ item = @items[index]
104
+ end
105
+ [item, index]
106
+ end
107
+
108
+ def size
109
+ @items.size
110
+ end
111
+
112
+ def pack(item, index)
113
+ producer? ? [item, index] : index
114
+ end
115
+
116
+ def unpack(data)
117
+ producer? ? data : [@items[data], data]
118
+ end
119
+
120
+ def queue_wrapper(array)
121
+ array.respond_to?(:num_waiting) && array.respond_to?(:pop) && lambda { array.pop(false) }
122
+ end
123
+ end
124
+
125
+ class << self
126
+ def in_threads(options={:count => 2})
127
+ count, options = extract_count_from_options(options)
128
+
129
+ out = []
130
+ threads = []
131
+
132
+ count.times do |i|
133
+ threads[i] = Thread.new do
134
+ out[i] = yield(i)
135
+ end
136
+ end
137
+
138
+ kill_on_ctrl_c(threads) { wait_for_threads(threads) }
139
+
140
+ out
141
+ end
142
+
143
+ def in_processes(options = {}, &block)
144
+ count, options = extract_count_from_options(options)
145
+ count ||= processor_count
146
+ map(0...count, options.merge(:in_processes => count), &block)
147
+ end
148
+
149
+ def each(array, options={}, &block)
150
+ map(array, options.merge(:preserve_results => false), &block)
151
+ array
152
+ end
153
+
154
+ def each_with_index(array, options={}, &block)
155
+ each(array, options.merge(:with_index => true), &block)
156
+ end
157
+
158
+ def map(array, options = {}, &block)
159
+ options[:mutex] = Mutex.new
160
+
161
+ if RUBY_PLATFORM =~ /java/ and not options[:in_processes]
162
+ method = :in_threads
163
+ size = options[method] || processor_count
164
+ elsif options[:in_threads]
165
+ method = :in_threads
166
+ size = options[method]
167
+ else
168
+ method = :in_processes
169
+ if Process.respond_to?(:fork)
170
+ size = options[method] || processor_count
171
+ else
172
+ $stderr.puts "Warning: Process.fork is not supported by this Ruby"
173
+ size = 0
174
+ end
175
+ end
176
+
177
+ items = ItemWrapper.new(array, options[:mutex])
178
+
179
+ size = [items.producer? ? size : items.size, size].min
180
+
181
+ options[:return_results] = (options[:preserve_results] != false || !!options[:finish])
182
+ add_progress_bar!(items, options)
183
+
184
+ if size == 0
185
+ work_direct(items, options, &block)
186
+ elsif method == :in_threads
187
+ work_in_threads(items, options.merge(:count => size), &block)
188
+ else
189
+ work_in_processes(items, options.merge(:count => size), &block)
190
+ end
191
+ end
192
+
193
+ def map_with_index(array, options={}, &block)
194
+ map(array, options.merge(:with_index => true), &block)
195
+ end
196
+
197
+ private
198
+
199
+ def add_progress_bar!(items, options)
200
+ if title = options[:progress]
201
+ raise "Progressbar and producers don't mix" if items.producer?
202
+ require 'ruby-progressbar'
203
+ progress = ProgressBar.create(
204
+ :title => title,
205
+ :total => items.size,
206
+ :format => '%t |%E | %B | %a'
207
+ )
208
+ old_finish = options[:finish]
209
+ options[:finish] = lambda do |item, i, result|
210
+ old_finish.call(item, i, result) if old_finish
211
+ progress.increment
212
+ end
213
+ end
214
+ end
215
+
216
+
217
+ def work_direct(items, options)
218
+ results = []
219
+ items.each_with_index do |e,i|
220
+ results << (options[:with_index] ? yield(e,i) : yield(e))
221
+ end
222
+ results
223
+ end
224
+
225
+ def work_in_threads(items, options, &block)
226
+ results = []
227
+ exception = nil
228
+
229
+ in_threads(options[:count]) do
230
+ # as long as there are more items, work on one of them
231
+ loop do
232
+ break if exception
233
+ item, index = items.next
234
+ break unless index
235
+
236
+ begin
237
+ results[index] = with_instrumentation item, index, options do
238
+ call_with_index(item, index, options, &block)
239
+ end
240
+ rescue StandardError => e
241
+ exception = e
242
+ break
243
+ end
244
+ end
245
+ end
246
+
247
+ handle_exception(exception, results)
248
+ end
249
+
250
+ def work_in_processes(items, options, &blk)
251
+ workers = create_workers(items, options, &blk)
252
+ results = []
253
+ exception = nil
254
+
255
+ kill_on_ctrl_c(workers.map(&:pid)) do
256
+ in_threads(options[:count]) do |i|
257
+ worker = workers[i]
258
+ worker.thread = Thread.current
259
+
260
+ begin
261
+ loop do
262
+ break if exception
263
+ item, index = items.next
264
+ break unless index
265
+
266
+ output = with_instrumentation item, index, options do
267
+ worker.work(items.pack(item, index))
268
+ end
269
+
270
+ if ExceptionWrapper === output
271
+ exception = output.exception
272
+ if Parallel::Kill === exception
273
+ (workers - [worker]).each do |w|
274
+ kill_that_thing!(w.thread)
275
+ kill_that_thing!(w.pid)
276
+ end
277
+ end
278
+ else
279
+ results[index] = output
280
+ end
281
+ end
282
+ ensure
283
+ worker.close_pipes
284
+ worker.wait # if it goes zombie, rather wait here to be able to debug
285
+ end
286
+ end
287
+ end
288
+
289
+ handle_exception(exception, results)
290
+ end
291
+
292
+ def create_workers(items, options, &block)
293
+ workers = []
294
+ Array.new(options[:count]).each do
295
+ workers << worker(items, options.merge(:started_workers => workers), &block)
296
+ end
297
+ workers
298
+ end
299
+
300
+ def worker(items, options, &block)
301
+ # use less memory on REE
302
+ GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
303
+
304
+ child_read, parent_write = IO.pipe
305
+ parent_read, child_write = IO.pipe
306
+
307
+ pid = Process.fork do
308
+ begin
309
+ options.delete(:started_workers).each(&:close_pipes)
310
+
311
+ parent_write.close
312
+ parent_read.close
313
+
314
+ process_incoming_jobs(child_read, child_write, items, options, &block)
315
+ ensure
316
+ child_read.close
317
+ child_write.close
318
+ end
319
+ end
320
+
321
+ child_read.close
322
+ child_write.close
323
+
324
+ Worker.new(parent_read, parent_write, pid)
325
+ end
326
+
327
+ def process_incoming_jobs(read, write, items, options, &block)
328
+ while !read.eof?
329
+ data = Marshal.load(read)
330
+ item, index = items.unpack(data)
331
+ result = begin
332
+ call_with_index(item, index, options, &block)
333
+ rescue StandardError => e
334
+ ExceptionWrapper.new(e)
335
+ end
336
+ Marshal.dump(result, write)
337
+ end
338
+ end
339
+
340
+ def wait_for_threads(threads)
341
+ interrupted = threads.compact.map do |t|
342
+ begin
343
+ t.join
344
+ nil
345
+ rescue Interrupt => e
346
+ e # thread died, do not stop other threads
347
+ end
348
+ end.compact
349
+ raise interrupted.first if interrupted.first
350
+ end
351
+
352
+ def handle_exception(exception, results)
353
+ return nil if [Parallel::Break, Parallel::Kill].include? exception.class
354
+ raise exception if exception
355
+ results
356
+ end
357
+
358
+ # options is either a Integer or a Hash with :count
359
+ def extract_count_from_options(options)
360
+ if options.is_a?(Hash)
361
+ count = options[:count]
362
+ else
363
+ count = options
364
+ options = {}
365
+ end
366
+ [count, options]
367
+ end
368
+
369
+ # kill all these pids or threads if user presses Ctrl+c
370
+ def kill_on_ctrl_c(things)
371
+ @to_be_killed ||= []
372
+ old_interrupt = nil
373
+
374
+ if @to_be_killed.empty?
375
+ old_interrupt = trap_interrupt do
376
+ $stderr.puts 'Parallel execution interrupted, exiting ...'
377
+ @to_be_killed.flatten.compact.each { |thing| kill_that_thing!(thing) }
378
+ end
379
+ end
380
+
381
+ @to_be_killed << things
382
+
383
+ yield
384
+ ensure
385
+ @to_be_killed.pop # free threads for GC and do not kill pids that could be used for new processes
386
+ restore_interrupt(old_interrupt) if @to_be_killed.empty?
387
+ end
388
+
389
+ def trap_interrupt
390
+ old = Signal.trap INTERRUPT_SIGNAL, 'IGNORE'
391
+
392
+ Signal.trap INTERRUPT_SIGNAL do
393
+ yield
394
+ if old == "DEFAULT"
395
+ raise Interrupt
396
+ else
397
+ old.call
398
+ end
399
+ end
400
+
401
+ old
402
+ end
403
+
404
+ def restore_interrupt(old)
405
+ Signal.trap INTERRUPT_SIGNAL, old
406
+ end
407
+
408
+ def kill_that_thing!(thing)
409
+ if thing.is_a?(Thread)
410
+ thing.kill
411
+ else
412
+ begin
413
+ Process.kill(:KILL, thing)
414
+ rescue Errno::ESRCH
415
+ # some linux systems already automatically killed the children at this point
416
+ # so we just ignore them not being there
417
+ end
418
+ end
419
+ end
420
+
421
+ def call_with_index(item, index, options, &block)
422
+ args = [item]
423
+ args << index if options[:with_index]
424
+ if options[:return_results]
425
+ block.call(*args)
426
+ else
427
+ block.call(*args)
428
+ nil # avoid GC overhead of passing large results around
429
+ end
430
+ end
431
+
432
+ def with_instrumentation(item, index, options)
433
+ on_start = options[:start]
434
+ on_finish = options[:finish]
435
+ options[:mutex].synchronize { on_start.call(item, index) } if on_start
436
+ result = yield
437
+ result unless options[:preserve_results] == false
438
+ ensure
439
+ options[:mutex].synchronize { on_finish.call(item, index, result) } if on_finish
440
+ end
441
+ end
442
+ end
@@ -0,0 +1,85 @@
1
+ module Parallel
2
+ module ProcessorCount
3
+ # Number of processors seen by the OS and used for process scheduling.
4
+ #
5
+ # * AIX: /usr/sbin/pmcycles (AIX 5+), /usr/sbin/lsdev
6
+ # * BSD: /sbin/sysctl
7
+ # * Cygwin: /proc/cpuinfo
8
+ # * Darwin: /usr/bin/hwprefs, /usr/sbin/sysctl
9
+ # * HP-UX: /usr/sbin/ioscan
10
+ # * IRIX: /usr/sbin/sysconf
11
+ # * Linux: /proc/cpuinfo
12
+ # * Minix 3+: /proc/cpuinfo
13
+ # * Solaris: /usr/sbin/psrinfo
14
+ # * Tru64 UNIX: /usr/sbin/psrinfo
15
+ # * UnixWare: /usr/sbin/psrinfo
16
+ #
17
+ def processor_count
18
+ @processor_count ||= begin
19
+ os_name = RbConfig::CONFIG["target_os"]
20
+ if os_name =~ /mingw|mswin/
21
+ require 'win32ole'
22
+ result = WIN32OLE.connect("winmgmts://").ExecQuery(
23
+ "select NumberOfLogicalProcessors from Win32_Processor")
24
+ result.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+)
25
+ elsif File.readable?("/proc/cpuinfo")
26
+ IO.read("/proc/cpuinfo").scan(/^processor/).size
27
+ elsif File.executable?("/usr/bin/hwprefs")
28
+ IO.popen("/usr/bin/hwprefs thread_count").read.to_i
29
+ elsif File.executable?("/usr/sbin/psrinfo")
30
+ IO.popen("/usr/sbin/psrinfo").read.scan(/^.*on-*line/).size
31
+ elsif File.executable?("/usr/sbin/ioscan")
32
+ IO.popen("/usr/sbin/ioscan -kC processor") do |out|
33
+ out.read.scan(/^.*processor/).size
34
+ end
35
+ elsif File.executable?("/usr/sbin/pmcycles")
36
+ IO.popen("/usr/sbin/pmcycles -m").read.count("\n")
37
+ elsif File.executable?("/usr/sbin/lsdev")
38
+ IO.popen("/usr/sbin/lsdev -Cc processor -S 1").read.count("\n")
39
+ elsif File.executable?("/usr/sbin/sysconf") and os_name =~ /irix/i
40
+ IO.popen("/usr/sbin/sysconf NPROC_ONLN").read.to_i
41
+ elsif File.executable?("/usr/sbin/sysctl")
42
+ IO.popen("/usr/sbin/sysctl -n hw.ncpu").read.to_i
43
+ elsif File.executable?("/sbin/sysctl")
44
+ IO.popen("/sbin/sysctl -n hw.ncpu").read.to_i
45
+ else
46
+ $stderr.puts "Unknown platform: " + RbConfig::CONFIG["target_os"]
47
+ $stderr.puts "Assuming 1 processor."
48
+ 1
49
+ end
50
+ end
51
+ end
52
+
53
+ # Number of physical processor cores on the current system.
54
+ #
55
+ def physical_processor_count
56
+ @physical_processor_count ||= begin
57
+ ppc = case RbConfig::CONFIG["target_os"]
58
+ when /darwin1/
59
+ IO.popen("/usr/sbin/sysctl -n hw.physicalcpu").read.to_i
60
+ when /linux/
61
+ cores = {} # unique physical ID / core ID combinations
62
+ phy = 0
63
+ IO.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln|
64
+ if ln.start_with?("physical")
65
+ phy = ln[/\d+/]
66
+ elsif ln.start_with?("core")
67
+ cid = phy + ":" + ln[/\d+/]
68
+ cores[cid] = true if not cores[cid]
69
+ end
70
+ end
71
+ cores.count
72
+ when /mswin|mingw/
73
+ require 'win32ole'
74
+ result_set = WIN32OLE.connect("winmgmts://").ExecQuery(
75
+ "select NumberOfCores from Win32_Processor")
76
+ result_set.to_enum.collect(&:NumberOfCores).reduce(:+)
77
+ else
78
+ processor_count
79
+ end
80
+ # fall back to logical count if physical info is invalid
81
+ ppc > 0 ? ppc : processor_count
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,32 @@
1
+ module Predicates
2
+
3
+ module Compare
4
+
5
+ def equals(value)
6
+ -> (v, meth=:self, invert=false) do
7
+ invert ? inverted_value(v, value, meth, :==) : regular_value(v, value, meth, :==)
8
+ end
9
+ end
10
+
11
+ alias equal_to equals
12
+
13
+ def greater_than(value)
14
+ -> (v, meth=:self, invert=false) do
15
+ invert ? inverted_value(v, value, meth, :>) : regular_value(v, value, meth, :>)
16
+ end
17
+ end
18
+
19
+ def less_than(value)
20
+ -> (v, meth=:self, invert=false) do
21
+ invert ? inverted_value(v, value, meth, :<) : regular_value(v, value, meth, :<)
22
+ end
23
+ end
24
+
25
+ def matches(regex)
26
+ -> (v, meth=:self, invert=false) do
27
+ invert ? inverted_regex(v, regex, meth) : regular_regex(v, regex, meth)
28
+ end
29
+ end
30
+ end
31
+
32
+ end
@@ -1,17 +1,5 @@
1
1
  module Predicates
2
2
 
3
- module Numbers
4
-
5
- def even
6
- -> (v) { Type.responds(v, :even?); v if v.even? }
7
- end
8
-
9
- def odd
10
- -> (v) { Type.responds(v, :odd?); v if v.odd? }
11
- end
12
-
13
- end
14
-
15
3
  module Conversions
16
4
 
17
5
  def as_string
@@ -31,5 +19,4 @@ module Predicates
31
19
  end
32
20
 
33
21
  end
34
-
35
22
  end
@@ -0,0 +1,25 @@
1
+ module Predicates
2
+
3
+ module Numbers
4
+
5
+ def even
6
+ -> (v, meth=:self, invert=false) do
7
+ invert ? inverted(v, meth, :even?) : regular(v, meth, :even?)
8
+ end
9
+ end
10
+
11
+ def odd
12
+ -> (v, meth=:self, invert=false) do
13
+ invert ? inverted(v, meth, :odd?) : regular(v, meth, :odd?)
14
+ end
15
+ end
16
+ #
17
+ # def between(lower, higher)
18
+ # -> (v, meth=:self, invert=false) do
19
+ # invert ? inverted(v, meth, :between?) : regular(v, meth, :between?)
20
+ # end
21
+ # end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,57 @@
1
+ module Predicates
2
+
3
+ def inverted(v, meth, pred)
4
+ if meth == :self
5
+ Type.responds(v, pred)
6
+ v unless v.send(pred)
7
+ end
8
+ end
9
+
10
+ def regular(v, meth, pred)
11
+ if meth == :self
12
+ Type.responds(v, pred)
13
+ v if v.send(pred)
14
+ else
15
+ r = v.send(meth)
16
+ Type.responds(r, pred)
17
+ v if r.send(pred)
18
+ end
19
+ end
20
+
21
+ def inverted_value(v, value, meth, pred)
22
+ if meth == :self
23
+ Type.responds(v, pred)
24
+ v unless v.send(pred, value)
25
+ end
26
+ end
27
+
28
+ def regular_value(v, value, meth, pred)
29
+ if meth == :self
30
+ Type.responds(v, pred)
31
+ v if v.send(pred, value)
32
+ else
33
+ r = v.send(meth)
34
+ Type.responds(r, pred)
35
+ v if r.send(pred, value)
36
+ end
37
+ end
38
+
39
+ def regular_regex(v, regex, meth)
40
+ if meth == :self
41
+ v if v.to_s.match(regex)
42
+ else
43
+ r = v.send(meth)
44
+ v if r.to_s.match(regex)
45
+ end
46
+ end
47
+
48
+ def inverted_regex(v, regex, meth)
49
+ if meth == :self
50
+ v unless v.to_s.match(regex)
51
+ else
52
+ r = v.send(meth)
53
+ v unless r.to_s.match(regex)
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,34 @@
1
+ module Predicates
2
+
3
+ module Where
4
+ class WherePredicate
5
+
6
+ attr_reader :predicates
7
+
8
+ def initialize
9
+ @predicates = empty
10
+ end
11
+
12
+ def where(predicates)
13
+ @predicates = predicates.is_a?(Pair::Pair) ? @predicates.append(predicates) : @predicates.join(Pair.from_map(predicates))
14
+ self
15
+ end
16
+
17
+ def and(predicates)
18
+ @predicates = predicates.is_a?(Pair::Pair) ? @predicates.append(predicates) : @predicates.join(Pair.from_map(predicates))
19
+ self
20
+ end
21
+
22
+ end
23
+
24
+ def where(predicate_map)
25
+ WherePredicate.new.where(predicate_map)
26
+ end
27
+
28
+ def is(single_predicate)
29
+ pair(:self, single_predicate)
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,13 @@
1
+ class WhereProcessor
2
+ def initialize(value)
3
+ @value = value
4
+ end
5
+
6
+ def apply(predicates, invert=false)
7
+ if invert
8
+ @value if predicates.map { |x| x.value.call(@value, x.key) }.contains?(nil)
9
+ else
10
+ @value unless predicates.map { |x| x.value.call(@value, x.key) }.contains?(nil)
11
+ end
12
+ end
13
+ end