lifeguard 0.0.8

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: 1e58ec1398c4d3ed7295eddc101b7e0782b7e410
4
+ data.tar.gz: a50f5bf81baf9c7b9f61756408dd9ce47affc12b
5
+ SHA512:
6
+ metadata.gz: e2c8b4660b69d933c3edfb118963e08ae94525a01755f134811e669feff118c84ad4549de81759d155a34f64b635df8361ef752fd0781432644f3b138fa5c599
7
+ data.tar.gz: 6b47b9c2b0c397c80819f96b550e79b8938c087293b05f50bf33aa8300ab206cd47bab224b8a34a5771c3c8fea7411c9c56a3bd8e20bdb1749afcc9d4d9b9a09
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .ruby-*
6
+ coverage
7
+ InstalledFiles
8
+ lib/bundler/man
9
+ pkg
10
+ rdoc
11
+ spec/reports
12
+ test/tmp
13
+ test/version_tmp
14
+ tmp
15
+
16
+ Gemfile.lock
17
+
18
+ # YARD artifacts
19
+ .yardoc
20
+ _yardoc
21
+ doc/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --order rand
3
+ --require rspec/pride --format RSpec::Pride
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in lifeguard.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Brian Stien
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,12 @@
1
+ lifeguard
2
+ =========
3
+
4
+ Do you have a threadpool? Do you need someone to watch it? Look no further!
5
+
6
+ #### Attributions
7
+
8
+ This gem is based on the thread pool implementation from [concurrent-ruby 0.5.0](https://github.com/jdantonio/concurrent-ruby/tree/v0.5.0)
9
+
10
+ After version 0.5.0 the author of concurrent-ruby decided to refactor the threadpool
11
+ implementation, this is an attempt improve the existing implementation rather
12
+ that to write something new from scratch.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ desc "Run specs"
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ desc "Run specs (default)"
8
+ task :default => :spec
@@ -0,0 +1,49 @@
1
+ require 'thread'
2
+ require 'lifeguard/threadpool'
3
+
4
+ module Lifeguard
5
+ class InfiniteThreadpool < ::Lifeguard::Threadpool
6
+
7
+ def initialize(opts = {})
8
+ super(opts)
9
+ @queued_jobs = ::Queue.new
10
+ @shutdown = false
11
+ end
12
+
13
+ def async(*args, &block)
14
+ return false if @shutdown
15
+
16
+ job_started = super
17
+
18
+ unless job_started
19
+ @queued_jobs << { :args => args, :block => block }
20
+ end
21
+
22
+ job_started
23
+ end
24
+
25
+ def check_queued_jobs
26
+ if @queued_jobs.size > 0
27
+ queued_job = @queued_jobs.pop
28
+ async(*queued_job[:args], &queued_job[:block])
29
+ end
30
+ end
31
+
32
+ def kill!(*args)
33
+ super
34
+ @shutdown = true
35
+ end
36
+
37
+ def on_thread_exit(thread)
38
+ return_value = super
39
+ check_queued_jobs
40
+ return_value
41
+ end
42
+
43
+ def shutdown(*args)
44
+ @shutdown = true
45
+ super
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,45 @@
1
+ module Lifeguard
2
+ class Reaper
3
+ ##
4
+ # Constructor
5
+ #
6
+ def initialize(threadpool, reaping_interval)
7
+ @threadpool = threadpool
8
+ @reaping_interval = reaping_interval
9
+ @thread = ::Thread.new { self.run! }
10
+ end
11
+
12
+ ##
13
+ # Public Instance Methods
14
+ #
15
+ def alive?
16
+ @thread.alive?
17
+ end
18
+
19
+ def reap!
20
+ @threadpool.prune_busy_threads
21
+ end
22
+
23
+ def run!
24
+ loop do
25
+ sleep(@reaping_interval)
26
+ reap!
27
+ timeout!
28
+ ready_thread_count = @threadpool.pool_size - @threadpool.busy_size
29
+
30
+ if ready_thread_count > 0 && @threadpool.respond_to?(:check_queued_jobs)
31
+ ready_thread_count.times do
32
+ @threadpool.check_queued_jobs
33
+ end
34
+ end
35
+ end
36
+ rescue
37
+ retry
38
+ end
39
+
40
+ def timeout!
41
+ @threadpool.timeout!
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,133 @@
1
+ require 'thread'
2
+
3
+ module Lifeguard
4
+ class Threadpool
5
+ DEFAULT_REAPING_INTERVAL = 5 # in seconds
6
+ DEFAULT_POOL_SIZE = 2
7
+
8
+ attr_accessor :pool_size
9
+
10
+ ##
11
+ # Constructor
12
+ #
13
+ def initialize(opts = {})
14
+ @pool_size = opts[:pool_size] || DEFAULT_POOL_SIZE
15
+
16
+ # Important info about "timeout", it is controlled by the reaper
17
+ # so you're threads won't timeout immediately, they will wait for
18
+ # the reaper to run. Make sure you account for reaper interval
19
+ # in how you want timeout to work, it may be a bit unexpected in
20
+ # it's tardiness of timing out threads
21
+ #
22
+ @timeout = opts[:timeout]
23
+ @mutex = ::Mutex.new
24
+ @busy_threads = []
25
+
26
+ @reaper = ::Lifeguard::Reaper.new(self, opts[:reaping_interval] || DEFAULT_REAPING_INTERVAL)
27
+ end
28
+
29
+ ##
30
+ # Public Instance Methods
31
+ #
32
+ def busy_size
33
+ @busy_threads.size
34
+ end
35
+
36
+ def kill!
37
+ @mutex.synchronize do
38
+ prune_busy_threads_without_mutex
39
+ @busy_threads.each { |busy_thread| busy_thread.kill }
40
+ prune_busy_threads_without_mutex
41
+ end
42
+ end
43
+
44
+ def on_thread_exit(thread)
45
+ @mutex.synchronize do
46
+ @busy_threads.delete(thread)
47
+ end
48
+ end
49
+
50
+ def async(*args, &block)
51
+ queued_the_work = false
52
+
53
+ unless block
54
+ raise "Threadpool#async must be passed a block"
55
+ end
56
+
57
+ @mutex.synchronize do
58
+ prune_busy_threads_without_mutex
59
+
60
+ if busy_size < pool_size
61
+ queued_the_work = true
62
+
63
+ @busy_threads << ::Thread.new(block, args, self) do |callable, call_args, parent|
64
+ begin
65
+ ::Thread.current[:__start_time_in_seconds__] = Time.now.to_i
66
+ ::Thread.current.abort_on_exception = false
67
+ callable.call(*call_args) # should we check the args? pass args?
68
+ ensure
69
+ parent.on_thread_exit(::Thread.current)
70
+ end
71
+ end
72
+ end
73
+
74
+ prune_busy_threads_without_mutex
75
+ queued_the_work
76
+ end
77
+ end
78
+
79
+ def prune_busy_threads
80
+ @mutex.synchronize do
81
+ prune_busy_threads_without_mutex
82
+ end
83
+ end
84
+
85
+ def shutdown(shutdown_timeout = 0)
86
+ @mutex.synchronize do
87
+ prune_busy_threads_without_mutex
88
+
89
+ if @busy_threads.size > 0
90
+ # Cut the shutdown_timeout by 10 and prune while things finish before the kill
91
+ (shutdown_timeout/10).times do
92
+ sleep (shutdown_timeout / 10.0)
93
+ prune_busy_threads_without_mutex
94
+ break if busy_size == 0
95
+ end
96
+
97
+ sleep(shutdown_timeout/10)
98
+ @busy_threads.each { |busy_thread| busy_thread.kill }
99
+ end
100
+
101
+ prune_busy_threads_without_mutex
102
+ end
103
+ end
104
+
105
+ def timeout!
106
+ return unless timeout?
107
+
108
+ @mutex.synchronize do
109
+ @busy_threads.each do |busy_thread|
110
+ if (Time.now.to_i - busy_thread[:__start_time_in_seconds__] > @timeout)
111
+ busy_thread.kill
112
+ end
113
+ end
114
+
115
+ prune_busy_threads_without_mutex
116
+ end
117
+ end
118
+
119
+ def timeout?
120
+ !!@timeout
121
+ end
122
+
123
+ private
124
+
125
+ ##
126
+ # Private Instance Methods
127
+ #
128
+ def prune_busy_threads_without_mutex
129
+ @busy_threads.select!(&:alive?)
130
+ end
131
+
132
+ end
133
+ end
@@ -0,0 +1,3 @@
1
+ module Lifeguard
2
+ VERSION = "0.0.8"
3
+ end
data/lib/lifeguard.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "lifeguard/version"
2
+
3
+ require "lifeguard/infinite_threadpool"
4
+ require "lifeguard/reaper"
5
+ require "lifeguard/threadpool"
6
+
7
+ module Lifeguard
8
+ end
data/lifeguard.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'lifeguard/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "lifeguard"
8
+ spec.version = Lifeguard::VERSION
9
+ spec.authors = ["Brian Stien","Brandon Dewitt"]
10
+ spec.email = ["brianastien@gmail.com", "brandonsdewitt@gmail.com"]
11
+ spec.summary = %q{A Supervised threadpool implementation in ruby.}
12
+ spec.description = %q{Do you have a threadpool? Do you need someone to watch it? Look no further!}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "better_receive"
22
+ spec.add_development_dependency "bundler", "~> 1.5"
23
+ spec.add_development_dependency "pry-nav"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "rspec-pride"
27
+ spec.add_development_dependency "simplecov"
28
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::Lifeguard::Reaper do
4
+ pending
5
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::Lifeguard::Threadpool do
4
+
5
+ subject { described_class.new(:pool_size => 5) }
6
+
7
+ after(:each) do
8
+ subject.kill!
9
+ sleep(0.1)
10
+ end
11
+
12
+ describe "#timeout!" do
13
+ it "doesn't timeout when no timeout set" do
14
+ threadpool = described_class.new()
15
+ threadpool.timeout?.should be_false
16
+ end
17
+
18
+ it "does timeout when timeout set" do
19
+ threadpool = described_class.new(:timeout => 30)
20
+ threadpool.timeout?.should be_true
21
+ end
22
+
23
+ it "uses the reaper to timeout threads that are all wiley" do
24
+ threadpool = described_class.new(:timeout => 1, :reaping_interval => 1)
25
+ threadpool.async do
26
+ sleep(10)
27
+ end
28
+
29
+ threadpool.busy_size.should eq(1)
30
+ sleep(4)
31
+ threadpool.busy_size.should eq(0)
32
+ end
33
+ end
34
+
35
+ describe '#kill!' do
36
+ it 'attempts to kill all in-progress tasks' do
37
+ @expected = false
38
+ subject.async{ sleep(1); @expected = true }
39
+ sleep(0.1)
40
+ subject.kill!
41
+ sleep(1)
42
+ @expected.should be_false
43
+ end
44
+
45
+ it 'rejects all pending tasks' do
46
+ @expected = false
47
+ subject.async{ sleep(0.5) }
48
+ subject.async{ sleep(0.5); @expected = true }
49
+ sleep(0.1)
50
+ subject.kill!
51
+ sleep(1)
52
+ @expected.should be_false
53
+ end
54
+
55
+ it 'kills all threads' do
56
+ before_thread_count = Thread.list.size
57
+ 100.times { subject.async{ sleep(1) } }
58
+ sleep(0.1)
59
+ Thread.list.size.should > before_thread_count
60
+ subject.kill!
61
+ sleep(0.1)
62
+ Thread.list.size.should eq(before_thread_count + 1) # +1 for the reaper
63
+ end
64
+ end
65
+
66
+ describe '#async' do
67
+ it 'raises an exception if no block is given' do
68
+ expect { subject.async }.to raise_error
69
+ end
70
+
71
+ it 'returns true when the block is added to the queue' do
72
+ subject.async{ sleep }.should be_true
73
+ end
74
+
75
+ it 'calls the block with the given arguments' do
76
+ @expected = nil
77
+ subject.async(1, 2, 3) do |a, b, c|
78
+ @expected = a + b + c
79
+ end
80
+ sleep(0.5)
81
+ @expected.should eq 6
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ require 'simplecov'
5
+
6
+ SimpleCov.start do
7
+ project_name 'lifeguard'
8
+ add_filter '/coverage/'
9
+ add_filter '/doc/'
10
+ add_filter '/pkg/'
11
+ add_filter '/spec/'
12
+ add_filter '/tasks/'
13
+ end
14
+
15
+ Bundler.require(:default, :development, :test)
16
+
17
+ require 'lifeguard'
18
+
19
+ # import all the support files
20
+ Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require File.expand_path(f) }
21
+
22
+ RSpec.configure do |config|
23
+ config.after(:each) do
24
+ Thread.list.each do |thread|
25
+ thread.kill unless thread == Thread.current
26
+ end
27
+ end
28
+ end
File without changes
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lifeguard
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.8
5
+ platform: ruby
6
+ authors:
7
+ - Brian Stien
8
+ - Brandon Dewitt
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-11-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: better_receive
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: bundler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.5'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.5'
42
+ - !ruby/object:Gem::Dependency
43
+ name: pry-nav
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rake
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rspec-pride
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: simplecov
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ description: Do you have a threadpool? Do you need someone to watch it? Look no further!
113
+ email:
114
+ - brianastien@gmail.com
115
+ - brandonsdewitt@gmail.com
116
+ executables: []
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - ".gitignore"
121
+ - ".rspec"
122
+ - Gemfile
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - lib/lifeguard.rb
127
+ - lib/lifeguard/infinite_threadpool.rb
128
+ - lib/lifeguard/reaper.rb
129
+ - lib/lifeguard/threadpool.rb
130
+ - lib/lifeguard/version.rb
131
+ - lifeguard.gemspec
132
+ - spec/lifeguard/reaper_spec.rb
133
+ - spec/lifeguard/threadpool_spec.rb
134
+ - spec/spec_helper.rb
135
+ - spec/support/.gitkeep
136
+ homepage: ''
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.4.2
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: A Supervised threadpool implementation in ruby.
160
+ test_files:
161
+ - spec/lifeguard/reaper_spec.rb
162
+ - spec/lifeguard/threadpool_spec.rb
163
+ - spec/spec_helper.rb
164
+ - spec/support/.gitkeep