eventmachine 0.5.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,714 @@
1
+ # $Id: pr_eventmachine.rb 264 2006-10-05 16:33:22Z blackhedd $
2
+ #
3
+ # Author:: blackhedd (gmail address: garbagecat10).
4
+ # Date:: 8 Apr 2006
5
+ #
6
+ # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
7
+ #
8
+ # This program is made available under the terms of the GPL version 2.
9
+ #
10
+ # See EventMachine and EventMachine::Connection for documentation and
11
+ # usage examples.
12
+ #
13
+ #----------------------------------------------------------------------------
14
+ #
15
+ # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
16
+ #
17
+ # Gmail: garbagecat10
18
+ #
19
+ # This program is free software; you can redistribute it and/or modify
20
+ # it under the terms of the GNU General Public License as published by
21
+ # the Free Software Foundation; either version 2 of the License, or
22
+ # (at your option) any later version.
23
+ #
24
+ # This program is distributed in the hope that it will be useful,
25
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
26
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
+ # GNU General Public License for more details.
28
+ #
29
+ # You should have received a copy of the GNU General Public License
30
+ # along with this program; if not, write to the Free Software
31
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
32
+ #
33
+ #---------------------------------------------------------------------------
34
+ #
35
+ #
36
+
37
+ # TODO List:
38
+ # TCP-connects currently assume non-blocking connect is available- need to
39
+ # degrade automatically on versions of Ruby prior to June 2006.
40
+ #
41
+
42
+ require 'singleton'
43
+ require 'forwardable'
44
+ require 'socket'
45
+ require 'fcntl'
46
+
47
+
48
+ module EventMachine
49
+
50
+
51
+ class << self
52
+ # This is mostly useful for automated tests.
53
+ # Return a distinctive symbol so the caller knows whether he's dealing
54
+ # with an extension or with a pure-Ruby library.
55
+ def library_type
56
+ :pure_ruby
57
+ end
58
+
59
+ # #initialize_event_machine
60
+ def initialize_event_machine
61
+ Reactor.instance.initialize_for_run
62
+ end
63
+
64
+ # #add_oneshot_timer
65
+ #--
66
+ # Changed 04Oct06: intervals from the caller are now in milliseconds, but our native-ruby
67
+ # processor still wants them in seconds.
68
+ def add_oneshot_timer interval
69
+ Reactor.instance.install_oneshot_timer(interval / 1000)
70
+ end
71
+
72
+ # run_machine
73
+ def run_machine
74
+ Reactor.instance.run
75
+ end
76
+
77
+ # release_machine. Probably a no-op.
78
+ def release_machine
79
+ end
80
+
81
+ # #stop
82
+ def stop
83
+ Reactor.instance.stop
84
+ end
85
+
86
+ # #connect_server. Return a connection descriptor to the caller.
87
+ # TODO, what do we return here if we can't connect?
88
+ def connect_server host, port
89
+ EvmaTCPClient.connect(host, port).uuid
90
+ end
91
+
92
+ # #send_data
93
+ def send_data target, data, datalength
94
+ selectable = Reactor.instance.get_selectable( target ) or raise "unknown send_data target"
95
+ selectable.send_data data
96
+ end
97
+
98
+ # #close_connection
99
+ # The extension version does NOT raise any kind of an error if an attempt is made
100
+ # to close a non-existent connection. Not sure whether we should. For now, we'll
101
+ # raise an error here in that case.
102
+ def close_connection target, after_writing
103
+ selectable = Reactor.instance.get_selectable( target ) or raise "unknown close_connection target"
104
+ selectable.schedule_close after_writing
105
+ end
106
+
107
+ # #start_tcp_server
108
+ def start_tcp_server host, port
109
+ (s = EvmaTCPServer.start_server host, port) or raise "no acceptor"
110
+ s.uuid
111
+ end
112
+
113
+ # #signal_loopbreak
114
+ def signal_loopbreak
115
+ Reactor.instance.signal_loopbreak
116
+ end
117
+
118
+ # #get_peername
119
+ def get_peername sig
120
+ selectable = Reactor.instance.get_selectable( sig ) or raise "unknown get_peername target"
121
+ selectable.get_peername
122
+ end
123
+
124
+ # #open_udp_socket
125
+ def open_udp_socket host, port
126
+ EvmaUDPSocket.create(host, port).uuid
127
+ end
128
+
129
+ # #send_datagram. This is currently only for UDP!
130
+ # We need to make it work with unix-domain sockets as well.
131
+ def send_datagram target, data, datalength, host, port
132
+ selectable = Reactor.instance.get_selectable( target ) or raise "unknown send_data target"
133
+ selectable.send_datagram data, Socket::pack_sockaddr_in(port, host)
134
+ end
135
+
136
+
137
+ # #set_timer_quantum in milliseconds. The underlying Reactor function wants a (possibly
138
+ # fractional) number of seconds.
139
+ def set_timer_quantum interval
140
+ Reactor.instance.set_timer_quantum(( 1.0 * interval) / 1000.0)
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+
147
+
148
+ #-----------------------------------------------------------------
149
+
150
+ module EventMachine
151
+
152
+ class Error < Exception; end
153
+
154
+ end
155
+
156
+ #-----------------------------------------------------------------
157
+
158
+ module EventMachine
159
+
160
+ # Factored out so we can substitute other implementations
161
+ # here if desired, such as the one in ActiveRBAC.
162
+ module UuidGenerator
163
+
164
+ def self.generate
165
+ if @ix and @ix >= 10000
166
+ @ix = nil
167
+ @seed = nil
168
+ end
169
+
170
+ @seed ||= `uuidgen`.chomp.gsub(/-/,"")
171
+ @ix ||= 0
172
+
173
+ "#{@seed}#{@ix += 1}"
174
+ end
175
+
176
+ end
177
+
178
+ end
179
+
180
+ #-----------------------------------------------------------------
181
+
182
+ module EventMachine
183
+
184
+ TimerFired = 100
185
+ ConnectionData = 101
186
+ ConnectionUnbound = 102
187
+ ConnectionAccepted = 103
188
+ ConnectionCompleted = 104
189
+ LoopbreakSignalled = 105
190
+
191
+ end
192
+
193
+ #-----------------------------------------------------------------
194
+
195
+ module EventMachine
196
+ class Reactor
197
+ include Singleton
198
+
199
+ def initialize
200
+ initialize_for_run
201
+ end
202
+
203
+ def install_oneshot_timer interval
204
+ uuid = UuidGenerator::generate
205
+ @timers << [Time.now + interval, uuid]
206
+ @timers.sort! {|a,b| a.first <=> b.first}
207
+ uuid
208
+ end
209
+
210
+ # Called before run, this is a good place to clear out arrays
211
+ # with cruft that may be left over from a previous run.
212
+ def initialize_for_run
213
+ @running = false
214
+ @stop_scheduled = false
215
+ @selectables ||= {}; @selectables.clear
216
+ @timers = []
217
+ set_timer_quantum(0.5)
218
+ end
219
+
220
+ def add_selectable io
221
+ @selectables[io.uuid] = io
222
+ end
223
+
224
+ def get_selectable uuid
225
+ @selectables[uuid]
226
+ end
227
+
228
+ def run
229
+ raise Error.new( "already running" ) if @running
230
+ @running = true
231
+ open_loopbreaker
232
+
233
+ loop {
234
+ break if @stop_scheduled
235
+ run_timers
236
+ break if @stop_scheduled
237
+ crank_selectables
238
+ }
239
+
240
+ close_loopbreaker
241
+ @selectables.each {|k, io| io.close}
242
+ @selectables.clear
243
+
244
+ @running = false
245
+ end
246
+
247
+ def run_timers
248
+ now = Time.now
249
+ while @timers.length > 0 and @timers.first.first <= now
250
+ t = @timers.shift
251
+ EventMachine::event_callback "", TimerFired, t.last
252
+ end
253
+ end
254
+
255
+ def crank_selectables
256
+ #$stderr.write 'R'
257
+
258
+ readers = @selectables.values.select {|io| io.select_for_reading?}
259
+ writers = @selectables.values.select {|io| io.select_for_writing?}
260
+
261
+ s = select( readers, writers, nil, @timer_quantum)
262
+
263
+ s and s[1] and s[1].each {|w| w.eventable_write }
264
+ s and s[0] and s[0].each {|r| r.eventable_read }
265
+
266
+ @selectables.delete_if {|k,io|
267
+ if io.close_scheduled?
268
+ io.close
269
+ true
270
+ end
271
+ }
272
+ end
273
+
274
+ # #stop
275
+ def stop
276
+ raise Error.new( "not running") unless @running
277
+ @stop_scheduled = true
278
+ end
279
+
280
+ def open_loopbreaker
281
+ @loopbreak_writer.close if @loopbreak_writer
282
+ rd,@loopbreak_writer = IO.pipe
283
+ LoopbreakReader.new rd
284
+ end
285
+
286
+ def close_loopbreaker
287
+ @loopbreak_writer.close
288
+ @loopbreak_writer = nil
289
+ end
290
+
291
+ def signal_loopbreak
292
+ @loopbreak_writer.write '+' if @loopbreak_writer
293
+ end
294
+
295
+ def set_timer_quantum interval_in_seconds
296
+ @timer_quantum = interval_in_seconds
297
+ end
298
+
299
+ end
300
+
301
+ end
302
+
303
+
304
+ #--------------------------------------------------------------
305
+
306
+ class IO
307
+ extend Forwardable
308
+ def_delegator :@my_selectable, :close_scheduled?
309
+ def_delegator :@my_selectable, :select_for_reading?
310
+ def_delegator :@my_selectable, :select_for_writing?
311
+ def_delegator :@my_selectable, :eventable_read
312
+ def_delegator :@my_selectable, :eventable_write
313
+ def_delegator :@my_selectable, :uuid
314
+ def_delegator :@my_selectable, :send_data
315
+ def_delegator :@my_selectable, :schedule_close
316
+ def_delegator :@my_selectable, :get_peername
317
+ def_delegator :@my_selectable, :send_datagram
318
+ end
319
+
320
+ #--------------------------------------------------------------
321
+
322
+ module EventMachine
323
+ class Selectable
324
+
325
+ attr_reader :io, :uuid
326
+
327
+ def initialize io
328
+ @uuid = UuidGenerator.generate
329
+ @io = io
330
+
331
+ m = @io.fcntl(Fcntl::F_GETFL, 0)
332
+ @io.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK | m)
333
+ # TODO, should set CLOEXEC on Unix?
334
+
335
+ @close_scheduled = false
336
+ @close_requested = false
337
+
338
+ se = self; @io.instance_eval { @my_selectable = se }
339
+ Reactor.instance.add_selectable @io
340
+ end
341
+
342
+ def close_scheduled?
343
+ @close_scheduled
344
+ end
345
+
346
+ def select_for_reading?
347
+ false
348
+ end
349
+
350
+ def select_for_writing?
351
+ false
352
+ end
353
+
354
+ def get_peername
355
+ nil
356
+ end
357
+
358
+ end
359
+
360
+ end
361
+
362
+ #--------------------------------------------------------------
363
+
364
+
365
+ module EventMachine
366
+
367
+ class StreamObject < Selectable
368
+ def initialize io
369
+ super io
370
+ @outbound_q = []
371
+ end
372
+
373
+ # If we have to close, or a close-after-writing has been requested,
374
+ # then don't read any more data.
375
+ def select_for_reading?
376
+ true unless (@close_scheduled || @close_requested)
377
+ end
378
+
379
+ # If we have to close, don't select for writing.
380
+ # Otherwise, see if the protocol is ready to close.
381
+ # If not, see if he has data to send.
382
+ # If a close-after-writing has been requested and the outbound queue
383
+ # is empty, convert the status to close_scheduled.
384
+ def select_for_writing?
385
+ unless @close_scheduled
386
+ if @outbound_q.empty?
387
+ @close_scheduled = true if @close_requested
388
+ false
389
+ else
390
+ true
391
+ end
392
+ end
393
+ end
394
+
395
+ # Proper nonblocking I/O was added to Ruby 1.8.4 in May 2006.
396
+ # If we have it, then we can read multiple times safely to improve
397
+ # performance.
398
+ # TODO, coalesce multiple reads into a single event.
399
+ # TODO, do the function check somewhere else and cache it.
400
+ def eventable_read
401
+ begin
402
+ if io.respond_to?(:read_nonblock)
403
+ 10.times {
404
+ data = io.read_nonblock(4096)
405
+ EventMachine::event_callback uuid, ConnectionData, data
406
+ }
407
+ else
408
+ data = io.sysread(4096)
409
+ EventMachine::event_callback uuid, ConnectionData, data
410
+ end
411
+ rescue Errno::EAGAIN
412
+ # no-op
413
+ rescue Errno::ECONNRESET, EOFError
414
+ @close_scheduled = true
415
+ EventMachine::event_callback uuid, ConnectionUnbound, nil
416
+ end
417
+
418
+ end
419
+
420
+ # Provisional implementation. Will be re-implemented in subclasses.
421
+ # TODO: Complete this implementation. As it stands, this only writes
422
+ # a single packet per cycle. Highly inefficient, but required unless
423
+ # we're running on a Ruby with proper nonblocking I/O (Ruby 1.8.4
424
+ # built from sources from May 25, 2006 or newer).
425
+ # We need to improve the loop so it writes multiple times, however
426
+ # not more than a certain number of bytes per cycle, otherwise
427
+ # one busy connection could hog output buffers and slow down other
428
+ # connections. Also we should coalesce small writes.
429
+ # URGENT TODO: Coalesce small writes. They are a performance killer.
430
+ def eventable_write
431
+ # coalesce the outbound array here, perhaps
432
+ while data = @outbound_q.shift do
433
+ begin
434
+ data = data.to_s
435
+ w = if io.respond_to?(:write_nonblock)
436
+ io.write_nonblock data
437
+ else
438
+ io.syswrite data
439
+ end
440
+
441
+ if w < data.length
442
+ $outbound_q.unshift data[w..-1]
443
+ break
444
+ end
445
+ rescue Errno::EAGAIN
446
+ @outbound_q.unshift data
447
+ rescue EOFError, Errno::ECONNRESET
448
+ @close_scheduled = true
449
+ @outbound_q.clear
450
+ end
451
+ end
452
+
453
+ end
454
+
455
+ # #send_data
456
+ def send_data data
457
+ # TODO, coalesce here perhaps by being smarter about appending to @outbound_q.last?
458
+ unless @close_scheduled or @close_requested or !data or data.length <= 0
459
+ @outbound_q << data.to_s
460
+ end
461
+ end
462
+
463
+ # #schedule_close
464
+ # The application wants to close the connection.
465
+ def schedule_close after_writing
466
+ if after_writing
467
+ @close_requested = true
468
+ else
469
+ @close_scheduled = true
470
+ end
471
+ end
472
+
473
+ # #get_peername
474
+ # This is defined in the normal way on connected stream objects.
475
+ # Return an object that is suitable for passing to Socket#unpack_sockaddr_in or variants.
476
+ # We could also use a convenience method that did the unpacking automatically.
477
+ def get_peername
478
+ io.getpeername
479
+ end
480
+
481
+ end
482
+
483
+
484
+ end
485
+
486
+
487
+ #--------------------------------------------------------------
488
+
489
+
490
+
491
+ module EventMachine
492
+ class EvmaTCPClient < StreamObject
493
+
494
+ def self.connect host, port
495
+ sd = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
496
+ begin
497
+ # TODO, this assumes a current Ruby snapshot.
498
+ # We need to degrade to a nonblocking connect otherwise.
499
+ sd.connect_nonblock( Socket.pack_sockaddr_in( port, host ))
500
+ rescue Errno::EINPROGRESS
501
+ end
502
+ EvmaTCPClient.new sd
503
+ end
504
+
505
+
506
+ def initialize io
507
+ super
508
+ @pending = true
509
+ end
510
+
511
+
512
+ def select_for_writing?
513
+ @pending ? true : super
514
+ end
515
+
516
+ def select_for_reading?
517
+ @pending ? false : super
518
+ end
519
+
520
+ def eventable_write
521
+ if @pending
522
+ @pending = false
523
+ EventMachine::event_callback uuid, ConnectionCompleted, ""
524
+ else
525
+ super
526
+ end
527
+ end
528
+
529
+
530
+
531
+ end
532
+ end
533
+
534
+
535
+ #--------------------------------------------------------------
536
+
537
+ module EventMachine
538
+ class EvmaTCPServer < Selectable
539
+
540
+ class << self
541
+ # Versions of ruby 1.8.4 later than May 26 2006 will work properly
542
+ # with an object of type TCPServer. Prior versions won't so we
543
+ # play it safe and just build a socket.
544
+ #
545
+ def start_server host, port
546
+ sd = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
547
+ sd.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true )
548
+ sd.bind( Socket.pack_sockaddr_in( port, host ))
549
+ sd.listen( 50 ) # 5 is what you see in all the books. Ain't enough.
550
+ EvmaTCPServer.new sd
551
+ end
552
+ end
553
+
554
+ def initialize io
555
+ super io
556
+ end
557
+
558
+
559
+ def select_for_reading?
560
+ true
561
+ end
562
+
563
+ #--
564
+ # accept_nonblock returns an array consisting of the accepted
565
+ # socket and a sockaddr_in which names the peer.
566
+ # Don't accept more than 10 at a time.
567
+ def eventable_read
568
+ begin
569
+ 10.times {
570
+ descriptor,peername = io.accept_nonblock
571
+ sd = StreamObject.new descriptor
572
+ EventMachine::event_callback uuid, ConnectionAccepted, sd.uuid
573
+ }
574
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
575
+ end
576
+ end
577
+
578
+
579
+ end
580
+ end
581
+
582
+
583
+
584
+ #--------------------------------------------------------------
585
+
586
+ module EventMachine
587
+ class LoopbreakReader < Selectable
588
+
589
+ def select_for_reading?
590
+ true
591
+ end
592
+
593
+ def eventable_read
594
+ io.sysread(128)
595
+ EventMachine::event_callback "", LoopbreakSignalled, ""
596
+ end
597
+
598
+ end
599
+ end
600
+
601
+ #--------------------------------------------------------------
602
+
603
+
604
+ module EventMachine
605
+
606
+ class DatagramObject < Selectable
607
+ def initialize io
608
+ super io
609
+ @outbound_q = []
610
+ end
611
+
612
+ # #send_datagram
613
+ def send_datagram data, target
614
+ # TODO, coalesce here perhaps by being smarter about appending to @outbound_q.last?
615
+ unless @close_scheduled or @close_requested
616
+ @outbound_q << [data.to_s, target]
617
+ end
618
+ end
619
+
620
+ # #select_for_writing?
621
+ def select_for_writing?
622
+ unless @close_scheduled
623
+ if @outbound_q.empty?
624
+ @close_scheduled = true if @close_requested
625
+ false
626
+ else
627
+ true
628
+ end
629
+ end
630
+ end
631
+
632
+ # #select_for_reading?
633
+ def select_for_reading?
634
+ true
635
+ end
636
+
637
+
638
+ end
639
+
640
+
641
+ end
642
+
643
+
644
+ #--------------------------------------------------------------
645
+
646
+ module EventMachine
647
+ class EvmaUDPSocket < DatagramObject
648
+
649
+ class << self
650
+ def create host, port
651
+ sd = Socket.new( Socket::AF_INET, Socket::SOCK_DGRAM, 0 )
652
+ sd.bind Socket::pack_sockaddr_in( port, host )
653
+ EvmaUDPSocket.new sd
654
+ end
655
+ end
656
+
657
+ # #eventable_write
658
+ # This really belongs in DatagramObject, but there is some UDP-specific stuff.
659
+ def eventable_write
660
+ 40.times {
661
+ break if @outbound_q.empty?
662
+ begin
663
+ data,target = @outbound_q.first
664
+
665
+ # This damn better be nonblocking.
666
+ io.send data.to_s, 0, target
667
+
668
+ @outbound_q.shift
669
+ rescue Errno::EAGAIN
670
+ # It's not been observed in testing that we ever get here.
671
+ # True to the definition, packets will be accepted and quietly dropped
672
+ # if the system is under pressure.
673
+ break
674
+ rescue EOFError, Errno::ECONNRESET
675
+ @close_scheduled = true
676
+ @outbound_q.clear
677
+ end
678
+ }
679
+ end
680
+
681
+ # Proper nonblocking I/O was added to Ruby 1.8.4 in May 2006.
682
+ # If we have it, then we can read multiple times safely to improve
683
+ # performance.
684
+ def eventable_read
685
+ begin
686
+ if io.respond_to?(:recvfrom_nonblock)
687
+ 40.times {
688
+ data,@return_address = io.recvfrom_nonblock(16384)
689
+ EventMachine::event_callback uuid, ConnectionData, data
690
+ @return_address = nil
691
+ }
692
+ else
693
+ raise "unimplemented datagram-read operation on this Ruby"
694
+ end
695
+ rescue Errno::EAGAIN
696
+ # no-op
697
+ rescue Errno::ECONNRESET, EOFError
698
+ @close_scheduled = true
699
+ EventMachine::event_callback uuid, ConnectionUnbound, nil
700
+ end
701
+
702
+ end
703
+
704
+
705
+ def send_data data
706
+ send_datagram data, @return_address
707
+ end
708
+
709
+ end
710
+ end
711
+
712
+ #--------------------------------------------------------------
713
+
714
+