thimble 0.1.0 → 0.2.0

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
- SHA1:
3
- metadata.gz: fd59e02a2d7544b9335f96d4021adf65375adb85
4
- data.tar.gz: baa7be7eb0be6c48846afc2d7e8c2a65c5b208ab
2
+ SHA256:
3
+ metadata.gz: 1fbffaeb62da2fbbb2c0cfa474aa2411b0bc32e32736af38f20e83890921c3af
4
+ data.tar.gz: 78ce3f45be02b118af41229ecd82ede236e3b8817cb9f7dfe742aad6171a560f
5
5
  SHA512:
6
- metadata.gz: 458735d7ec051f92d76c9537799cd20bf122308c97393771eaaa86023faf59181351deedb0ae3ec052f39d430cc4618551b2471375d05a7a10cb0132cc3fe9c9
7
- data.tar.gz: ec3c6cabbbf5471278e65dbd724349eb9e08100d121526c349d673a61744470d93e4b7bc9498360ba3d1f3f8ac727df9c78b19d7ebeaa2163450ead3dccceea8
6
+ metadata.gz: 8df7d0f0444515437e2d70038d399f85d3732456b299c2ab92f128abc5b7cfbde4f9ec71221c2b1a0c6e76dd9dcfefe91358d27582086ed5135ea9dc4974062a
7
+ data.tar.gz: 61f615dd222a89da1624f96ea35ca308db5fd2137e59395753e4db2dab6c66917d224667d158525b64574eee18c06156960f99e70b5263dce2746536e257ad56
@@ -1,13 +1,19 @@
1
- Thread.abort_on_exception=true
1
+ # frozen_string_literal: true
2
+
3
+ Thread.abort_on_exception = true
2
4
 
3
5
  module Thimble
4
6
  class Manager
5
7
  attr_reader :max_workers, :batch_size, :queue_size, :worker_type
6
- def initialize(max_workers: 6,batch_size: 1000, queue_size: 1000, worker_type: :fork)
7
- raise ArgumentError.new ("worker type must be either :fork or :thread") unless worker_type == :thread || worker_type == :fork
8
- raise ArgumentError.new ("Your system does not respond to fork please use threads.") unless worker_type == :thread || Process.respond_to?(:fork)
9
- raise ArgumentError.new ("max_workers must be greater than 0") if max_workers < 1
10
- raise ArgumentError.new ("batch size must be greater than 0") if batch_size < 1
8
+
9
+ def initialize(max_workers: 6, batch_size: 1000, queue_size: 1000, worker_type: :fork)
10
+ raise ArgumentError, 'worker type must be either :fork or :thread' unless %i[thread fork].include?(worker_type)
11
+ unless worker_type == :thread || Process.respond_to?(:fork)
12
+ raise ArgumentError, 'Your system does not respond to fork please use threads.'
13
+ end
14
+ raise ArgumentError, 'max_workers must be greater than 0' if max_workers < 1
15
+ raise ArgumentError, 'batch size must be greater than 0' if batch_size < 1
16
+
11
17
  @worker_type = worker_type
12
18
  @max_workers = max_workers
13
19
  @batch_size = batch_size
@@ -16,16 +22,20 @@ module Thimble
16
22
  @current_workers = {}
17
23
  end
18
24
 
25
+ # @return [TrueClass, FalseClass]
19
26
  def worker_available?
20
27
  @current_workers.size < @max_workers
21
28
  end
22
29
 
30
+ # @return [TrueClass, FalseClass]
23
31
  def working?
24
- @current_workers.size > 0
32
+ @current_workers.size.positive?
25
33
  end
26
34
 
35
+ # @param [Object] id
27
36
  def sub_worker(worker, id)
28
- raise "Worker must contain a pid!" if worker.pid.nil?
37
+ raise 'Worker must contain a pid!' if worker.pid.nil?
38
+
29
39
  new_worker = OpenStruct.new
30
40
  new_worker.worker = worker
31
41
  new_worker.id = id
@@ -34,40 +44,44 @@ module Thimble
34
44
  end
35
45
  end
36
46
 
47
+ # @param [Object] worker
37
48
  def rem_worker(worker)
38
49
  @mutex.synchronize do
39
50
  @current_workers.delete(worker.pid)
40
51
  end
41
52
  end
42
53
 
54
+ # @param [Object] id
43
55
  def current_workers(id)
44
56
  @mutex.synchronize do
45
- @current_workers.select { |k,v| v.id == id }
57
+ @current_workers.select { |_k, v| v.id == id }
46
58
  end
47
59
  end
48
60
 
49
- def get_worker (batch)
61
+ # @param [Object] batch
62
+ # @param [Proc] block
63
+ # @return [Object]
64
+ def get_worker(batch, &block)
50
65
  @mutex.synchronize do
51
66
  if @worker_type == :fork
52
- get_fork_worker(batch, &Proc.new)
67
+ get_fork_worker(batch, &block)
53
68
  else
54
- get_thread_worker(batch, &Proc.new)
69
+ get_thread_worker(batch, &block)
55
70
  end
56
71
  end
57
72
  end
58
73
 
74
+ # @param [Object] batch
59
75
  def get_fork_worker(batch)
60
76
  rd, wr = IO.pipe
61
77
  tup = OpenStruct.new
62
78
  pid = fork do
63
- Signal.trap("HUP") {exit}
79
+ Signal.trap('HUP') { exit }
64
80
  rd.close
65
81
  t = Marshal.dump(batch.item.map do |item|
66
- begin
67
- yield (item.item)
68
- rescue Exception => e
69
- e
70
- end
82
+ yield item.item
83
+ rescue Exception => e
84
+ e
71
85
  end)
72
86
  wr.write(t)
73
87
  wr.close
@@ -78,6 +92,8 @@ module Thimble
78
92
  tup
79
93
  end
80
94
 
95
+ # @param [Object] batch
96
+ # @param [Proc] block
81
97
  def get_thread_worker(batch)
82
98
  tup = OpenStruct.new
83
99
  tup.pid = Thread.new do
@@ -89,12 +105,14 @@ module Thimble
89
105
  tup
90
106
  end
91
107
 
108
+ # @return [Thimble::Manager]
92
109
  def self.deterministic
93
- self.new(max_workers: 1, batch_size: 1, queue_size: 1)
110
+ new(max_workers: 1, batch_size: 1, queue_size: 1)
94
111
  end
95
112
 
113
+ # @return [Thimble::Manager]
96
114
  def self.small
97
- self.new(max_workers: 1, batch_size: 3, queue_size: 3)
115
+ new(max_workers: 1, batch_size: 3, queue_size: 3)
98
116
  end
99
117
  end
100
118
  end
@@ -1,8 +1,11 @@
1
- require "digest"
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
2
4
  module Thimble
3
5
  class QueueItem
4
6
  attr_reader :id, :item
5
- def initialize(item, name= "Item")
7
+
8
+ def initialize(item, name = 'Item')
6
9
  @id = Digest::SHA256.digest(rand(10**100).to_s + Time.now.to_i.to_s)
7
10
  @item = item
8
11
  @name = name
@@ -11,6 +14,5 @@ module Thimble
11
14
  def to_s
12
15
  "#{@name}: #{@item} ID: #{@id}"
13
16
  end
14
-
15
17
  end
16
- end
18
+ end
@@ -1,41 +1,48 @@
1
+ # frozen_string_literal: true
1
2
 
2
- require_relative 'Manager'
3
- require_relative 'ThimbleQueue'
4
- require_relative 'QueueItem'
3
+ require_relative 'manager'
4
+ require_relative 'thimble_queue'
5
+ require_relative 'queue_item'
5
6
  require 'io/wait'
6
7
  require 'ostruct'
7
8
 
8
9
  module Thimble
9
-
10
10
  class Thimble < ThimbleQueue
11
- def initialize(array, manager = Manager.new, result = nil, name = "Main")
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
11
+ def initialize(array, manager = Manager.new, result = nil, name = 'Main')
12
+ raise ArgumentError, 'You need to pass a manager to Thimble!' unless manager.instance_of?(Manager)
13
+
14
+ unless array.respond_to? :each
15
+ raise ArgumentError,
16
+ 'There needs to be an iterable object passed to Thimble to start.'
17
+ end
18
+
14
19
  @result = if result.nil?
15
- ThimbleQueue.new(array.size, "Result")
16
- else
17
- result
20
+ ThimbleQueue.new(array.size, 'Result')
21
+ else
22
+ result
23
+ end
24
+ unless @result.instance_of?(ThimbleQueue) && !@result.closed?
25
+ raise ArgumentError,
26
+ 'result needs to be an open ThimbleQueue'
18
27
  end
19
- raise ArgumentError.new ("result needs to be an open ThimbleQueue") unless (@result.class == ThimbleQueue && !@result.closed?)
28
+
20
29
  @manager = manager
21
30
  @running = true
22
31
  super(array.size, name)
23
32
  @logger.debug("loading thimble #{name}")
24
- array.each {|item| push(item)}
33
+ array.each { |item| push(item) }
25
34
  @logger.debug("finished loading thimble #{name}")
26
- close()
35
+ close
27
36
  end
28
37
 
29
38
  # This will use the manager and transform your thimble queue.
30
- # requires a block
39
+ # requires a block
31
40
  # @return [ThimbleQueue]
32
- def map
41
+ def map(&block)
33
42
  @logger.debug("starting map in #{@name} with id #{Thread.current.object_id}")
34
43
  @running = true
35
- while @running
36
- manage_workers &Proc.new
37
- end
38
- @result.close()
44
+ manage_workers(&block) while @running
45
+ @result.close
39
46
  @logger.debug("finishing map in #{@name} with id #{Thread.current.object_id}")
40
47
  @result
41
48
  end
@@ -44,46 +51,47 @@ module Thimble
44
51
  # Will return the result instantly, so you can use it for next stage processing.
45
52
  # requires a block
46
53
  # @return [ThimbleQueue]
47
- def map_async
54
+ # @param [Proc] block
55
+ def map_async(&block)
48
56
  @logger.debug("starting async map in #{@name} with id #{Thread.current.object_id}")
49
57
  @logger.debug("queue: #{@queue}")
50
58
  Thimble.async do
51
- map &Proc.new
59
+ map(&block)
52
60
  end
53
61
  @logger.debug("finished async map in #{@name} with id #{Thread.current.object_id}")
54
62
  @result
55
63
  end
56
64
 
57
- # Will perform anything handed to this asynchronously.
65
+ # Will perform anything handed to this asynchronously.
58
66
  # Requires a block
59
67
  # @return [Thread]
60
- def self.async
61
- Thread.new do |e|
62
- yield e
63
- end
68
+ def self.async(&block)
69
+ Thread.new(&block)
64
70
  end
65
71
 
66
72
  private
73
+
67
74
  def get_batch
68
75
  batch = []
69
76
  while batch.size < @manager.batch_size
70
77
  item = self.next
71
78
  if item.nil?
72
- return nil if batch.size == 0
73
- return QueueItem.new(batch, "Batch")
79
+ return nil if batch.size.zero?
80
+
81
+ return QueueItem.new(batch, 'Batch')
74
82
  else
75
83
  batch << item
76
84
  end
77
85
  end
78
- QueueItem.new(batch, "Batch")
86
+ QueueItem.new(batch, 'Batch')
79
87
  end
80
88
 
81
- def manage_workers
82
- @manager.current_workers(@id).each do |pid, pair|
89
+ def manage_workers(&block)
90
+ @manager.current_workers(@id).each do |_pid, pair|
83
91
  get_result(pair.worker)
84
92
  end
85
- while (@manager.worker_available? && batch = get_batch)
86
- @manager.sub_worker( @manager.get_worker(batch, &Proc.new), @id)
93
+ while @manager.worker_available? && batch = get_batch
94
+ @manager.sub_worker(@manager.get_worker(batch, &block), @id)
87
95
  end
88
96
  @running = false if !@manager.working? && !batch
89
97
  end
@@ -95,20 +103,18 @@ module Thimble
95
103
  loadedResult = Marshal.load(piped_result)
96
104
  loadedResult.each { |r| raise r if r.class <= Exception }
97
105
  push_result(loadedResult)
98
- Process.kill("HUP", tuple.pid)
99
- @manager.rem_worker(tuple)
100
- end
101
- else
102
- if tuple.done == true
103
- push_result(tuple.result)
106
+ Process.kill('HUP', tuple.pid)
104
107
  @manager.rem_worker(tuple)
105
108
  end
109
+ elsif tuple.done == true
110
+ push_result(tuple.result)
111
+ @manager.rem_worker(tuple)
106
112
  end
107
113
  end
108
114
 
109
115
  def push_result(result)
110
116
  if result.respond_to? :each
111
- result.each {|r| @result.push(r)}
117
+ result.each { |r| @result.push(r) }
112
118
  else
113
119
  @result.push(result)
114
120
  end
@@ -1,13 +1,16 @@
1
- require 'thread'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'logger'
3
- require_relative 'QueueItem'
4
+ require_relative 'queue_item'
4
5
 
5
6
  module Thimble
7
+ # noinspection RubyTooManyInstanceVariablesInspection
6
8
  class ThimbleQueue
7
- include Enumerable
8
- attr_reader :size
9
9
  def initialize(size, name)
10
- raise ArgumentError.new("make sure there is a size for the queue greater than 1! size received #{size}") unless size >= 1
10
+ unless size >= 1
11
+ raise ArgumentError, "make sure there is a size for the queue greater than 1! size received #{size}"
12
+ end
13
+
11
14
  @id = Digest::SHA256.digest(rand(10**100).to_s + Time.now.to_i.to_s)
12
15
  @name = name
13
16
  @size = size
@@ -17,51 +20,56 @@ module Thimble
17
20
  @close_now = false
18
21
  @empty = ConditionVariable.new
19
22
  @full = ConditionVariable.new
20
- @logger = Logger.new(STDOUT)
23
+ @logger = Logger.new($stdout)
21
24
  @logger.sev_threshold = Logger::UNKNOWN
22
25
  end
23
26
 
24
- def setLogger(level)
27
+ include Enumerable
28
+ attr_reader :size
29
+
30
+ def set_logger(level)
25
31
  @logger.sev_threshold = level
26
32
  end
27
33
 
28
34
  def each
29
- while item = self.next
35
+ while (item = self.next)
30
36
  yield item.item
31
37
  end
32
38
  end
33
39
 
34
40
  # Returns the size of the ThimbleQueue
35
- # @return [Fixnum]
41
+ # @return [Integer]
36
42
  def length
37
43
  size
38
44
  end
39
45
 
40
46
  # Will concatenate an enumerable to the ThimbleQueue
41
- # @param [Enumerable]
42
47
  # @return [ThimbleQueue]
48
+ # @param [Module<Enumerable>] other
43
49
  def +(other)
44
- raise ArgumentError.new("+ requires another Enumerable!") unless other.class < Enumerable
50
+ raise ArgumentError, '+ requires another Enumerable!' unless other.class < Enumerable
51
+
45
52
  merged_thimble = ThimbleQueue.new(length + other.length, @name)
46
- self.each {|item| merged_thimble.push(item)}
47
- other.each {|item| merged_thimble.push(item)}
53
+ each { |item| merged_thimble.push(item) }
54
+ other.each { |item| merged_thimble.push(item) }
48
55
  merged_thimble
49
56
  end
50
57
 
51
58
  # Returns the first item in the queue
52
59
  # @return [Object]
53
60
  def next
54
- @mutex.synchronize do
55
- while !@close_now
61
+ @mutex.synchronize do
62
+ until @close_now
56
63
  a = @queue.shift
57
64
  @logger.debug("#{@name}'s queue shifted to: #{a}")
58
65
  if !a.nil?
59
66
  @full.broadcast
60
67
  @empty.broadcast
61
68
  return a
62
- else
69
+ else
63
70
  @logger.debug("#{@name}'s queue is currently closed?: #{closed?}")
64
71
  return nil if closed?
72
+
65
73
  @empty.wait(@mutex)
66
74
  end
67
75
  end
@@ -69,46 +77,49 @@ module Thimble
69
77
  end
70
78
 
71
79
  # This will push whatever it is handed to the queue
72
- # @param [Object]
73
- def push(x)
74
- raise RuntimeError.new("Queue is closed!") if @closed
75
- @logger.debug("Pushing into #{@name} values: #{x}")
80
+ # @param [Object] input_item
81
+ def push(input_item)
82
+ raise 'Queue is closed!' if @closed
83
+
84
+ @logger.debug("Pushing into #{@name} values: #{input_item}")
76
85
  @mutex.synchronize do
77
- while !offer(x)
86
+ until offer(input_item)
78
87
  @full.wait(@mutex)
79
88
  @logger.debug("#{@name} is waiting on full")
80
89
  end
81
90
  @empty.broadcast
82
91
  end
83
- @logger.debug("Finished pushing int #{@name}: #{x}")
92
+ @logger.debug("Finished pushing int #{@name}: #{input_item}")
84
93
  end
85
94
 
86
95
  # This will flatten any nested arrays out and feed them one at
87
96
  # a time to the queue.
88
- # @param [Object, Enumerable]
89
97
  # @return [nil]
90
- def push_flat(x)
91
- raise RuntimeError.new("Queue is closed!") if @closed
92
- @logger.debug("Pushing flat into #{@name} values: #{x}")
93
- if x.respond_to? :each
94
- x.each {|item| push(item)}
98
+ # @param [Object] input_item
99
+ def push_flat(input_item)
100
+ raise 'Queue is closed!' if @closed
101
+
102
+ @logger.debug("Pushing flat into #{@name} values: #{input_item}")
103
+ if input_item.respond_to? :each
104
+ input_item.each { |item| push(item) }
95
105
  else
96
106
  @mutex.synchronize do
97
- while !offer(x)
107
+ until offer(input_item)
98
108
  @logger.debug("#{@name} is waiting on full")
99
109
  @full.wait(@mutex)
100
110
  end
101
111
  @empty.broadcast
102
112
  end
103
113
  end
104
- @logger.debug("Finished pushing flat into #{@name} values: #{x}")
114
+ @logger.debug("Finished pushing flat into #{@name} values: #{input_item}")
105
115
  end
106
116
 
107
- # Closes the ThibleQueue
117
+ # Closes the ThimbleQueue
108
118
  # @param [TrueClass, FalseClass]
109
119
  # @return [nil]
110
120
  def close(now = false)
111
- raise ArgumentError.new("now must be true or false") unless (now == true || now == false)
121
+ raise ArgumentError, 'now must be true or false' unless [true, false].include?(now)
122
+
112
123
  @logger.debug("#{@name} is closing")
113
124
  @mutex.synchronize do
114
125
  @closed = true
@@ -123,7 +134,7 @@ module Thimble
123
134
  # @return [Array[Object]]
124
135
  def to_a
125
136
  a = []
126
- while item = self.next
137
+ while (item = self.next)
127
138
  a << item.item
128
139
  end
129
140
  a
@@ -136,6 +147,8 @@ module Thimble
136
147
  end
137
148
 
138
149
  private
150
+
151
+ # @param [Object] x
139
152
  def offer(x)
140
153
  if @queue.size < @size
141
154
  @queue << QueueItem.new(x)
@@ -146,4 +159,4 @@ module Thimble
146
159
  end
147
160
  end
148
161
  end
149
- end
162
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thimble
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kovanda
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-11 00:00:00.000000000 Z
11
+ date: 2022-06-29 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Thimble is a ruby gem for parallelism and concurrency. It allows you
14
14
  to decide if you want to use separate processes, or if you want to use threads in
@@ -19,15 +19,15 @@ executables: []
19
19
  extensions: []
20
20
  extra_rdoc_files: []
21
21
  files:
22
- - lib/Manager.rb
23
- - lib/QueueItem.rb
24
- - lib/Thimble.rb
25
- - lib/ThimbleQueue.rb
22
+ - lib/manager.rb
23
+ - lib/queue_item.rb
24
+ - lib/thimble.rb
25
+ - lib/thimble_queue.rb
26
26
  homepage: https://github.com/akovanda/thimble
27
27
  licenses:
28
28
  - MIT
29
29
  metadata: {}
30
- post_install_message:
30
+ post_install_message:
31
31
  rdoc_options: []
32
32
  require_paths:
33
33
  - lib
@@ -42,9 +42,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
42
42
  - !ruby/object:Gem::Version
43
43
  version: '0'
44
44
  requirements: []
45
- rubyforge_project:
46
- rubygems_version: 2.6.10
47
- signing_key:
45
+ rubygems_version: 3.3.7
46
+ signing_key:
48
47
  specification_version: 4
49
48
  summary: Concurrency and Parallelism gem that uses blocks to move data
50
49
  test_files: []