cod 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|