cod 0.5.0 → 0.6.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/HISTORY.txt +12 -0
- data/examples/master_child_bidir.rb +20 -0
- data/lib/cod.rb +29 -9
- data/lib/cod/bidir.rb +102 -0
- data/lib/cod/bidir_server.rb +34 -0
- data/lib/cod/callbacks.rb +24 -0
- data/lib/cod/iopair.rb +0 -2
- data/lib/cod/pipe.rb +2 -1
- data/lib/cod/process.rb +13 -8
- data/lib/cod/select.rb +6 -5
- data/lib/cod/socket_server.rb +172 -0
- data/lib/cod/tcp_client.rb +6 -3
- data/lib/cod/tcp_server.rb +11 -156
- data/lib/cod/work_queue.rb +7 -3
- metadata +38 -69
- data/Rakefile +0 -35
- data/lib/cod/bidir_pipe.rb +0 -32
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 849f13cc13d03934bdf098bc46697880be2380e7
|
4
|
+
data.tar.gz: e31877e11c7b73a9e725b3083b4e8508df3cecda
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d37a103c94ccb8ce3f092a6b208809eac7b84ceaf560a4bd99f6958860f4f67c47991c33e9315d5d0197671fead58b92cfe949809aa821936786ba251a72bd72
|
7
|
+
data.tar.gz: d34fc64a7c8162855083775b99914987b6292d75db704f66b140e1c502b08839a714fd1a4b992663f305e5e479f0a5b5e2b2f688a2145ddf7c97c52c37daf1fd
|
data/HISTORY.txt
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
= 0.6.0 / ???
|
2
|
+
|
3
|
+
+ Cod.bidir is now based on UNIXSocket.pair (socketpair) and is called just
|
4
|
+
#bidir. Thanks to Yves Senn (senny) for the idea and the pairing sessions!
|
5
|
+
! Eliminated some warnings for exceptions that were reraised anyway. Thanks
|
6
|
+
to Chris Schmich (schmich).
|
7
|
+
! Updates project to work with modern rubies.
|
8
|
+
|
9
|
+
= 0.5.1 / 30Aug2012
|
10
|
+
|
11
|
+
+ Cod.bidir_pipe is now selectable.
|
12
|
+
|
1
13
|
= 0.5.0 / 12Jun2012
|
2
14
|
|
3
15
|
+ Process#wait will not throw Errno::ECHILD anymore.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
|
2
|
+
require 'cod'
|
3
|
+
|
4
|
+
pipe = Cod.bidir
|
5
|
+
|
6
|
+
child_pid = fork do
|
7
|
+
pipe.swap!
|
8
|
+
pipe.put 'test'
|
9
|
+
pipe.put Process.pid
|
10
|
+
command = pipe.get
|
11
|
+
p command
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
p pipe.get
|
16
|
+
p pipe.get
|
17
|
+
pipe.put :foobar
|
18
|
+
ensure
|
19
|
+
Process.wait(child_pid)
|
20
|
+
end
|
data/lib/cod.rb
CHANGED
@@ -65,18 +65,32 @@ module Cod
|
|
65
65
|
end
|
66
66
|
module_function :pipe
|
67
67
|
|
68
|
-
# Creates
|
69
|
-
#
|
70
|
-
# #out and reads come from #in.
|
68
|
+
# Creates a channel based on socketpair (UNIXSocket.pair). This is a
|
69
|
+
# IPC-kind of channel that can be used to exchange messages both ways.
|
71
70
|
#
|
72
|
-
# @overload
|
71
|
+
# @overload bidir(serializer=nil)
|
73
72
|
# @param serializer [#en,#de] optional serializer to use
|
74
|
-
# @return [Cod::
|
73
|
+
# @return [Cod::Bidir]
|
75
74
|
#
|
76
|
-
def
|
77
|
-
Cod::
|
75
|
+
def bidir(serializer=nil)
|
76
|
+
Cod::Bidir.pair(serializer)
|
78
77
|
end
|
79
|
-
module_function :
|
78
|
+
module_function :bidir
|
79
|
+
|
80
|
+
# Creates a named bidirectional channel. Only one end of this will be
|
81
|
+
# connected to the unix socket in the file system identified by path.
|
82
|
+
#
|
83
|
+
def bidir_named(name, serializer=nil)
|
84
|
+
Cod::Bidir.named(name, serializer)
|
85
|
+
end
|
86
|
+
module_function :bidir_named
|
87
|
+
|
88
|
+
# Creates the server side for a named unix socket connection.
|
89
|
+
#
|
90
|
+
def bidir_server(name, serializer=nil)
|
91
|
+
Cod::BidirServer.new(name, serializer)
|
92
|
+
end
|
93
|
+
module_function :bidir_server
|
80
94
|
|
81
95
|
# Creates a tcp connection to the destination and returns a channel for it.
|
82
96
|
#
|
@@ -169,9 +183,13 @@ module Cod
|
|
169
183
|
end
|
170
184
|
end
|
171
185
|
|
186
|
+
require 'cod/callbacks'
|
187
|
+
|
172
188
|
require 'cod/select_group'
|
173
189
|
require 'cod/select'
|
174
190
|
|
191
|
+
require 'cod/socket_server'
|
192
|
+
|
175
193
|
require 'cod/iopair'
|
176
194
|
|
177
195
|
require 'cod/channel'
|
@@ -180,7 +198,9 @@ require 'cod/simple_serializer'
|
|
180
198
|
require 'cod/line_serializer'
|
181
199
|
|
182
200
|
require 'cod/pipe'
|
183
|
-
|
201
|
+
|
202
|
+
require 'cod/bidir'
|
203
|
+
require 'cod/bidir_server'
|
184
204
|
|
185
205
|
require 'cod/process'
|
186
206
|
|
data/lib/cod/bidir.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
module Cod
|
2
|
+
|
3
|
+
# A bidirectional pipe, also called a socket pair.
|
4
|
+
#
|
5
|
+
class Bidir < Channel
|
6
|
+
|
7
|
+
include Callbacks
|
8
|
+
|
9
|
+
# Serializer to use for messages on this transport.
|
10
|
+
attr_reader :serializer
|
11
|
+
|
12
|
+
# This process' end of the pipe, can be used for both reading and writing.
|
13
|
+
attr_accessor :socket
|
14
|
+
|
15
|
+
# The other side of the pipe.
|
16
|
+
attr_reader :other
|
17
|
+
|
18
|
+
# Path of the socket (.named), if based on a file system FIFO.
|
19
|
+
attr_reader :path
|
20
|
+
|
21
|
+
def self.named(name, serializer=nil)
|
22
|
+
new(serializer || SimpleSerializer.new,
|
23
|
+
name,
|
24
|
+
UNIXSocket.new(name), nil)
|
25
|
+
end
|
26
|
+
def self.pair(serializer=nil)
|
27
|
+
new(serializer || SimpleSerializer.new,
|
28
|
+
nil,
|
29
|
+
*UNIXSocket.pair)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Initializes a Bidir channel given two or alternatively one end of a
|
33
|
+
# bidirectional pipe. (socketpair)
|
34
|
+
#
|
35
|
+
# socket ---- other
|
36
|
+
#
|
37
|
+
def initialize(serializer, path, socket, other)
|
38
|
+
@serializer = serializer
|
39
|
+
@socket, @other = socket, other
|
40
|
+
@path = path
|
41
|
+
end
|
42
|
+
|
43
|
+
def put(obj)
|
44
|
+
using_callbacks(socket) do
|
45
|
+
socket.write(
|
46
|
+
serializer.en(obj))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def get
|
51
|
+
serializer.de(socket)
|
52
|
+
rescue EOFError, IOError
|
53
|
+
raise Cod::ConnectionLost,
|
54
|
+
"All pipe ends seem to be closed. Reading from this pipe will not "+
|
55
|
+
"return any data."
|
56
|
+
end
|
57
|
+
|
58
|
+
def close
|
59
|
+
socket.close;
|
60
|
+
other.close if other
|
61
|
+
rescue IOError
|
62
|
+
# One code path through Cod::Process will close other prematurely. This
|
63
|
+
# is to avoid an error.
|
64
|
+
end
|
65
|
+
|
66
|
+
# Swaps the end of this pipe around.
|
67
|
+
#
|
68
|
+
def swap!
|
69
|
+
@socket, @other = @other, @socket
|
70
|
+
end
|
71
|
+
|
72
|
+
# ---------------------------------------------------------- serialization
|
73
|
+
|
74
|
+
# A small structure that is constructed for a serialized tcp client on
|
75
|
+
# the other end (the deserializing end). What the deserializing code does
|
76
|
+
# with this is his problem.
|
77
|
+
#
|
78
|
+
# @private
|
79
|
+
#
|
80
|
+
OtherEnd = Struct.new(:path) # :nodoc:
|
81
|
+
|
82
|
+
def _dump(level) # :nodoc:
|
83
|
+
if !path && callbacks_enabled?
|
84
|
+
register_callback { |conn| conn.send_io(other) }
|
85
|
+
end
|
86
|
+
|
87
|
+
Marshal.dump(path)
|
88
|
+
end
|
89
|
+
def self._load(params) # :nodoc:
|
90
|
+
# Instead of a tcp client (no way to construct one at this point), we'll
|
91
|
+
# insert a kind of marker in the object stream that will be replaced
|
92
|
+
# with a valid client later on. (hopefully)
|
93
|
+
OtherEnd.new(Marshal.load(params))
|
94
|
+
end
|
95
|
+
|
96
|
+
# @private
|
97
|
+
#
|
98
|
+
def to_read_fds
|
99
|
+
[socket]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Cod
|
2
|
+
class BidirServer < SocketServer
|
3
|
+
include Callbacks
|
4
|
+
|
5
|
+
attr_reader :path
|
6
|
+
|
7
|
+
def initialize(path, serializer)
|
8
|
+
super serializer || SimpleSerializer.new,
|
9
|
+
UNIXServer.new(path)
|
10
|
+
|
11
|
+
@path = path
|
12
|
+
end
|
13
|
+
|
14
|
+
def deserialize_special(socket, obj)
|
15
|
+
case obj
|
16
|
+
when Bidir::OtherEnd
|
17
|
+
if obj.path == self.path
|
18
|
+
return back_channel(socket)
|
19
|
+
end
|
20
|
+
|
21
|
+
channel = Bidir.new(serializer, nil, nil, nil)
|
22
|
+
register_callback { |conn|
|
23
|
+
channel.socket= conn.recv_io(UNIXSocket) }
|
24
|
+
return channel
|
25
|
+
end
|
26
|
+
|
27
|
+
obj
|
28
|
+
end
|
29
|
+
|
30
|
+
def back_channel(socket)
|
31
|
+
Bidir.new(serializer, nil, socket, nil)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Cod
|
2
|
+
module Callbacks
|
3
|
+
def using_callbacks(*args)
|
4
|
+
Thread.current[:callbacks] = []
|
5
|
+
|
6
|
+
result = yield
|
7
|
+
|
8
|
+
Thread.current[:callbacks].each do |cb|
|
9
|
+
cb.call(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
return result
|
13
|
+
ensure
|
14
|
+
Thread.current[:callbacks] = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def callbacks_enabled?
|
18
|
+
Thread.current[:callbacks]
|
19
|
+
end
|
20
|
+
def register_callback(&block)
|
21
|
+
Thread.current[:callbacks] << block
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/cod/iopair.rb
CHANGED
data/lib/cod/pipe.rb
CHANGED
@@ -104,6 +104,7 @@ module Cod
|
|
104
104
|
#
|
105
105
|
def put(obj)
|
106
106
|
raise Cod::ReadOnlyChannel unless can_write?
|
107
|
+
pipe.close_r
|
107
108
|
|
108
109
|
pipe.write(
|
109
110
|
serializer.en(obj))
|
@@ -145,7 +146,7 @@ module Cod
|
|
145
146
|
# @private
|
146
147
|
#
|
147
148
|
def to_read_fds
|
148
|
-
r
|
149
|
+
[r]
|
149
150
|
end
|
150
151
|
|
151
152
|
# Returns true if you can read from this pipe.
|
data/lib/cod/process.rb
CHANGED
@@ -29,19 +29,24 @@ module Cod
|
|
29
29
|
|
30
30
|
# @private
|
31
31
|
def run(command)
|
32
|
-
@pipe = Cod.
|
32
|
+
@pipe = Cod.bidir(@serializer)
|
33
33
|
|
34
34
|
@pid = ::Process.spawn(command,
|
35
|
-
:in => @pipe.
|
36
|
-
:out => @pipe.
|
35
|
+
:in => @pipe.other,
|
36
|
+
:out => @pipe.other)
|
37
|
+
|
38
|
+
# Close the end we just dedicated to the process we've spawned.
|
39
|
+
# As a consequence, when the process exists, we'll get a read error
|
40
|
+
# on our side of the pipe (@pipe.socket, #put, #get)
|
41
|
+
@pipe.other.close
|
37
42
|
end
|
38
43
|
|
39
44
|
# Returns the cod channel associated with this process. The channel will
|
40
45
|
# have the process' standard output bound to its #get (input), and the
|
41
46
|
# process' standard input will be bound to #put (output).
|
42
47
|
#
|
43
|
-
# Note that when the process exits and all communication has been read
|
44
|
-
# the channel, it will probably raise a Cod::ConnectionLost error.
|
48
|
+
# Note that when the process exits and all communication has been read
|
49
|
+
# from the channel, it will probably raise a Cod::ConnectionLost error.
|
45
50
|
#
|
46
51
|
# @example
|
47
52
|
# process = Cod.process('uname', LineSerializer.new)
|
@@ -62,13 +67,13 @@ module Cod
|
|
62
67
|
::Process.kill :TERM, @pid
|
63
68
|
end
|
64
69
|
|
65
|
-
# Asks the process to terminate by closing its stanard input. This
|
66
|
-
# closes down the process, but no guarantees are made.
|
70
|
+
# Asks the process to terminate by closing its stanard input. This
|
71
|
+
# normally closes down the process, but no guarantees are made.
|
67
72
|
#
|
68
73
|
# @return [void]
|
69
74
|
#
|
70
75
|
def terminate
|
71
|
-
@pipe.
|
76
|
+
@pipe.socket.close_write
|
72
77
|
end
|
73
78
|
|
74
79
|
# Waits for the process to terminate and returns its exit value. May
|
data/lib/cod/select.rb
CHANGED
@@ -35,7 +35,7 @@ module Cod
|
|
35
35
|
# @return [Hash,Array,Cod::Channel,IO]
|
36
36
|
#
|
37
37
|
def do
|
38
|
-
fds = groups.values { |e|
|
38
|
+
fds = groups.values { |e| to_read_fds(e) }.compact
|
39
39
|
|
40
40
|
# Perform select
|
41
41
|
r,w,e = IO.select(fds, nil, nil, timeout)
|
@@ -45,13 +45,14 @@ module Cod
|
|
45
45
|
|
46
46
|
# Prepare a return value: The original hash, where the fds are ready.
|
47
47
|
groups.
|
48
|
-
keep_if { |e|
|
48
|
+
keep_if { |e| to_read_fds(e).
|
49
|
+
any? { |fd| r.include?(fd) } }.
|
49
50
|
unpack
|
50
51
|
end
|
51
52
|
private
|
52
|
-
def
|
53
|
-
return
|
54
|
-
return
|
53
|
+
def to_read_fds(channel)
|
54
|
+
return channel.to_read_fds if channel.respond_to?(:to_read_fds)
|
55
|
+
return [channel]
|
55
56
|
end
|
56
57
|
end
|
57
58
|
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Cod
|
4
|
+
|
5
|
+
# Abstract base class for all kinds of socket based servers. Useful for all
|
6
|
+
# types of channels that know an #accept.
|
7
|
+
#
|
8
|
+
class SocketServer
|
9
|
+
include Callbacks
|
10
|
+
|
11
|
+
attr_reader :socket
|
12
|
+
attr_reader :serializer
|
13
|
+
|
14
|
+
def initialize(serializer, socket)
|
15
|
+
@socket = socket
|
16
|
+
@client_sockets = []
|
17
|
+
@round_robin_index = 0
|
18
|
+
@messages = Array.new
|
19
|
+
@serializer = serializer
|
20
|
+
end
|
21
|
+
|
22
|
+
# Receives one object from the channel. This will receive one message from
|
23
|
+
# one of the connected clients in a round-robin fashion.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# channel.get # => object
|
27
|
+
#
|
28
|
+
# @param opts [Hash]
|
29
|
+
# @return [Object] message sent by one of the clients
|
30
|
+
#
|
31
|
+
def get(opts={})
|
32
|
+
msg, socket = _get(opts)
|
33
|
+
return msg
|
34
|
+
end
|
35
|
+
|
36
|
+
# Receives one object from the channel. Returns a tuple of
|
37
|
+
# <message,channel> where channel is a tcp channel that links back to the
|
38
|
+
# client that sent message.
|
39
|
+
#
|
40
|
+
# Using this method, the server can communicate back to its clients
|
41
|
+
# individually instead of collectively.
|
42
|
+
#
|
43
|
+
# @example Answering to the client that sent a specific message
|
44
|
+
# msg, chan = server.get_ext
|
45
|
+
# chan.put :answer
|
46
|
+
#
|
47
|
+
# @param opts [Hash]
|
48
|
+
# @return [Array<Object, TcpClient>] tuple of the message that was sent
|
49
|
+
# a channel back to the client that sent the message
|
50
|
+
def get_ext(opts={})
|
51
|
+
msg, socket = _get(opts)
|
52
|
+
return [
|
53
|
+
msg,
|
54
|
+
back_channel(socket)]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Closes the channel.
|
58
|
+
#
|
59
|
+
def close
|
60
|
+
@socket.close
|
61
|
+
@client_sockets.each { |io| io.close }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns an array of IOs that Cod.select should select on.
|
65
|
+
#
|
66
|
+
def to_read_fds
|
67
|
+
@client_sockets + [@socket]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the number of clients that are connected to this server
|
71
|
+
# currently.
|
72
|
+
#
|
73
|
+
def connections
|
74
|
+
@client_sockets.size
|
75
|
+
end
|
76
|
+
|
77
|
+
# ------------------------------------------------------- connection owner
|
78
|
+
|
79
|
+
# Notifies the TcpServer that one of its connections needs to be closed.
|
80
|
+
# This can be triggered by using #get_ext to obtain a handle to
|
81
|
+
# connections and then calling #close on that connection.
|
82
|
+
#
|
83
|
+
# @param socket [TCPSocket] the socket that needs to be closed @return
|
84
|
+
# [void]
|
85
|
+
#
|
86
|
+
def request_close(socket)
|
87
|
+
@client_sockets.delete(socket)
|
88
|
+
socket.close
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
def _get(opts)
|
93
|
+
loop do
|
94
|
+
# Return a buffered message if there is one left.
|
95
|
+
return @messages.shift unless @messages.empty?
|
96
|
+
|
97
|
+
# Shuffle the socket list around, so we don't always read from the
|
98
|
+
# same client first.
|
99
|
+
socket_list = round_robin(@client_sockets)
|
100
|
+
|
101
|
+
# Append the server socket to be able to react to new connections
|
102
|
+
# that are made.
|
103
|
+
socket_list << socket
|
104
|
+
|
105
|
+
# Sleep until either a new connection is made or data is available on
|
106
|
+
# one of the old connections.
|
107
|
+
rr, _, _ = IO.select(socket_list, nil, nil)
|
108
|
+
next unless rr
|
109
|
+
|
110
|
+
# Accept new connections
|
111
|
+
if rr.include?(@socket)
|
112
|
+
accept_new_connections
|
113
|
+
rr.delete(@socket)
|
114
|
+
end
|
115
|
+
|
116
|
+
handle_socket_events(rr, opts)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def handle_socket_events(sockets, opts)
|
121
|
+
sockets.each do |io|
|
122
|
+
if io.eof?
|
123
|
+
@client_sockets.delete(io)
|
124
|
+
io.close
|
125
|
+
else
|
126
|
+
consume_pending io, opts
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def consume_pending(io, opts)
|
132
|
+
until io.eof?
|
133
|
+
@messages << [
|
134
|
+
deserialize(io),
|
135
|
+
io]
|
136
|
+
|
137
|
+
# More messages from this socket?
|
138
|
+
return unless IO.select([io], nil, nil, 0.0001)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Hooks deserialisation (if the programmer provided for this) and calls
|
143
|
+
# #deserialize_special for each object in the object stream. This allows
|
144
|
+
# construction of back channels from replacement tokens, for example.
|
145
|
+
#
|
146
|
+
def deserialize(io)
|
147
|
+
using_callbacks(io) do
|
148
|
+
@serializer.de(io) { |obj| deserialize_special(io, obj) }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def round_robin(list)
|
153
|
+
@round_robin_index += 1
|
154
|
+
if @round_robin_index >= list.size
|
155
|
+
@round_robin_index = 0
|
156
|
+
end
|
157
|
+
|
158
|
+
# Create a duplicate of list that has its elements rotated by
|
159
|
+
# @round_robin_index
|
160
|
+
list = list.dup
|
161
|
+
list = list + list.shift(@round_robin_index)
|
162
|
+
end
|
163
|
+
|
164
|
+
def accept_new_connections
|
165
|
+
loop do
|
166
|
+
@client_sockets << @socket.accept_nonblock
|
167
|
+
end
|
168
|
+
rescue Errno::EAGAIN
|
169
|
+
# This means that there are no sockets to accept. Continue.
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
data/lib/cod/tcp_client.rb
CHANGED
@@ -135,7 +135,11 @@ module Cod
|
|
135
135
|
# @private
|
136
136
|
#
|
137
137
|
def to_read_fds
|
138
|
-
|
138
|
+
if @connection
|
139
|
+
[@connection.socket]
|
140
|
+
else
|
141
|
+
[]
|
142
|
+
end
|
139
143
|
end
|
140
144
|
private
|
141
145
|
# Checks to see in which of the three connection phases we're in. If we're
|
@@ -169,7 +173,6 @@ module Cod
|
|
169
173
|
@connection.write(
|
170
174
|
@serializer.en(msg))
|
171
175
|
rescue Exception => e
|
172
|
-
warn e
|
173
176
|
raise
|
174
177
|
end
|
175
178
|
|
@@ -270,4 +273,4 @@ module Cod
|
|
270
273
|
end
|
271
274
|
end
|
272
275
|
end
|
273
|
-
end
|
276
|
+
end
|
data/lib/cod/tcp_server.rb
CHANGED
@@ -26,70 +26,24 @@ module Cod
|
|
26
26
|
# This means that you can transmit the client channel through the connection
|
27
27
|
# as part of the message you send.
|
28
28
|
#
|
29
|
-
class TcpServer
|
29
|
+
class TcpServer < SocketServer
|
30
30
|
def initialize(bind_to, serializer)
|
31
|
-
|
32
|
-
@client_sockets = []
|
33
|
-
@round_robin_index = 0
|
34
|
-
@messages = Array.new
|
35
|
-
@serializer = serializer
|
31
|
+
super(serializer, TCPServer.new(*bind_to.split(':')))
|
36
32
|
end
|
37
33
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
# @param opts [Hash]
|
45
|
-
# @return [Object] message sent by one of the clients
|
46
|
-
#
|
47
|
-
def get(opts={})
|
48
|
-
msg, socket = _get(opts)
|
49
|
-
return msg
|
50
|
-
end
|
51
|
-
|
52
|
-
# Receives one object from the channel. Returns a tuple of
|
53
|
-
# <message,channel> where channel is a tcp channel that links back to the
|
54
|
-
# client that sent message.
|
55
|
-
#
|
56
|
-
# Using this method, the server can communicate back to its clients
|
57
|
-
# individually instead of collectively.
|
58
|
-
#
|
59
|
-
# @example Answering to the client that sent a specific message
|
60
|
-
# msg, chan = server.get_ext
|
61
|
-
# chan.put :answer
|
62
|
-
#
|
63
|
-
# @param opts [Hash]
|
64
|
-
# @return [Array<Object, TcpClient>] tuple of the message that was sent
|
65
|
-
# a channel back to the client that sent the message
|
66
|
-
def get_ext(opts={})
|
67
|
-
msg, socket = _get(opts)
|
68
|
-
return [
|
69
|
-
msg,
|
70
|
-
produce_back_channel(socket)]
|
34
|
+
def deserialize_special(socket, obj)
|
35
|
+
if obj.kind_of?(TcpClient::OtherEnd)
|
36
|
+
return back_channel(socket)
|
37
|
+
end
|
38
|
+
return obj
|
71
39
|
end
|
72
40
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
@client_sockets.each { |io| io.close }
|
78
|
-
end
|
79
|
-
|
80
|
-
# Returns an array of IOs that Cod.select should select on.
|
81
|
-
#
|
82
|
-
def to_read_fds
|
83
|
-
@client_sockets
|
41
|
+
def back_channel(socket)
|
42
|
+
TcpClient.new(
|
43
|
+
TcpClient::Connection.new(socket, self),
|
44
|
+
serializer)
|
84
45
|
end
|
85
46
|
|
86
|
-
# Returns the number of clients that are connected to this server
|
87
|
-
# currently.
|
88
|
-
#
|
89
|
-
def connections
|
90
|
-
@client_sockets.size
|
91
|
-
end
|
92
|
-
|
93
47
|
# --------------------------------------------------------- service/client
|
94
48
|
|
95
49
|
def service
|
@@ -102,104 +56,5 @@ module Cod
|
|
102
56
|
def client(answers_to)
|
103
57
|
Service::Client.new(answers_to, answers_to)
|
104
58
|
end
|
105
|
-
|
106
|
-
# ------------------------------------------------------- connection owner
|
107
|
-
|
108
|
-
# Notifies the TcpServer that one of its connections needs to be closed.
|
109
|
-
# This can be triggered by using #get_ext to obtain a handle to connections
|
110
|
-
# and then calling #close on that connection.
|
111
|
-
#
|
112
|
-
# @param socket [TCPSocket] the socket that needs to be closed
|
113
|
-
# @return [void]
|
114
|
-
#
|
115
|
-
def request_close(socket)
|
116
|
-
@client_sockets.delete(socket)
|
117
|
-
socket.close
|
118
|
-
end
|
119
|
-
|
120
|
-
private
|
121
|
-
def _get(opts)
|
122
|
-
loop do
|
123
|
-
# Return a buffered message if there is one left.
|
124
|
-
return @messages.shift unless @messages.empty?
|
125
|
-
|
126
|
-
# Shuffle the socket list around, so we don't always read from the
|
127
|
-
# same client first.
|
128
|
-
socket_list = round_robin(@client_sockets)
|
129
|
-
|
130
|
-
# Append the server socket to be able to react to new connections
|
131
|
-
# that are made.
|
132
|
-
socket_list << @socket
|
133
|
-
|
134
|
-
# Sleep until either a new connection is made or data is available on
|
135
|
-
# one of the old connections.
|
136
|
-
rr, _, _ = IO.select(socket_list, nil, nil)
|
137
|
-
next unless rr
|
138
|
-
|
139
|
-
# Accept new connections
|
140
|
-
if rr.include?(@socket)
|
141
|
-
accept_new_connections
|
142
|
-
rr.delete(@socket)
|
143
|
-
end
|
144
|
-
|
145
|
-
handle_socket_events(rr, opts)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def handle_socket_events(sockets, opts)
|
150
|
-
sockets.each do |io|
|
151
|
-
if io.eof?
|
152
|
-
@client_sockets.delete(io)
|
153
|
-
io.close
|
154
|
-
else
|
155
|
-
consume_pending io, opts
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
def consume_pending(io, opts)
|
161
|
-
until io.eof?
|
162
|
-
@messages << [
|
163
|
-
deserialize(io),
|
164
|
-
io]
|
165
|
-
|
166
|
-
# More messages from this socket?
|
167
|
-
return unless IO.select([io], nil, nil, 0.0001)
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def deserialize(io)
|
172
|
-
@serializer.de(io) { |obj|
|
173
|
-
obj.kind_of?(TcpClient::OtherEnd) ?
|
174
|
-
produce_back_channel(io) :
|
175
|
-
obj
|
176
|
-
}
|
177
|
-
end
|
178
|
-
|
179
|
-
def round_robin(list)
|
180
|
-
@round_robin_index += 1
|
181
|
-
if @round_robin_index >= list.size
|
182
|
-
@round_robin_index = 0
|
183
|
-
end
|
184
|
-
|
185
|
-
# Create a duplicate of list that has its elements rotated by
|
186
|
-
# @round_robin_index
|
187
|
-
list = list.dup
|
188
|
-
list = list + list.shift(@round_robin_index)
|
189
|
-
end
|
190
|
-
|
191
|
-
def accept_new_connections
|
192
|
-
loop do
|
193
|
-
@client_sockets << @socket.accept_nonblock
|
194
|
-
end
|
195
|
-
rescue Errno::EAGAIN
|
196
|
-
# This means that there are no sockets to accept. Continue.
|
197
|
-
end
|
198
|
-
|
199
|
-
def produce_back_channel(socket)
|
200
|
-
TcpClient.new(
|
201
|
-
TcpClient::Connection.new(socket, self),
|
202
|
-
@serializer)
|
203
|
-
end
|
204
59
|
end
|
205
60
|
end
|
data/lib/cod/work_queue.rb
CHANGED
@@ -35,7 +35,7 @@ module Cod
|
|
35
35
|
attr_reader :thread
|
36
36
|
|
37
37
|
def try_work
|
38
|
-
|
38
|
+
exclusive {
|
39
39
|
# NOTE if predicate is nil or not set, no work will be accomplished.
|
40
40
|
# This is the way I need it.
|
41
41
|
while !@queue.empty? && predicate?
|
@@ -73,7 +73,9 @@ module Cod
|
|
73
73
|
# work_queue.schedule { a_piece_of_work }
|
74
74
|
#
|
75
75
|
def schedule(&work)
|
76
|
-
|
76
|
+
exclusive {
|
77
|
+
@queue << work
|
78
|
+
}
|
77
79
|
end
|
78
80
|
|
79
81
|
# Shuts down the queue properly, without waiting for work to be completed.
|
@@ -89,7 +91,9 @@ module Cod
|
|
89
91
|
# Returns the size of the queue.
|
90
92
|
#
|
91
93
|
def size
|
92
|
-
|
94
|
+
exclusive {
|
95
|
+
@queue.size
|
96
|
+
}
|
93
97
|
end
|
94
98
|
|
95
99
|
def clear_thread_semaphore
|
metadata
CHANGED
@@ -1,110 +1,83 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cod
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.6.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Kaspar Schiess
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-09-23 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: rake
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
|
-
requirements:
|
19
|
-
- - ! '>='
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
version: '0'
|
22
|
-
type: :development
|
23
|
-
prerelease: false
|
24
|
-
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
|
-
requirements:
|
27
|
-
- - ! '>='
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '0'
|
30
13
|
- !ruby/object:Gem::Dependency
|
31
14
|
name: rspec
|
32
15
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
16
|
requirements:
|
35
|
-
- -
|
17
|
+
- - ">="
|
36
18
|
- !ruby/object:Gem::Version
|
37
19
|
version: '0'
|
38
20
|
type: :development
|
39
21
|
prerelease: false
|
40
22
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
23
|
requirements:
|
43
|
-
- -
|
24
|
+
- - ">="
|
44
25
|
- !ruby/object:Gem::Version
|
45
26
|
version: '0'
|
46
27
|
- !ruby/object:Gem::Dependency
|
47
28
|
name: flexmock
|
48
29
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
30
|
requirements:
|
51
|
-
- -
|
31
|
+
- - ">="
|
52
32
|
- !ruby/object:Gem::Version
|
53
33
|
version: '0'
|
54
34
|
type: :development
|
55
35
|
prerelease: false
|
56
36
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
37
|
requirements:
|
59
|
-
- -
|
38
|
+
- - ">="
|
60
39
|
- !ruby/object:Gem::Version
|
61
40
|
version: '0'
|
62
41
|
- !ruby/object:Gem::Dependency
|
63
42
|
name: yard
|
64
43
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
44
|
requirements:
|
67
|
-
- -
|
45
|
+
- - ">="
|
68
46
|
- !ruby/object:Gem::Version
|
69
47
|
version: '0'
|
70
48
|
type: :development
|
71
49
|
prerelease: false
|
72
50
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
51
|
requirements:
|
75
|
-
- -
|
52
|
+
- - ">="
|
76
53
|
- !ruby/object:Gem::Version
|
77
54
|
version: '0'
|
78
55
|
- !ruby/object:Gem::Dependency
|
79
56
|
name: guard
|
80
57
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
58
|
requirements:
|
83
|
-
- -
|
59
|
+
- - ">="
|
84
60
|
- !ruby/object:Gem::Version
|
85
61
|
version: '0'
|
86
62
|
type: :development
|
87
63
|
prerelease: false
|
88
64
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
65
|
requirements:
|
91
|
-
- -
|
66
|
+
- - ">="
|
92
67
|
- !ruby/object:Gem::Version
|
93
68
|
version: '0'
|
94
69
|
- !ruby/object:Gem::Dependency
|
95
70
|
name: growl
|
96
71
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
72
|
requirements:
|
99
|
-
- -
|
73
|
+
- - ">="
|
100
74
|
- !ruby/object:Gem::Version
|
101
75
|
version: '0'
|
102
76
|
type: :development
|
103
77
|
prerelease: false
|
104
78
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
79
|
requirements:
|
107
|
-
- -
|
80
|
+
- - ">="
|
108
81
|
- !ruby/object:Gem::Version
|
109
82
|
version: '0'
|
110
83
|
description:
|
@@ -116,13 +89,29 @@ extra_rdoc_files:
|
|
116
89
|
files:
|
117
90
|
- HISTORY.txt
|
118
91
|
- LICENSE
|
119
|
-
- Rakefile
|
120
92
|
- README
|
93
|
+
- examples/bs_ping_pong/ping.rb
|
94
|
+
- examples/bs_ping_pong/pong.rb
|
95
|
+
- examples/example_scaffold.rb
|
96
|
+
- examples/forked_server/README
|
97
|
+
- examples/forked_server/forked.rb
|
98
|
+
- examples/master_child.rb
|
99
|
+
- examples/master_child_bidir.rb
|
100
|
+
- examples/netcat/README
|
101
|
+
- examples/netcat/server.rb
|
102
|
+
- examples/protocol-buffers/README
|
103
|
+
- examples/protocol-buffers/master_child.rb
|
104
|
+
- examples/service.rb
|
105
|
+
- examples/tcp.rb
|
106
|
+
- examples/tcp_get_ext.rb
|
107
|
+
- lib/cod.rb
|
108
|
+
- lib/cod/beanstalk.rb
|
121
109
|
- lib/cod/beanstalk/channel.rb
|
122
110
|
- lib/cod/beanstalk/serializer.rb
|
123
111
|
- lib/cod/beanstalk/service.rb
|
124
|
-
- lib/cod/
|
125
|
-
- lib/cod/
|
112
|
+
- lib/cod/bidir.rb
|
113
|
+
- lib/cod/bidir_server.rb
|
114
|
+
- lib/cod/callbacks.rb
|
126
115
|
- lib/cod/channel.rb
|
127
116
|
- lib/cod/iopair.rb
|
128
117
|
- lib/cod/line_serializer.rb
|
@@ -133,54 +122,34 @@ files:
|
|
133
122
|
- lib/cod/select_group.rb
|
134
123
|
- lib/cod/service.rb
|
135
124
|
- lib/cod/simple_serializer.rb
|
125
|
+
- lib/cod/socket_server.rb
|
136
126
|
- lib/cod/tcp_client.rb
|
137
127
|
- lib/cod/tcp_server.rb
|
138
128
|
- lib/cod/work_queue.rb
|
139
|
-
- lib/cod.rb
|
140
129
|
- lib/tcp_proxy.rb
|
141
|
-
- examples/bs_ping_pong/ping.rb
|
142
|
-
- examples/bs_ping_pong/pong.rb
|
143
|
-
- examples/example_scaffold.rb
|
144
|
-
- examples/forked_server/forked.rb
|
145
|
-
- examples/forked_server/README
|
146
|
-
- examples/master_child.rb
|
147
|
-
- examples/netcat/README
|
148
|
-
- examples/netcat/server.rb
|
149
|
-
- examples/protocol-buffers/master_child.rb
|
150
|
-
- examples/protocol-buffers/README
|
151
|
-
- examples/service.rb
|
152
|
-
- examples/tcp.rb
|
153
|
-
- examples/tcp_get_ext.rb
|
154
130
|
homepage: http://kschiess.github.com/cod
|
155
131
|
licenses: []
|
132
|
+
metadata: {}
|
156
133
|
post_install_message:
|
157
134
|
rdoc_options:
|
158
|
-
- --main
|
135
|
+
- "--main"
|
159
136
|
- README
|
160
137
|
require_paths:
|
161
138
|
- lib
|
162
139
|
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
-
none: false
|
164
140
|
requirements:
|
165
|
-
- -
|
141
|
+
- - ">="
|
166
142
|
- !ruby/object:Gem::Version
|
167
143
|
version: '0'
|
168
|
-
segments:
|
169
|
-
- 0
|
170
|
-
hash: 4413858275826022328
|
171
144
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
172
|
-
none: false
|
173
145
|
requirements:
|
174
|
-
- -
|
146
|
+
- - ">="
|
175
147
|
- !ruby/object:Gem::Version
|
176
148
|
version: '0'
|
177
|
-
segments:
|
178
|
-
- 0
|
179
|
-
hash: 4413858275826022328
|
180
149
|
requirements: []
|
181
150
|
rubyforge_project:
|
182
|
-
rubygems_version:
|
151
|
+
rubygems_version: 2.2.2
|
183
152
|
signing_key:
|
184
|
-
specification_version:
|
153
|
+
specification_version: 4
|
185
154
|
summary: Really simple IPC. Pipes, TCP sockets, beanstalkd, ...
|
186
155
|
test_files: []
|
data/Rakefile
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
require "rubygems"
|
2
|
-
require "rdoc/task"
|
3
|
-
require 'rspec/core/rake_task'
|
4
|
-
require 'rubygems/package_task'
|
5
|
-
require 'rake/clean'
|
6
|
-
|
7
|
-
desc "Run all tests: Exhaustive."
|
8
|
-
RSpec::Core::RakeTask.new
|
9
|
-
|
10
|
-
task :default => :spec
|
11
|
-
|
12
|
-
task :stats do
|
13
|
-
%w(lib spec).each do |path|
|
14
|
-
printf "%10s:", path
|
15
|
-
system %Q(find #{path} -name "*.rb" | xargs wc -l | grep total)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
require 'yard'
|
20
|
-
YARD::Rake::YardocTask.new do |t|
|
21
|
-
# t.files = ['lib/**/*.rb']
|
22
|
-
# t.options = ['--any', '--extra', '--opts'] # optional
|
23
|
-
end
|
24
|
-
|
25
|
-
# This task actually builds the gem.
|
26
|
-
task :gem => :spec
|
27
|
-
spec = eval(File.read('cod.gemspec'))
|
28
|
-
|
29
|
-
desc "Generate the gem package."
|
30
|
-
Gem::PackageTask.new(spec) do |pkg|
|
31
|
-
# pkg.need_tar = true
|
32
|
-
end
|
33
|
-
|
34
|
-
CLEAN << 'pkg'
|
35
|
-
CLEAN << 'doc'
|
data/lib/cod/bidir_pipe.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
module Cod
|
2
|
-
class BidirPipe < Channel
|
3
|
-
# The Cod::Pipe instance we're currently #put'ting to.
|
4
|
-
attr_reader :w
|
5
|
-
# The Cod::Pipe instance we're currently #get'ting from.
|
6
|
-
attr_reader :r
|
7
|
-
|
8
|
-
def initialize(serializer=nil, pipe_pair=nil)
|
9
|
-
@serializer = serializer || SimpleSerializer.new
|
10
|
-
@r, @w = pipe_pair || [Cod.pipe(@serializer), Cod.pipe(@serializer)]
|
11
|
-
end
|
12
|
-
|
13
|
-
def put(msg)
|
14
|
-
w.put(msg)
|
15
|
-
end
|
16
|
-
|
17
|
-
def get
|
18
|
-
r.get
|
19
|
-
end
|
20
|
-
|
21
|
-
def close
|
22
|
-
r.close
|
23
|
-
w.close
|
24
|
-
end
|
25
|
-
|
26
|
-
# Swaps the end of this pipe around.
|
27
|
-
#
|
28
|
-
def swap!
|
29
|
-
@r, @w = w, r
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|