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 +4 -4
- data/Gemfile +5 -7
- data/Gemfile.lock +60 -61
- data/VERSION +1 -1
- data/bixby-common.gemspec +9 -3
- data/lib/bixby-common/util/signal.rb +32 -0
- data/lib/bixby-common/util/thread_dump.rb +73 -0
- data/lib/bixby-common/util/thread_pool/task.rb +15 -0
- data/lib/bixby-common/util/thread_pool/worker.rb +90 -0
- data/lib/bixby-common/util/thread_pool.rb +203 -0
- data/lib/bixby-common/websocket/api_channel.rb +11 -5
- data/lib/bixby-common/websocket/async_response.rb +6 -3
- data/lib/bixby-common.rb +3 -0
- data/test/base.rb +7 -1
- data/test/util/thread_pool_test.rb +80 -0
- data/test/websocket/api_channel_test.rb +13 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01cbdbfa0c13d2e8614a46e771ef0e9e16e21308
|
4
|
+
data.tar.gz: 6457c2b1f50b6569dbcc6cb97d9053e63ec01b80
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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", :
|
15
|
+
gem "jeweler", "~> 2.0", :github => "chetan/jeweler", :branch => "bixby"
|
16
16
|
gem "pry", "~> 0.9"
|
17
17
|
|
18
|
-
gem "test_guard", "~> 0.2", :
|
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 => [:
|
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 => [:
|
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/
|
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:
|
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:
|
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.
|
61
|
-
bixby-auth (0.1.
|
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.
|
67
|
-
coveralls (0.7.
|
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.
|
79
|
-
eventmachine (1.0.
|
80
|
-
faraday (0.9.
|
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.
|
81
|
+
faye-websocket (0.9.2)
|
83
82
|
eventmachine (>= 0.12.0)
|
84
|
-
websocket-driver (>= 0.
|
85
|
-
ffi (1.9.
|
86
|
-
ffi (1.9.
|
87
|
-
git (1.2.
|
88
|
-
github_api (0.12.
|
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.
|
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.
|
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.
|
103
|
-
httpi (2.
|
101
|
+
httpclient (2.6.0.1)
|
102
|
+
httpi (2.3.0)
|
104
103
|
rack
|
105
|
-
jruby-openssl (0.9.
|
106
|
-
json (1.8.
|
107
|
-
json (1.8.
|
108
|
-
jwt (1.
|
109
|
-
listen (2.
|
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.
|
121
|
-
mixlib-shellout (
|
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
|
-
|
128
|
-
|
129
|
-
|
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.
|
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.
|
148
|
-
rake (10.
|
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
|
155
|
-
mime-types (>= 1.16
|
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.
|
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.
|
168
|
-
tins (~>
|
169
|
-
thor (0.
|
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 (
|
175
|
-
webmock (1.
|
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.
|
179
|
-
|
180
|
-
|
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.
|
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
|
+
# 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.
|
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 = "
|
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,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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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.
|
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
|
-
|
57
|
-
|
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 <
|
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
|
-
@
|
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.
|
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:
|
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
|