reactomatic 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 10fb81989a12a6d13e2c866a68f7d4834f452170
4
+ data.tar.gz: 678098ae122e7fe1af799865bb4e6b1183c94a54
5
+ SHA512:
6
+ metadata.gz: aa2e188f6c81f71bd006ca24c318123ae3efa8bc5fb5dad81aa6ad04235a3361f6c1ffd36ca9b45a30dacdb172a1f60e830a7a08e77c5ab782be7bc9074c16b6
7
+ data.tar.gz: 0f89453b08af8af5e0dae8793a3e9fc320d7fe710537121163c4d11bc7225541c104461487eb2ca0852f157686f091d255634698e9ca15f4242e72cd3df0747d
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.ruby-version
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ before_install: gem install bundler -v 1.10.5
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in reactomatic.gemspec
4
+ gemspec
5
+
6
+ platforms :ruby do
7
+ gem "byebug"
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Chad Remesch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Reactomatic
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/reactomatic`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'reactomatic'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install reactomatic
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/reactomatic.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
41
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "reactomatic"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+ require "thread"
2
+
3
+ require "nio"
4
+
5
+ require "reactomatic/version"
6
+ require "reactomatic/exceptions"
7
+ require "reactomatic/buffer"
8
+ require "reactomatic/reactor"
9
+ require "reactomatic/tcp_server"
10
+ require "reactomatic/tcp_connection"
11
+
12
+ module Reactomatic
13
+ @lock = Monitor.new
14
+
15
+ class << self
16
+ attr_accessor :lock
17
+ end
18
+
19
+ def self.reactor
20
+ Reactomatic.lock.synchronize do
21
+ unless @reactor
22
+ @reactor = Reactor.new
23
+ @reactor.start
24
+ end
25
+
26
+ return @reactor
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ module Reactomatic
2
+ class Buffer
3
+ attr_reader :max_length
4
+
5
+ def initialize(opts = {})
6
+ @max_length = opts[:max_length]
7
+ @opts = opts
8
+ @lock = Mutex.new
9
+ @buffer = ""
10
+ end
11
+
12
+ def append(data)
13
+ if @max_length.nil? || length + data.bytesize <= @max_length
14
+ @buffer.concat(data)
15
+ else
16
+ raise BufferFull
17
+ end
18
+ end
19
+
20
+ def read
21
+ return @buffer
22
+ end
23
+
24
+ def consume(length)
25
+ return @buffer.slice!(0...length)
26
+ end
27
+
28
+ def length
29
+ return @buffer.bytesize
30
+ end
31
+
32
+ def full?
33
+ return false if @max_length.nil?
34
+ return @buffer.bytesize >= @max_length
35
+ end
36
+
37
+ def empty?
38
+ return @buffer.empty?
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ module Reactomatic
2
+ class ReactomaticError < RuntimeError; end
3
+ class AlreadyStarted < ReactomaticError; end
4
+ class MustOverrideMethodError < RuntimeError; end
5
+ class BufferFull < RuntimeError; end
6
+ end
@@ -0,0 +1,94 @@
1
+ module Reactomatic
2
+ class Reactor
3
+ SELECTOR_TIMEOUT = 0.1 # Seconds.
4
+
5
+ def initialize(opts = {})
6
+ @selector = NIO::Selector.new
7
+ @selector_timeout = opts[:selector_timeout] || SELECTOR_TIMEOUT
8
+ @run_lock = Mutex.new
9
+ @next_tick_queue = Queue.new
10
+ end
11
+
12
+ def start(opts = {})
13
+ @run_lock.synchronize do
14
+ raise AlreadyStarted if @thread
15
+ @thread = Thread.new do
16
+ begin
17
+ while !@selector.closed?
18
+ process_next_tick_queue
19
+ monitor_io_objects
20
+ end
21
+ rescue Exception => e
22
+ exception_handler(e)
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ def stop
29
+ @run_lock.synchronize do
30
+ @selector.close
31
+ @thread.join
32
+ end
33
+ end
34
+
35
+ def register(io, interest, target)
36
+ monitor = @selector.register(io, interest)
37
+ monitor.value = target
38
+
39
+ nil
40
+ end
41
+
42
+ def deregister(io)
43
+ @selector.deregister(io)
44
+
45
+ nil
46
+ end
47
+
48
+ def registered?(io)
49
+ @selector.registered?(io)
50
+ end
51
+
52
+ def next_tick(callback = nil, &block)
53
+ func = callback || block
54
+
55
+ @next_tick_queue.push(func)
56
+
57
+ nil
58
+ end
59
+
60
+ def schedule(callback = nil, &block)
61
+ func = callback || block
62
+
63
+ if Thread.current == @thread
64
+ func.call
65
+ else
66
+ next_tick(func)
67
+ end
68
+
69
+ nil
70
+ end
71
+
72
+ private
73
+
74
+ def process_next_tick_queue
75
+ @next_tick_queue.length.times do
76
+ begin
77
+ @next_tick_queue.pop.call
78
+ rescue Exception => e
79
+ exception_handler(e)
80
+ end
81
+ end
82
+ end
83
+
84
+ def monitor_io_objects
85
+ @selector.select(SELECTOR_TIMEOUT) do |monitor|
86
+ monitor.value.call(monitor)
87
+ end
88
+ end
89
+
90
+ def exception_handler(e)
91
+ puts "EXCEPTION #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}"
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,131 @@
1
+ module Reactomatic
2
+ class TcpConnection
3
+ def initialize(opts = {})
4
+ @opts = opts
5
+
6
+ @reactor = opts[:reactor] || Reactomatic.reactor
7
+ @socket = opts[:socket]
8
+ @write_buffer = opts[:write_buffer] || Buffer.new
9
+ @read_count = 0
10
+ @write_count = 0
11
+ @read_eof = false
12
+ @lock = Monitor.new
13
+
14
+ on_initialize
15
+ register if @socket
16
+ end
17
+
18
+ #
19
+ # Public methods.
20
+ #
21
+
22
+ def reactor
23
+ return @reactor
24
+ end
25
+
26
+ def connect(host, port)
27
+ end
28
+
29
+ def send_data(data)
30
+ @lock.synchronize do
31
+ @write_buffer.append(data)
32
+ write_nonblock
33
+ register
34
+ end
35
+
36
+ nil
37
+ end
38
+
39
+ def close
40
+ @lock.synchronize do
41
+ if @socket
42
+ @reactor.deregister(@socket)
43
+ @socket.close
44
+ @socket = nil
45
+ on_disconnect
46
+ end
47
+ end
48
+
49
+ nil
50
+ end
51
+
52
+ private
53
+
54
+ #
55
+ # Event handlers (override these in your subclasses).
56
+ #
57
+
58
+ def on_initialize
59
+ puts "initialized!"
60
+ end
61
+
62
+ def on_connect
63
+ puts "connected!"
64
+ end
65
+
66
+ def on_receive_data(data)
67
+ puts "received #{data.bytesize} bytes of data and echoing back!"
68
+ send_data(data)
69
+ end
70
+
71
+ def on_sent_data(num_bytes)
72
+ puts "sent #{num_bytes} of data!"
73
+ end
74
+
75
+ def on_disconnect
76
+ puts "disconnected! read bytes: #{@read_count}, wrote bytes: #{@write_count}"
77
+ end
78
+
79
+ #
80
+ # Internal methods (don't use).
81
+ #
82
+
83
+ def register
84
+ @reactor.deregister(@socket)
85
+
86
+ if !@write_buffer.empty? && !@read_eof
87
+ interest = :rw
88
+ elsif @write_buffer.empty? && !@read_eof
89
+ interest = :r
90
+ elsif !@write_buffer.empty?
91
+ interest = :w
92
+ elsif @read_eof
93
+ close
94
+ return
95
+ end
96
+
97
+ @reactor.register(@socket, interest, method(:selected))
98
+ end
99
+
100
+ def selected(monitor)
101
+ @lock.synchronize do
102
+ read_nonblock if monitor.readable?
103
+ write_nonblock if monitor.writable?
104
+ register
105
+ end
106
+ end
107
+
108
+ def read_nonblock
109
+ begin
110
+ data = @socket.read_nonblock(1024**2)
111
+ on_receive_data(data)
112
+ @read_count += data.bytesize
113
+ rescue EOFError
114
+ @read_eof = true
115
+ rescue IO::WaitReadable
116
+ end
117
+ end
118
+
119
+ def write_nonblock
120
+ return if @write_buffer.empty?
121
+
122
+ begin
123
+ num_bytes = @socket.write_nonblock(@write_buffer.read)
124
+ @write_buffer.consume(num_bytes)
125
+ @write_count += num_bytes
126
+ on_sent_data(num_bytes)
127
+ rescue IO::WaitWritable
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,50 @@
1
+ module Reactomatic
2
+ class TcpServer
3
+ attr_accessor :reactor
4
+
5
+ def initialize(opts = {})
6
+ @opts = opts
7
+ @reactor = opts[:reactor] || Reactomatic.reactor
8
+ end
9
+
10
+ def listen(host, port, klass)
11
+ raise AlreadyStarted if @server
12
+
13
+ @host = host
14
+ @port = port
15
+ @klass = klass
16
+
17
+ @socket = TCPServer.new(@host, @port)
18
+ @reactor.register(@socket, :r, method(:__selected__))
19
+
20
+ nil
21
+ end
22
+
23
+ def close
24
+ if @socket
25
+ @reactor.deregister(@socket)
26
+ @socket.close
27
+ @socket = nil
28
+ end
29
+
30
+ nil
31
+ end
32
+
33
+ private
34
+
35
+ #
36
+ # Internal methods (don't use).
37
+ #
38
+
39
+ def __selected__(monitor)
40
+ if monitor.closed?
41
+ @reactor.deregister(@server)
42
+ return
43
+ end
44
+
45
+ if monitor.readable?
46
+ @klass.new({:reactor => @reactor, :socket => monitor.io.accept_nonblock})
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,3 @@
1
+ module Reactomatic
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'reactomatic/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "reactomatic"
8
+ spec.version = Reactomatic::VERSION
9
+ spec.authors = ["Chad Remesch"]
10
+ spec.email = ["chad@remesch.com"]
11
+
12
+ spec.summary = %q{A network library based on the reactor pattern.}
13
+ spec.homepage = "https://github.com/chadrem/reactomatic"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "nio4r", "~> 1.1"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.10"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "minitest"
26
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reactomatic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chad Remesch
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-07-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ~>
17
+ - !ruby/object:Gem::Version
18
+ version: '1.1'
19
+ name: nio4r
20
+ prerelease: false
21
+ type: :runtime
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ - !ruby/object:Gem::Dependency
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '1.10'
33
+ name: bundler
34
+ prerelease: false
35
+ type: :development
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.10'
41
+ - !ruby/object:Gem::Dependency
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: '10.0'
47
+ name: rake
48
+ prerelease: false
49
+ type: :development
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ name: minitest
62
+ prerelease: false
63
+ type: :development
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - chad@remesch.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .travis.yml
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - bin/console
83
+ - bin/setup
84
+ - lib/reactomatic.rb
85
+ - lib/reactomatic/buffer.rb
86
+ - lib/reactomatic/exceptions.rb
87
+ - lib/reactomatic/reactor.rb
88
+ - lib/reactomatic/tcp_connection.rb
89
+ - lib/reactomatic/tcp_server.rb
90
+ - lib/reactomatic/version.rb
91
+ - reactomatic.gemspec
92
+ homepage: https://github.com/chadrem/reactomatic
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 2.4.6
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: A network library based on the reactor pattern.
116
+ test_files: []