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