bixby-common 0.5.0 → 0.6.0

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: 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