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 +7 -0
- data/lib/Manager.rb +94 -0
- data/lib/QueueItem.rb +14 -0
- data/lib/Thimble.rb +89 -0
- data/lib/ThimbleQueue.rb +91 -0
- metadata +47 -0
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
|
data/lib/ThimbleQueue.rb
ADDED
@@ -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: []
|