iomultiplex 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 +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
|