bixby-common 0.5.0 → 0.6.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
2
  SHA1:
3
- metadata.gz: 53d9095c479685b561505ef01da1e249db993eb3
4
- data.tar.gz: 8626dfecd1da57efafb187bbba29210d98b0c69f
3
+ metadata.gz: 01cbdbfa0c13d2e8614a46e771ef0e9e16e21308
4
+ data.tar.gz: 6457c2b1f50b6569dbcc6cb97d9053e63ec01b80
5
5
  SHA512:
6
- metadata.gz: e1596a2eb6dcdc5bb46f33ef6af1d63bb756de9f282aecaefe3beee59ef47b5be59111838fbfaf3f9aba057d5d8d27b0f85ba72a2571fefc1c0a80929447b06a
7
- data.tar.gz: b103621dcd1b617491b8eafd5f4582c34b6a7a714dcc0b6abf21867a9a7904e3a4fc75e0df792752a32e830af51154ebbe19461da79b933fcdcb21e8601e8836
6
+ metadata.gz: 445c45438b177e91cde8c25b71f70ebc28fcff1fffa290a2247c5db3b952a3a68cb151d697dde0882684f6fe5eaeb943b9d696c44342e3a830dcd9070401b21c
7
+ data.tar.gz: bc88104b4157c8986d64078fa836c5acee5df142c7968474069b77d5e208ea855eed198e0b8ce93b3ea6da89b549f249a72cbba53e69822f3161c41cb15d3c51
data/Gemfile CHANGED
@@ -12,17 +12,17 @@ gem "semver2", "~> 3.3"
12
12
  group :development do
13
13
  gem "yard", "~> 0.8"
14
14
  gem "bundler", "~> 1.1"
15
- gem "jeweler", "~> 2.0", :git => "https://github.com/chetan/jeweler.git", :branch => "bixby"
15
+ gem "jeweler", "~> 2.0", :github => "chetan/jeweler", :branch => "bixby"
16
16
  gem "pry", "~> 0.9"
17
17
 
18
- gem "test_guard", "~> 0.2", :git => "https://github.com/chetan/test_guard.git"
18
+ gem "test_guard", "~> 0.2", :github => "chetan/test_guard"
19
19
  gem 'rb-inotify', "~> 0.9", :require => false
20
20
  gem 'rb-fsevent', "~> 0.9", :require => false
21
21
  gem 'rb-fchange', "~> 0.0", :require => false
22
22
  end
23
23
 
24
24
  group :test do
25
- gem "simplecov", :platforms => [:mri_19, :mri_20, :rbx], :git => "https://github.com/chetan/simplecov.git", :branch => "inline_nocov"
25
+ gem "simplecov", :platforms => [:ruby_19, :ruby_20, :ruby_21, :ruby_22], :github => "chetan/simplecov", :branch => "inline_nocov"
26
26
  gem "easycov", :github => "chetan/easycov"
27
27
  gem "micron", :github => "chetan/micron"
28
28
  gem "coveralls", :require => false
@@ -36,11 +36,9 @@ group :test do
36
36
  # platform specific gemms
37
37
  # not sure we need to include these at all
38
38
  gem "json", :platforms => [:mri, :jruby]
39
- gem "oj", :platforms => [:mri, :rbx]
40
-
41
- gem "httpclient", :platforms => [:jruby]
42
- gem "curb", :platforms => [:mri, :rbx]
39
+ gem "oj", :platforms => [:ruby]
43
40
 
41
+ gem "httpclient", :platforms => [:jruby]
44
42
  gem "jruby-openssl", :platforms => [:jruby]
45
43
  end
46
44
 
data/Gemfile.lock CHANGED
@@ -9,16 +9,7 @@ GIT
9
9
  simplecov-html
10
10
 
11
11
  GIT
12
- remote: git://github.com/chetan/micron.git
13
- revision: 0c1e9c0b9d9e052805f43485fe3454cbd25913c5
14
- specs:
15
- micron (0.5.1)
16
- ansi
17
- easycov
18
- hitimes
19
-
20
- GIT
21
- remote: https://github.com/chetan/jeweler.git
12
+ remote: git://github.com/chetan/jeweler.git
22
13
  revision: b90381a3958daae7f3ce3d8c4d710fe39e72443b
23
14
  branch: bixby
24
15
  specs:
@@ -32,7 +23,16 @@ GIT
32
23
  rake
33
24
 
34
25
  GIT
35
- remote: https://github.com/chetan/simplecov.git
26
+ remote: git://github.com/chetan/micron.git
27
+ revision: 0c1e9c0b9d9e052805f43485fe3454cbd25913c5
28
+ specs:
29
+ micron (0.5.1)
30
+ ansi
31
+ easycov
32
+ hitimes
33
+
34
+ GIT
35
+ remote: git://github.com/chetan/simplecov.git
36
36
  revision: 308449b2193700f7a9a4291821a323110838fbc0
37
37
  branch: inline_nocov
38
38
  specs:
@@ -41,7 +41,7 @@ GIT
41
41
  simplecov-html (~> 0.7.1)
42
42
 
43
43
  GIT
44
- remote: https://github.com/chetan/test_guard.git
44
+ remote: git://github.com/chetan/test_guard.git
45
45
  revision: 178e47e2e57dc83060d6cabc18f206916b9d02f2
46
46
  specs:
47
47
  test_guard (0.2.1)
@@ -57,56 +57,55 @@ GEM
57
57
  specs:
58
58
  addressable (2.3.6)
59
59
  ansi (1.4.3)
60
- awesome_print (1.2.0)
61
- bixby-auth (0.1.0)
60
+ awesome_print (1.6.1)
61
+ bixby-auth (0.1.1)
62
62
  builder (3.2.2)
63
63
  celluloid (0.16.0)
64
64
  timers (~> 4.0.0)
65
65
  coderay (1.1.0)
66
- colorize (0.7.3)
67
- coveralls (0.7.1)
66
+ colorize (0.7.5)
67
+ coveralls (0.7.2)
68
68
  multi_json (~> 1.3)
69
- rest-client
69
+ rest-client (= 1.6.7)
70
70
  simplecov (>= 0.7)
71
- term-ansicolor
72
- thor
71
+ term-ansicolor (= 1.2.2)
72
+ thor (= 0.18.1)
73
73
  crack (0.4.2)
74
74
  safe_yaml (~> 1.0.0)
75
- curb (0.8.6)
76
75
  descendants_tracker (0.0.4)
77
76
  thread_safe (~> 0.3, >= 0.3.1)
78
- eventmachine (1.0.3)
79
- eventmachine (1.0.3-java)
80
- faraday (0.9.0)
77
+ eventmachine (1.0.4)
78
+ eventmachine (1.0.4-java)
79
+ faraday (0.9.1)
81
80
  multipart-post (>= 1.2, < 3)
82
- faye-websocket (0.7.4)
81
+ faye-websocket (0.9.2)
83
82
  eventmachine (>= 0.12.0)
84
- websocket-driver (>= 0.3.1)
85
- ffi (1.9.5)
86
- ffi (1.9.5-java)
87
- git (1.2.8)
88
- github_api (0.12.1)
83
+ websocket-driver (>= 0.5.1)
84
+ ffi (1.9.6)
85
+ ffi (1.9.6-java)
86
+ git (1.2.9.1)
87
+ github_api (0.12.2)
89
88
  addressable (~> 2.3)
90
89
  descendants_tracker (~> 0.0.4)
91
90
  faraday (~> 0.8, < 0.10)
92
- hashie (>= 3.2)
91
+ hashie (>= 3.3)
93
92
  multi_json (>= 1.7.5, < 2.0)
94
93
  nokogiri (~> 1.6.3)
95
94
  oauth2
96
95
  growl (1.0.3)
97
- hashie (3.3.1)
96
+ hashie (3.3.2)
98
97
  highline (1.6.21)
99
98
  hirb (0.7.2)
100
99
  hitimes (1.2.2)
101
100
  hitimes (1.2.2-java)
102
- httpclient (2.4.0)
103
- httpi (2.2.7)
101
+ httpclient (2.6.0.1)
102
+ httpi (2.3.0)
104
103
  rack
105
- jruby-openssl (0.9.5-java)
106
- json (1.8.1)
107
- json (1.8.1-java)
108
- jwt (1.0.0)
109
- listen (2.7.11)
104
+ jruby-openssl (0.9.6-java)
105
+ json (1.8.2)
106
+ json (1.8.2-java)
107
+ jwt (1.2.0)
108
+ listen (2.8.5)
110
109
  celluloid (>= 0.15.2)
111
110
  rb-fsevent (>= 0.9.3)
112
111
  rb-inotify (>= 0.9)
@@ -116,25 +115,24 @@ GEM
116
115
  multi_json (>= 1.8.4)
117
116
  metaclass (0.0.4)
118
117
  method_source (0.8.2)
119
- mime-types (2.3)
120
- mini_portile (0.6.0)
121
- mixlib-shellout (1.4.0)
118
+ mime-types (2.4.3)
119
+ mini_portile (0.6.2)
120
+ mixlib-shellout (2.0.1)
122
121
  mocha (1.1.0)
123
122
  metaclass (~> 0.0.1)
124
123
  multi_json (1.10.1)
125
124
  multi_xml (0.5.5)
126
125
  multipart-post (2.0.0)
127
- netrc (0.7.7)
128
- nokogiri (1.6.3.1)
129
- mini_portile (= 0.6.0)
130
- nokogiri (1.6.3.1-java)
126
+ nokogiri (1.6.5)
127
+ mini_portile (~> 0.6.0)
128
+ nokogiri (1.6.5-java)
131
129
  oauth2 (1.0.0)
132
130
  faraday (>= 0.8, < 0.10)
133
131
  jwt (~> 1.0)
134
132
  multi_json (~> 1.3)
135
133
  multi_xml (~> 0.5)
136
134
  rack (~> 1.2)
137
- oj (2.10.2)
135
+ oj (2.11.2)
138
136
  pry (0.10.1)
139
137
  coderay (~> 1.1.0)
140
138
  method_source (~> 0.8.1)
@@ -144,18 +142,17 @@ GEM
144
142
  method_source (~> 0.8.1)
145
143
  slop (~> 3.4)
146
144
  spoon (~> 0.0)
147
- rack (1.5.2)
148
- rake (10.3.2)
145
+ rack (1.6.0)
146
+ rake (10.4.2)
149
147
  rb-fchange (0.0.6)
150
148
  ffi
151
149
  rb-fsevent (0.9.4)
152
150
  rb-inotify (0.9.5)
153
151
  ffi (>= 0.5.0)
154
- rest-client (1.7.2)
155
- mime-types (>= 1.16, < 3.0)
156
- netrc (~> 0.7)
152
+ rest-client (1.6.7)
153
+ mime-types (>= 1.16)
157
154
  safe_yaml (1.0.4)
158
- semver2 (3.4.0)
155
+ semver2 (3.4.1)
159
156
  simplecov-console (0.2.0)
160
157
  colorize
161
158
  hirb
@@ -164,20 +161,23 @@ GEM
164
161
  slop (3.6.0)
165
162
  spoon (0.0.4)
166
163
  ffi
167
- term-ansicolor (1.3.0)
168
- tins (~> 1.0)
169
- thor (0.19.1)
164
+ term-ansicolor (1.2.2)
165
+ tins (~> 0.8)
166
+ thor (0.18.1)
170
167
  thread_safe (0.3.4)
171
168
  thread_safe (0.3.4-java)
172
169
  timers (4.0.1)
173
170
  hitimes
174
- tins (1.3.3)
175
- webmock (1.19.0)
171
+ tins (0.13.2)
172
+ webmock (1.20.4)
176
173
  addressable (>= 2.3.6)
177
174
  crack (>= 0.3.2)
178
- websocket-driver (0.3.4)
179
- websocket-driver (0.3.4-java)
180
- yard (0.8.7.4)
175
+ websocket-driver (0.5.1)
176
+ websocket-extensions (>= 0.1.0)
177
+ websocket-driver (0.5.1-java)
178
+ websocket-extensions (>= 0.1.0)
179
+ websocket-extensions (0.1.1)
180
+ yard (0.8.7.6)
181
181
 
182
182
  PLATFORMS
183
183
  java
@@ -187,7 +187,6 @@ DEPENDENCIES
187
187
  bixby-auth (~> 0.1)
188
188
  bundler (~> 1.1)
189
189
  coveralls
190
- curb
191
190
  easycov!
192
191
  faye-websocket (~> 0.7)
193
192
  httpclient
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.6.0
data/bixby-common.gemspec CHANGED
@@ -2,16 +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: bixby-common 0.5.0 ruby lib
5
+ # stub: bixby-common 0.6.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "bixby-common"
9
- s.version = "0.5.0"
9
+ s.version = "0.6.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Chetan Sarva"]
14
- s.date = "2014-11-20"
14
+ s.date = "2015-01-16"
15
15
  s.description = "Bixby Common files/libs"
16
16
  s.email = "chetan@pixelcop.net"
17
17
  s.extra_rdoc_files = [
@@ -50,6 +50,11 @@ Gem::Specification.new do |s|
50
50
  "lib/bixby-common/util/log.rb",
51
51
  "lib/bixby-common/util/log/filtering_layout.rb",
52
52
  "lib/bixby-common/util/log/logger.rb",
53
+ "lib/bixby-common/util/signal.rb",
54
+ "lib/bixby-common/util/thread_dump.rb",
55
+ "lib/bixby-common/util/thread_pool.rb",
56
+ "lib/bixby-common/util/thread_pool/task.rb",
57
+ "lib/bixby-common/util/thread_pool/worker.rb",
53
58
  "lib/bixby-common/websocket/api_channel.rb",
54
59
  "lib/bixby-common/websocket/async_response.rb",
55
60
  "lib/bixby-common/websocket/message.rb",
@@ -72,6 +77,7 @@ Gem::Specification.new do |s|
72
77
  "test/util/http_client_test.rb",
73
78
  "test/util/jsonify_test.rb",
74
79
  "test/util/log_test.rb",
80
+ "test/util/thread_pool_test.rb",
75
81
  "test/websocket/api_channel_test.rb",
76
82
  "test/websocket/async_response_test.rb",
77
83
  "test/websocket/request_test.rb",
@@ -0,0 +1,32 @@
1
+
2
+ module Bixby
3
+ module Signal
4
+
5
+ # Helper for trapping signals and handling them via a dedicated thread
6
+ #
7
+ # @param [String] *signals Signals to trap either as a space-separate string or array
8
+ # @param [Block] block Callback when signal is trapped
9
+ #
10
+ # @return [Thread]
11
+ def self.trap(*signals, &block)
12
+ sigs = signals.flatten.map{ |s| s.split(/[\s,]/) }.flatten.sort.uniq
13
+
14
+ trap_r, trap_w = IO.pipe
15
+ sigs.each do |sig|
16
+ Kernel.trap(sig) do
17
+ trap_w.puts(sig)
18
+ end
19
+ end
20
+
21
+ # handle signals from a dedicated thread
22
+ Thread.new do
23
+ while true
24
+ sig = trap_r.readline.strip
25
+ block.call(sig)
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,73 @@
1
+
2
+ module Bixby
3
+ module ThreadDump
4
+
5
+ class LoggerIO
6
+ def initialize(logger)
7
+ @logger = logger
8
+ end
9
+ def puts(str="")
10
+ @logger.warn(str)
11
+ end
12
+ end
13
+
14
+ class << self
15
+
16
+ # Prints a thread dump on ALRM signal
17
+ # kill -ALRM <pid>
18
+ def trap!
19
+ t = Bixby::Signal.trap("SIGALRM") do
20
+ write(LoggerIO.new(Logging.logger[ThreadDump]))
21
+ end
22
+ t[:_name] = "dumper [ignore me]"
23
+
24
+ Logging.logger[ThreadDump].info "Trapping SIGALRM: kill -ALRM #{Process.pid}"
25
+ end
26
+
27
+ # Write thread dump to the given IO-like handle (must respond to #puts)
28
+ #
29
+ # @param [IO] io
30
+ def write(io=STDERR)
31
+ out = []
32
+ out << "=== thread dump for #{$0} (pid=#{Process.pid}): #{Time.now} ==="
33
+ out << ""
34
+
35
+ Thread.list.each do |thread|
36
+
37
+ # compute thread name, filling in some common libraries
38
+ n = thread.inspect
39
+ if thread.key?(:_name) then
40
+ n += " #{thread[:_name]}"
41
+ else
42
+ t = thread.backtrace.first.strip
43
+ if t =~ %r{rack/handler/puma.rb:.*`join'} then
44
+ n += " puma daemon thread"
45
+ elsif t =~ %r{puma/reactor.rb:.*`select'} then
46
+ n += " puma runloop"
47
+ elsif t =~ %r{puma/server.rb:.*`select'} then
48
+ n += " puma i/o runloop"
49
+ elsif t =~ %r{puma/thread_pool.rb:.*`sleep'} then
50
+ n += " puma threadpool cleanup"
51
+ elsif t =~ %r{lib/eventmachine.rb:.*`run_machine'} then
52
+ n += " EventMachine runloop"
53
+ elsif t =~ %r{celluloid/mailbox.rb:.*`sleep'} then
54
+ n += " celluloid actor thread"
55
+ elsif t =~ %r{bixby-common/util/thread_pool/worker.rb:.*} then
56
+ state = (t =~ /`pop'/ ? "idle" : "busy")
57
+ n += " Bixby::ThreadPool::Worker thread, #{state}"
58
+ end
59
+ end
60
+
61
+ out << n
62
+ out << " " + thread.backtrace.join("\n \\_ ")
63
+ out << "-"
64
+ out << ""
65
+ end
66
+ out << "=== end thread dump ==="
67
+
68
+ io.puts out.join("\n")
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,15 @@
1
+
2
+ module Bixby
3
+ class ThreadPool
4
+ class Task
5
+
6
+ attr_accessor :command, :block
7
+
8
+ def initialize(command, block=nil)
9
+ @command = command
10
+ @block = block
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,90 @@
1
+
2
+ require "timeout"
3
+
4
+ module Bixby
5
+ class ThreadPool
6
+ class Worker
7
+
8
+ include Bixby::Log
9
+
10
+ attr_reader :thread
11
+
12
+ def initialize(queue, idle_timeout, exit_handler)
13
+ @input_queue = queue
14
+ @idle_timeout = idle_timeout
15
+ @exit_handler = exit_handler
16
+ @running = true
17
+ @working = false
18
+ @thread = Thread.new { start_run_loop }
19
+ end
20
+
21
+ def join(max_wait = nil)
22
+ raise "Worker can't join itself." if @thread == Thread.current
23
+
24
+ return true if @thread.nil? || !@thread.join(max_wait).nil?
25
+
26
+ @thread.kill and return false
27
+ end
28
+
29
+ def alive?
30
+ return @running && @thread && @thread.alive?
31
+ end
32
+
33
+ def working?
34
+ @working
35
+ end
36
+
37
+ def inspect
38
+ return "#<#{self.class.to_s}:0x#{(object_id << 1).to_s(16)} #{alive? ? 'alive' : 'dead'}>"
39
+ end
40
+
41
+ def to_s
42
+ inspect
43
+ end
44
+
45
+
46
+ private
47
+
48
+ def start_run_loop
49
+ begin
50
+
51
+ while true
52
+
53
+ task = nil
54
+ Timeout::timeout(@idle_timeout) {
55
+ task = @input_queue.pop
56
+ }
57
+
58
+ case task.command
59
+ when :shutdown
60
+ # logger.debug "#{inspect} thread shutting down"
61
+ task.block.call(self) if task.block
62
+ @running = false
63
+ @thread = nil
64
+ return nil
65
+ when :perform
66
+ @working = true
67
+ # logger.debug "#{inspect} got work"
68
+ task.block.call if task.block
69
+ @working = false
70
+ end
71
+ end
72
+
73
+ rescue Timeout::Error => ex
74
+ if @exit_handler.call(self, :timeout) then
75
+ # true result means we should exit
76
+ # logger.debug "worker exiting due to idle timeout (#{@idle_timeout} sec)"
77
+ @running = false
78
+ return
79
+ end
80
+
81
+ rescue Exception => e
82
+ @running = false
83
+ logger.error "Worker runloop died: #{e.message}\n" + e.backtrace.join("\n")
84
+ @exit_handler.call(self, :exception)
85
+ end
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,203 @@
1
+
2
+ # ThreadPool implementation based on MIT-licensed `workers` gem, v0.2.2, Copyright (c) 2013 Chad Remesch
3
+
4
+ require "bixby-common/util/thread_pool/task"
5
+ require "bixby-common/util/thread_pool/worker"
6
+
7
+ require "thread"
8
+ require "monitor"
9
+
10
+ module Bixby
11
+ class ThreadPool
12
+
13
+ DEFAULT_MIN = 1
14
+ DEFAULT_MAX = 8
15
+ DEFAULT_IDLE_TIMEOUT = 60
16
+
17
+ include Bixby::Log
18
+
19
+ def initialize(options = {})
20
+ @input_queue = Queue.new
21
+ @lock = Monitor.new
22
+ @workers = []
23
+ @min_size = options[:min_size] || DEFAULT_MIN
24
+ @max_size = options[:max_size] || DEFAULT_MAX
25
+ @idle_timeout = options[:idle_timeout] || DEFAULT_IDLE_TIMEOUT
26
+ @size = 0
27
+
28
+ expand(@min_size)
29
+ end
30
+
31
+ def enqueue(command, block=nil)
32
+ @input_queue.push(Task.new(command, block))
33
+ if command == :perform then
34
+ grow_pool
35
+ end
36
+ nil
37
+ end
38
+
39
+ def perform(&block)
40
+ enqueue(:perform, block)
41
+ nil
42
+ end
43
+
44
+ def <<(proc)
45
+ enqueue(:perform, block)
46
+ nil
47
+ end
48
+
49
+ def num_jobs
50
+ @input_queue.size
51
+ end
52
+
53
+ def num_idle
54
+ @size - num_busy
55
+ end
56
+
57
+ def num_busy
58
+ @lock.synchronize do
59
+ return @workers.find_all{ |w| w.working? }.size
60
+ end
61
+ end
62
+ alias_method :num_working, :num_busy
63
+
64
+ def shutdown(&block)
65
+ @lock.synchronize do
66
+ @size.times do
67
+ enqueue(:shutdown, block)
68
+ end
69
+ end
70
+
71
+ nil
72
+ end
73
+
74
+ def join(max_wait = nil)
75
+ results = @workers.map { |w| w.join(max_wait) }
76
+ @workers.clear
77
+ @size = 0
78
+
79
+ return results
80
+ end
81
+
82
+ def dispose
83
+ @lock.synchronize do
84
+ shutdown
85
+ join
86
+ end
87
+
88
+ nil
89
+ end
90
+
91
+ def inspect
92
+ "#<#{self.class.to_s}:0x#{(object_id << 1).to_s(16)} threads=#{size} jobs=#{num_jobs}>"
93
+ end
94
+
95
+ def to_s
96
+ inspect
97
+ end
98
+
99
+ def size
100
+ @lock.synchronize do
101
+ return @size
102
+ end
103
+ end
104
+
105
+ def expand(count)
106
+ @lock.synchronize do
107
+ # logger.debug "expanding by #{count} threads (from #{@size})"
108
+ count.times do
109
+ create_worker
110
+ end
111
+ end
112
+
113
+ nil
114
+ end
115
+
116
+ def contract(count, &block)
117
+ @lock.synchronize do
118
+ raise 'Count is too large.' if count > @size
119
+
120
+ count.times do
121
+ callback = Proc.new do |worker|
122
+ remove_worker(worker)
123
+ block.call if block
124
+ end
125
+
126
+ enqueue(:shutdown, callback)
127
+ @size -= 1
128
+ end
129
+ end
130
+
131
+ nil
132
+ end
133
+
134
+ def resize(new_size)
135
+ @lock.synchronize do
136
+ if new_size > @size
137
+ expand(new_size - @size)
138
+ elsif new_size < @size
139
+ contract(@size - new_size)
140
+ end
141
+ end
142
+
143
+ nil
144
+ end
145
+
146
+ # For debugging
147
+ def summary
148
+ @lock.synchronize do
149
+ puts "jobs: #{@input_queue.size}"
150
+ puts "workers: #{@workers.size}"
151
+ @workers.each do |w|
152
+ puts " " + w.thread.inspect
153
+ end
154
+ end
155
+ end
156
+
157
+
158
+ private
159
+
160
+ def create_worker
161
+ @lock.synchronize do
162
+ # logger.debug "spawning new worker thread"
163
+
164
+ exit_handler = lambda { |worker, reason|
165
+ @lock.synchronize do
166
+ if reason == :exception or (reason == :timeout && @size > @min_size) then
167
+ @size -= 1
168
+ remove_worker(worker)
169
+ return true
170
+ end
171
+ false
172
+ end
173
+ }
174
+
175
+ @workers << Worker.new(@input_queue, @idle_timeout, exit_handler)
176
+ @size += 1
177
+ end
178
+ end
179
+
180
+ # Grow the pool by one if we have more jobs than idle workers
181
+ def grow_pool
182
+ @lock.synchronize do
183
+ # logger.debug { "busy: #{num_working}; idle: #{num_idle}" }
184
+ if @size < @max_size && num_jobs > 0 && num_jobs > num_idle then
185
+ space = @max_size-@size
186
+ jobs = num_jobs-num_idle
187
+ needed = space < jobs ? space : jobs
188
+ expand(needed)
189
+ end
190
+ end
191
+
192
+ nil
193
+ end
194
+
195
+ def remove_worker(worker)
196
+ @lock.synchronize do
197
+ @workers.delete(worker)
198
+ end
199
+
200
+ nil
201
+ end
202
+ end
203
+ end
@@ -15,11 +15,12 @@ module Bixby
15
15
 
16
16
  attr_reader :ws
17
17
 
18
- def initialize(ws, handler)
18
+ def initialize(ws, handler, thread_pool)
19
19
  @ws = ws
20
20
  @handler = handler
21
21
  @responses = {}
22
22
  @connected = false
23
+ @thread_pool = thread_pool
23
24
  end
24
25
 
25
26
  # Perform RPC
@@ -96,10 +97,13 @@ module Bixby
96
97
 
97
98
  if req.type == "rpc" then
98
99
  # Execute the requested method and return the result
99
- json_req = req.json_request
100
- logger.debug { "RPC request\n#{json_req}" }
101
- json_response = @handler.new(req).handle(json_req)
102
- ws.send(Response.new(json_response, req.id).to_wire)
100
+ # Do it asynchronously so as not to hold up the EM-loop while the command is running
101
+ @thread_pool.perform do
102
+ json_req = req.json_request
103
+ logger.debug { "RPC request\n#{json_req}" }
104
+ json_response = @handler.new(req).handle(json_req)
105
+ EM.next_tick { ws.send(Response.new(json_response, req.id).to_wire) }
106
+ end
103
107
 
104
108
  elsif req.type == "rpc_result" then
105
109
  # Pass the result back to the caller
@@ -108,6 +112,8 @@ module Bixby
108
112
  @responses[req.id].response = res
109
113
 
110
114
  elsif req.type == "connect" then
115
+ # Agent request to CONNECT to the manager
116
+ # will only be received by the server-end of the channel
111
117
  logger.debug { "CONNECT request #{req.id}"}
112
118
  ret = @handler.new(req).connect(req.json_request, self)
113
119
  if ret.kind_of? JsonResponse then
@@ -34,7 +34,7 @@ module Bixby
34
34
  @mutex.synchronize {
35
35
  @response = obj
36
36
  @completed = true
37
- @cond.signal
37
+ @cond.broadcast
38
38
  }
39
39
 
40
40
  if not @block.nil? then
@@ -53,8 +53,11 @@ module Bixby
53
53
  #
54
54
  # @return [Object] response data
55
55
  def response
56
- return @response if @completed
57
- @mutex.synchronize { @cond.wait(@mutex) }
56
+ @mutex.synchronize {
57
+ if !@completed then
58
+ @cond.wait(@mutex)
59
+ end
60
+ }
58
61
  return @response
59
62
  end
60
63
 
data/lib/bixby-common.rb CHANGED
@@ -33,5 +33,8 @@ module Bixby
33
33
  autoload :Hashify, "bixby-common/util/hashify"
34
34
  autoload :Log, "bixby-common/util/log"
35
35
  autoload :Debug, "bixby-common/util/debug"
36
+ autoload :Signal, "bixby-common/util/signal"
37
+ autoload :ThreadDump, "bixby-common/util/thread_dump"
38
+ autoload :ThreadPool, "bixby-common/util/thread_pool"
36
39
 
37
40
  end
data/test/base.rb CHANGED
@@ -1,7 +1,13 @@
1
1
 
2
+ require 'micron/test_case/redir_logging'
3
+
2
4
  module Bixby
3
5
  module Test
4
- class TestCase < MiniTest::Unit::TestCase
6
+ class TestCase < Micron::TestCase
7
+
8
+ include Bixby::Log
9
+ include Micron::TestCase::RedirLogging
10
+ self.redir_logger = Logging.logger[Bixby]
5
11
 
6
12
  def setup
7
13
  ENV["BIXBY_HOME"] = File.join(File.expand_path(File.dirname(__FILE__)), "support")
@@ -0,0 +1,80 @@
1
+
2
+ require 'helper'
3
+
4
+ module Bixby
5
+ module Test
6
+ class TestThreadPool < TestCase
7
+
8
+ def setup
9
+ @pool = Bixby::ThreadPool.new(:idle_timeout => 1)
10
+ assert_equal 1, @pool.size
11
+ assert_equal 2, Thread.list.size
12
+ end
13
+
14
+ def teardown
15
+ begin
16
+ puts "tearing down, shutting down pool"
17
+ @pool.shutdown
18
+ @pool.join(5)
19
+ assert_equal 0, @pool.size
20
+ rescue Exception => ex
21
+ end
22
+ end
23
+
24
+ def test_simple_job
25
+ foo = []
26
+ @pool.perform do
27
+ foo << :bar
28
+ end
29
+
30
+ @pool.dispose
31
+ logger.debug "pool disposed, joined"
32
+
33
+ @pool.summary
34
+
35
+ assert_equal 0, @pool.num_jobs, "no jobs left"
36
+ assert_equal 1, foo.size
37
+ assert_equal :bar, foo.first
38
+ end
39
+
40
+ def test_pool_grows_to_max
41
+ foo = []
42
+ 4.times do |i|
43
+ @pool.perform do
44
+ foo << "thread #{i}"
45
+ end
46
+ end
47
+ assert_equal 4, @pool.size
48
+
49
+ 10.times do |i|
50
+ @pool.perform do
51
+ foo << "thread #{i}"
52
+ end
53
+ end
54
+ assert_equal 8, @pool.size
55
+
56
+ @pool.dispose
57
+ assert_equal 14, foo.size
58
+ end
59
+
60
+ def test_pool_shrinks_on_idle
61
+ foo = []
62
+ 2.times do |i|
63
+ @pool.perform do
64
+ foo << "thread #{i}"
65
+ end
66
+ end
67
+ assert_equal 2, @pool.size, "pool should grow to 2"
68
+
69
+ while @pool.num_busy > 0 do
70
+ sleep 0.1
71
+ end
72
+ sleep 1.1
73
+
74
+ logger.debug "shrank yet?!"
75
+ assert_equal 1, @pool.size, "pool should shrink to 1"
76
+ end
77
+
78
+ end # TestThreadPool
79
+ end # Test
80
+ end # Bixby
@@ -22,7 +22,12 @@ class TestAPIChannel < TestCase
22
22
  def setup
23
23
  @em_thread = Thread.new { EM.run{} }
24
24
  @ws = mock("websocket")
25
- @api_chan = Bixby::WebSocket::APIChannel.new(ws, SampleHandler)
25
+ @thread_pool = Bixby::ThreadPool.new
26
+ @api_chan = Bixby::WebSocket::APIChannel.new(ws, SampleHandler, @thread_pool)
27
+ end
28
+
29
+ def teardown
30
+ @thread_pool.dispose
26
31
  end
27
32
 
28
33
  def test_open
@@ -56,6 +61,13 @@ class TestAPIChannel < TestCase
56
61
  !res.nil? && res.json_response.to_wire == json_res.to_wire
57
62
  }
58
63
  api_chan.message(event)
64
+
65
+ # wait for jobs to finish and cleanup
66
+ @thread_pool.dispose
67
+ sleep 0.05
68
+ while !EM.defers_finished? do
69
+ sleep 0.01
70
+ end
59
71
  end
60
72
 
61
73
  def test_message_response
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bixby-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chetan Sarva
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-20 00:00:00.000000000 Z
11
+ date: 2015-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bixby-auth
@@ -245,6 +245,11 @@ files:
245
245
  - lib/bixby-common/util/log.rb
246
246
  - lib/bixby-common/util/log/filtering_layout.rb
247
247
  - lib/bixby-common/util/log/logger.rb
248
+ - lib/bixby-common/util/signal.rb
249
+ - lib/bixby-common/util/thread_dump.rb
250
+ - lib/bixby-common/util/thread_pool.rb
251
+ - lib/bixby-common/util/thread_pool/task.rb
252
+ - lib/bixby-common/util/thread_pool/worker.rb
248
253
  - lib/bixby-common/websocket/api_channel.rb
249
254
  - lib/bixby-common/websocket/async_response.rb
250
255
  - lib/bixby-common/websocket/message.rb
@@ -267,6 +272,7 @@ files:
267
272
  - test/util/http_client_test.rb
268
273
  - test/util/jsonify_test.rb
269
274
  - test/util/log_test.rb
275
+ - test/util/thread_pool_test.rb
270
276
  - test/websocket/api_channel_test.rb
271
277
  - test/websocket/async_response_test.rb
272
278
  - test/websocket/request_test.rb