iomultiplex 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/iomultiplex.rb +26 -0
- data/lib/iomultiplex/iomultiplex.rb +126 -0
- data/lib/iomultiplex/ioreactor.rb +118 -0
- data/lib/iomultiplex/ioreactor/buffered.rb +55 -0
- data/lib/iomultiplex/ioreactor/openssl.rb +91 -0
- data/lib/iomultiplex/mixins/callback.rb +44 -0
- data/lib/iomultiplex/mixins/ioreactor/read.rb +180 -0
- data/lib/iomultiplex/mixins/ioreactor/write.rb +109 -0
- data/lib/iomultiplex/mixins/logger.rb +64 -0
- data/lib/iomultiplex/mixins/logslow.rb +40 -0
- data/lib/iomultiplex/mixins/openssl.rb +148 -0
- data/lib/iomultiplex/mixins/post.rb +89 -0
- data/lib/iomultiplex/mixins/select.rb +145 -0
- data/lib/iomultiplex/mixins/state.rb +79 -0
- data/lib/iomultiplex/mixins/timer.rb +87 -0
- data/lib/iomultiplex/pool.rb +124 -0
- data/lib/iomultiplex/stringbuffer.rb +87 -0
- data/lib/iomultiplex/tcplistener.rb +56 -0
- data/lib/iomultiplex/version.rb +4 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e3ac2bf50945d2563b5ba09b1343fdb23dcdeff5
|
4
|
+
data.tar.gz: 68c237d349ea45972799c40028ceeb4d03fa4e2a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3a32b0d814b4de29daa438646e88dcde578003dadea981c604e5fe4069f30a2bf9e8224f2f2035a4d5f1b0d937d7f1a8d706ce67f2e373fb5d2096bb69a68d53
|
7
|
+
data.tar.gz: 3f94d132f6d4d62208a9b0295b6cf462901d39292326f08280429c93a206bb1b14c97bff14d21bad281223a9c3be38e303ed01bd73d5cab1d566c7a52dfc813c
|
data/lib/iomultiplex.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Copyright 2014-2016 Jason Woods
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'cabin'
|
18
|
+
require 'iomultiplex/mixins/callback'
|
19
|
+
require 'iomultiplex/mixins/logslow'
|
20
|
+
require 'iomultiplex/mixins/post'
|
21
|
+
require 'iomultiplex/mixins/select'
|
22
|
+
require 'iomultiplex/mixins/state'
|
23
|
+
require 'iomultiplex/mixins/timer'
|
24
|
+
require 'iomultiplex/iomultiplex'
|
25
|
+
require 'iomultiplex/ioreactor'
|
26
|
+
require 'iomultiplex/tcplistener'
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Copyright 2014-2016 Jason Woods
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module IOMultiplex
|
18
|
+
# A single multiplexer that can process hundreds of clients in a single thread
|
19
|
+
class Multiplexer
|
20
|
+
include Mixins::Logger
|
21
|
+
include Mixins::State
|
22
|
+
include Mixins::Select
|
23
|
+
include Mixins::Post
|
24
|
+
include Mixins::Timer
|
25
|
+
include Mixins::Callback
|
26
|
+
|
27
|
+
attr_reader :id
|
28
|
+
|
29
|
+
def initialize(options = {})
|
30
|
+
@mutex = Mutex.new
|
31
|
+
@connections = 0
|
32
|
+
|
33
|
+
initialize_logger options[:logger], options[:logger_context]
|
34
|
+
|
35
|
+
initialize_state
|
36
|
+
initialize_select options
|
37
|
+
initialize_post
|
38
|
+
initialize_timers
|
39
|
+
initialize_callbacks
|
40
|
+
|
41
|
+
@id = options[:id] || object_id
|
42
|
+
add_logger_context 'multiplexer', @id
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def run
|
47
|
+
run_once until @shutdown
|
48
|
+
|
49
|
+
log_debug 'Shutdown'
|
50
|
+
|
51
|
+
# Forced shutdown
|
52
|
+
each_registered_client(&:force_close)
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def add(client)
|
57
|
+
raise ArgumentError,
|
58
|
+
'Client must be an instance of IOMultiplex::IOReactor' \
|
59
|
+
unless client.is_a? IOReactor
|
60
|
+
raise ArgumentError,
|
61
|
+
'Client is already attached' \
|
62
|
+
unless get_state(client).nil?
|
63
|
+
|
64
|
+
register_state client
|
65
|
+
client.attach self
|
66
|
+
|
67
|
+
@mutex.synchronize do
|
68
|
+
@connections += 1
|
69
|
+
end
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def remove(client)
|
74
|
+
must_get_state(client)
|
75
|
+
|
76
|
+
@mutex.synchronize do
|
77
|
+
@connections -= 1
|
78
|
+
end
|
79
|
+
|
80
|
+
client.detach
|
81
|
+
stop_all client
|
82
|
+
remove_post client
|
83
|
+
remove_timer client
|
84
|
+
deregister_state client
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def connections
|
89
|
+
@mutex.synchronize do
|
90
|
+
@connections
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def shutdown
|
95
|
+
@shutdown = true
|
96
|
+
@nio.wakeup
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
def run_once
|
103
|
+
# If post processing is scheduled, do not block on select
|
104
|
+
# Otherwise, only block until next timer
|
105
|
+
# And if no timers, bock indefinitely
|
106
|
+
timeout = nil
|
107
|
+
if schedule_post_processing
|
108
|
+
timeout = 0
|
109
|
+
else
|
110
|
+
timeout = next_timer
|
111
|
+
unless timeout.nil?
|
112
|
+
timeout = (timeout - Time.now).ceil
|
113
|
+
timeout = 0 if timeout < 0
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
select_io timeout
|
118
|
+
|
119
|
+
trigger_post_processing
|
120
|
+
|
121
|
+
# Trigger callbacks and timers
|
122
|
+
trigger_callbacks
|
123
|
+
trigger_timers
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Copyright 2014-2016 Jason Woods
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'iomultiplex/mixins/ioreactor/read'
|
18
|
+
require 'iomultiplex/mixins/ioreactor/write'
|
19
|
+
require 'iomultiplex/stringbuffer'
|
20
|
+
|
21
|
+
module IOMultiplex
|
22
|
+
# IOReactor - reactor style wrapper around IO objects
|
23
|
+
class IOReactor
|
24
|
+
include Mixins::Logger
|
25
|
+
include Mixins::IOReactor::Read
|
26
|
+
include Mixins::IOReactor::Write
|
27
|
+
|
28
|
+
attr_reader :id
|
29
|
+
attr_reader :io
|
30
|
+
attr_reader :mode
|
31
|
+
attr_reader :peer
|
32
|
+
|
33
|
+
def initialize(io, mode = 'rw', id = nil)
|
34
|
+
@io = io
|
35
|
+
@multiplexer = nil
|
36
|
+
@attached = false
|
37
|
+
@close_scheduled = false
|
38
|
+
@eof_scheduled = false
|
39
|
+
@exception = nil
|
40
|
+
|
41
|
+
@r = mode.index('r').nil? ? false : true
|
42
|
+
@w = mode.index('w').nil? ? false : true
|
43
|
+
|
44
|
+
if @r
|
45
|
+
@read_buffer = StringBuffer.new
|
46
|
+
@pause = false
|
47
|
+
end
|
48
|
+
if @w
|
49
|
+
@write_buffer = StringBuffer.new
|
50
|
+
@write_immediately = true
|
51
|
+
end
|
52
|
+
|
53
|
+
@id = id || calculate_id
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def addr
|
58
|
+
@io.addr
|
59
|
+
end
|
60
|
+
|
61
|
+
def peeraddr
|
62
|
+
@io.peeraddr
|
63
|
+
end
|
64
|
+
|
65
|
+
def attach(multiplexer)
|
66
|
+
raise ArgumentError, 'Socket is already attached' if @attached
|
67
|
+
|
68
|
+
@multiplexer = multiplexer
|
69
|
+
initialize_logger multiplexer.logger, multiplexer.logger_context.dup
|
70
|
+
add_logger_context 'client', @id
|
71
|
+
|
72
|
+
@multiplexer.wait_read self if @r
|
73
|
+
|
74
|
+
@attached = true
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def detach
|
79
|
+
raise ArgumentError, 'Socket is not yet attached' unless @attached
|
80
|
+
@attached = false
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
|
84
|
+
def close
|
85
|
+
@read_buffer.reset
|
86
|
+
if !@w
|
87
|
+
@close_scheduled = true
|
88
|
+
else
|
89
|
+
force_close
|
90
|
+
end
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def force_close
|
95
|
+
@multiplexer.remove self
|
96
|
+
@io.close unless @io.closed?
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
def calculate_id
|
103
|
+
if @io.respond_to?(:peeraddr)
|
104
|
+
begin
|
105
|
+
peer = @io.peeraddr(:numeric)
|
106
|
+
# IPv4 format
|
107
|
+
return "#{peer[2]}:#{peer[1]}" if peer[2].index(':').nil?
|
108
|
+
# IPv6 format
|
109
|
+
return "[#{peer[2]}]:#{peer[1]}"
|
110
|
+
rescue NotImplementedError, Errno::ENOTCONN
|
111
|
+
return @io.inspect
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
@io.inspect
|
116
|
+
end
|
117
|
+
end # ::IOReactor
|
118
|
+
end # ::IOMultiplex
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Copyright 2014-2016 Jason Woods
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'iomultiplex/ioreactor'
|
18
|
+
|
19
|
+
module IOMultiplex
|
20
|
+
class IOReactor
|
21
|
+
# Wraps around a buffered IO object
|
22
|
+
# When the read signal triggers for the IO object, it will continuously read
|
23
|
+
# in the main loops post processing until it receives a WaitReadable signal.
|
24
|
+
# This ensures any data left in the IO buffer after a read is correctly read
|
25
|
+
# without waiting for a read signal
|
26
|
+
class Buffered < IOReactor
|
27
|
+
def initialize(io, mode = 'rw', id = nil)
|
28
|
+
super io, mode, id
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def do_read
|
34
|
+
read_action
|
35
|
+
rescue IO::WaitReadable, Errno::EINTR, Errno::EAGAIN
|
36
|
+
@wait_readable = true
|
37
|
+
else
|
38
|
+
@wait_readable = false
|
39
|
+
end
|
40
|
+
|
41
|
+
def schedule_read
|
42
|
+
@multiplexer.defer self unless @read_buffer.empty?
|
43
|
+
|
44
|
+
# Keep forcing reads until we hit a WaitReadable, in case there is
|
45
|
+
# buffered data in the IO
|
46
|
+
if @wait_readable
|
47
|
+
@multiplexer.wait_read self
|
48
|
+
else
|
49
|
+
@multiplexer.stop_read self
|
50
|
+
@multiplexer.force_read self
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end # ::Buffered
|
54
|
+
end # ::IOReactor
|
55
|
+
end # ::IOMultiplex
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Copyright 2014-2016 Jason Woods
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'iomultiplex/ioreactor/buffered'
|
18
|
+
require 'iomultiplex/mixins/openssl'
|
19
|
+
|
20
|
+
module IOMultiplex
|
21
|
+
class IOReactor
|
22
|
+
# Wraps an OpenSSL IO object which receives TLS connections
|
23
|
+
class OpenSSL < Buffered
|
24
|
+
include Mixins::OpenSSL
|
25
|
+
|
26
|
+
def initialize(io, _ = nil, id = nil, ssl_ctx = nil)
|
27
|
+
# OpenSSL is implicitly read/write due to key-exchange so we ignore the
|
28
|
+
# mode parameter
|
29
|
+
super io, 'rw', id
|
30
|
+
initialize_ssl ssl_ctx
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def read_nonblock(n)
|
36
|
+
ssl_read_nonblock n
|
37
|
+
end
|
38
|
+
|
39
|
+
def write_nonblock(data)
|
40
|
+
ssl_write_nonblock data
|
41
|
+
end
|
42
|
+
|
43
|
+
def read_action
|
44
|
+
return super if @handshake_completed
|
45
|
+
|
46
|
+
process_handshake
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end # ::OpenSSL
|
50
|
+
|
51
|
+
# OpenSSLUpgrading wraps an IO object that acts like a regular
|
52
|
+
# IOReactor but can be upgraded to a TLS connection mid-connection
|
53
|
+
class OpenSSLUpgrading < Buffered
|
54
|
+
include Mixins::OpenSSL
|
55
|
+
|
56
|
+
def initialize(io, _ = nil, id = nil)
|
57
|
+
# OpenSSL is implicitly read/write due to key-exchange so we ignore the
|
58
|
+
# mode parameter
|
59
|
+
super io, 'rw', id
|
60
|
+
@ssl_enabled = false
|
61
|
+
end
|
62
|
+
|
63
|
+
def start_ssl(ssl_ctx)
|
64
|
+
raise 'SSL already started', nil if @ssl_enabled
|
65
|
+
initialize_ssl ssl_ctx
|
66
|
+
@ssl_enabled = true
|
67
|
+
log_debug 'Upgrading connection to SSL'
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def read_nonblock(n)
|
74
|
+
return super(n) unless @ssl_enabled
|
75
|
+
ssl_read_nonblock n
|
76
|
+
end
|
77
|
+
|
78
|
+
def write_nonblock(data)
|
79
|
+
return super(data) unless @ssl_enabled
|
80
|
+
ssl_write_nonblock data
|
81
|
+
end
|
82
|
+
|
83
|
+
def read_action
|
84
|
+
return super unless @ssl_enabled && !@handshake_completed
|
85
|
+
|
86
|
+
process_handshake
|
87
|
+
super
|
88
|
+
end
|
89
|
+
end # ::OpenSSLUpgrading
|
90
|
+
end # ::IOReactor
|
91
|
+
end # ::IOMultiplex
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Copyright 2014-2016 Jason Woods
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module IOMultiplex
|
18
|
+
module Mixins
|
19
|
+
# Callback methods
|
20
|
+
# Depends on Mixins::State
|
21
|
+
module Callback
|
22
|
+
# Run a callback on the IO thread
|
23
|
+
# Can be safely triggered from any thread
|
24
|
+
def callback(&block)
|
25
|
+
@callbacks.push block
|
26
|
+
@nio.wakeup
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def initialize_callbacks
|
33
|
+
@callbacks = []
|
34
|
+
end
|
35
|
+
|
36
|
+
def trigger_callbacks
|
37
|
+
return if @callbacks.empty?
|
38
|
+
@callbacks.each(&:call)
|
39
|
+
@callbacks = []
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end # ::Callback
|
43
|
+
end # ::Mixins
|
44
|
+
end # ::IOMultiplex
|