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.
@@ -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
@@ -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