chantier 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 733102f569568fbb2f2953e74298840e6cddd625
4
- data.tar.gz: 0753643bc032abc303a72f212dab29f7691338ec
3
+ metadata.gz: 335d81b6d4f243e4d8516d63f27444fc786f1b43
4
+ data.tar.gz: a4918eb5ca7855e60056abef9441735ee63e474e
5
5
  SHA512:
6
- metadata.gz: 8b34b053dba7b457a0d4561c5fff71f14a8fa7e96314c0c9d209c539f874255da769ecd89b1fd96e4e8d1011c4dce4c4ef9b30ddbdd67f9ce8147e3396b93193
7
- data.tar.gz: dd1101a603f7f83a28af4ebfd74734daadeecadeeef069987ced8947d5a73b5b527fda0a8f5040bd47409ecd39972b5a8d2e6b502f8f8813f942887fb160dff3
6
+ metadata.gz: d1fa0022f0fa6d41f715ba08677cb4a8ad6863b9d2da73ccee86d42612c1693a56c92bf61abe7a427d51a46d13eae10242a5d5bd250ba18b6e46a35efed1854d
7
+ data.tar.gz: b3a59f59fdf5d17bfcc62ee2772f9a15c835dce742e6e65daf1ab28478a45fd2d19dd409ea4582a9f3b951e18f942948e3e27a1ba1e5ce9007b39f91eb18d15d
@@ -2,14 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
+ # stub: chantier 0.0.3 ruby lib
5
6
 
6
7
  Gem::Specification.new do |s|
7
8
  s.name = "chantier"
8
- s.version = "0.0.2"
9
+ s.version = "0.0.3"
9
10
 
10
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib"]
11
13
  s.authors = ["Julik Tarkhanov"]
12
- s.date = "2014-07-15"
14
+ s.date = "2014-08-02"
13
15
  s.description = " Process your jobs in parallel with a simple table of processes or threads "
14
16
  s.email = "me@julik.nl"
15
17
  s.extra_rdoc_files = [
@@ -26,15 +28,16 @@ Gem::Specification.new do |s|
26
28
  "chantier.gemspec",
27
29
  "lib/chantier.rb",
28
30
  "lib/process_pool.rb",
31
+ "lib/process_pool_with_kill.rb",
29
32
  "lib/thread_pool.rb",
30
33
  "spec/process_pool_spec.rb",
34
+ "spec/process_pool_with_kill_spec.rb",
31
35
  "spec/spec_helper.rb",
32
36
  "spec/thread_pool_spec.rb"
33
37
  ]
34
38
  s.homepage = "http://github.com/julik/chantier"
35
39
  s.licenses = ["MIT"]
36
- s.require_paths = ["lib"]
37
- s.rubygems_version = "2.0.3"
40
+ s.rubygems_version = "2.2.2"
38
41
  s.summary = "Dead-simple worker table based multiprocessing/multithreading"
39
42
 
40
43
  if s.respond_to? :specification_version then
@@ -1,5 +1,6 @@
1
1
  module Chantier
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  require_relative 'process_pool'
4
+ require_relative 'process_pool_with_kill'
4
5
  require_relative 'thread_pool'
5
6
  end
@@ -26,11 +26,6 @@
26
26
  #
27
27
  # Can be rewritten using Threads if operation on JVM/Rubinius will be feasible.
28
28
  class Chantier::ProcessPool
29
- # Kill the spawned processes after at most X seconds
30
- # KILL_AFTER_SECONDS = 60 * 2
31
-
32
- # http://linuxman.wikispaces.com/killing+me+softly
33
- # TERMINATION_SIGNALS = %w( TERM HUP INT QUIT PIPE KILL )
34
29
 
35
30
  # The manager uses loops in a few places. By doing a little sleep()
36
31
  # in those loops we can yield process control back to the OS which brings
@@ -97,6 +92,8 @@ class Chantier::ProcessPool
97
92
  @semaphore.synchronize { @pids[destination_slot_idx] = nil }
98
93
  end
99
94
 
95
+ return task_pid
96
+
100
97
  # Dispatch the killer thread which kicks in after KILL_AFTER_SECONDS.
101
98
  # Note that we do not manage the @pids table here because once the process
102
99
  # gets terminated it will bounce back to the standard wait() above.
@@ -0,0 +1,52 @@
1
+ # Allows you to spin off a pool of subprocesses that is not larger than X, and
2
+ # maintains a pool of those proceses (same as ProcessPool). Will also forcibly quit
3
+ # those processes after a certain period to ensure they do not hang
4
+ #
5
+ # manager = ProcessPoolWithKill.new(slots = 4, kill_after = 5) # seconds
6
+ # jobs_hose.each_job do | job |
7
+ # # this call will block until a slot becomes available
8
+ # manager.fork_task do # this block runs in a subprocess
9
+ # Churner.new(job).churn
10
+ # end
11
+ # manager.still_running? # => most likely "true"
12
+ # end
13
+ #
14
+ # manager.block_until_complete! #=> Will block until all the subprocesses have terminated
15
+ class Chantier::ProcessPoolWithKill < Chantier::ProcessPool
16
+
17
+ # http://linuxman.wikispaces.com/killing+me+softly
18
+ TERMINATION_SIGNALS = %w( TERM HUP INT QUIT PIPE KILL )
19
+
20
+ DEFAULT_KILL_TIMEOUT = 60
21
+ def initialize(num_procs, kill_after_seconds = DEFAULT_KILL_TIMEOUT)
22
+ @kill_after_seconds = kill_after_seconds.to_f
23
+ super(num_procs)
24
+ end
25
+
26
+ # Run the given block in a forked subprocess. This method will block
27
+ # the thread it is called from until a slot in the process table
28
+ # becomes free. Once that happens, the given block will be forked off
29
+ # and the method will return.
30
+ def fork_task(&blk)
31
+ task_pid = super
32
+ Thread.abort_on_exception = true
33
+ # Dispatch the killer thread which kicks in after KILL_AFTER_SECONDS.
34
+ # Note that we do not manage the @pids table here because once the process
35
+ # gets terminated it will bounce back to the standard wait() above.
36
+ Thread.new do
37
+ sleep @kill_after_seconds
38
+ TERMINATION_SIGNALS.each do | sig |
39
+ puts "Dispatching #{sig}"
40
+ begin
41
+ Process.kill(sig, task_pid)
42
+ sleep 1 # Give it some time to react
43
+ rescue Errno::ESRCH
44
+ # It has already quit, nothing to do
45
+ end
46
+ end
47
+ end
48
+
49
+ return task_pid
50
+ end
51
+
52
+ end
@@ -55,10 +55,13 @@ describe Chantier::ProcessPool do
55
55
 
56
56
  it 'processes 1 file' do
57
57
  filename = @files[0]
58
- manager.fork_task do
58
+ pid = manager.fork_task do
59
59
  sleep(0.05 + (rand / 10))
60
60
  File.open(filename, "wb"){|f| f.write("Worker completed") }
61
61
  end
62
+
63
+ expect(pid).to be_kind_of(Fixnum)
64
+
62
65
  manager.block_until_complete!
63
66
 
64
67
  expect(File.read(filename)).to eq('Worker completed')
@@ -0,0 +1,125 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Chantier::ProcessPoolWithKill do
4
+
5
+ before(:each) do
6
+ @files = (0...20).map do
7
+ SecureRandom.hex(12).tap { |filename| FileUtils.touch(filename) }
8
+ end
9
+ end
10
+
11
+ after(:each) do
12
+ @files.map(&File.method(:unlink))
13
+ end
14
+
15
+ context '#map_fork' do
16
+ let(:manager) { described_class.new(5) }
17
+
18
+ it 'processes multiple files' do
19
+
20
+ data_chunks = (0..10).map{|e| Digest::SHA1.hexdigest(e.to_s) }
21
+
22
+ expect(manager).not_to be_still_running
23
+
24
+ manager.map_fork(@files) do | filename |
25
+ sleep(0.05 + (rand / 10))
26
+ File.open(filename, "wb"){|f| f.write("Worker completed for #{filename}") }
27
+ end
28
+
29
+ expect(manager).not_to be_still_running
30
+
31
+ @files.each do | filename |
32
+ expect(File.read(filename)).to eq("Worker completed for #{filename}")
33
+ end
34
+ end
35
+ end
36
+
37
+ context 'with 0 concurrent slots' do
38
+ it 'raises an exception' do
39
+ expect {
40
+ described_class.new(0)
41
+ }.to raise_error(RuntimeError, 'Need at least 1 slot, given 0')
42
+
43
+ expect {
44
+ described_class.new(-1)
45
+ }.to raise_error(RuntimeError, 'Need at least 1 slot, given -1')
46
+ end
47
+ end
48
+
49
+ context 'with 1 slot' do
50
+ let(:manager) { described_class.new(1) }
51
+
52
+ it 'processes 1 file' do
53
+ filename = @files[0]
54
+ pid = manager.fork_task do
55
+ sleep(0.05 + (rand / 10))
56
+ File.open(filename, "wb"){|f| f.write("Worker completed") }
57
+ end
58
+
59
+ expect(pid).to be_kind_of(Fixnum)
60
+
61
+ manager.block_until_complete!
62
+
63
+ expect(File.read(filename)).to eq('Worker completed')
64
+ end
65
+
66
+ it 'processes multiple files' do
67
+ expect(manager).not_to be_still_running
68
+
69
+ @files.each do | filename |
70
+ manager.fork_task do
71
+ sleep(0.05 + (rand / 10))
72
+ File.open(filename, "wb"){|f| f.write("Worker completed for #{filename}") }
73
+ end
74
+ end
75
+
76
+ expect(manager).to be_still_running
77
+
78
+ manager.block_until_complete!
79
+
80
+ @files.each do | filename |
81
+ expect(File.read(filename)).to eq("Worker completed for #{filename}")
82
+ end
83
+ end
84
+
85
+ it 'forcibly quites a process that is hung for too long' do
86
+ manager_with_short_timeout = described_class.new(1, timeout=0.4)
87
+
88
+ filename = SecureRandom.hex(22)
89
+ manager_with_short_timeout.fork_task do
90
+ 10.times do
91
+ sleep 1 # WAY longer than the timeout
92
+ end
93
+ File.open(filename, "wb") {|f| f.write("Should never happen")}
94
+ end
95
+
96
+ manager_with_short_timeout.block_until_complete!
97
+ expect(File.exist?(filename)).to eq(false)
98
+ end
99
+ end
100
+
101
+ context 'with 5 slots' do
102
+ let(:manager) { described_class.new(5) }
103
+
104
+ it 'processes multiple files' do
105
+
106
+ expect(manager).not_to be_still_running
107
+
108
+ @files.each do | filename |
109
+ manager.fork_task do
110
+ sleep(0.05 + (rand / 10))
111
+ File.open(filename, "wb"){|f| f.write("Worker completed for #{filename}") }
112
+ end
113
+ end
114
+
115
+ expect(manager).to be_still_running
116
+
117
+ manager.block_until_complete!
118
+
119
+ @files.each do | filename |
120
+ expect(File.read(filename)).to eq("Worker completed for #{filename}")
121
+ end
122
+ end
123
+ end
124
+ end
125
+
metadata CHANGED
@@ -1,86 +1,86 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chantier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-15 00:00:00.000000000 Z
11
+ date: 2014-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '2.9'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.9'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rdoc
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '3.12'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.12'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '1.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: jeweler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: 2.0.1
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ~>
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 2.0.1
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: simplecov
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '>='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
- description: ' Process your jobs in parallel with a simple table of processes or threads '
83
+ description: " Process your jobs in parallel with a simple table of processes or threads "
84
84
  email: me@julik.nl
85
85
  executables: []
86
86
  extensions: []
@@ -88,8 +88,8 @@ extra_rdoc_files:
88
88
  - LICENSE.txt
89
89
  - README.rdoc
90
90
  files:
91
- - .document
92
- - .rspec
91
+ - ".document"
92
+ - ".rspec"
93
93
  - Gemfile
94
94
  - LICENSE.txt
95
95
  - README.rdoc
@@ -97,8 +97,10 @@ files:
97
97
  - chantier.gemspec
98
98
  - lib/chantier.rb
99
99
  - lib/process_pool.rb
100
+ - lib/process_pool_with_kill.rb
100
101
  - lib/thread_pool.rb
101
102
  - spec/process_pool_spec.rb
103
+ - spec/process_pool_with_kill_spec.rb
102
104
  - spec/spec_helper.rb
103
105
  - spec/thread_pool_spec.rb
104
106
  homepage: http://github.com/julik/chantier
@@ -111,17 +113,17 @@ require_paths:
111
113
  - lib
112
114
  required_ruby_version: !ruby/object:Gem::Requirement
113
115
  requirements:
114
- - - '>='
116
+ - - ">="
115
117
  - !ruby/object:Gem::Version
116
118
  version: '0'
117
119
  required_rubygems_version: !ruby/object:Gem::Requirement
118
120
  requirements:
119
- - - '>='
121
+ - - ">="
120
122
  - !ruby/object:Gem::Version
121
123
  version: '0'
122
124
  requirements: []
123
125
  rubyforge_project:
124
- rubygems_version: 2.0.3
126
+ rubygems_version: 2.2.2
125
127
  signing_key:
126
128
  specification_version: 4
127
129
  summary: Dead-simple worker table based multiprocessing/multithreading