lightio 0.1.0.pre → 0.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 64cfa9cd4f221073198b6f2e2beba25830ee2e46
4
- data.tar.gz: 53e310d65402475c686b263bec4ed269ce342020
3
+ metadata.gz: 0b2408fef926bf934a659bdd99bb75218c0bb940
4
+ data.tar.gz: 21c19305f235b572a8131a8d8059b0038701c79e
5
5
  SHA512:
6
- metadata.gz: '0199c278d56bfad9eb43e23f4e65eec9bac6a9ac9fbbcd7a85232fd4133e8af0f6a76641a962cc26981c36dcd2de61b61cce98d25e9991e9cab96f224eb83db0'
7
- data.tar.gz: 157c3c8a07ee0e6fe2bdc5e3a78a4b6dac6c1c11181db416275d90704b01e9f46e83156923e2badf9c07a92203292c59d5c6626d0770c6dda96f64e2252b9226
6
+ metadata.gz: 3f11ea750958ac0d3c64c4db0ec97c5e1025ada8d1354f7e858f956b0e8e7aacd64fd0983831c9421039dd70c939bc4df466de24888587527962bf095257eb8c
7
+ data.tar.gz: 00db900dd4891ab01d6dc971e769d8863e64751e2ccf7a70d936413049316ec956c9ca3c5d3db62a30db87f143f0987d36160c8056f589a5e3e39df3484cba91
data/.travis.yml CHANGED
@@ -1,5 +1,18 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
+ - jruby-9.1.15.0 # latest stable
5
+ - 2.2.7
6
+ - 2.3.4
4
7
  - 2.4.1
5
- before_install: gem install bundler -v 1.16.0
8
+ - ruby-head
9
+
10
+ env:
11
+ global:
12
+ - JRUBY_OPTS="--dev -J-Djruby.launch.inproc=true -J-Xmx1024M"
13
+
14
+ matrix:
15
+ allow_failures:
16
+ - rvm: ruby-head
17
+ - rvm: jruby-9.1.15.0
18
+ fast_finish: true
data/Gemfile.lock CHANGED
@@ -2,7 +2,7 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  lightio (0.1.0.pre)
5
- nio4r
5
+ nio4r (~> 2.1)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,8 +1,28 @@
1
- # Lightio
1
+ # LightIO
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/lightio`. To experiment with that code, run `bin/console` for an interactive prompt.
4
3
 
5
- TODO: Delete this and the text above, and describe your gem
4
+ [![Gem Version](https://badge.fury.io/rb/lightio.svg)](http://rubygems.org/gems/lightio)
5
+ [![Build Status](https://travis-ci.org/jjyr/lightio.svg?branch=master)](https://travis-ci.org/jjyr/lightio)
6
+ [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jjyr/lightio/blob/master/LICENSE.txt)
7
+
8
+ LightIO is a ruby networking library, that combines ruby fiber and IO event loop to provide both simple synchrony library interface and high performance networking IO.
9
+
10
+
11
+ LightIO is heavily inspired by [gevent](http://www.gevent.org/).
12
+
13
+ ## Current Status
14
+
15
+ This library is still **WIP**, *watch* or *star* this repo for further information.
16
+ Its not recommendation to use LightIO in production now, but you can always give a try. open a issue if you have any question.
17
+
18
+ Before we release a full networking framework at the version 1.0.0, there are three targets(milestones).
19
+
20
+ 1. Provide a bare-bone framework, include fiber based lightweight executor and a way to collebrate with ruby socket library
21
+ 2. Provide lightio networking libraries, that have the same API with ruby stdlib
22
+ 3. Implement ruby stdlib monkey patch, user can apply monkey patch and just write normal ruby code to get the power of lightio
23
+
24
+ Thanks to [nio4r](https://github.com/socketry/nio4r), the first target is already achieved.
25
+
6
26
 
7
27
  ## Installation
8
28
 
@@ -22,7 +42,35 @@ Or install it yourself as:
22
42
 
23
43
  ## Usage
24
44
 
25
- TODO: Write usage instructions here
45
+ ``` ruby
46
+ require 'lightio'
47
+
48
+ start = Time.now
49
+
50
+ beams = 1000.times.map do
51
+ # LightIO::Beam is a thread-like executor, use it instead Thread
52
+ LightIO::Beam.new do
53
+ # do some io operations in beam
54
+ LightIO.sleep(1)
55
+ end
56
+ end
57
+
58
+ beams.each(&:join)
59
+ seconds = Time.now - start
60
+ puts "1000 beams take #{seconds - 1} seconds to create"
61
+ ```
62
+
63
+ View more [examples](/examples).
64
+
65
+ ## Documentation
66
+
67
+ See [wiki](https://github.com/jjyr/lightio/wiki) for more information
68
+
69
+ [API Documentation](http://www.rubydoc.info/gems/lightio/frames)
70
+
71
+ ## Discussion
72
+
73
+ [Discussion on Gitter](https://gitter.im/lightio-dev/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link)
26
74
 
27
75
  ## Development
28
76
 
@@ -32,7 +80,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
80
 
33
81
  ## Contributing
34
82
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/lightio. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
83
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jjyr/lightio. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
84
 
37
85
  ## License
38
86
 
data/examples/beams.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'lightio'
2
+
3
+ start = Time.now
4
+
5
+ beams = 1000.times.map do
6
+ # LightIO::Beam is a thread-like executor, use it instead Thread
7
+ LightIO::Beam.new do
8
+ # do some io operations in beam
9
+ LightIO.sleep(1)
10
+ end
11
+ end
12
+
13
+ beams.each(&:join)
14
+ seconds = Time.now - start
15
+ puts "1000 beams take #{seconds - 1} seconds to create"
@@ -0,0 +1,47 @@
1
+ # Example from https://github.com/socketry/nio4r/blob/master/examples/echo_server.rb
2
+ # rewrite it in lightio for demonstrate
3
+ # this example demonstrate how to use ruby 'raw' socket with LightIO
4
+
5
+ require 'lightio'
6
+ require 'socket'
7
+
8
+ class EchoServer
9
+ def initialize(host, port)
10
+ @server = TCPServer.new(host, port)
11
+ end
12
+
13
+ def run
14
+ # wait server until readable
15
+ server_watcher = LightIO::Watchers::IO.new(@server, :r)
16
+ while server_watcher.wait_read
17
+ socket = @server.accept
18
+ _, port, host = socket.peeraddr
19
+ puts "accept connection from #{host}:#{port}"
20
+
21
+ # LightIO::Beam is lightweight executor, provide thread-like interface
22
+ # just start new beam for per socket
23
+ LightIO::Beam.new(socket) do |socket|
24
+ socket_watcher = LightIO::Watchers::IO.new(socket, :r)
25
+ begin
26
+ while socket_watcher.wait_read
27
+ echo(socket)
28
+ end
29
+ rescue EOFError
30
+ _, port, host = socket.peeraddr
31
+ puts "*** #{host}:#{port} disconnected"
32
+ # remove close socket watcher
33
+ socket_watcher.close
34
+ socket.close
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ def echo(socket)
41
+ data = socket.read_nonblock(4096)
42
+ socket.write_nonblock(data)
43
+ end
44
+ end
45
+
46
+
47
+ EchoServer.new('localhost', 3000).run if __FILE__ == $0
data/lib/lightio.rb CHANGED
@@ -1,10 +1,15 @@
1
+ # LightIO
1
2
  require 'lightio/version'
2
- require 'lightio/beam'
3
- require 'lightio/future'
4
- require 'lightio/ioloop'
5
- require 'lightio/watcher'
6
- require 'lightio/timer'
7
- require 'lightio/backend/nio'
3
+ require 'lightio/errors'
4
+ require 'lightio/core'
5
+ require 'lightio/watchers'
6
+ require 'lightio/library'
8
7
 
8
+ # LightIO provide light-weight executor: LightIO::Beam and batch io libraries,
9
+ # view LightIO::Core::Beam to learn how to concurrent programming with Beam,
10
+ # view LightIO::Watchers::IO to learn how to manage 'raw' io objects,
11
+ # Core and Library modules are included under LightIO namespace, so you can use LightIO::Beam for convenient
9
12
  module LightIO
13
+ include Core
14
+ include Library
10
15
  end
@@ -0,0 +1,11 @@
1
+ # Core
2
+ require 'lightio/core/ioloop'
3
+ require 'lightio/core/light_fiber'
4
+ require 'lightio/core/future'
5
+ require 'lightio/core/beam'
6
+
7
+ # LightIO::Core include core classes: Beam, IOloop, Future
8
+ #
9
+ # view examples for clues
10
+ module LightIO::Core
11
+ end
@@ -1,6 +1,7 @@
1
1
  # use nio4r implement event loop, inspired from eventmachine/pure_ruby implement
2
+ require 'nio'
2
3
  require 'set'
3
- module LightIO
4
+ module LightIO::Core
4
5
  module Backend
5
6
 
6
7
  class Error < RuntimeError
@@ -46,12 +47,15 @@ module LightIO
46
47
  end
47
48
  end
48
49
 
50
+ # LightIO use NIO as default event-driving backend
49
51
  class NIO
50
52
  def initialize
51
53
  # @selector = NIO::Selector.new
52
54
  @current_loop_time = nil
53
55
  @running = false
54
56
  @timers = Timers.new
57
+ @callbacks = []
58
+ @selector = ::NIO::Selector.new
55
59
  end
56
60
 
57
61
  def run
@@ -60,11 +64,14 @@ module LightIO
60
64
  loop do
61
65
  @current_loop_time = Time.now
62
66
  run_timers
67
+ run_callbacks
68
+ handle_selectables
63
69
  end
64
70
  end
65
71
 
66
- def run_timers
67
- @timers.fire(@current_loop_time)
72
+
73
+ def add_callback(&blk)
74
+ @callbacks << blk
68
75
  end
69
76
 
70
77
  def add_timer(timer)
@@ -75,11 +82,42 @@ module LightIO
75
82
  @timers.cancel_timer(timer)
76
83
  end
77
84
 
85
+ def add_io_wait(io, interests, &blk)
86
+ monitor = @selector.register(io, interests)
87
+ monitor.value = blk
88
+ monitor
89
+ end
90
+
91
+ def cancel_io_wait(io)
92
+ @selector.deregister(io)
93
+ end
94
+
78
95
  def stop
79
96
  return unless @running
80
97
  @running = false
81
98
  raise
82
99
  end
100
+
101
+ private
102
+
103
+ def run_timers
104
+ @timers.fire(@current_loop_time)
105
+ end
106
+
107
+ def handle_selectables
108
+ @selector.select(0) do |monitor|
109
+ # invoke callback if io is ready
110
+ if monitor.readiness
111
+ monitor.value.call(monitor.io)
112
+ end
113
+ end
114
+ end
115
+
116
+ def run_callbacks
117
+ while (callback = @callbacks.shift)
118
+ callback.call
119
+ end
120
+ end
83
121
  end
84
122
  end
85
123
  end
@@ -0,0 +1,133 @@
1
+ module LightIO::Core
2
+ # Beam is light-weight executor, provide thread-like interface
3
+ #
4
+ # @Example:
5
+ # #- initialize with block
6
+ # b = Beam.new{puts "hello"}
7
+ # b.join
8
+ # #output: hello
9
+ #
10
+ # b = Beam.new(1,2,3){|one, two, three| puts [one, two, three].join(",") }
11
+ # b.join
12
+ # #output: 1,2,3
13
+ #
14
+ # #- use join wait beam done
15
+ # b = Beam.new(){LightIO.sleep 3}
16
+ # b.join
17
+ # b.alive? # false
18
+ class Beam < LightFiber
19
+
20
+ # Create a new beam
21
+ #
22
+ # Beam is light-weight executor, provide thread-like interface
23
+ #
24
+ # Beam.new("hello"){|hello| puts hello }
25
+ #
26
+ # @param [Array] args pass arguments to Beam block
27
+ # @param [Proc] blk block to execute
28
+ # @return [Beam]
29
+ def initialize(*args, &blk)
30
+ raise Error, "must be called with a block" unless blk
31
+ super() {
32
+ begin
33
+ @value = yield(*args)
34
+ rescue StandardError => e
35
+ @error = e
36
+ end
37
+ # mark as dead
38
+ dead
39
+ # transfer back to parent(caller fiber) after schedule
40
+ parent.transfer
41
+ }
42
+ # schedule beam in ioloop
43
+ ioloop.add_callback {transfer}
44
+ @alive = true
45
+ end
46
+
47
+ def alive?
48
+ super && @alive
49
+ end
50
+
51
+ # block and wait beam return a value
52
+ def value
53
+ if alive?
54
+ self.parent = Beam.current
55
+ ioloop.transfer
56
+ end
57
+ check_and_raise_error
58
+ @value
59
+ end
60
+
61
+ # Block and wait beam dead
62
+ #
63
+ # @param [Numeric] limit wait limit seconds if limit > 0, return nil if beam still alive, else return beam self
64
+ # @return [Beam, nil]
65
+ def join(limit=0)
66
+ # try directly get result
67
+ if !alive? || limit <= 0
68
+ # call value to raise error
69
+ value
70
+ return self
71
+ end
72
+
73
+ # set a transfer back timer
74
+ parent = Beam.current
75
+ timer = LightIO::Watchers::Timer.new(limit)
76
+ timer.set_callback {parent.transfer}
77
+ ioloop.add_timer(timer)
78
+ ioloop.transfer
79
+
80
+ if alive?
81
+ nil
82
+ else
83
+ check_and_raise_error
84
+ self
85
+ end
86
+ end
87
+
88
+ # Kill beam
89
+ #
90
+ # @return [Beam]
91
+ def kill
92
+ dead
93
+ parent.transfer if self == Beam.current
94
+ self
95
+ end
96
+
97
+ class << self
98
+
99
+ # Schedule beams
100
+ #
101
+ # normally beam should be auto scheduled, use this method to manually trigger a schedule
102
+ #
103
+ # @return [nil]
104
+ def pass
105
+ schedule = LightIO::Watchers::Schedule.new
106
+ IOloop.current.wait(schedule)
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ # mark beam as dead
113
+ def dead
114
+ @alive = false
115
+ end
116
+
117
+ # Beam transfer back to parent after schedule
118
+ # parent is fiber or beam who called value/join methods
119
+ # if not present a parent, Beam will transfer to ioloop
120
+ def parent=(parent)
121
+ @parent = parent
122
+ end
123
+
124
+ # get parent/ioloop to transfer back
125
+ def parent
126
+ @parent || ioloop
127
+ end
128
+
129
+ def check_and_raise_error
130
+ raise @error if @error
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,48 @@
1
+ module LightIO::Core
2
+ # Provide a safe way to transfer beam/fiber control flow.
3
+ #
4
+ # @Example:
5
+ # future = Future.new
6
+ # # future#value will block current beam
7
+ # Beam.new{future.value}
8
+ # # use transfer to set value
9
+ # future.transfer(1)
10
+ class Future
11
+ def initialize
12
+ @value = nil
13
+ @ioloop = IOloop.current
14
+ @state = :init
15
+ end
16
+
17
+ def done?
18
+ @state == :done
19
+ end
20
+
21
+ # Transfer and set result value
22
+ #
23
+ # use this method to set back result
24
+ def transfer(value=nil)
25
+ raise Error, "state error" if done?
26
+ @value = value
27
+ done!
28
+ @light_fiber.transfer if @light_fiber
29
+ end
30
+
31
+ # Get value
32
+ #
33
+ # this method will block current beam/fiber, until future result is set.
34
+ def value
35
+ return @value if done?
36
+ raise Error, 'already used' if @light_fiber
37
+ @light_fiber = LightFiber.current
38
+ @ioloop.transfer
39
+ @value
40
+ end
41
+
42
+ private
43
+
44
+ def done!
45
+ @state = :done
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,66 @@
1
+ require 'lightio/core/backend/nio'
2
+ module LightIO::Core
3
+ # IOloop like a per-threaded EventMachine (cause fiber cannot resume cross threads)
4
+ #
5
+ # IOloop handle io waiting and schedule beams, user do not supposed to directly use this class
6
+ class IOloop
7
+
8
+ def initialize
9
+ @fiber = Fiber.new {run}
10
+ @backend = Backend::NIO.new
11
+ end
12
+
13
+ # should never invoke explicitly
14
+ def run
15
+ # start io loop and never return...
16
+ @backend.run
17
+ end
18
+
19
+ def add_timer(timer)
20
+ @backend.add_timer(timer)
21
+ end
22
+
23
+ def add_callback(&blk)
24
+ @backend.add_callback(&blk)
25
+ end
26
+
27
+ def add_io_wait(io, interests, &blk)
28
+ @backend.add_io_wait(io, interests, &blk)
29
+ end
30
+
31
+ def cancel_io_wait(io)
32
+ @backend.cancel_io_wait(io)
33
+ end
34
+
35
+ # Wait a watcher, watcher can be a timer or socket.
36
+ # see LightIO::Watchers module for detail
37
+ def wait(watcher)
38
+ future = Future.new
39
+ # add watcher to loop
40
+ id = Object.new
41
+ watcher.set_callback {future.transfer id}
42
+ watcher.start(self)
43
+ # trigger a fiber switch
44
+ # wait until watcher is ok
45
+ # then do work
46
+ if (result = future.value) != id
47
+ raise InvalidTransferError, "expect #{id}, but get #{result}"
48
+ end
49
+ end
50
+
51
+ def transfer
52
+ @fiber.transfer
53
+ end
54
+
55
+ class << self
56
+ # return current ioloop or create new one
57
+ def current
58
+ key = :"lightio.ioloop"
59
+ unless Thread.current.thread_variable?(key)
60
+ Thread.current.thread_variable_set(key, IOloop.new)
61
+ end
62
+ Thread.current.thread_variable_get(key)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,15 @@
1
+ require 'fiber'
2
+
3
+ module LightIO::Core
4
+ # LightFiber is internal represent, we make slight extend on ruby Fiber to bind fibers to IOLoop
5
+ #
6
+ # SHOULD NOT BE USED DIRECTLY
7
+ class LightFiber < Fiber
8
+ attr_reader :ioloop
9
+
10
+ def initialize(ioloop: IOloop.current, &blk)
11
+ @ioloop = ioloop
12
+ super(&blk)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module LightIO
2
+ class Error < RuntimeError
3
+ end
4
+
5
+ class InvalidTransferError < Error
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ require 'lightio/library/queue'
2
+ require 'lightio/library/kernel_ext'
3
+ require 'lightio/library/timeout'
4
+
5
+ module LightIO
6
+ # Library include modules can cooperative with LightIO::Beam
7
+ module Library
8
+ # extend library modules
9
+ def self.included(base)
10
+ base.extend KernelExt
11
+ base.extend Timeout
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module LightIO::Library
2
+ module KernelExt
3
+ def sleep(*duration)
4
+ if duration.size > 1
5
+ raise ArgumentError, "wrong number of arguments (given #{duration.size}, expected 0..1)"
6
+ elsif duration.size == 0
7
+ LightIO::IOloop.current.transfer
8
+ end
9
+ duration = duration[0]
10
+ if duration.zero? && LightIO::Beam.current.respond_to?(:pass)
11
+ LightIO::Beam.current.pass
12
+ return
13
+ end
14
+ timer = LightIO::Watchers::Timer.new duration
15
+ LightIO::IOloop.current.wait(timer)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,100 @@
1
+ module LightIO::Library
2
+ class Queue
3
+ def initialize
4
+ @queue = []
5
+ @waiters = []
6
+ @close = false
7
+ end
8
+
9
+ def close()
10
+ #This is a stub, used for indexing
11
+ @close = true
12
+ @waiters.each {|w| w.transfer nil}
13
+ self
14
+ end
15
+
16
+ # closed?
17
+ #
18
+ # Returns +true+ if the queue is closed.
19
+ def closed?()
20
+ @close
21
+ end
22
+
23
+ # push(object)
24
+ # enq(object)
25
+ # <<(object)
26
+ #
27
+ # Pushes the given +object+ to the queue.
28
+ def push(object)
29
+ raise ClosedQueueError, "queue closed" if @close
30
+ if @waiters.any?
31
+ future = LightIO::Future.new
32
+ LightIO::IOloop.current.add_callback {
33
+ @waiters.shift.transfer(object)
34
+ future.transfer
35
+ }
36
+ future.value
37
+ else
38
+ @queue << object
39
+ end
40
+ self
41
+ end
42
+
43
+ alias enq push
44
+ alias << push
45
+ # pop(non_block=false)
46
+ # deq(non_block=false)
47
+ # shift(non_block=false)
48
+ #
49
+ # Retrieves data from the queue.
50
+ #
51
+ # If the queue is empty, the calling thread is suspended until data is pushed
52
+ # onto the queue. If +non_block+ is true, the thread isn't suspended, and an
53
+ # exception is raised.
54
+ def pop(non_block=false)
55
+ if @close
56
+ return empty? ? nil : @queue.pop
57
+ end
58
+ if empty?
59
+ if non_block
60
+ raise ThreadError, 'queue empty'
61
+ else
62
+ future = LightIO::Future.new
63
+ @waiters << future
64
+ future.value
65
+ end
66
+ else
67
+ @queue.pop
68
+ end
69
+ end
70
+
71
+ alias deq pop
72
+ alias shift pop
73
+ # empty?
74
+ #
75
+ # Returns +true+ if the queue is empty.
76
+ def empty?()
77
+ @queue.empty?
78
+ end
79
+
80
+ # Removes all objects from the queue.
81
+ def clear()
82
+ @queue.clear
83
+ self
84
+ end
85
+
86
+ # length
87
+ # size
88
+ #
89
+ # Returns the length of the queue.
90
+ def length()
91
+ @queue.size
92
+ end
93
+
94
+ alias size length
95
+ # Returns the number of threads waiting on the queue.
96
+ def num_waiting()
97
+ @waiters.size
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,18 @@
1
+ require 'timeout'
2
+ module LightIO::Library
3
+ module Timeout
4
+ extend self
5
+ Error = ::Timeout::Error
6
+
7
+ def timeout(sec, klass=Error, &blk)
8
+ return yield(sec) if sec.nil? or sec.zero?
9
+ beam = LightIO::Beam.new(sec, &blk)
10
+ message = "execution expired"
11
+ if beam.join(sec).nil?
12
+ raise klass, message
13
+ else
14
+ beam.value
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
- module Lightio
2
- VERSION = "0.1.0.pre"
1
+ module LightIO
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,9 @@
1
+ require 'lightio/watchers/watcher'
2
+ require 'lightio/watchers/timer'
3
+ require 'lightio/watchers/schedule'
4
+ require 'lightio/watchers/io'
5
+
6
+ # Watcher is a abstract struct, for libraries to interact with ioloop
7
+ # see IOloop#wait method
8
+ module LightIO::Watchers
9
+ end
@@ -0,0 +1,110 @@
1
+ module LightIO::Watchers
2
+ # LightIO::Watchers::IO provide a NIO::Monitor wrap to manage 'raw' socket / io
3
+ #
4
+ # @Example:
5
+ # #- wait_read for server socket
6
+ # io_watcher = LightIO::Watchers::IO.new(server_socket, :r)
7
+ # loop do
8
+ # io_watcher.wait_read
9
+ # client_socket = server_socket.accept
10
+ # # do something
11
+ # end
12
+ # io_watcher.close
13
+ class IO < Watcher
14
+ # Create a io watcher
15
+ # @param [Socket] io An IO-able object
16
+ # @param [Symbol] interests :r, :w, :rw - Is io readable? writeable? or both
17
+ # @return [LightIO::Watchers::IO]
18
+ def initialize(io, interests)
19
+ @io = io
20
+ @ioloop = LightIO::Core::IOloop.current
21
+ @waiting = false
22
+ @wait_for = nil
23
+ # NIO monitor
24
+ @monitor = @ioloop.add_io_wait(@io, interests) {callback_on_waiting}
25
+ end
26
+
27
+ def interests
28
+ @monitor.interests
29
+ end
30
+
31
+ # Replace current interests
32
+ def interests=(interests)
33
+ @monitor.interests = interests
34
+ end
35
+
36
+ # Blocking until io interests is satisfied
37
+ def wait_for(interests)
38
+ if (self.interests == :w || self.interests == :r) && interests != self.interests
39
+ raise ArgumentError, "IO interests is #{self.interests}, can't waiting for #{interests}"
40
+ end
41
+ @wait_for = interests
42
+ wait
43
+ end
44
+
45
+ # Blocking until io is readable
46
+ # @param [Numeric] timeout return nil after timeout seconds, otherwise return self
47
+ # @return [LightIO::Watchers::IO, nil]
48
+ def wait_read(timeout=nil)
49
+ LightIO::Timeout.timeout(timeout) do
50
+ wait_for :r
51
+ self
52
+ end
53
+ rescue Timeout::Error
54
+ nil
55
+ end
56
+
57
+ # Blocking until io is writeable
58
+ # @param [Numeric] timeout return nil after timeout seconds, otherwise return self
59
+ # @return [LightIO::Watchers::IO, nil]
60
+ def wait_write(timeout=nil)
61
+ LightIO::Timeout.timeout(timeout) do
62
+ wait_for :w
63
+ self
64
+ end
65
+ rescue Timeout::Error
66
+ nil
67
+ end
68
+
69
+
70
+ def start(ioloop)
71
+ # do nothing
72
+ end
73
+
74
+ # stop io listening
75
+ def close
76
+ @monitor.close
77
+ end
78
+
79
+ def close?
80
+ @monitor.close?
81
+ end
82
+
83
+ def wait
84
+ raise LightIO::Error, "Watchers::IO can't cross threads" if @ioloop != LightIO::Core::IOloop.current
85
+ raise EOFError, "can't wait closed IO watcher" if @monitor.closed?
86
+ @waiting = true
87
+ @ioloop.wait(self)
88
+ @waiting = false
89
+ end
90
+
91
+ def set_callback(&blk)
92
+ @callback = blk
93
+ end
94
+
95
+ private
96
+
97
+ def callback_on_waiting
98
+ # only call callback on waiting
99
+ callback.call if @waiting && io_is_ready?
100
+ end
101
+
102
+ def io_is_ready?
103
+ if @wait_for == :r
104
+ @monitor.readable?
105
+ elsif @wait_for == :w
106
+ @monitor.writeable?
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,9 @@
1
+ module LightIO
2
+ module Watchers
3
+ class Schedule < Watcher
4
+ def start(ioloop)
5
+ ioloop.add_callback(&@callback)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module LightIO
2
+ module Watchers
3
+ class Timer < Watcher
4
+ attr_reader :interval
5
+ attr_accessor :uuid
6
+
7
+ def initialize(interval)
8
+ @interval = interval
9
+ end
10
+
11
+ def start(ioloop)
12
+ ioloop.add_timer(self)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module LightIO
2
+ module Watchers
3
+ class Watcher
4
+ attr_reader :callback
5
+
6
+ def set_callback(&blk)
7
+ raise Error, "already has callback" if @callback
8
+ @callback = blk
9
+ end
10
+
11
+ def start(backend)
12
+ raise
13
+ end
14
+ end
15
+ end
16
+ end
data/lightio.gemspec CHANGED
@@ -5,12 +5,12 @@ require "lightio/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "lightio"
8
- spec.version = Lightio::VERSION
8
+ spec.version = LightIO::VERSION
9
9
  spec.authors = ["jjy"]
10
10
  spec.email = ["jjyruby@gmail.com"]
11
11
 
12
- spec.summary = %q{LightIO is a light weight, user-transparent asynchronous IO library}
13
- spec.description = %q{LightIO's goal is provide simple, transparent and efficient IO operation to ruby world}
12
+ spec.summary = %q{LightIO is a high performance ruby networking library}
13
+ spec.description = %q{LightIO combines ruby fiber and IO event loop to provide both simple synchrony library interface and high performance networking IO}
14
14
  spec.homepage = "https://github.com/jjyr/lightio"
15
15
  spec.license = "MIT"
16
16
 
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_runtime_dependency "nio4r"
24
+ spec.add_runtime_dependency "nio4r", "~> 2.1"
25
25
  spec.add_development_dependency "bundler", "~> 1.16"
26
26
  spec.add_development_dependency "rake", "~> 10.0"
27
27
  spec.add_development_dependency "rspec", "~> 3.0"
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lightio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - jjy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-11-29 00:00:00.000000000 Z
11
+ date: 2017-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '2.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '2.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -66,8 +66,8 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.0'
69
- description: LightIO's goal is provide simple, transparent and efficient IO operation
70
- to ruby world
69
+ description: LightIO combines ruby fiber and IO event loop to provide both simple
70
+ synchrony library interface and high performance networking IO
71
71
  email:
72
72
  - jjyruby@gmail.com
73
73
  executables: []
@@ -85,14 +85,26 @@ files:
85
85
  - Rakefile
86
86
  - bin/console
87
87
  - bin/setup
88
+ - examples/beams.rb
89
+ - examples/echo_server_with_raw_socket.rb
88
90
  - lib/lightio.rb
89
- - lib/lightio/backend/nio.rb
90
- - lib/lightio/beam.rb
91
- - lib/lightio/future.rb
92
- - lib/lightio/ioloop.rb
93
- - lib/lightio/timer.rb
91
+ - lib/lightio/core.rb
92
+ - lib/lightio/core/backend/nio.rb
93
+ - lib/lightio/core/beam.rb
94
+ - lib/lightio/core/future.rb
95
+ - lib/lightio/core/ioloop.rb
96
+ - lib/lightio/core/light_fiber.rb
97
+ - lib/lightio/errors.rb
98
+ - lib/lightio/library.rb
99
+ - lib/lightio/library/kernel_ext.rb
100
+ - lib/lightio/library/queue.rb
101
+ - lib/lightio/library/timeout.rb
94
102
  - lib/lightio/version.rb
95
- - lib/lightio/watcher.rb
103
+ - lib/lightio/watchers.rb
104
+ - lib/lightio/watchers/io.rb
105
+ - lib/lightio/watchers/schedule.rb
106
+ - lib/lightio/watchers/timer.rb
107
+ - lib/lightio/watchers/watcher.rb
96
108
  - lightio.gemspec
97
109
  homepage: https://github.com/jjyr/lightio
98
110
  licenses:
@@ -109,13 +121,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
121
  version: '0'
110
122
  required_rubygems_version: !ruby/object:Gem::Requirement
111
123
  requirements:
112
- - - ">"
124
+ - - ">="
113
125
  - !ruby/object:Gem::Version
114
- version: 1.3.1
126
+ version: '0'
115
127
  requirements: []
116
128
  rubyforge_project:
117
129
  rubygems_version: 2.6.11
118
130
  signing_key:
119
131
  specification_version: 4
120
- summary: LightIO is a light weight, user-transparent asynchronous IO library
132
+ summary: LightIO is a high performance ruby networking library
121
133
  test_files: []
data/lib/lightio/beam.rb DELETED
@@ -1,12 +0,0 @@
1
- require 'fiber'
2
-
3
- # Beam, a minimal executor unit, like thread but lightweight
4
- # must have a ioloop
5
- module LightIO
6
- class Beam < Fiber
7
- def initialize ioloop:, &blk
8
- @ioloop = ioloop
9
- super(&blk)
10
- end
11
- end
12
- end
@@ -1,35 +0,0 @@
1
- # Future, provide another way to operate Beam and Ioloop
2
-
3
- module LightIO
4
- class Future
5
- def initialize
6
- @value = nil
7
- @ioloop = IOloop.current
8
- @state = :init
9
- end
10
-
11
- def done?
12
- @state == :done
13
- end
14
-
15
- def done!
16
- @state = :done
17
- end
18
-
19
- # transfer and set value
20
- def transfer(value=nil)
21
- raise Error, "state error" if done?
22
- @value = value
23
- done!
24
- @beam.transfer if @beam
25
- end
26
-
27
- # block current beam/fiber and get value
28
- def value
29
- return @value if done?
30
- @beam = Beam.current
31
- @ioloop.transfer
32
- @value
33
- end
34
- end
35
- end
@@ -1,40 +0,0 @@
1
- # IOloop like a per-threaded Eventmachine (cause fiber cannot resume cross threads)
2
- # join, 等待 IOloop 结束
3
- # 在 loop 中为 waiter (future-like proxy object) 对象返回结果
4
- module LightIO
5
- class IOloop
6
- def initialize
7
- @fiber = Fiber.new {run}
8
- @backend = Backend::NIO.new
9
- end
10
-
11
- # should never invoke explicitly
12
- def run
13
- # start io loop and never return...
14
- @backend.run
15
- end
16
-
17
- # wait a watcher, maybe a timer or socket
18
- def wait(watcher)
19
- future = Future.new
20
- # add watcher to loop
21
- watcher.set_callback {future.transfer}
22
- watcher.register(@backend)
23
- # trigger a fiber switch
24
- # wait until watcher is ok
25
- # then do work
26
- future.value
27
- end
28
-
29
- def transfer
30
- @fiber.transfer
31
- end
32
-
33
- class << self
34
- # return current ioloop or create new one
35
- def current
36
- Thread.current[:"lightio.ioloop"] ||= IOloop.new
37
- end
38
- end
39
- end
40
- end
data/lib/lightio/timer.rb DELETED
@@ -1,15 +0,0 @@
1
- module LightIO
2
- class Timer < Watcher
3
- attr_reader :interval
4
- attr_accessor :uuid
5
-
6
- def initialize(interval, &blk)
7
- @interval = interval
8
- @callback = blk
9
- end
10
-
11
- def register(backend)
12
- backend.add_timer(self)
13
- end
14
- end
15
- end
@@ -1,14 +0,0 @@
1
- module LightIO
2
- class Watcher
3
- attr_reader :callback
4
-
5
- def set_callback(&blk)
6
- raise Error, "already has callback" if @callback
7
- @callback = blk
8
- end
9
-
10
- def register(backend)
11
- raise
12
- end
13
- end
14
- end