shooting_star 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +42 -0
- data/Manifest.txt +38 -0
- data/README.txt +80 -0
- data/Rakefile +54 -0
- data/bin/shooting_star +70 -0
- data/ext/asteroid.c +262 -0
- data/ext/asteroid.h +4 -0
- data/ext/extconf.rb +9 -0
- data/lib/form_encoder.rb +23 -0
- data/lib/shooting_star/channel.rb +41 -0
- data/lib/shooting_star/config.rb +19 -0
- data/lib/shooting_star/server.rb +219 -0
- data/lib/shooting_star/shooter.rb +62 -0
- data/lib/shooting_star.rb +142 -0
- data/test/ext/asteroid_test.rb +60 -0
- data/test/lib/shooting_star_test.rb +55 -0
- data/test/lib/test_c10k_problem.c +47 -0
- data/test/test_helper.rb +33 -0
- data/test/test_shooting_star.rb +64 -0
- data/vendor/plugins/meteor_strike/README +50 -0
- data/vendor/plugins/meteor_strike/Rakefile +22 -0
- data/vendor/plugins/meteor_strike/generators/meteor/meteor_generator.rb +39 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/controller.rb +25 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/functional_test.rb +17 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/migration.rb +13 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/model.rb +15 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/unit_test.rb +11 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/view.rhtml +13 -0
- data/vendor/plugins/meteor_strike/init.rb +3 -0
- data/vendor/plugins/meteor_strike/lib/meteor_strike.rb +61 -0
- data/vendor/plugins/meteor_strike/test/meteor_strike_test.rb +15 -0
- metadata +100 -0
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'cgi'
|
3
|
+
require 'md5'
|
4
|
+
require 'set'
|
5
|
+
require 'form_encoder'
|
6
|
+
|
7
|
+
module ShootingStar
|
8
|
+
# The module which will be included by servant who was born in the Asteroid.
|
9
|
+
# This idea is from EventMachine.
|
10
|
+
module Server
|
11
|
+
attr_reader :signature
|
12
|
+
@@servers = {}
|
13
|
+
@@uids = {}
|
14
|
+
@@tags = {}
|
15
|
+
@@executings = {}
|
16
|
+
|
17
|
+
# initialize servant waked up.
|
18
|
+
def post_init
|
19
|
+
@execution = ''
|
20
|
+
@data = ''
|
21
|
+
end
|
22
|
+
|
23
|
+
# receive the data sent from client.
|
24
|
+
def receive_data(data)
|
25
|
+
@data += data
|
26
|
+
response if @data[-4..-1] == "\r\n\r\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
# detect disconnection from the client and clean it up.
|
30
|
+
def unbind
|
31
|
+
@unbound = true
|
32
|
+
if channel = Channel[@channel]
|
33
|
+
channel.leave(self)
|
34
|
+
notify(:event => :leave, :uid => @uid, :tag => @tag)
|
35
|
+
Channel.cleanup(@channel)
|
36
|
+
end
|
37
|
+
@@servers.delete(@signature)
|
38
|
+
@@uids.delete(@signature)
|
39
|
+
@@tags.delete(@signature)
|
40
|
+
@@executings.delete(@signature)
|
41
|
+
log "Disconnected: #{@uid}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# respond to an execution command. it'll be buffered.
|
45
|
+
def respond(id, params)
|
46
|
+
@executing = @@executings[@signature] ||= Hash.new
|
47
|
+
if params[:tag] && !params[:tag].empty? && !@tag.empty?
|
48
|
+
return false if (params[:tag] & @tag).empty?
|
49
|
+
end
|
50
|
+
@executing[id] = params
|
51
|
+
@waiting
|
52
|
+
end
|
53
|
+
|
54
|
+
# perform buffered executions.
|
55
|
+
def commit
|
56
|
+
return false if @unbound
|
57
|
+
@executing.each{|id, params| execute(id, params)}
|
58
|
+
return false if @execution.empty?
|
59
|
+
send_data "HTTP/1.1 200 OK\nContent-Type: text/javascript\n\n"
|
60
|
+
send_data @execution
|
61
|
+
@waiting = nil
|
62
|
+
@execution = ''
|
63
|
+
@executing = Hash.new
|
64
|
+
@@executings.delete(@signature)
|
65
|
+
write_and_close
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
# noticed execution and remove the command from execution buffer.
|
70
|
+
def executed(id)
|
71
|
+
@executing = @@executings[@signature] ||= Hash.new
|
72
|
+
@executing.delete(id)
|
73
|
+
end
|
74
|
+
|
75
|
+
# update current status of servant.
|
76
|
+
def update(uid, tag)
|
77
|
+
if @uid != uid || @tag != tag
|
78
|
+
notify(:event => :leave, :uid => @uid, :tag => @tag)
|
79
|
+
@@uids[@signature] = @uid = uid
|
80
|
+
@@tags[@signature] = @tag = tag
|
81
|
+
notify(:event => :enter, :uid => @uid, :tag => @tag)
|
82
|
+
end
|
83
|
+
log "Update: #{@uid}:#{@tag.join(',')}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def uid; @@uids[@signature] end
|
87
|
+
def tag; @@tags[@signature] end
|
88
|
+
|
89
|
+
# an accessor which maps signatures to servers.
|
90
|
+
def self.[](signature)
|
91
|
+
@@servers[signature]
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
def log(*arg, &block) ShootingStar::log(*arg, &block) end
|
96
|
+
|
97
|
+
# broadcast an event to clients.
|
98
|
+
def notify(params = {})
|
99
|
+
return unless Channel[@channel]
|
100
|
+
event_id = ShootingStar::timestamp
|
101
|
+
log "Event(#{event_id}): #{@channel}:#{params.inspect}"
|
102
|
+
Channel[@channel].transmit("event-#{event_id}", params)
|
103
|
+
end
|
104
|
+
|
105
|
+
# wait for commands or events until they occur. if they're already in
|
106
|
+
# the execution buffer, they'll be flushed and return on the spot.
|
107
|
+
def wait_for
|
108
|
+
log "Wait for: #{@channel}:#{@uid}:#{@tag.join(',')}"
|
109
|
+
if Channel[@channel].join(self)
|
110
|
+
log "Flushed: #{@channel}:#{@uid}:#{@tag.join(',')}"
|
111
|
+
end
|
112
|
+
@waiting = true
|
113
|
+
end
|
114
|
+
|
115
|
+
# clean up channel and it'll be closed if no one's listening.
|
116
|
+
def cleanup(channel)
|
117
|
+
if Channel.cleanup(channel)
|
118
|
+
log "Channel closed: #{@channel}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# give a response to the request or keep them waiting.
|
123
|
+
def response
|
124
|
+
headers = @data.split("\n")
|
125
|
+
head = headers.shift
|
126
|
+
method, path, protocol = head.split(/\s+/)
|
127
|
+
# recognize header
|
128
|
+
hdr = headers.inject({}) do |hash, line|
|
129
|
+
key, value = line.chop.split(/ *?: */, 2)
|
130
|
+
hash[key.downcase] = value if key
|
131
|
+
hash
|
132
|
+
end
|
133
|
+
# recognize parameter
|
134
|
+
@params = Hash.new
|
135
|
+
if @query = path.split('?', 2)[1]
|
136
|
+
if @query = @query.split('#', 2)[0]
|
137
|
+
@query.split('&').each do |item|
|
138
|
+
key, value = item.split('=', 2)
|
139
|
+
@params[key] = CGI.unescape(value) if value && value.length > 0
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
# load or create session informations
|
144
|
+
@signature ||= @params['sig']
|
145
|
+
@channel ||= path[1..-1].split('?', 2)[0]
|
146
|
+
@uid = @@uids[@signature] ||= @params['uid']
|
147
|
+
@tag = @@tags[@signature] ||=
|
148
|
+
(@params['tag'] || '').split(',').map{|i| CGI.unescape(i)}
|
149
|
+
@executing = @@executings[@signature] ||= Hash.new
|
150
|
+
@@servers[@signature] = self
|
151
|
+
# make uncacheable path
|
152
|
+
@timestamp = ShootingStar::timestamp
|
153
|
+
path += (path.index('?') ? '&' : '?') + "timestamp=#{@timestamp}"
|
154
|
+
@query = "channel=#{@channel}&sig=#{@signature}"
|
155
|
+
# prepare channel
|
156
|
+
unless Channel[@channel]
|
157
|
+
Channel.new(@channel)
|
158
|
+
log "Channel opened: #{@channel}"
|
159
|
+
end
|
160
|
+
# process verb
|
161
|
+
if method == 'GET'
|
162
|
+
make_connection(path)
|
163
|
+
notify(:event => :enter, :uid => @uid, :tag => @tag)
|
164
|
+
log "Connected: #{@uid}"
|
165
|
+
else
|
166
|
+
wait_for
|
167
|
+
end
|
168
|
+
rescue
|
169
|
+
log "ERROR: #{$!.message}\n#{@data}"
|
170
|
+
raise
|
171
|
+
ensure
|
172
|
+
@data = ''
|
173
|
+
end
|
174
|
+
|
175
|
+
# add execution line to the buffer.
|
176
|
+
def execute(id, params)
|
177
|
+
@executing[id] = params
|
178
|
+
@query += "&" +FormEncoder.encode(params) if params
|
179
|
+
@execution += <<-"EOH"
|
180
|
+
(function(){
|
181
|
+
var iframe = document.createElement('iframe');
|
182
|
+
var remove = function(){document.body.removeChild(iframe)};
|
183
|
+
iframe.onload = function(){setTimeout(remove, 0)};
|
184
|
+
iframe.src = '#{@params['execute']}/#{id}?#{@query}';
|
185
|
+
document.body.appendChild(iframe);
|
186
|
+
})();
|
187
|
+
EOH
|
188
|
+
end
|
189
|
+
|
190
|
+
# make client connect us.
|
191
|
+
def make_connection(path)
|
192
|
+
send_data "HTTP/1.1 200 OK\nContent-Type: text/html\n\n" +
|
193
|
+
<<-"EOH"
|
194
|
+
<html><head><script type='text/javascript'
|
195
|
+
src='http://alphastars.drecom.jp/javascripts/prototype.js'
|
196
|
+
></script>
|
197
|
+
<script type='text/javascript'>
|
198
|
+
//<![CDATA[
|
199
|
+
var connect = function()
|
200
|
+
{ var request = new Ajax.Request(
|
201
|
+
#{path.to_json}, {method: 'post', evalScript: true,
|
202
|
+
onComplete: function(xhr){
|
203
|
+
setTimeout(connect,
|
204
|
+
xhr.getResponseHeader('Content-Type') ? 0 : 1000);
|
205
|
+
}});
|
206
|
+
var disconnect = function()
|
207
|
+
{ request.options.onComplete = function(){};
|
208
|
+
request.transport.abort();
|
209
|
+
};
|
210
|
+
Event.observe(window, 'unload', disconnect);
|
211
|
+
};
|
212
|
+
setTimeout(connect, 0);
|
213
|
+
//]]>
|
214
|
+
</script></head><body></body></html>
|
215
|
+
EOH
|
216
|
+
write_and_close
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'shooting_star/channel'
|
2
|
+
|
3
|
+
# DRbObject
|
4
|
+
module ShootingStar
|
5
|
+
class Shooter
|
6
|
+
def shoot(channel, id, tag)
|
7
|
+
return unless Channel[channel]
|
8
|
+
log "Shot: #{channel}:#{id}:#{tag.join(',')}"
|
9
|
+
Channel[channel].transmit(id, :tag => tag)
|
10
|
+
end
|
11
|
+
|
12
|
+
def update(sig, uid, tag)
|
13
|
+
::ShootingStar::Server[sig].update(uid, tag || [])
|
14
|
+
end
|
15
|
+
|
16
|
+
def signature; ShootingStar::timestamp end
|
17
|
+
def channels; Channel.list end
|
18
|
+
def sweep; Channel.sweep end
|
19
|
+
|
20
|
+
def count(channel, tag = nil)
|
21
|
+
servers(channel, tag).size
|
22
|
+
end
|
23
|
+
|
24
|
+
def count_with(sig, channel, tag = nil)
|
25
|
+
(signatures(channel, tag) | [sig]).size
|
26
|
+
end
|
27
|
+
|
28
|
+
def listeners(channel, tag = nil)
|
29
|
+
servers(channel, tag).map{|s| s.uid}
|
30
|
+
end
|
31
|
+
|
32
|
+
def listeners_with(uid, sig, channel, tag = nil)
|
33
|
+
servers(channel, tag).inject([uid]) do |result, server|
|
34
|
+
result << server.uid unless server.signature == sig
|
35
|
+
result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def signatures(channel, tag = nil)
|
40
|
+
servers(channel, tag).map{|s| s.signature}
|
41
|
+
end
|
42
|
+
|
43
|
+
def executed(sig, id)
|
44
|
+
::ShootingStar::Server[sig].executed(id)
|
45
|
+
rescue
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def log(*arg, &block) ShootingStar::log(*arg, &block) end
|
50
|
+
|
51
|
+
def servers(channel, tag = nil)
|
52
|
+
return [] unless Channel[channel]
|
53
|
+
result = Channel[channel].waiters.values
|
54
|
+
if tag && !tag.empty?
|
55
|
+
result = result.select do |server|
|
56
|
+
server.tag.empty? || !(server.tag & tag).empty?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
result
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'asteroid'
|
3
|
+
require 'drb/drb'
|
4
|
+
require 'yaml'
|
5
|
+
require 'ftools'
|
6
|
+
require 'shooting_star/config'
|
7
|
+
require 'shooting_star/shooter'
|
8
|
+
|
9
|
+
module ShootingStar
|
10
|
+
VERSION = '1.0.3'
|
11
|
+
CONFIG = Config.new(
|
12
|
+
:config => 'config/shooting_star.yml',
|
13
|
+
:pid_file => 'log/shooting_star.pid',
|
14
|
+
:log_file => 'log/shooting_star.log',
|
15
|
+
:daemon => false,
|
16
|
+
:slient => false)
|
17
|
+
|
18
|
+
def self.configure(options = {})
|
19
|
+
if @log_file
|
20
|
+
@log_file.close
|
21
|
+
@log_file = nil
|
22
|
+
end
|
23
|
+
config_file = options[:config] || CONFIG.config
|
24
|
+
CONFIG.merge!(YAML.load_file(config_file)) if File.exist?(config_file)
|
25
|
+
CONFIG.merge!(options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.shooter
|
29
|
+
@@shooter ||= DRbObject.new_with_uri(CONFIG.shooter.uri)
|
30
|
+
end
|
31
|
+
|
32
|
+
# install config file and plugin
|
33
|
+
def self.init
|
34
|
+
base_dir = CONFIG.directory || `pwd`.chop
|
35
|
+
config_dir = File.join(base_dir, 'config')
|
36
|
+
`mkdir -p #{config_dir}` unless File.exist? config_dir
|
37
|
+
config_file = File.join(config_dir, 'shooting_star.yml')
|
38
|
+
unless File.exist? config_file
|
39
|
+
open(config_file, 'w') do |file|
|
40
|
+
open(__FILE__) do |data|
|
41
|
+
data.gets("__END__\n")
|
42
|
+
file.write data.read
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
log_dir = File.join(base_dir, 'log')
|
47
|
+
`mkdir -p #{log_dir}` unless File.exist? log_dir
|
48
|
+
plugin_dir = File.join(base_dir, 'vendor/plugins')
|
49
|
+
`mkdir -p #{plugin_dir}` unless File.exist? plugin_dir
|
50
|
+
meteor_strike_dir = File.join(plugin_dir, 'meteor_strike')
|
51
|
+
unless File.exist?(meteor_strike_dir)
|
52
|
+
src_dir = File.join(File.dirname(__FILE__),
|
53
|
+
'../vendor/plugins/meteor_strike')
|
54
|
+
`cp -R #{src_dir} #{meteor_strike_dir}`
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.start(&block)
|
59
|
+
if File.exist?(CONFIG.pid_file)
|
60
|
+
log 'shooting_star is already running.'
|
61
|
+
return
|
62
|
+
end
|
63
|
+
if CONFIG.daemon
|
64
|
+
Signal.trap(:ALRM){exit} and sleep if fork
|
65
|
+
Process.setsid
|
66
|
+
end
|
67
|
+
require 'shooting_star/shooter'
|
68
|
+
@@druby = DRb.start_service(CONFIG.shooter.uri, Shooter.new)
|
69
|
+
require 'shooting_star/server'
|
70
|
+
Asteroid::run(CONFIG.server.host, CONFIG.server.port, Server) do
|
71
|
+
File.open(CONFIG.pid_file, "w") do |file|
|
72
|
+
file.puts Process.pid
|
73
|
+
file.puts $command_line
|
74
|
+
end
|
75
|
+
Signal.trap(:INT) do
|
76
|
+
Asteroid::stop
|
77
|
+
@@druby.stop_service
|
78
|
+
log "shooting_star service stopped."
|
79
|
+
File.rm_f(CONFIG.pid_file)
|
80
|
+
end
|
81
|
+
Signal.trap(:EXIT) do
|
82
|
+
File.rm_f(CONFIG.pid_file)
|
83
|
+
@log_file.close if @log_file
|
84
|
+
end
|
85
|
+
log "shooting_star service started."
|
86
|
+
Process.kill(:ALRM, Process.ppid) if CONFIG.daemon
|
87
|
+
block.call if block
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.stop
|
92
|
+
command = ''
|
93
|
+
File.open(CONFIG.pid_file) do |file|
|
94
|
+
Process.kill(:INT, pid = file.gets.to_i)
|
95
|
+
command = file.gets
|
96
|
+
end
|
97
|
+
Thread.pass while File.exist?(CONFIG.pid_file)
|
98
|
+
rescue Errno::ENOENT
|
99
|
+
log "shooting_star service is not running."
|
100
|
+
rescue Errno::ESRCH
|
101
|
+
File.unlink(CONFIG.pid_file)
|
102
|
+
ensure
|
103
|
+
return command
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.restart
|
107
|
+
command = stop
|
108
|
+
Thread.pass while File.exist?(CONFIG.pid_file)
|
109
|
+
system(command)
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.report
|
113
|
+
puts "#{'-' * 79}\nconnections channel name\n#{'-' * 79}"
|
114
|
+
total_connections = 0
|
115
|
+
shooter.channels.each do |channel|
|
116
|
+
count = shooter.count(channel)
|
117
|
+
puts "%11d %s" % [count, channel]
|
118
|
+
puts shooter.listeners(channel).join(',') if CONFIG.with_uid
|
119
|
+
puts shooter.signatures(channel).join(',') if CONFIG.with_sig
|
120
|
+
total_connections += count
|
121
|
+
end
|
122
|
+
puts "#{'-' * 79}\n%11d %s\n#{'-' * 79}" % [total_connections, 'TOTAL']
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.timestamp
|
126
|
+
now = Time.now
|
127
|
+
"%d%06d" % [now.tv_sec, now.tv_usec]
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
def self.log(*arg, &block)
|
132
|
+
puts(*arg, &block) unless CONFIG.silent
|
133
|
+
@log_file ||= open(CONFIG.log_file, 'a')
|
134
|
+
@log_file.puts(*arg, &block) if @log_file
|
135
|
+
end
|
136
|
+
end
|
137
|
+
__END__
|
138
|
+
server:
|
139
|
+
host: 0.0.0.0
|
140
|
+
port: 8080
|
141
|
+
shooter:
|
142
|
+
uri: druby://0.0.0.0:7123
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../test_helper')
|
2
|
+
require 'thread'
|
3
|
+
require 'ext/asteroid'
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
module Server
|
7
|
+
def receive_data(data)
|
8
|
+
send_data data
|
9
|
+
write_and_close
|
10
|
+
end
|
11
|
+
|
12
|
+
def post_init
|
13
|
+
$post_init_test = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def unbind
|
17
|
+
$unbind_test = true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class ShootingStarTest < Test::Unit::TestCase
|
22
|
+
def setup
|
23
|
+
mutex = Mutex.new
|
24
|
+
mutex.lock
|
25
|
+
@thread = Thread.new do
|
26
|
+
Asteroid::run('127.0.0.1', 7124, Server) do
|
27
|
+
mutex.unlock
|
28
|
+
end
|
29
|
+
end
|
30
|
+
mutex.lock
|
31
|
+
end
|
32
|
+
|
33
|
+
def teardown
|
34
|
+
Asteroid::stop
|
35
|
+
@thread.join
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_communication
|
39
|
+
c = TCPSocket.open('127.0.0.1', 7124)
|
40
|
+
c.write "test"
|
41
|
+
assert_equal "test", c.read
|
42
|
+
assert $post_init_test
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_unbind
|
46
|
+
c = TCPSocket.open('127.0.0.1', 7124)
|
47
|
+
c.close
|
48
|
+
Thread.pass
|
49
|
+
assert $unbind_test
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_broad_cast
|
53
|
+
c1 = TCPSocket.open('127.0.0.1', 7124)
|
54
|
+
c2 = TCPSocket.open('127.0.0.1', 7124)
|
55
|
+
c1.write "test1"
|
56
|
+
c2.write "test2"
|
57
|
+
assert_equal "test1", c1.read
|
58
|
+
assert_equal "test2", c2.read
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../test_helper')
|
2
|
+
require 'shooting_star'
|
3
|
+
require 'socket'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
$command_line = 'echo "testing"'
|
7
|
+
|
8
|
+
class ShootingStarTest < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
@config = ShootingStar.configure :silent => true,
|
11
|
+
:pid_file => 'log/shooting_star.test.pid',
|
12
|
+
:log_file => 'log/shooting_star.test.log',
|
13
|
+
:server => {:host => '127.0.0.1', :port => 8081},
|
14
|
+
:shooter => {:uri => 'druby://127.0.0.1:7124'}
|
15
|
+
mutex = Mutex.new
|
16
|
+
mutex.lock
|
17
|
+
@thread = Thread.new{ShootingStar.start{mutex.unlock}}
|
18
|
+
mutex.lock
|
19
|
+
end
|
20
|
+
|
21
|
+
def teardown
|
22
|
+
ShootingStar.stop
|
23
|
+
File.rm_f(@config.pid_file)
|
24
|
+
File.rm_f @config.log_file
|
25
|
+
@thread.join
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_connection
|
29
|
+
client = TCPSocket.open('127.0.0.1', 8081)
|
30
|
+
assert_not_nil client
|
31
|
+
send client, "GET", "test/channel"
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_shooter_exists
|
35
|
+
shooter = ShootingStar.shooter
|
36
|
+
assert_not_nil shooter
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_c10k_problem
|
40
|
+
bin = File.join(RAILS_ROOT, 'bin/test_c10k_problem')
|
41
|
+
src = File.join(File.dirname(__FILE__), 'test_c10k_problem.c')
|
42
|
+
if !File.exist?(bin) || File.mtime(src) > File.mtime(bin)
|
43
|
+
system "gcc #{src} -o #{bin}"
|
44
|
+
end
|
45
|
+
system bin
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def send(client, method, path)
|
50
|
+
client.write "#{method} #{path} HTTP/1.1\n\r" +
|
51
|
+
"Host: #{@config.server.host}:#{@config.server.port}\n\r" +
|
52
|
+
"Keep-Alive: 300\n\r" +
|
53
|
+
"Connection: keep-alive\n\r\n\r"
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
#include <sys/types.h>
|
4
|
+
#include <sys/socket.h>
|
5
|
+
#include <sys/epoll.h>
|
6
|
+
#include <netinet/in.h>
|
7
|
+
|
8
|
+
#define NUM_CLIENT (10000)
|
9
|
+
|
10
|
+
int main(int argc, char **argv){
|
11
|
+
int status = EXIT_SUCCESS;
|
12
|
+
|
13
|
+
/* fill sockattr */
|
14
|
+
struct sockaddr_in addr;
|
15
|
+
addr.sin_family = AF_INET;
|
16
|
+
addr.sin_port = htons(8081);
|
17
|
+
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
18
|
+
|
19
|
+
int i, j, s[NUM_CLIENT];
|
20
|
+
for(i = 0; i < NUM_CLIENT; ++i){
|
21
|
+
/* create socket */
|
22
|
+
s[i] = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
23
|
+
int result = connect(s[i], (struct sockaddr*)&addr, sizeof(addr));
|
24
|
+
if(result != 0){
|
25
|
+
printf("failed to connect.");
|
26
|
+
for(j = 0; j < i; ++j){
|
27
|
+
close(s[j]);
|
28
|
+
}
|
29
|
+
return EXIT_FAILURE;
|
30
|
+
}
|
31
|
+
|
32
|
+
char hdr[] = "POST /chat/test HTTP/1.1\r\n";
|
33
|
+
char host[] = "Host: 127.0.0.1\r\n\r\n";
|
34
|
+
write(s[i], hdr, strlen(hdr));
|
35
|
+
write(s[i], host, strlen(host));
|
36
|
+
|
37
|
+
int n = i + 1;
|
38
|
+
if(n % 10 == 0) printf(".");
|
39
|
+
if(n % 100 == 0) printf(" %d connections\n", n);
|
40
|
+
}
|
41
|
+
|
42
|
+
/* close fd and exit */
|
43
|
+
for(i = 0; i < NUM_CLIENT; ++i){
|
44
|
+
close(s[i]);
|
45
|
+
}
|
46
|
+
return status;
|
47
|
+
}
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
ENV["RAILS_ENV"] = "test"
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
|
3
|
+
require 'test_help'
|
4
|
+
begin
|
5
|
+
require 'redgreen'
|
6
|
+
rescue Exception
|
7
|
+
end
|
8
|
+
$: << File.join(RAILS_ROOT, 'ext')
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
# Transactional fixtures accelerate your tests by wrapping each test method
|
12
|
+
# in a transaction that's rolled back on completion. This ensures that the
|
13
|
+
# test database remains unchanged so your fixtures don't have to be reloaded
|
14
|
+
# between every test method. Fewer database queries means faster tests.
|
15
|
+
#
|
16
|
+
# Read Mike Clark's excellent walkthrough at
|
17
|
+
# http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
|
18
|
+
#
|
19
|
+
# Every Active Record database supports transactions except MyISAM tables
|
20
|
+
# in MySQL. Turn off transactional fixtures in this case; however, if you
|
21
|
+
# don't care one way or the other, switching from MyISAM to InnoDB tables
|
22
|
+
# is recommended.
|
23
|
+
self.use_transactional_fixtures = true
|
24
|
+
|
25
|
+
# Instantiated fixtures are slow, but give you @david where otherwise you
|
26
|
+
# would need people(:david). If you don't want to migrate your existing
|
27
|
+
# test cases which use the @david style and don't mind the speed hit (each
|
28
|
+
# instantiated fixtures translates to a database query per test method),
|
29
|
+
# then set this back to true.
|
30
|
+
self.use_instantiated_fixtures = false
|
31
|
+
|
32
|
+
# Add more helper methods to be used by all tests here...
|
33
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), '../lib')
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shooting_star'
|
4
|
+
require 'socket'
|
5
|
+
require 'thread'
|
6
|
+
require 'redgreen' rescue nil
|
7
|
+
|
8
|
+
COMMAND_LINE = 'echo "testing"'
|
9
|
+
|
10
|
+
class ShootingStarTest < Test::Unit::TestCase
|
11
|
+
def setup
|
12
|
+
@config = ShootingStar.configure(
|
13
|
+
:silent => true,
|
14
|
+
:server => {:host => '127.0.0.1', :port => 8080},
|
15
|
+
:shooter => {:uri => 'druby://127.0.0.1:7123'})
|
16
|
+
@thread = Thread.new do
|
17
|
+
ShootingStar.start
|
18
|
+
end
|
19
|
+
Thread.pass while !File.exist?(@config.pid_file)
|
20
|
+
@client = TCPSocket.open('127.0.0.1', 8080)
|
21
|
+
@shooter = ShootingStar.shooter
|
22
|
+
end
|
23
|
+
|
24
|
+
def teardown
|
25
|
+
ShootingStar.stop
|
26
|
+
@thread.join
|
27
|
+
File.rm_f(@config.pid_file)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_activation
|
31
|
+
assert_not_nil @thread
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_shooter
|
35
|
+
assert_not_nil @shooter
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_client
|
39
|
+
assert_not_nil @client
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_disconnection
|
43
|
+
@client.close
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_communication
|
47
|
+
mutex = Mutex.new
|
48
|
+
mutex.lock
|
49
|
+
thread = Thread.new do
|
50
|
+
#send 'GET', 'test_application/test_channel_name'
|
51
|
+
mutex.unlock
|
52
|
+
end
|
53
|
+
mutex.lock
|
54
|
+
thread.join
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def send(method, path)
|
59
|
+
@client.write "#{method} #{path} HTTP/1.1\n" +
|
60
|
+
"Host: #{ShootingStar.host}:#{ShootingStar.port}\n" +
|
61
|
+
"Keep-Alive: 300\n" +
|
62
|
+
"Connection: keep-alive\n\n"
|
63
|
+
end
|
64
|
+
end
|