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