thimble 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8b7df5f35312f4bea62c895b077a13d6e3d080de
4
+ data.tar.gz: c82c709be630358c4c30aa637e2ceac80653dcda
5
+ SHA512:
6
+ metadata.gz: 755797989277b7d74fbdb549ffa0f1fb9037c0394f1da765aa5ae1da1c9b23f7e0b54165457cacb9c0f4d07edd77e6f7614baac50e9815afb84c69e16e363d4d
7
+ data.tar.gz: 2ec821b444f98d3c8cc85b35a3b0e977c8928abc2a531ebd36effc406a4e20963d784bdd74fdca07c30a73c837ea4ea4f4f18f15aed983961f676e1a83dfce09
data/lib/Manager.rb ADDED
@@ -0,0 +1,94 @@
1
+ module Thimble
2
+ class Manager
3
+ attr_reader :max_workers, :batch_size, :queue_size, :worker_type
4
+ def initialize(max_workers: 6,batch_size: 1000, queue_size: 1000, worker_type: :fork)
5
+ raise ArgumentError.new ("worker type must be either :fork or :thread") unless worker_type == :thread || worker_type == :fork
6
+ raise ArgumentError.new ("Your system does not respond to fork please use threads.") unless worker_type == :thread || Process.respond_to?(:fork)
7
+ raise ArgumentError.new ("max_workers must be greater than 0") if max_workers < 1
8
+ raise ArgumentError.new ("batch size must be greater than 0") if batch_size < 1
9
+ @worker_type = worker_type
10
+ @max_workers = max_workers
11
+ @batch_size = batch_size
12
+ @queue_size = queue_size
13
+ @mutex = Mutex.new
14
+ @current_workers = {}
15
+ end
16
+
17
+ def worker_available?
18
+ @current_workers.size < @max_workers
19
+ end
20
+
21
+ def working?
22
+ @current_workers.size > 0
23
+ end
24
+
25
+ def sub_worker(worker, id)
26
+ raise "Worker must contain a pid!" if worker.pid.nil?
27
+ new_worker = OpenStruct.new
28
+ new_worker.worker = worker
29
+ new_worker.id = id
30
+ @mutex.synchronize do
31
+ @current_workers[worker.pid] = new_worker
32
+ end
33
+ end
34
+
35
+ def rem_worker(worker)
36
+ @mutex.synchronize do
37
+ @current_workers.delete(worker.pid)
38
+ end
39
+ end
40
+
41
+ def current_workers(id)
42
+ @mutex.synchronize do
43
+ @current_workers.select { |k,v| v.id == id }
44
+ end
45
+ end
46
+
47
+ def get_worker (batch)
48
+ @mutex.synchronize do
49
+ if @worker_type == :fork
50
+ get_fork_worker(batch, &Proc.new)
51
+ else
52
+ get_thread_worker(batch, &Proc.new)
53
+ end
54
+ end
55
+ end
56
+
57
+ def get_fork_worker(batch)
58
+ rd, wr = IO.pipe
59
+ tup = OpenStruct.new
60
+ pid = fork do
61
+ Signal.trap("HUP") {exit}
62
+ rd.close
63
+ t = Marshal.dump(batch.item.map do |item|
64
+ yield (item.item)
65
+ end)
66
+ wr.write(t)
67
+ wr.close
68
+ end
69
+ wr.close
70
+ tup.pid = pid
71
+ tup.reader = rd
72
+ tup
73
+ end
74
+
75
+ def get_thread_worker(batch)
76
+ tup = OpenStruct.new
77
+ tup.pid = Thread.new do
78
+ tup.result = batch.item.map do |item|
79
+ yield item.item
80
+ end
81
+ tup.done = true
82
+ end
83
+ tup
84
+ end
85
+
86
+ def self.deterministic
87
+ self.new(max_workers: 1, batch_size: 1, queue_size: 1)
88
+ end
89
+
90
+ def self.small
91
+ self.new(max_workers: 1, batch_size: 3, queue_size: 3)
92
+ end
93
+ end
94
+ end
data/lib/QueueItem.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "digest"
2
+ class QueueItem
3
+ attr_reader :id, :item
4
+ def initialize(item, name= "Item")
5
+ @id = Digest::SHA256.digest(rand(10**100).to_s + Time.now.to_i.to_s)
6
+ @item = item
7
+ @name = name
8
+ end
9
+
10
+ def to_s
11
+ "#{@name}: #{@item} \nID: #{@id}"
12
+ end
13
+
14
+ end
data/lib/Thimble.rb ADDED
@@ -0,0 +1,89 @@
1
+
2
+ require_relative 'Manager'
3
+ require_relative 'ThimbleQueue'
4
+ require_relative 'QueueItem'
5
+ require 'io/wait'
6
+ require 'ostruct'
7
+
8
+ module Thimble
9
+
10
+ class Thimble < ThimbleQueue
11
+ def initialize(array, manager = Manager.new)
12
+ raise ArgumentError.new ("You need to pass a manager to Thimble!") unless manager.class == Manager
13
+ raise ArgumentError.new ("There needs to be an iterable object passed to Thimble to start.") unless array.respond_to? :each
14
+ @manager = manager
15
+ @result = ThimbleQueue.new(array.size, "Result")
16
+ @running = true
17
+ super(array.size, "Main")
18
+ array.each {|item| push(item)}
19
+ close()
20
+ end
21
+
22
+ # Takes a block
23
+ def par_map
24
+ @running = true
25
+ while @running
26
+ manage_workers &Proc.new
27
+ end
28
+ @result.close()
29
+ @result
30
+ end
31
+
32
+ # Will perform anything handed to this asynchronously.
33
+ # Requires a block
34
+ def self.a_sync
35
+ Thread.new do |e|
36
+ yield e
37
+ end
38
+ end
39
+
40
+ private
41
+ def get_batch
42
+ batch = []
43
+ while batch.size < @manager.batch_size
44
+ item = take
45
+ if item.nil?
46
+ return nil if batch.size == 0
47
+ return QueueItem.new(batch, "Batch")
48
+ else
49
+ batch << item
50
+ end
51
+ end
52
+ QueueItem.new(batch, "Batch")
53
+ end
54
+
55
+ def manage_workers
56
+ while (@manager.worker_available? && batch = get_batch)
57
+ @manager.sub_worker( @manager.get_worker(batch, &Proc.new), @id)
58
+ end
59
+ @manager.current_workers(@id).each do |pid, pair|
60
+ get_result(pair.worker)
61
+ end
62
+ @running = false if !@manager.working? && !batch
63
+ end
64
+
65
+ def get_result(tuple)
66
+ if @manager.worker_type == :fork
67
+ if tuple.reader.ready?
68
+ piped_result = tuple.reader.read
69
+ push_result(Marshal.load(piped_result))
70
+ Process.kill("HUP", tuple.pid)
71
+ @manager.rem_worker(tuple)
72
+ end
73
+ else
74
+ if tuple.done == true
75
+ push_result(tuple.result)
76
+ @manager.rem_worker(tuple)
77
+ end
78
+ end
79
+ end
80
+
81
+ def push_result(result)
82
+ if result.respond_to? :each
83
+ result.each {|r| @result.push(r)}
84
+ else
85
+ @result.push(result)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,91 @@
1
+ require 'thread'
2
+ require_relative 'QueueItem'
3
+
4
+ module Thimble
5
+ class ThimbleQueue
6
+ attr_reader :size
7
+ def initialize(size, name)
8
+ raise ArgumentError.new("make sure there is a size for the queue greater than 1! size received #{size}") unless size >= 1
9
+ @id = Digest::SHA256.digest(rand(10**100).to_s + Time.now.to_i.to_s)
10
+ @name = name
11
+ @size = size
12
+ @mutex = Mutex.new
13
+ @queue = []
14
+ @closed = false
15
+ @close_now = false
16
+ @empty = ConditionVariable.new
17
+ @full = ConditionVariable.new
18
+ end
19
+
20
+ def take
21
+ @mutex.synchronize do
22
+ while !@close_now
23
+ a = @queue.shift
24
+ if !a.nil?
25
+ @full.broadcast
26
+ return a
27
+ else
28
+ return nil if @closed
29
+ @empty.wait(@mutex)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # This will push whatever it is handed to the queue
36
+ def push(x)
37
+ @mutex.synchronize do
38
+ while !offer(x)
39
+ @full.wait(@mutex)
40
+ end
41
+ @empty.broadcast
42
+ end
43
+ end
44
+
45
+ # This will flatten any nested arrays out and feed them one at
46
+ # a time to the queue.
47
+ def push_flat(x)
48
+ if x.respond_to? :each
49
+ x.each {|item| push(item)}
50
+ else
51
+ @mutex.synchronize do
52
+ while !offer(x)
53
+ @full.wait(@mutex)
54
+ end
55
+ @empty.broadcast
56
+ end
57
+ end
58
+ end
59
+
60
+ def close(now = false)
61
+ @mutex.synchronize do
62
+ @closed = true
63
+ @close_now = true if now
64
+ @full.broadcast
65
+ @empty.broadcast
66
+ end
67
+ end
68
+
69
+ def to_a
70
+ a = []
71
+ while item = take
72
+ a << item.item
73
+ end
74
+ a
75
+ end
76
+
77
+ def closed?
78
+ @close
79
+ end
80
+
81
+ private
82
+ def offer(x)
83
+ if @queue.size < @size
84
+ @queue << QueueItem.new(x)
85
+ true
86
+ else
87
+ false
88
+ end
89
+ end
90
+ end
91
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: thimble
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kovanda
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-15 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Pass a block and get some results
14
+ email: andrew.kovanda@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/Manager.rb
20
+ - lib/QueueItem.rb
21
+ - lib/Thimble.rb
22
+ - lib/ThimbleQueue.rb
23
+ homepage: https://github.com/akovanda/thimble
24
+ licenses:
25
+ - MIT
26
+ metadata: {}
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubyforge_project:
43
+ rubygems_version: 2.4.5
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: Concurrency and Parallelism gem that uses blocks to move data
47
+ test_files: []