barlume 0.0.1.rc.1 → 0.0.1.rc.2
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/barlume.gemspec +2 -1
- data/examples/echo.rb +78 -0
- data/examples/slowpokes.rb +75 -0
- data/lib/barlume/lanterna.rb +243 -65
- data/lib/barlume/lanterna/dpoll.rb +204 -0
- data/lib/barlume/lanterna/epoll.rb +98 -70
- data/lib/barlume/lanterna/{utils.rb → helpers.rb} +6 -3
- data/lib/barlume/lanterna/kqueue.rb +88 -101
- data/lib/barlume/lanterna/poll.rb +67 -66
- data/lib/barlume/lanterna/port.rb +211 -0
- data/lib/barlume/lanterna/select.rb +22 -44
- data/lib/barlume/lucciola.rb +221 -10
- data/lib/barlume/version.rb +1 -1
- data/test/benchmark.client.rb +17 -0
- data/test/benchmark.server.rb +35 -0
- data/test/flood.js +28 -0
- metadata +30 -5
- data/examples/test.rb +0 -27
data/barlume.gemspec
CHANGED
data/examples/echo.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'barlume'
|
4
|
+
|
5
|
+
class Echo < Barlume::Lucciola
|
6
|
+
CHUNK_SIZE = 2048
|
7
|
+
|
8
|
+
def initialize (*)
|
9
|
+
super
|
10
|
+
|
11
|
+
@buffer = ''
|
12
|
+
end
|
13
|
+
|
14
|
+
def read_all
|
15
|
+
while chunk = read(CHUNK_SIZE)
|
16
|
+
@buffer << chunk
|
17
|
+
end
|
18
|
+
rescue Errno::EAGAIN; end
|
19
|
+
|
20
|
+
def write_all
|
21
|
+
while @buffer.length > 0
|
22
|
+
if @buffer.length <= CHUNK_SIZE
|
23
|
+
written = write @buffer
|
24
|
+
|
25
|
+
if written < @buffer.length
|
26
|
+
@buffer[0, written] = ''
|
27
|
+
|
28
|
+
return false
|
29
|
+
else
|
30
|
+
@buffer.clear
|
31
|
+
end
|
32
|
+
else
|
33
|
+
written = write @buffer[0, CHUNK_SIZE]
|
34
|
+
|
35
|
+
@buffer[0, written] = ''
|
36
|
+
|
37
|
+
if written < CHUNK_SIZE
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
true
|
44
|
+
rescue Errno::EAGAIN
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
lanterna = Barlume::Lanterna.best
|
50
|
+
server = lanterna.add(TCPServer.new(1337)).asynchronous!.readable!
|
51
|
+
server.listen(1024)
|
52
|
+
|
53
|
+
puts "Using #{lanterna.name}..."
|
54
|
+
|
55
|
+
loop do
|
56
|
+
lanterna.available {|event, lucciola|
|
57
|
+
if event == :error
|
58
|
+
lucciola.delete!.close rescue nil
|
59
|
+
elsif event == :readable
|
60
|
+
if lucciola == server
|
61
|
+
while client = lucciola.accept rescue nil
|
62
|
+
lanterna.add(Echo.new(client)).asynchronous!.readable!
|
63
|
+
end
|
64
|
+
else
|
65
|
+
begin
|
66
|
+
lucciola.read_all
|
67
|
+
lucciola.writable!
|
68
|
+
rescue EOFError, Errno::ECONNRESET
|
69
|
+
lucciola.delete!
|
70
|
+
end
|
71
|
+
end
|
72
|
+
elsif event == :writable
|
73
|
+
if lucciola.write_all
|
74
|
+
lucciola.no_writable!
|
75
|
+
end
|
76
|
+
end
|
77
|
+
}
|
78
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
require 'rubygems'
|
4
|
+
require 'barlume'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
options = {}
|
8
|
+
|
9
|
+
OptionParser.new do |o|
|
10
|
+
options[:host] = 'localhost'
|
11
|
+
options[:port] = 4567
|
12
|
+
options[:threads] = 16
|
13
|
+
options[:content] = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"
|
14
|
+
options[:sleep] = 1
|
15
|
+
|
16
|
+
o.on '-h', '--host host', 'the host of the server' do |value|
|
17
|
+
options[:host] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
o.on '-p', '--port port', Integer, 'the port of the server' do |value|
|
21
|
+
options[:port] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
o.on '-t', '--threads [MAX_THREADS]', 'the max amount of threads on the server' do |value|
|
25
|
+
options[:threads] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
o.on '-c', '--content CONTENT', 'the string to send' do |value|
|
29
|
+
options[:content] = value
|
30
|
+
end
|
31
|
+
|
32
|
+
o.on '-s', '--sleep SECONDS', Float, 'the time to sleep before sending every character' do |value|
|
33
|
+
options[:sleep] = value
|
34
|
+
end
|
35
|
+
end.parse!
|
36
|
+
|
37
|
+
class Slowpoke < Barlume::Lucciola
|
38
|
+
def initialize (socket, message)
|
39
|
+
super(socket)
|
40
|
+
|
41
|
+
@message = message
|
42
|
+
@offset = 0
|
43
|
+
end
|
44
|
+
|
45
|
+
def send_next
|
46
|
+
return if done?
|
47
|
+
|
48
|
+
write_nonblock @message[@offset]
|
49
|
+
|
50
|
+
@offset += 1
|
51
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
52
|
+
end
|
53
|
+
|
54
|
+
def done?
|
55
|
+
@offset >= @message.length
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
lantern = Barlume::Lanterna.best
|
60
|
+
|
61
|
+
options[:threads].times {
|
62
|
+
lantern << Slowpoke.new(TCPSocket.new(options[:host], options[:port]), options[:content])
|
63
|
+
}
|
64
|
+
|
65
|
+
puts "oh noes, a wall on my path D:"
|
66
|
+
|
67
|
+
until lantern.descriptors.all?(&:done?)
|
68
|
+
lantern.writable.each {|s|
|
69
|
+
s.send_next
|
70
|
+
}
|
71
|
+
|
72
|
+
sleep options[:sleep]
|
73
|
+
end
|
74
|
+
|
75
|
+
puts "oh, there's a door ( ・ ◡◡・)"
|
data/lib/barlume/lanterna.rb
CHANGED
@@ -19,94 +19,88 @@
|
|
19
19
|
|
20
20
|
require 'barlume/lucciola'
|
21
21
|
|
22
|
+
require 'barlume/lanterna/helpers'
|
23
|
+
require 'barlume/lanterna/select'
|
24
|
+
require 'barlume/lanterna/poll'
|
25
|
+
require 'barlume/lanterna/epoll'
|
26
|
+
require 'barlume/lanterna/kqueue'
|
27
|
+
require 'barlume/lanterna/port'
|
28
|
+
require 'barlume/lanterna/dpoll'
|
29
|
+
|
22
30
|
module Barlume
|
23
31
|
|
24
32
|
class Lanterna
|
25
|
-
|
26
|
-
|
27
|
-
define_method "#{name}?" do
|
28
|
-
const_get(name.capitalize).supported?
|
29
|
-
end
|
33
|
+
%w[select poll epoll kqueue port dpoll].each {|name|
|
34
|
+
klass = Lanterna.const_get(Lanterna.constants.find { |c| c.downcase.to_s == name })
|
30
35
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
}
|
36
|
+
define_singleton_method "#{name}?" do
|
37
|
+
klass.supported?
|
38
|
+
end
|
35
39
|
|
36
|
-
|
37
|
-
|
40
|
+
define_singleton_method name do
|
41
|
+
klass.new
|
42
|
+
end
|
43
|
+
}
|
38
44
|
|
39
|
-
|
45
|
+
def self.best
|
46
|
+
return kqueue if kqueue?
|
40
47
|
|
41
|
-
|
48
|
+
return epoll if epoll?
|
49
|
+
|
50
|
+
return port if port?
|
42
51
|
|
43
|
-
|
44
|
-
end
|
52
|
+
return dpoll if dpoll? && RUBY_PLATFORM =~ /solaris/i
|
45
53
|
|
46
|
-
|
47
|
-
raise 'unsupported platform' unless supported?
|
54
|
+
return poll if poll?
|
48
55
|
|
49
|
-
|
50
|
-
end
|
56
|
+
return select
|
51
57
|
end
|
52
58
|
|
53
|
-
|
59
|
+
def self.best_edge_triggered
|
60
|
+
return kqueue.edge_triggered! if kqueue?
|
54
61
|
|
55
|
-
|
56
|
-
def initialize
|
57
|
-
@pipes = IO.pipe
|
58
|
-
end
|
59
|
-
|
60
|
-
def break
|
61
|
-
@pipes.last.write_nonblock 'x'
|
62
|
-
end
|
62
|
+
return epoll.edge_triggered! if epoll?
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
end
|
64
|
+
raise 'this platform does not support edge triggered primitives'
|
65
|
+
end
|
67
66
|
|
68
|
-
|
69
|
-
|
70
|
-
end
|
67
|
+
def self.new (*)
|
68
|
+
raise 'unsupported platform' unless supported?
|
71
69
|
|
72
|
-
|
73
|
-
to_io.to_i
|
74
|
-
end
|
70
|
+
super
|
75
71
|
end
|
76
72
|
|
77
|
-
|
73
|
+
include Enumerable
|
78
74
|
|
79
75
|
def initialize
|
80
|
-
@breaker
|
81
|
-
@descriptors
|
82
|
-
@
|
76
|
+
@breaker = Breaker.new
|
77
|
+
@descriptors = {}
|
78
|
+
@readable = {}
|
79
|
+
@writable = {}
|
83
80
|
end
|
84
81
|
|
85
82
|
def name
|
86
83
|
self.class.name[/(?:::)?([^:]*)$/, 1].downcase.to_sym
|
87
84
|
end
|
88
85
|
|
89
|
-
def
|
90
|
-
@
|
91
|
-
end
|
92
|
-
|
93
|
-
def report_errors!
|
94
|
-
@report_errors = true
|
95
|
-
end
|
86
|
+
def break (with = nil)
|
87
|
+
@breaker.break(with)
|
96
88
|
|
97
|
-
|
98
|
-
@report_errors = false
|
89
|
+
self
|
99
90
|
end
|
100
91
|
|
101
|
-
def
|
102
|
-
|
103
|
-
|
92
|
+
def add (what, mode = nil, &block)
|
93
|
+
Lucciola.wrap(what).tap {|lucciola|
|
94
|
+
if @descriptors.has_key?(lucciola.to_i)
|
95
|
+
raise ArgumentError, "#{what.inspect} is already trapped"
|
96
|
+
end
|
104
97
|
|
105
|
-
|
106
|
-
|
107
|
-
|
98
|
+
@descriptors[lucciola.to_i] = lucciola
|
99
|
+
lucciola.trap_in self
|
100
|
+
block.call lucciola if block
|
108
101
|
|
109
|
-
|
102
|
+
readable! lucciola if mode == :readable || mode == :both
|
103
|
+
writable! lucciola if mode == :writable || mode == :both
|
110
104
|
}
|
111
105
|
end
|
112
106
|
|
@@ -118,17 +112,201 @@ class Lanterna
|
|
118
112
|
|
119
113
|
alias << push
|
120
114
|
|
121
|
-
def remove (what)
|
122
|
-
@descriptors.
|
115
|
+
def remove (what, &block)
|
116
|
+
unless lucciola = @descriptors[what.to_i]
|
117
|
+
raise ArgumentError, "#{what.inspect} isn't trapped here"
|
118
|
+
end
|
119
|
+
|
120
|
+
block.call lucciola if block
|
121
|
+
|
122
|
+
@descriptors.delete(lucciola.to_i)
|
123
|
+
@readable.delete(lucciola.to_i)
|
124
|
+
@writable.delete(lucciola.to_i)
|
125
|
+
|
126
|
+
lucciola.set_free
|
123
127
|
end
|
124
128
|
|
125
129
|
alias delete remove
|
126
|
-
end
|
127
130
|
|
131
|
+
def has? (what)
|
132
|
+
@descriptors.has_key?(what.to_i)
|
133
|
+
end
|
134
|
+
|
135
|
+
def [] (what)
|
136
|
+
@descriptors[what.to_i]
|
137
|
+
end
|
138
|
+
|
139
|
+
def each (mode = nil, &block)
|
140
|
+
return enum_for :each, mode unless block
|
141
|
+
|
142
|
+
if mode.nil?
|
143
|
+
@descriptors.each_value(&block)
|
144
|
+
elsif mode == :readable
|
145
|
+
@readable.each_value(&block)
|
146
|
+
elsif mode == :writable
|
147
|
+
@writable.each_value(&block)
|
148
|
+
else
|
149
|
+
raise ArgumentError, "#{mode} is an unknown mode"
|
150
|
+
end
|
151
|
+
|
152
|
+
self
|
153
|
+
end
|
154
|
+
|
155
|
+
def each_readable (&block)
|
156
|
+
each(:readable, &block)
|
157
|
+
end
|
158
|
+
|
159
|
+
def each_writable (&block)
|
160
|
+
each(:writable, &block)
|
161
|
+
end
|
162
|
+
|
163
|
+
def readable! (*args, &block)
|
164
|
+
args.flatten!
|
165
|
+
args.compact!
|
166
|
+
|
167
|
+
if args.empty?
|
168
|
+
each { |c| readable! c, &block }
|
169
|
+
else
|
170
|
+
args.each {|what|
|
171
|
+
unless readable?(what)
|
172
|
+
what = self[what]
|
173
|
+
|
174
|
+
block.call what if block
|
175
|
+
@readable[what.to_i] = what
|
176
|
+
end
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
self
|
181
|
+
end
|
182
|
+
|
183
|
+
def no_readable! (*args, &block)
|
184
|
+
args.flatten!
|
185
|
+
args.compact!
|
186
|
+
|
187
|
+
if args.empty?
|
188
|
+
each(:readable) { |c| readable! c, &block }
|
189
|
+
else
|
190
|
+
args.each {|what|
|
191
|
+
if what = readable?(what)
|
192
|
+
@readable.delete(what.to_i)
|
193
|
+
block.call what if block
|
194
|
+
end
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
def readable? (what = nil)
|
202
|
+
return !@readable.empty? unless what
|
203
|
+
|
204
|
+
return false unless what = @readable[what.to_i]
|
205
|
+
|
206
|
+
what
|
207
|
+
end
|
208
|
+
|
209
|
+
def writable! (*args, &block)
|
210
|
+
args.flatten!
|
211
|
+
args.compact!
|
212
|
+
|
213
|
+
if args.empty?
|
214
|
+
each { |c| writable! c, &block }
|
215
|
+
else
|
216
|
+
args.each {|what|
|
217
|
+
unless writable?(what)
|
218
|
+
what = self[what]
|
219
|
+
|
220
|
+
block.call what if block
|
221
|
+
@writable[what.to_i] = what
|
222
|
+
end
|
223
|
+
}
|
224
|
+
end
|
225
|
+
|
226
|
+
self
|
227
|
+
end
|
228
|
+
|
229
|
+
def no_writable! (*args, &block)
|
230
|
+
args.flatten!
|
231
|
+
args.compact!
|
232
|
+
|
233
|
+
if args.empty?
|
234
|
+
each(:writable) { |c| writable! c, &block }
|
235
|
+
else
|
236
|
+
args.each {|what|
|
237
|
+
if what = writable?(what)
|
238
|
+
@writable.delete(what.to_i)
|
239
|
+
block.call what if block
|
240
|
+
end
|
241
|
+
}
|
242
|
+
end
|
243
|
+
|
244
|
+
self
|
245
|
+
end
|
246
|
+
|
247
|
+
def writable? (what = nil)
|
248
|
+
return !@writable.empty? unless what
|
249
|
+
|
250
|
+
return false unless what = @writable[what.to_i]
|
251
|
+
|
252
|
+
what
|
253
|
+
end
|
254
|
+
|
255
|
+
def available (timeout = nil, &block)
|
256
|
+
raise NotImplementedError, 'available has not been implemented'
|
257
|
+
end
|
258
|
+
|
259
|
+
def readable (timeout = nil, &block)
|
260
|
+
readable = @readable.dup
|
261
|
+
writable = @writable.dup
|
262
|
+
|
263
|
+
no_writable!
|
264
|
+
readable!
|
265
|
+
|
266
|
+
result = available(timeout, &block)
|
267
|
+
|
268
|
+
no_readable! @readable - readable
|
269
|
+
writable! writable
|
270
|
+
|
271
|
+
result
|
272
|
+
end
|
273
|
+
|
274
|
+
def writable (timeout = nil, &block)
|
275
|
+
readable = @readable.dup
|
276
|
+
writable = @writable.dup
|
277
|
+
|
278
|
+
no_readable!
|
279
|
+
writable!
|
280
|
+
|
281
|
+
result = available(timeout, &block)
|
282
|
+
|
283
|
+
no_writable! @writable - writable
|
284
|
+
readable! readable
|
285
|
+
|
286
|
+
result
|
287
|
+
end
|
288
|
+
|
289
|
+
class Breaker
|
290
|
+
def initialize
|
291
|
+
@pipes = IO.pipe
|
292
|
+
end
|
293
|
+
|
294
|
+
def break (with = nil)
|
295
|
+
@pipes.last.write(Marshal.dump(with))
|
296
|
+
end
|
297
|
+
|
298
|
+
def reason
|
299
|
+
Marshal.load(@pipes.first)
|
300
|
+
end
|
301
|
+
|
302
|
+
def to_io
|
303
|
+
@pipes.first
|
304
|
+
end
|
305
|
+
|
306
|
+
def to_i
|
307
|
+
to_io.to_i
|
308
|
+
end
|
309
|
+
end
|
128
310
|
end
|
129
311
|
|
130
|
-
|
131
|
-
require 'barlume/lanterna/select'
|
132
|
-
require 'barlume/lanterna/poll'
|
133
|
-
require 'barlume/lanterna/epoll'
|
134
|
-
require 'barlume/lanterna/kqueue'
|
312
|
+
end
|