shooting_star 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/History.txt +42 -0
  2. data/Manifest.txt +38 -0
  3. data/README.txt +80 -0
  4. data/Rakefile +54 -0
  5. data/bin/shooting_star +70 -0
  6. data/ext/asteroid.c +262 -0
  7. data/ext/asteroid.h +4 -0
  8. data/ext/extconf.rb +9 -0
  9. data/lib/form_encoder.rb +23 -0
  10. data/lib/shooting_star/channel.rb +41 -0
  11. data/lib/shooting_star/config.rb +19 -0
  12. data/lib/shooting_star/server.rb +219 -0
  13. data/lib/shooting_star/shooter.rb +62 -0
  14. data/lib/shooting_star.rb +142 -0
  15. data/test/ext/asteroid_test.rb +60 -0
  16. data/test/lib/shooting_star_test.rb +55 -0
  17. data/test/lib/test_c10k_problem.c +47 -0
  18. data/test/test_helper.rb +33 -0
  19. data/test/test_shooting_star.rb +64 -0
  20. data/vendor/plugins/meteor_strike/README +50 -0
  21. data/vendor/plugins/meteor_strike/Rakefile +22 -0
  22. data/vendor/plugins/meteor_strike/generators/meteor/meteor_generator.rb +39 -0
  23. data/vendor/plugins/meteor_strike/generators/meteor/templates/controller.rb +25 -0
  24. data/vendor/plugins/meteor_strike/generators/meteor/templates/functional_test.rb +17 -0
  25. data/vendor/plugins/meteor_strike/generators/meteor/templates/migration.rb +13 -0
  26. data/vendor/plugins/meteor_strike/generators/meteor/templates/model.rb +15 -0
  27. data/vendor/plugins/meteor_strike/generators/meteor/templates/unit_test.rb +11 -0
  28. data/vendor/plugins/meteor_strike/generators/meteor/templates/view.rhtml +13 -0
  29. data/vendor/plugins/meteor_strike/init.rb +3 -0
  30. data/vendor/plugins/meteor_strike/lib/meteor_strike.rb +61 -0
  31. data/vendor/plugins/meteor_strike/test/meteor_strike_test.rb +15 -0
  32. metadata +100 -0
data/History.txt ADDED
@@ -0,0 +1,42 @@
1
+ *** 1.0.3 / 2007-03-22
2
+ + 2 major enhancements:
3
+ + Rails plugin is installed when shooting_star init.
4
+ + Implemented logging.
5
+ + 1 major bug fix:
6
+ + Fixed execution queuing and flashing.
7
+ + 3 minor enhancements:
8
+ + Suppressed warnings reported while installing gem.
9
+ + Added simple documentation.
10
+ + Independent from pthread.
11
+ + 1 minor bug fix:
12
+ + encodeURIComponent() is used for escaping tag list instead of escape().
13
+
14
+ *** 1.0.2 / 2007-03-16
15
+ + 1 major enhancement:
16
+ + Separated notification by tag grouping.
17
+
18
+ *** 1.0.1 / 2007-03-12
19
+ + 2 major enhancements:
20
+ + Corrected management of sessions.
21
+ + Added implementation using kqueue (Thank you Takanori Ishikawa!)
22
+
23
+ *** 1.0.0 / 2007-03-09
24
+ + 1 major enhancement:
25
+ + Supported tags in order to handle various events via single connection.
26
+
27
+ *** 1.0.0 / 2007-02-27
28
+ + 2 major enhancements:
29
+ + Added the ruby extension named Asteroid which is socket manager using epoll.
30
+ + Merged gem and rails app for testing.
31
+
32
+ *** 0.0.3 / 2006-11-18
33
+ + 2 major enhancements:
34
+ + Added function to initialize directory.
35
+ + Changed status code of execution response from 200 to 201.
36
+ + 1 minor enhancements:
37
+ + Exploited better algorithm for reconnection.
38
+
39
+ *** 0.0.2 / 2006-11-04
40
+ + 2 major enhancements:
41
+ + Supported install_gem task.
42
+ + Converted Rakefile+gemspec to Hoe-based Rakefile.
data/Manifest.txt ADDED
@@ -0,0 +1,38 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/shooting_star
6
+ lib/shooting_star.rb
7
+ lib/shooting_star/server.rb
8
+ lib/shooting_star/shooter.rb
9
+ lib/shooting_star/channel.rb
10
+ lib/shooting_star/config.rb
11
+ lib/form_encoder.rb
12
+ ext/asteroid.c
13
+ ext/extconf.rb
14
+ ext/asteroid.h
15
+ test/test_helper.rb
16
+ test/lib/shooting_star_test.rb
17
+ test/lib/test_c10k_problem.c
18
+ test/ext/asteroid_test.rb
19
+ vendor
20
+ vendor/plugins
21
+ vendor/plugins/meteor_strike
22
+ vendor/plugins/meteor_strike/README
23
+ vendor/plugins/meteor_strike/Rakefile
24
+ vendor/plugins/meteor_strike/init.rb
25
+ vendor/plugins/meteor_strike/lib
26
+ vendor/plugins/meteor_strike/lib/meteor_strike.rb
27
+ vendor/plugins/meteor_strike/test
28
+ vendor/plugins/meteor_strike/test/meteor_strike_test.rb
29
+ vendor/plugins/meteor_strike/generators
30
+ vendor/plugins/meteor_strike/generators/meteor
31
+ vendor/plugins/meteor_strike/generators/meteor/meteor_generator.rb
32
+ vendor/plugins/meteor_strike/generators/meteor/templates
33
+ vendor/plugins/meteor_strike/generators/meteor/templates/controller.rb
34
+ vendor/plugins/meteor_strike/generators/meteor/templates/model.rb
35
+ vendor/plugins/meteor_strike/generators/meteor/templates/view.rhtml
36
+ vendor/plugins/meteor_strike/generators/meteor/templates/migration.rb
37
+ vendor/plugins/meteor_strike/generators/meteor/templates/unit_test.rb
38
+ vendor/plugins/meteor_strike/generators/meteor/templates/functional_test.rb
data/README.txt ADDED
@@ -0,0 +1,80 @@
1
+ shooting_star
2
+ by Genki Takiuchi <takiuchi@drecom.co.jp>
3
+ http://rails.office.drecom.jp/takiuchi/
4
+ http://labs.drecom.jp/
5
+
6
+ == DESCRIPTION:
7
+
8
+ Our goal is development of practical comet server which will be achieving
9
+ over 100,000 simultaneous connections per host. On this purpose, we abandon
10
+ portability and use system calls depending on particular OS such as epoll
11
+ and kqueue.
12
+
13
+ == FEATURES/PROBLEMS:
14
+
15
+ * Comet server
16
+ * Comet client implementation (Rails plugin)
17
+
18
+ == SYNOPSYS:
19
+
20
+ shooting_star {init|restart|start|stat|stop} [options]
21
+
22
+ -f <config file>
23
+ -g debug mode.
24
+ -s silent mode.
25
+ -v, --version Show version.
26
+ -h, --help Show this message.
27
+
28
+ Options for subcommand `init':
29
+ -d <base dir> ShootingStarize directory.
30
+
31
+ Options for subcommand `start':
32
+ -d daemon mode.
33
+
34
+ Options for subcommand `stat':
35
+ -u with uid.
36
+ -s with signature.
37
+
38
+ == REQUIREMENTS:
39
+
40
+ * Linux or xBSD OS having epoll or kqueue.
41
+ * Increase ulimit of nofile up to over 100,000.
42
+ (edit /etc/security/limits.conf file.)
43
+ * prototype.js 1.5.0+
44
+ * Ruby 1.8.5+
45
+ * Ruby on Rails 1.2.0+
46
+
47
+ == INSTALL:
48
+
49
+ $ sudo gem install shooting_star
50
+ $ cd /path/to/rails/root
51
+ $ shooting_star init
52
+ $ ./script/generate meteor
53
+
54
+ And then, you need to configure config/shooting_star.yml and database.yml.
55
+ See also vendor/plugins/meteor_strike/README.
56
+
57
+ == LICENSE:
58
+
59
+ (The MIT License)
60
+
61
+ Copyright (c) 2007 Genki Takiuchi
62
+
63
+ Permission is hereby granted, free of charge, to any person obtaining
64
+ a copy of this software and associated documentation files (the
65
+ 'Software'), to deal in the Software without restriction, including
66
+ without limitation the rights to use, copy, modify, merge, publish,
67
+ distribute, sublicense, and/or sell copies of the Software, and to
68
+ permit persons to whom the Software is furnished to do so, subject to
69
+ the following conditions:
70
+
71
+ The above copyright notice and this permission notice shall be
72
+ included in all copies or substantial portions of the Software.
73
+
74
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
75
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
76
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
77
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
78
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
79
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
80
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require(File.join(File.dirname(__FILE__), 'config', 'boot'))
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'rake/rdoctask'
6
+ require 'tasks/rails'
7
+
8
+ namespace :gem do
9
+ require 'hoe'
10
+
11
+ $: << './lib'
12
+ $: << './ext'
13
+ require 'shooting_star'
14
+
15
+ Hoe.new('shooting_star', ShootingStar::VERSION) do |hoe|
16
+ hoe.author = 'Genki Takiuchi'
17
+ hoe.email = 'takiuchi@drecom.co.jp'
18
+ hoe.description = 'Comet server.'
19
+ hoe.rubyforge_name = 'shooting-star'
20
+ hoe.summary = hoe.paragraphs_of('README.txt', 2)[0]
21
+ hoe.description = hoe.paragraphs_of('README.txt', 2..5).join("\n\n")
22
+ hoe.url = hoe.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
23
+ hoe.changes = hoe.paragraphs_of('History.txt', 0..1).join("\n\n")
24
+ hoe.spec_extras = {
25
+ :extensions => 'ext/extconf.rb',
26
+ :rdoc_options => ['-S',
27
+ '--template', 'kilmer',
28
+ '--main', 'README.txt',
29
+ '--exclude', 'ext/asteroid',
30
+ 'README.txt']
31
+ }
32
+ end
33
+ end
34
+
35
+ desc 'default gem task.'
36
+ task :gem => 'gem:default'
37
+
38
+ desc 'update generator template files.'
39
+ task 'update_generator_template_files' do
40
+ templates = 'vendor/plugins/meteor_strike/generators/meteor/templates'
41
+ cp 'app/controllers/meteor_controller.rb',
42
+ templates + '/controller.rb'
43
+ cp 'app/models/meteor.rb',
44
+ templates + '/model.rb'
45
+ cp 'app/views/meteor/strike.rhtml',
46
+ templates + '/view.rhtml'
47
+ cp 'test/unit/meteor_test.rb',
48
+ templates + '/unit_test.rb'
49
+ cp 'test/functional/meteor_controller_test.rb',
50
+ templates + '/functional_test.rb'
51
+ end
52
+
53
+ desc 'test all tests'
54
+ task 'test:all' => [:test, 'test:plugins', 'test:exts', 'test:libs']
data/bin/shooting_star ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+
4
+ $:.unshift File.join(File.dirname(__FILE__), '../lib')
5
+ $:.unshift File.join(File.dirname(__FILE__), '../ext')
6
+ require 'shooting_star'
7
+
8
+ # define options
9
+ $command_line = "#{$0} #{ARGV.join(' ')}"
10
+ OPT = {}
11
+ opt = OptionParser.new
12
+ opt.on('-f <config file>'){|v| OPT[:config] = v}
13
+ opt.on('-g', 'debug mode.'){$debug = true}
14
+ opt.on('-s', 'silent mode.'){OPT[:silent] = true}
15
+ subopt = Hash.new{|h,k| $stderr.puts "no such subcommand: #{k}."; exit 1}
16
+ subopt['init'] = OptionParser.new do |cmd|
17
+ cmd.banner = ['Usage:', cmd.program_name, 'init [options]'].join(' ')
18
+ cmd.on('-d <base dir>', 'ShootingStarize directory.'){|v| OPT[:directory] = v}
19
+ end
20
+ subopt['start'] = OptionParser.new do |cmd|
21
+ cmd.banner = ['Usage:', cmd.program_name, 'start [options]'].join(' ')
22
+ cmd.on('-d', 'daemon mode.'){OPT[:daemon] = true}
23
+ end
24
+ subopt['stop'] = OptionParser.new do |cmd| end
25
+ subopt['restart'] = OptionParser.new do |cmd| end
26
+ subopt['stat'] = OptionParser.new do |cmd|
27
+ cmd.banner = ['Usage:', cmd.program_name, 'stat [options]'].join(' ')
28
+ cmd.on('-u', 'with uid.'){OPT[:with_uid] = true}
29
+ cmd.on('-s', 'with signature.'){OPT[:with_sig] = true}
30
+ end
31
+ opt.on_tail('-v', '--version', 'Show version.'){puts opt.ver; exit}
32
+ opt.on_tail('-h', '--help', 'Show this message.') do
33
+ opt.banner = " " + ['Usage:', opt.program_name,
34
+ "{#{subopt.keys.sort.join('|')}}", '[options]'].join(' ') + "\n\n"
35
+ puts "#{'-'*opt.ver.length}\n#{opt.ver}\n#{'-'*opt.ver.length}"
36
+ puts opt.help
37
+ subopt.keys.sort.each do |key| cmd = subopt[key]
38
+ next if cmd.to_a.size <= 1
39
+ cmd.banner = "\n Options for subcommand `#{key}':\n"
40
+ puts cmd.help
41
+ end; exit
42
+ end
43
+
44
+ # parse options
45
+ Version = ShootingStar::VERSION
46
+ opt.order!(ARGV)
47
+ CMD = ARGV.shift
48
+ begin
49
+ subopt[CMD].parse!(ARGV) unless ARGV.empty?
50
+ rescue OptionParser::InvalidOption => e
51
+ puts e.message; exit
52
+ end
53
+ ShootingStar.configure(OPT)
54
+ OPT[:daemon] = true if CMD == 'restart'
55
+
56
+ # execute
57
+ case CMD
58
+ when "init"
59
+ ShootingStar.init
60
+ when "start"
61
+ ShootingStar.start
62
+ when "stop"
63
+ ShootingStar.stop
64
+ when "restart"
65
+ ShootingStar.restart
66
+ when "stat"
67
+ ShootingStar.report
68
+ else
69
+ puts `#{$0} --help`
70
+ end
data/ext/asteroid.c ADDED
@@ -0,0 +1,262 @@
1
+ /* --------------------------------------------------------------------------
2
+ * Asteroid ruby extension.
3
+ * by Genki Takiuchi <takiuchi@drecom.co.jp>
4
+ * -------------------------------------------------------------------------- */
5
+ #include <ruby.h>
6
+ #include <rubysig.h>
7
+ #include <unistd.h>
8
+ #include <fcntl.h>
9
+ #include <errno.h>
10
+ #include <sys/types.h>
11
+ #include <sys/socket.h>
12
+ #include <netinet/in.h>
13
+ #include <arpa/inet.h>
14
+ #include "extconf.h"
15
+ #include "asteroid.h"
16
+
17
+ /* --------------------------------------------------------------------------
18
+ * epoll / kqueue
19
+ * - 2007/03/09 *BSD kqueue port, Mac OS X MSG_NOSIGNAL missing.
20
+ * (Takanori Ishikawa)
21
+ * -------------------------------------------------------------------------- */
22
+ #ifdef HAVE_SYS_EVENT_H
23
+ #include <sys/event.h>
24
+ #include <sys/time.h>
25
+ typedef int asteroid_pollfd_t;
26
+ typedef struct kevent asteroid_poll_event_t;
27
+ #endif
28
+
29
+ #ifdef HAVE_SYS_EPOLL_H
30
+ #include <sys/epoll.h>
31
+ typedef int asteroid_pollfd_t;
32
+ typedef struct epoll_event asteroid_poll_event_t;
33
+ #endif
34
+
35
+ static asteroid_pollfd_t asteroid_poll_create(int sizeHint) {
36
+ #ifdef HAVE_SYS_EVENT_H
37
+ return kqueue();
38
+ #endif
39
+ #ifdef HAVE_SYS_EPOLL_H
40
+ return epoll_create(sizeHint);
41
+ #endif
42
+ }
43
+
44
+ static int asteroid_poll_add(
45
+ asteroid_pollfd_t pollfd,
46
+ asteroid_poll_event_t *event,
47
+ int fd) {
48
+ #ifdef HAVE_SYS_EVENT_H
49
+ EV_SET(event, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
50
+ return kevent(pollfd, event, 1, NULL, 0, NULL);
51
+ #endif
52
+ #ifdef HAVE_SYS_EPOLL_H
53
+ event->events = (EPOLLIN | EPOLLPRI);
54
+ event->data.fd = fd;
55
+ return epoll_ctl(pollfd, EPOLL_CTL_ADD, fd, event);
56
+ #endif
57
+ }
58
+
59
+ static int asteroid_poll_remove(
60
+ asteroid_pollfd_t pollfd,
61
+ asteroid_poll_event_t *event,
62
+ int fd) {
63
+ #ifdef HAVE_SYS_EVENT_H
64
+ EV_SET(event, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
65
+ return kevent(pollfd, event, 1, NULL, 0, NULL);
66
+ #endif
67
+ #ifdef HAVE_SYS_EPOLL_H
68
+ return epoll_ctl(pollfd, EPOLL_CTL_DEL, fd, event);
69
+ #endif
70
+ }
71
+
72
+ static int asteroid_poll_wait(
73
+ asteroid_pollfd_t pollfd,
74
+ asteroid_poll_event_t *events,
75
+ int maxevents,
76
+ int timeout) {
77
+ #ifdef HAVE_SYS_EVENT_H
78
+ struct timespec tv, *tvptr;
79
+ if (timeout < 0) {
80
+ tvptr = NULL;
81
+ }
82
+ else {
83
+ tv.tv_sec = (long) (timeout/1000);
84
+ tv.tv_nsec = (long) (timeout%1000)*1000;
85
+ tvptr = &tv;
86
+ }
87
+ return kevent(pollfd, NULL, 0, events, maxevents, tvptr);
88
+ #endif
89
+ #ifdef HAVE_SYS_EPOLL_H
90
+ return epoll_wait(pollfd, events, maxevents, timeout);
91
+ #endif
92
+ }
93
+
94
+ #ifdef HAVE_SYS_EVENT_H
95
+ #define AST_POLL_EVENT_SOCK(event) ((event)->ident)
96
+ #define AST_POLL_EVENT_CAN_READ(event) ((event)->filter == EVFILT_READ)
97
+ #endif
98
+ #ifdef HAVE_SYS_EPOLL_H
99
+ #define AST_POLL_EVENT_SOCK(event) ((event)->data.fd)
100
+ #define AST_POLL_EVENT_CAN_READ(event) ((event)->events & (EPOLLIN | EPOLLPRI))
101
+ #endif
102
+
103
+ #ifdef SO_NOSIGPIPE
104
+ #ifndef MSG_NOSIGNAL
105
+ #define MSG_NOSIGNAL 0
106
+ #endif
107
+
108
+ /**
109
+ * The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when
110
+ * sending data to a dead peer (instead of relying on the 4th argument to send
111
+ * being MSG_NOSIGNAL). Possibly also existing and in use on other BSD
112
+ * systems?
113
+ *
114
+ * curl-7.15.5/lib/connect.c
115
+ */
116
+ static void nosigpipe(int sockfd) {
117
+ int one = 1;
118
+ setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&one, sizeof(one));
119
+ }
120
+ #else
121
+ #define nosigpipe(x)
122
+ #endif
123
+
124
+ #define MAX_CONNECTION (102400)
125
+ #define EVENT_BUF_SIZE (1024)
126
+
127
+ static VALUE Asteroid;
128
+ static VALUE clients;
129
+ static volatile int running = 0;
130
+ static asteroid_pollfd_t epoll_fd = 0;
131
+ static asteroid_poll_event_t events[EVENT_BUF_SIZE];
132
+
133
+ int dispatch();
134
+ void runtime_error();
135
+
136
+ void Init_asteroid(){
137
+ Asteroid = rb_define_module("Asteroid");
138
+ rb_define_singleton_method(Asteroid, "run", asteroid_s_run, 3);
139
+ rb_define_singleton_method(Asteroid, "stop", asteroid_s_stop, 0);
140
+ rb_define_class_variable(Asteroid, "@@clients", clients = rb_hash_new());
141
+ }
142
+
143
+ static VALUE close_socket_proc(VALUE Pair, VALUE Arg, VALUE Self) {
144
+ close(FIX2INT(RARRAY(Pair)->ptr[0]));
145
+ return Qnil;
146
+ }
147
+
148
+ static VALUE asteroid_s_run(VALUE Self, VALUE Host, VALUE Port, VALUE Module){
149
+ char *host = StringValuePtr(Host);
150
+ int port = FIX2INT(Port);
151
+
152
+ epoll_fd = asteroid_poll_create(1024);
153
+ if(epoll_fd == -1) runtime_error();
154
+
155
+ struct sockaddr_in addr;
156
+ addr.sin_family = AF_INET;
157
+ addr.sin_port = htons(port);
158
+ addr.sin_addr.s_addr = inet_addr(host);
159
+ int s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP), c, one = 1;
160
+ if(s == -1) runtime_error();
161
+ fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK);
162
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
163
+ nosigpipe(s);
164
+ if(bind(s, (struct sockaddr*)&addr, sizeof(addr)) != 0) runtime_error();
165
+ if(listen(s, MAX_CONNECTION) != 0) runtime_error();
166
+ if(rb_block_given_p()) rb_yield(Qnil);
167
+
168
+ VALUE Class = rb_define_class_under(Asteroid, "Server", rb_cObject);
169
+ rb_define_method(Class, "send_data",
170
+ asteroid_server_send_data, 1);
171
+ rb_define_method(Class, "write_and_close",
172
+ asteroid_server_write_and_close, 0);
173
+ rb_include_module(Class, Module);
174
+ // Mac OS X, Fedora needs explicit rb_thread_schedule call.
175
+ for(running = 1; running; rb_thread_schedule()){
176
+ socklen_t len = sizeof(addr);
177
+ while((c = accept(s, (struct sockaddr*)&addr, &len)) != -1){
178
+ fcntl(c, F_SETFL, fcntl(c, F_GETFL, 0) | O_NONBLOCK);
179
+ asteroid_poll_event_t event;
180
+ memset(&event, 0, sizeof(event));
181
+ if(asteroid_poll_add(epoll_fd, &event, c) == -1) runtime_error();
182
+ // instantiate server class which responds to client.
183
+ VALUE Server = rb_class_new_instance(0, NULL, Class);
184
+ rb_iv_set(Server, "@fd", rb_fix_new(c));
185
+ rb_hash_aset(clients, rb_fix_new(c), Server);
186
+ if(rb_respond_to(Server, rb_intern("post_init"))){
187
+ rb_funcall(Server, rb_intern("post_init"), 0);
188
+ }
189
+ }
190
+ if(dispatch() != 0) asteroid_s_stop(Asteroid);
191
+ // You must call them to give a chance for ruby to handle system events.
192
+ CHECK_INTS;
193
+ }
194
+
195
+ rb_iterate(rb_each, clients, close_socket_proc, Qnil);
196
+ rb_funcall(clients, rb_intern("clear"), 0);
197
+ close(s);
198
+ close(epoll_fd);
199
+ return Qnil;
200
+ }
201
+
202
+ static VALUE asteroid_s_stop(VALUE Self){
203
+ running = 0;
204
+ return Qnil;
205
+ }
206
+
207
+ static VALUE asteroid_server_send_data(VALUE Self, VALUE Data){
208
+ VALUE Fd = rb_iv_get(Self, "@fd");
209
+ int fd = FIX2INT(Fd);
210
+ char *data = StringValuePtr(Data);
211
+ send(fd, data, RSTRING(Data)->len, MSG_NOSIGNAL);
212
+ return Qnil;
213
+ }
214
+
215
+ static VALUE asteroid_server_write_and_close(VALUE Self){
216
+ VALUE Fd = rb_iv_get(Self, "@fd");
217
+ int fd = FIX2INT(Fd);
218
+ asteroid_poll_event_t event;
219
+ memset(&event, 0, sizeof(event));
220
+ asteroid_poll_remove(epoll_fd, &event, fd);
221
+ close(fd);
222
+ rb_hash_delete(clients, Fd);
223
+ return Qnil;
224
+ }
225
+
226
+ int dispatch(){
227
+ int i, s, len;
228
+ // We need to wait at least 1 millisecond to avoid busy loop.
229
+ while((s = asteroid_poll_wait(epoll_fd, events, EVENT_BUF_SIZE, 1)) > 0){
230
+ for(i = 0; i < s; ++i){
231
+ asteroid_poll_event_t event = events[i];
232
+ int fd = AST_POLL_EVENT_SOCK(&event);
233
+ VALUE Fd = rb_fix_new(fd);
234
+ VALUE Server = rb_hash_aref(clients, Fd);
235
+ if(AST_POLL_EVENT_CAN_READ(&event)){
236
+ VALUE Buf = rb_str_new("", 0);
237
+ char buf[1024];
238
+ while((len = read(fd, buf, 1023)) > 0){
239
+ buf[len] = '\0';
240
+ rb_str_concat(Buf, rb_str_new2(buf));
241
+ }
242
+ if(len == -1){
243
+ if(rb_respond_to(Server, rb_intern("receive_data"))){
244
+ rb_funcall(Server, rb_intern("receive_data"), 1, Buf);
245
+ }
246
+ }else{
247
+ if(rb_respond_to(Server, rb_intern("unbind"))){
248
+ rb_funcall(Server, rb_intern("unbind"), 0);
249
+ }
250
+ asteroid_poll_remove(epoll_fd, &event, fd);
251
+ rb_hash_delete(clients, Fd);
252
+ close(fd);
253
+ }
254
+ }
255
+ }
256
+ }
257
+ return 0;
258
+ }
259
+
260
+ void runtime_error(){
261
+ rb_raise(rb_eRuntimeError, strerror(errno));
262
+ }
data/ext/asteroid.h ADDED
@@ -0,0 +1,4 @@
1
+ static VALUE asteroid_s_run(VALUE Self, VALUE Host, VALUE Port, VALUE Module);
2
+ static VALUE asteroid_s_stop(VALUE Self);
3
+ static VALUE asteroid_server_send_data(VALUE Self, VALUE Data);
4
+ static VALUE asteroid_server_write_and_close(VALUE Self);
data/ext/extconf.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'mkmf'
2
+
3
+ unless have_header("sys/epoll.h") || have_header("sys/event.h")
4
+ message "epoll or kqueue required.\n"
5
+ exit 1
6
+ end
7
+
8
+ create_header
9
+ create_makefile 'asteroid'
@@ -0,0 +1,23 @@
1
+ module FormEncoder
2
+ def self.encode(parameters, prefix = "")
3
+ case parameters
4
+ when Hash; encode_hash(parameters, prefix)
5
+ when Array; encode_array(parameters, prefix)
6
+ else "#{prefix}=#{CGI.escape(parameters.to_s)}"
7
+ end
8
+ end
9
+
10
+ private
11
+ def self.encode_hash(hash, prefix)
12
+ hash.inject([]) do |result, (key, value)|
13
+ key = CGI.escape(key.to_s)
14
+ result << encode(value, prefix.empty? ? key : "#{prefix}[#{key}]")
15
+ end.join('&')
16
+ end
17
+
18
+ def self.encode_array(array, prefix)
19
+ array.inject([]) do |result, value|
20
+ result << encode(value, "#{prefix}[]")
21
+ end.join('&')
22
+ end
23
+ end
@@ -0,0 +1,41 @@
1
+ require 'set'
2
+
3
+ module ShootingStar
4
+ class Channel
5
+ class InvalidIdError < StandardError; end
6
+ attr_reader :path, :waiters
7
+ @@channels = {}
8
+
9
+ def initialize(path)
10
+ @path = path
11
+ @waiters = Hash.new
12
+ @event_id = 0
13
+ @@channels[path] = self
14
+ end
15
+
16
+ def transmit(id, params)
17
+ @waiters.each do |signature, server|
18
+ server.commit if server.respond(id, params)
19
+ end
20
+ end
21
+
22
+ def join(server)
23
+ @waiters[server.signature] = server
24
+ server.commit
25
+ end
26
+
27
+ def leave(server)
28
+ @waiters.delete(server.signature)
29
+ end
30
+
31
+ def self.[](channel); @@channels[channel] end
32
+ def self.list; @@channels.keys end
33
+ def self.sweep; @@channels.delete_if{|k,v| v.waiters.empty?} end
34
+
35
+ def self.cleanup(channel)
36
+ result = @@channels[channel] && @@channels[channel].waiters.empty?
37
+ @@channels.delete(channel) if result
38
+ result
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,19 @@
1
+ module ShootingStar
2
+ class Config
3
+ def initialize(hash)
4
+ @hash = hash.keys.inject({}) do |new_hash, key|
5
+ new_hash[key.to_s] = hash[key]
6
+ new_hash
7
+ end
8
+ end
9
+
10
+ def merge!(hash)
11
+ hash.keys.each{|key| @hash[key.to_s] = hash[key]}; self
12
+ end
13
+
14
+ def method_missing(method, *arg, &block)
15
+ value = @hash[method.to_s]
16
+ value.is_a?(Hash) ? Config.new(value) : value
17
+ end
18
+ end
19
+ end