bosh_common 0.4.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.
- data/README +1 -0
- data/Rakefile +51 -0
- data/lib/common/thread_formatter.rb +51 -0
- data/lib/common/thread_pool.rb +130 -0
- data/lib/common/version.rb +7 -0
- data/spec/Rakefile +87 -0
- data/spec/lib/cloud/spec.rb +8 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/unit/thread_pool_spec.rb +69 -0
- metadata +74 -0
data/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
BOSH common / shared classes
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
$:.unshift(File.expand_path("../../rake", __FILE__))
|
4
|
+
|
5
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __FILE__)
|
6
|
+
|
7
|
+
require "rubygems"
|
8
|
+
require "bundler"
|
9
|
+
Bundler.setup(:default, :test)
|
10
|
+
|
11
|
+
require "rake"
|
12
|
+
begin
|
13
|
+
require "rspec/core/rake_task"
|
14
|
+
rescue LoadError
|
15
|
+
end
|
16
|
+
|
17
|
+
require "bundler_task"
|
18
|
+
require "ci_task"
|
19
|
+
|
20
|
+
gem_helper = Bundler::GemHelper.new(Dir.pwd)
|
21
|
+
|
22
|
+
desc "Build Blobstore Client gem into the pkg directory"
|
23
|
+
task "build" do
|
24
|
+
gem_helper.build_gem
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Build and install Blobstore Client into system gems"
|
28
|
+
task "install" do
|
29
|
+
sh("bundle install --local --without test development")
|
30
|
+
gem_helper.install_gem
|
31
|
+
end
|
32
|
+
|
33
|
+
BundlerTask.new
|
34
|
+
|
35
|
+
if defined?(RSpec)
|
36
|
+
namespace :spec do
|
37
|
+
desc "Run Unit Tests"
|
38
|
+
rspec_task = RSpec::Core::RakeTask.new(:unit) do |t|
|
39
|
+
t.gemfile = "Gemfile"
|
40
|
+
t.pattern = "spec/unit/**/*_spec.rb"
|
41
|
+
t.rspec_opts = %w(--format progress --colour)
|
42
|
+
end
|
43
|
+
|
44
|
+
CiTask.new do |task|
|
45
|
+
task.rspec_task = rspec_task
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Install dependencies and run tests"
|
50
|
+
task :spec => %w(bundler:install:test spec:unit)
|
51
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
class ThreadFormatter
|
4
|
+
FORMAT = "%s, [%s#%d] [%s] %5s -- %s: %s\n"
|
5
|
+
|
6
|
+
attr_accessor :datetime_format
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@datetime_format = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(severity, time, progname, msg)
|
13
|
+
thread_name = Thread.current[:name] || "0x#{Thread.current.object_id.to_s(16)}"
|
14
|
+
FORMAT % [severity[0..0], format_datetime(time), $$, thread_name, severity, progname,
|
15
|
+
msg2str(msg)]
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def format_datetime(time)
|
21
|
+
if @datetime_format.nil?
|
22
|
+
time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d " % time.usec
|
23
|
+
else
|
24
|
+
time.strftime(@datetime_format)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def msg2str(msg)
|
29
|
+
case msg
|
30
|
+
when ::String
|
31
|
+
msg
|
32
|
+
when ::Exception
|
33
|
+
"#{ msg.message } (#{ msg.class })\n" <<
|
34
|
+
(msg.backtrace || []).join("\n")
|
35
|
+
else
|
36
|
+
msg.inspect
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module Kernel
|
42
|
+
|
43
|
+
def with_thread_name(name)
|
44
|
+
old_name = Thread.current[:name]
|
45
|
+
Thread.current[:name] = name
|
46
|
+
yield
|
47
|
+
ensure
|
48
|
+
Thread.current[:name] = old_name
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
|
5
|
+
module Bosh
|
6
|
+
|
7
|
+
class ThreadPool
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@actions = []
|
11
|
+
@lock = Mutex.new
|
12
|
+
@cv = ConditionVariable.new
|
13
|
+
@max_threads = options[:max_threads] || 1
|
14
|
+
@available_threads = @max_threads
|
15
|
+
@logger = options[:logger]
|
16
|
+
@boom = nil
|
17
|
+
@original_thread = Thread.current
|
18
|
+
@threads = []
|
19
|
+
@state = :open
|
20
|
+
end
|
21
|
+
|
22
|
+
def wrap
|
23
|
+
begin
|
24
|
+
yield self
|
25
|
+
wait
|
26
|
+
ensure
|
27
|
+
shutdown
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def pause
|
32
|
+
@lock.synchronize do
|
33
|
+
@state = :paused
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def resume
|
38
|
+
@lock.synchronize do
|
39
|
+
@state = :open
|
40
|
+
[@available_threads, @actions.size].min.times do
|
41
|
+
@available_threads -= 1
|
42
|
+
create_thread
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def process(&block)
|
48
|
+
@lock.synchronize do
|
49
|
+
@actions << block
|
50
|
+
if @state == :open
|
51
|
+
if @available_threads > 0
|
52
|
+
@logger.debug("Creating new thread")
|
53
|
+
@available_threads -= 1
|
54
|
+
create_thread
|
55
|
+
else
|
56
|
+
@logger.debug("All threads are currently busy, queuing action")
|
57
|
+
end
|
58
|
+
elsif @state == :paused
|
59
|
+
@logger.debug("Pool is paused, queueing action.")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_thread
|
65
|
+
thread = Thread.new do
|
66
|
+
begin
|
67
|
+
loop do
|
68
|
+
action = nil
|
69
|
+
@lock.synchronize do
|
70
|
+
action = @actions.shift unless @boom
|
71
|
+
if action
|
72
|
+
@logger.debug("Found an action that needs to be processed")
|
73
|
+
else
|
74
|
+
@logger.debug("Thread is no longer needed, cleaning up")
|
75
|
+
@available_threads += 1
|
76
|
+
@threads.delete(thread) if @state == :open
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
break unless action
|
81
|
+
|
82
|
+
begin
|
83
|
+
action.call
|
84
|
+
rescue Exception => e
|
85
|
+
raise_worker_exception(e)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
@lock.synchronize { @cv.signal unless working? }
|
90
|
+
end
|
91
|
+
@threads << thread
|
92
|
+
end
|
93
|
+
|
94
|
+
def raise_worker_exception(exception)
|
95
|
+
if exception.respond_to?(:backtrace)
|
96
|
+
@logger.debug("Worker thread raised exception: #{exception} - #{exception.backtrace.join("\n")}")
|
97
|
+
else
|
98
|
+
@logger.debug("Worker thread raised exception: #{exception}")
|
99
|
+
end
|
100
|
+
@lock.synchronize do
|
101
|
+
@boom = exception if @boom.nil?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def working?
|
106
|
+
@boom.nil? && (@available_threads != @max_threads || !@actions.empty?)
|
107
|
+
end
|
108
|
+
|
109
|
+
def wait
|
110
|
+
@logger.debug("Waiting for tasks to complete")
|
111
|
+
@lock.synchronize do
|
112
|
+
@cv.wait(@lock) while working?
|
113
|
+
raise @boom if @boom
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def shutdown
|
118
|
+
return if @state == :closed
|
119
|
+
@logger.debug("Shutting down pool")
|
120
|
+
@lock.synchronize do
|
121
|
+
return if @state == :closed
|
122
|
+
@state = :closed
|
123
|
+
@actions.clear
|
124
|
+
end
|
125
|
+
@threads.each { |t| t.join }
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
data/spec/Rakefile
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
require "rake"
|
3
|
+
|
4
|
+
APP_DIR = File.expand_path(File.join("..", ".."), __FILE__)
|
5
|
+
ENV["BUNDLE_GEMFILE"] ||= File.join(APP_DIR, "Gemfile")
|
6
|
+
require "rubygems"
|
7
|
+
require "bundler"
|
8
|
+
Bundler.setup(:default, :test)
|
9
|
+
|
10
|
+
require "rspec/core/rake_task"
|
11
|
+
require "ci/reporter/rake/rspec"
|
12
|
+
|
13
|
+
desc "Run all examples"
|
14
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
15
|
+
t.pattern = "**/*_spec.rb"
|
16
|
+
t.rspec_opts = %w[--color]
|
17
|
+
end
|
18
|
+
|
19
|
+
task :default => [:spec]
|
20
|
+
|
21
|
+
coverage_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_coverage"))
|
22
|
+
reports_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_reports"))
|
23
|
+
dump_file = File.join(Dir.tmpdir, "bosh-common.rcov")
|
24
|
+
|
25
|
+
ENV["CI_REPORTS"] = reports_dir
|
26
|
+
|
27
|
+
namespace "spec" do
|
28
|
+
gemfile = "../Gemfile"
|
29
|
+
spec_opts = ["--format", "documentation", "--colour"]
|
30
|
+
|
31
|
+
if RUBY_VERSION < "1.9"
|
32
|
+
desc "Run specs for ci"
|
33
|
+
task "ci" => [ "ci:setup:rspec", "spec:rcov", "convert_rcov_to_clover" ]
|
34
|
+
|
35
|
+
desc "Run spec with coverage"
|
36
|
+
RSpec::Core::RakeTask.new("rcov") do |t|
|
37
|
+
FileUtils.rm_rf(dump_file)
|
38
|
+
t.gemfile = gemfile
|
39
|
+
t.pattern = "**/*_spec.rb"
|
40
|
+
t.rspec_opts = ["--format", "progress", "--colour"]
|
41
|
+
t.rcov = true
|
42
|
+
t.rcov_opts = %W{--aggregate #{dump_file} --exclude osx\/objc,gems\/,spec\/,unit\/,features\/ -o "#{coverage_dir}"}
|
43
|
+
end
|
44
|
+
|
45
|
+
task "convert_rcov_to_clover" do |t|
|
46
|
+
ignore_pattern = "spec,[.]bundle,[/]gems[/]"
|
47
|
+
clover_output = File.join(coverage_dir, "clover.xml")
|
48
|
+
|
49
|
+
sh("bundle exec rcov_analyzer #{dump_file} #{ignore_pattern} > #{clover_output}")
|
50
|
+
FileUtils.rm_rf(dump_file)
|
51
|
+
end
|
52
|
+
|
53
|
+
else
|
54
|
+
desc "Run specs for ci"
|
55
|
+
task "ci" => [ "ci:setup:rspec", "spec:rcov" ]
|
56
|
+
|
57
|
+
desc "Run spec with coverage"
|
58
|
+
task :rcov => :cleanup_coverage do
|
59
|
+
require "simplecov"
|
60
|
+
require "simplecov-rcov"
|
61
|
+
require "simplecov-clover"
|
62
|
+
|
63
|
+
class SimpleCov::Formatter::CombinedFormatter
|
64
|
+
def format(result)
|
65
|
+
SimpleCov::Formatter::CloverFormatter.new.format(result)
|
66
|
+
SimpleCov::Formatter::RcovFormatter.new.format(result)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
SimpleCov.formatter = SimpleCov::Formatter::CombinedFormatter
|
71
|
+
SimpleCov.root('..')
|
72
|
+
SimpleCov.coverage_dir('cov')
|
73
|
+
SimpleCov.start do
|
74
|
+
require "rspec/core"
|
75
|
+
add_filter "/spec/"
|
76
|
+
spec_dir = File.expand_path("..", __FILE__)
|
77
|
+
RSpec::Core::Runner.disable_autorun!
|
78
|
+
RSpec::Core::Runner.run([spec_dir], STDERR, STDOUT)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
task "cleanup_coverage" do
|
84
|
+
rm_rf "cov"
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
4
|
+
|
5
|
+
require "common/thread_pool"
|
6
|
+
|
7
|
+
describe Bosh::ThreadPool do
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
@logger = Logger.new(STDOUT)
|
11
|
+
@logger.level = Logger::INFO
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should respect max threads" do
|
15
|
+
max = 0
|
16
|
+
current = 0
|
17
|
+
lock = Mutex.new
|
18
|
+
|
19
|
+
Bosh::ThreadPool.new(:max_threads => 2, :logger => @logger).wrap do |pool|
|
20
|
+
4.times do
|
21
|
+
pool.process do
|
22
|
+
lock.synchronize do
|
23
|
+
current += 1
|
24
|
+
max = current if current > max
|
25
|
+
end
|
26
|
+
sleep(0.050)
|
27
|
+
lock.synchronize do
|
28
|
+
max = current if current > max
|
29
|
+
current -= 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
max.should be <= 2
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should raise exceptions" do
|
38
|
+
lambda {
|
39
|
+
Bosh::ThreadPool.new(:max_threads => 2, :logger => @logger).wrap do |pool|
|
40
|
+
5.times do |index|
|
41
|
+
pool.process do
|
42
|
+
sleep(0.050)
|
43
|
+
raise "bad" if index == 4
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
}.should raise_exception("bad")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should stop processing new work when there was an exception" do
|
51
|
+
max = 0
|
52
|
+
lock = Mutex.new
|
53
|
+
|
54
|
+
lambda {
|
55
|
+
Bosh::ThreadPool.new(:max_threads => 1, :logger => @logger).wrap do |pool|
|
56
|
+
10.times do |index|
|
57
|
+
pool.process do
|
58
|
+
lock.synchronize { max = index if index > max }
|
59
|
+
sleep(0.050)
|
60
|
+
raise "bad" if index == 4
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
}.should raise_exception("bad")
|
65
|
+
|
66
|
+
max.should be == 4
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bosh_common
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- VMware
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-22 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &2156296060 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2156296060
|
25
|
+
description: Bosh common
|
26
|
+
email: support@vmware.com
|
27
|
+
executables: []
|
28
|
+
extensions: []
|
29
|
+
extra_rdoc_files: []
|
30
|
+
files:
|
31
|
+
- README
|
32
|
+
- Rakefile
|
33
|
+
- lib/common/thread_formatter.rb
|
34
|
+
- lib/common/thread_pool.rb
|
35
|
+
- lib/common/version.rb
|
36
|
+
- spec/Rakefile
|
37
|
+
- spec/lib/cloud/spec.rb
|
38
|
+
- spec/spec_helper.rb
|
39
|
+
- spec/unit/thread_pool_spec.rb
|
40
|
+
homepage: http://www.vmware.com
|
41
|
+
licenses: []
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
hash: -472840810854006979
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
hash: -472840810854006979
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.8.10
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: Bosh common
|
70
|
+
test_files:
|
71
|
+
- spec/Rakefile
|
72
|
+
- spec/lib/cloud/spec.rb
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
- spec/unit/thread_pool_spec.rb
|