totally_lazy 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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