cod 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -16
- data/HISTORY.txt +12 -0
- data/README +8 -6
- data/Rakefile +1 -1
- data/examples/netcat/README +2 -0
- data/examples/netcat/server.rb +25 -0
- data/examples/protocol-buffers/README +3 -0
- data/examples/protocol-buffers/master_child.rb +27 -0
- data/lib/cod.rb +35 -2
- data/lib/cod/beanstalk/channel.rb +14 -0
- data/lib/cod/bidir_pipe.rb +32 -0
- data/lib/cod/iopair.rb +40 -0
- data/lib/cod/pipe.rb +21 -33
- data/lib/cod/process.rb +34 -0
- data/lib/cod/protocol_buffers_serializer.rb +59 -0
- data/lib/cod/tcp_server.rb +30 -1
- data/lib/tcp_proxy.rb +132 -0
- metadata +54 -9
data/Gemfile
CHANGED
data/HISTORY.txt
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
= 0.4.1 / 23Jan2012
|
2
|
+
+ Cod.stdio returns a channel that is linked to this process' stdin and
|
3
|
+
stdout.
|
4
|
+
|
5
|
+
+ Cod.process spawns a process and allows communicating with that process
|
6
|
+
via stdin/stdout.
|
7
|
+
|
8
|
+
+ Cod.bidir_pipe spawns two Cod.pipe's, one for writing to the other end,
|
9
|
+
one for reading from it. (see above...)
|
10
|
+
|
11
|
+
+ Small fixes, preparation for connection robustness
|
12
|
+
|
1
13
|
== 0.4.0 / 29Nov2011
|
2
14
|
|
3
15
|
* A complete rewrite, focusing on lean implementation and logical/useful
|
data/README
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
+
Some people, when confronted with a problem, think "I know, I'll use
|
2
|
+
multithreading". Nothhw tpe yawrve o oblems.[1]
|
1
3
|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
A good place to start is the documentation for the Cod module.
|
4
|
+
Don't use threads, use processes. This is the toolkit for interprocess
|
5
|
+
communication that makes it real simple. A good place to start is the
|
6
|
+
documentation for the Cod module.
|
7
7
|
|
8
8
|
SYNOPSIS
|
9
9
|
|
@@ -29,4 +29,6 @@ that we had before, but some things have been designed more logically.
|
|
29
29
|
|
30
30
|
At version 0.4.0
|
31
31
|
|
32
|
-
(c) 2011 Kaspar Schiess
|
32
|
+
(c) 2011 Kaspar Schiess
|
33
|
+
|
34
|
+
[1] https://twitter.com/rogerbraun/status/160813717502705664
|
data/Rakefile
CHANGED
@@ -19,7 +19,7 @@ require 'sdoc'
|
|
19
19
|
|
20
20
|
# Generate documentation
|
21
21
|
RDoc::Task.new do |rdoc|
|
22
|
-
rdoc.title = "
|
22
|
+
rdoc.title = "cod - IPC made really simple."
|
23
23
|
rdoc.options << '--line-numbers'
|
24
24
|
rdoc.options << '--fmt' << 'shtml' # explictly set shtml generator
|
25
25
|
rdoc.template = 'direct' # lighter template used on railsapi.com
|
@@ -0,0 +1,25 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + "/../../lib")
|
2
|
+
require 'cod'
|
3
|
+
|
4
|
+
fork do
|
5
|
+
server = Cod.tcp_server('localhost:12345')
|
6
|
+
|
7
|
+
request, channel = server.get_ext
|
8
|
+
case request
|
9
|
+
when :get_time
|
10
|
+
channel.put Time.now
|
11
|
+
else
|
12
|
+
fail
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
fork do
|
17
|
+
sleep 1
|
18
|
+
process = Cod.process('nc localhost 12345')
|
19
|
+
channel = process.channel
|
20
|
+
|
21
|
+
channel.put :get_time
|
22
|
+
puts channel.get
|
23
|
+
end
|
24
|
+
|
25
|
+
Process.waitall
|
@@ -0,0 +1,27 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + "/../../lib")
|
2
|
+
require 'cod'
|
3
|
+
require 'cod/protocol_buffers_serializer'
|
4
|
+
|
5
|
+
require 'protocol_buffers'
|
6
|
+
require 'protocol_buffers/compiler'
|
7
|
+
|
8
|
+
|
9
|
+
ProtocolBuffers::Compiler.compile_and_load_string <<-EOS
|
10
|
+
message Foo {
|
11
|
+
required string bar = 1;
|
12
|
+
};
|
13
|
+
EOS
|
14
|
+
|
15
|
+
pipe = Cod.pipe(Cod::ProtocolBuffersSerializer.new)
|
16
|
+
|
17
|
+
child_pid = fork do
|
18
|
+
pipe.put Foo.new(:bar => 'bar')
|
19
|
+
pipe.put Foo.new(:bar => 'baz')
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
p pipe.get
|
24
|
+
p pipe.get
|
25
|
+
ensure
|
26
|
+
Process.wait(child_pid)
|
27
|
+
end
|
data/lib/cod.rb
CHANGED
@@ -26,11 +26,20 @@ module Cod
|
|
26
26
|
# Creates a pipe connection that is visible to this process and its
|
27
27
|
# children. (see Cod::Pipe)
|
28
28
|
#
|
29
|
-
def pipe
|
30
|
-
Cod::Pipe.new
|
29
|
+
def pipe(serializer=nil, pipe_pair=nil)
|
30
|
+
Cod::Pipe.new(serializer)
|
31
31
|
end
|
32
32
|
module_function :pipe
|
33
33
|
|
34
|
+
# Creates two channels based on Cod.pipe (unidirectional IO.pipe) and links
|
35
|
+
# things up so that you communication is bidirectional. Writes go to
|
36
|
+
# #out and reads come from #in.
|
37
|
+
#
|
38
|
+
def bidir_pipe(serializer=nil, pipe_pair=nil)
|
39
|
+
Cod::BidirPipe.new(serializer, pipe_pair)
|
40
|
+
end
|
41
|
+
module_function :bidir_pipe
|
42
|
+
|
34
43
|
# Creates a tcp connection to the destination and returns a channel for it.
|
35
44
|
# (see Cod::TcpClient)
|
36
45
|
#
|
@@ -57,6 +66,25 @@ module Cod
|
|
57
66
|
end
|
58
67
|
module_function :beanstalk
|
59
68
|
|
69
|
+
# Runs a command via Process.spawn, then links a channel to the commands
|
70
|
+
# stdout and stdin. Returns the commands pid and the channel.
|
71
|
+
#
|
72
|
+
# Example:
|
73
|
+
# pid, channel = Cod.process('cat')
|
74
|
+
def process(command, serializer=nil)
|
75
|
+
Cod::Process.new(command, serializer)
|
76
|
+
end
|
77
|
+
module_function :process
|
78
|
+
|
79
|
+
# Links a process' stdin and stdout up with a pipe. This means that the
|
80
|
+
# pipes #put method will print to stdout, and the #get method will read from
|
81
|
+
# stdin.
|
82
|
+
#
|
83
|
+
def stdio(serializer=nil)
|
84
|
+
Cod::Pipe.new(serializer, [$stdin, $stdout])
|
85
|
+
end
|
86
|
+
module_function :stdio
|
87
|
+
|
60
88
|
# Indicates that the given channel is write only. This gets raised on
|
61
89
|
# operations like #put.
|
62
90
|
#
|
@@ -79,11 +107,16 @@ end
|
|
79
107
|
require 'cod/select_group'
|
80
108
|
require 'cod/select'
|
81
109
|
|
110
|
+
require 'cod/iopair'
|
111
|
+
|
82
112
|
require 'cod/channel'
|
83
113
|
|
84
114
|
require 'cod/simple_serializer'
|
85
115
|
|
86
116
|
require 'cod/pipe'
|
117
|
+
require 'cod/bidir_pipe'
|
118
|
+
|
119
|
+
require 'cod/process'
|
87
120
|
|
88
121
|
require 'cod/tcp_client'
|
89
122
|
require 'cod/tcp_server'
|
@@ -11,13 +11,27 @@ module Cod::Beanstalk
|
|
11
11
|
class Channel < Cod::Channel
|
12
12
|
JOB_PRIORITY = 0
|
13
13
|
|
14
|
+
# Which tube this channel is connected to
|
15
|
+
attr_reader :tube_name
|
16
|
+
# Beanstalkd server this channel is connected to
|
17
|
+
attr_reader :server_url
|
18
|
+
|
14
19
|
def initialize(tube_name, server_url)
|
20
|
+
super()
|
15
21
|
@tube_name, @server_url = tube_name, server_url
|
16
22
|
|
17
23
|
@body_serializer = Cod::SimpleSerializer.new
|
18
24
|
@transport = connection(server_url, tube_name)
|
19
25
|
end
|
20
26
|
|
27
|
+
# Allow #dup on beanstalkd channels, resulting in a _new_ connection to
|
28
|
+
# the beanstalkd server.
|
29
|
+
#
|
30
|
+
def initialize_copy(other)
|
31
|
+
super(other)
|
32
|
+
initialize(other.tube_name, other.server_url)
|
33
|
+
end
|
34
|
+
|
21
35
|
def put(msg)
|
22
36
|
pri = JOB_PRIORITY
|
23
37
|
delay = 0
|
@@ -0,0 +1,32 @@
|
|
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
|
data/lib/cod/iopair.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Cod
|
2
|
+
IOPair = Struct.new(:r, :w) do
|
3
|
+
def initialize(r=nil, w=nil)
|
4
|
+
if r && w
|
5
|
+
super(r, w)
|
6
|
+
else
|
7
|
+
super(*IO.pipe)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Performs a deep copy of the structure.
|
12
|
+
def initialize_copy(other)
|
13
|
+
super
|
14
|
+
self.r = other.r.dup if other.r
|
15
|
+
self.w = other.w.dup if other.w
|
16
|
+
end
|
17
|
+
def write(buf)
|
18
|
+
close_r
|
19
|
+
raise Cod::ReadOnlyChannel unless w
|
20
|
+
w.write(buf)
|
21
|
+
end
|
22
|
+
def read(serializer)
|
23
|
+
close_w
|
24
|
+
raise Cod::WriteOnlyChannel unless r
|
25
|
+
serializer.de(r)
|
26
|
+
end
|
27
|
+
def close
|
28
|
+
close_r
|
29
|
+
close_w
|
30
|
+
end
|
31
|
+
def close_r
|
32
|
+
r.close if r
|
33
|
+
self.r = nil
|
34
|
+
end
|
35
|
+
def close_w
|
36
|
+
w.close if w
|
37
|
+
self.w = nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/cod/pipe.rb
CHANGED
@@ -12,36 +12,10 @@ module Cod
|
|
12
12
|
# create the channel before forking the child, since master and child will
|
13
13
|
# share all objects that were available before the fork.
|
14
14
|
#
|
15
|
-
class Pipe
|
15
|
+
class Pipe < Channel
|
16
16
|
attr_reader :pipe
|
17
17
|
attr_reader :serializer
|
18
18
|
|
19
|
-
IOPair = Struct.new(:r, :w) do
|
20
|
-
# Performs a deep copy of the structure.
|
21
|
-
def initialize_copy(other)
|
22
|
-
super
|
23
|
-
self.r = other.r.dup if other.r
|
24
|
-
self.w = other.w.dup if other.w
|
25
|
-
end
|
26
|
-
def write(buf)
|
27
|
-
close_r
|
28
|
-
raise Cod::ReadOnlyChannel unless w
|
29
|
-
w.write(buf)
|
30
|
-
end
|
31
|
-
def close
|
32
|
-
close_r
|
33
|
-
close_w
|
34
|
-
end
|
35
|
-
def close_r
|
36
|
-
r.close if r
|
37
|
-
self.r = nil
|
38
|
-
end
|
39
|
-
def close_w
|
40
|
-
w.close if w
|
41
|
-
self.w = nil
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
19
|
# A few methods that a pipe split must answer to. The split itself is
|
46
20
|
# basically an array instance; these methods add some calling safety and
|
47
21
|
# convenience.
|
@@ -51,10 +25,10 @@ module Cod
|
|
51
25
|
def write; last; end
|
52
26
|
end
|
53
27
|
|
54
|
-
def initialize(serializer=nil)
|
55
|
-
super
|
28
|
+
def initialize(serializer=nil, pipe_pair=nil)
|
29
|
+
super()
|
56
30
|
@serializer = serializer || SimpleSerializer.new
|
57
|
-
@pipe = IOPair.new(*
|
31
|
+
@pipe = IOPair.new(*pipe_pair)
|
58
32
|
end
|
59
33
|
|
60
34
|
# Creates a copy of this pipe channel. This performs a shallow #dup except
|
@@ -148,13 +122,13 @@ module Cod
|
|
148
122
|
not result.nil?
|
149
123
|
end
|
150
124
|
def to_read_fds
|
151
|
-
|
125
|
+
r
|
152
126
|
end
|
153
127
|
|
154
128
|
# Returns true if you can read from this pipe.
|
155
129
|
#
|
156
130
|
def can_read?
|
157
|
-
not
|
131
|
+
not r.nil?
|
158
132
|
end
|
159
133
|
|
160
134
|
# Returns true if you can write to this pipe.
|
@@ -163,6 +137,20 @@ module Cod
|
|
163
137
|
not pipe.w.nil?
|
164
138
|
end
|
165
139
|
|
140
|
+
# ------------------------------------------------------- internal helpers
|
141
|
+
|
142
|
+
# Returns the read end of the pipe
|
143
|
+
#
|
144
|
+
def r
|
145
|
+
pipe.r
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns the write end of the pipe
|
149
|
+
#
|
150
|
+
def w
|
151
|
+
pipe.w
|
152
|
+
end
|
153
|
+
|
166
154
|
# --------------------------------------------------------- service/client
|
167
155
|
|
168
156
|
def service
|
@@ -182,7 +170,7 @@ module Cod
|
|
182
170
|
private
|
183
171
|
def deserialize_one
|
184
172
|
# Now deserialize one message from the buffer in io
|
185
|
-
|
173
|
+
pipe.read(serializer)
|
186
174
|
end
|
187
175
|
end
|
188
176
|
end
|
data/lib/cod/process.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Cod
|
2
|
+
class Process
|
3
|
+
attr_reader :pid
|
4
|
+
|
5
|
+
def initialize(command, serializer=nil)
|
6
|
+
@serializer = serializer || SimpleSerializer.new
|
7
|
+
|
8
|
+
run(command)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(command)
|
12
|
+
@pipe = Cod.bidir_pipe(@serializer)
|
13
|
+
|
14
|
+
@pid = ::Process.spawn(command,
|
15
|
+
:in => @pipe.w.r,
|
16
|
+
:out => @pipe.r.w)
|
17
|
+
end
|
18
|
+
|
19
|
+
def channel
|
20
|
+
@pipe
|
21
|
+
end
|
22
|
+
|
23
|
+
def kill
|
24
|
+
::Process.kill :TERM, @pid
|
25
|
+
end
|
26
|
+
def terminate
|
27
|
+
@pipe.w.close
|
28
|
+
end
|
29
|
+
|
30
|
+
def wait
|
31
|
+
::Process.wait(@pid)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Install the ruby-protocol-buffers gem for this to work.
|
2
|
+
require 'protocol_buffers'
|
3
|
+
|
4
|
+
module Cod
|
5
|
+
# Serializes a protocol buffer (googles protobufs) message to the wire and
|
6
|
+
# reads it back. Protobufs are not self-delimited, this is why we store the
|
7
|
+
# messages in the following on-wire format:
|
8
|
+
#
|
9
|
+
# LEN(class) BYTES(class) LEN(message) BYTES(message)
|
10
|
+
#
|
11
|
+
# where LEN is a varint representation of the length of the contained item
|
12
|
+
# and BYTES are the binary bytes of said item.
|
13
|
+
#
|
14
|
+
# This is not the most space efficient manner of representing things on the
|
15
|
+
# wire. It also assumes that you have defined the message classes on both
|
16
|
+
# sides (client and server).
|
17
|
+
#
|
18
|
+
# For applications where this is a problem, you can always use this
|
19
|
+
# implementation as a guide for your own implementation. For example,
|
20
|
+
# message polymorphism could be coded as a single byte on the wire, allowing
|
21
|
+
# for one of 255 messages to be sent each time. For really efficient
|
22
|
+
# transfers, you could even send a fixed amount of bytes and one message,
|
23
|
+
# getting the most out of protobufs.
|
24
|
+
#
|
25
|
+
# Please see examples/protocol-buffers/master_child.rb for information
|
26
|
+
# on how to use this.
|
27
|
+
#
|
28
|
+
class ProtocolBuffersSerializer
|
29
|
+
Varint = ProtocolBuffers::Varint
|
30
|
+
|
31
|
+
def en(obj)
|
32
|
+
sio = ProtocolBuffers.bin_sio
|
33
|
+
|
34
|
+
# Assuming that obj is a protocol buffers message object, this should
|
35
|
+
# work:
|
36
|
+
klass_name = obj.class.name
|
37
|
+
buffer = obj.to_s
|
38
|
+
|
39
|
+
Varint.encode(sio, klass_name.size)
|
40
|
+
sio.write(klass_name)
|
41
|
+
|
42
|
+
Varint.encode(sio, buffer.size)
|
43
|
+
sio.write(buffer)
|
44
|
+
|
45
|
+
sio.string
|
46
|
+
end
|
47
|
+
|
48
|
+
def de(io)
|
49
|
+
klass_size = Varint.decode(io)
|
50
|
+
klass_name = io.read(klass_size)
|
51
|
+
|
52
|
+
klass = self.class.const_get(klass_name)
|
53
|
+
|
54
|
+
msg_size = Varint.decode(io)
|
55
|
+
limited_io = LimitedIO.new(io, msg_size)
|
56
|
+
klass.parse(limited_io)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/cod/tcp_server.rb
CHANGED
@@ -1,4 +1,33 @@
|
|
1
|
-
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Cod
|
4
|
+
# A tcp server channel. Messages are read from any of the connected sockets
|
5
|
+
# in a round robin fashion.
|
6
|
+
#
|
7
|
+
# Synopsis:
|
8
|
+
# server = Cod.tcp_server('localhost:12345')
|
9
|
+
# server.get # 'a message'
|
10
|
+
# msg, chan = server.get_ext
|
11
|
+
#
|
12
|
+
# There is no implementation of #put that would broadcast back to all
|
13
|
+
# connected sockets, this is up to you to implement. Instead, you can use
|
14
|
+
# one of two ways to obtain a channel for talking back to a specific client:
|
15
|
+
#
|
16
|
+
# Using #get_ext:
|
17
|
+
# msg, chan = server.get_ext
|
18
|
+
#
|
19
|
+
# chan is a two way connected channel to the specific client that has opened
|
20
|
+
# its communication with msg.
|
21
|
+
#
|
22
|
+
# Using plain #get:
|
23
|
+
# # on the client:
|
24
|
+
# client.put [client, :msg]
|
25
|
+
# # on the server
|
26
|
+
# chan, msg = server.get
|
27
|
+
#
|
28
|
+
# This means that you can transmit the client channel through the connection
|
29
|
+
# as part of the message you send.
|
30
|
+
#
|
2
31
|
class TcpServer
|
3
32
|
def initialize(bind_to)
|
4
33
|
@socket = TCPServer.new(*bind_to.split(':'))
|
data/lib/tcp_proxy.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
class TCPProxy
|
2
|
+
attr_reader :connections
|
3
|
+
|
4
|
+
def initialize(host, from_port, to_port)
|
5
|
+
@ins = TCPServer.new(host, from_port)
|
6
|
+
|
7
|
+
@host = host
|
8
|
+
@from_port, @to_port = from_port, to_port
|
9
|
+
|
10
|
+
# Active connections and mutex to protect access to it.
|
11
|
+
@connections = []
|
12
|
+
@connections_m = Mutex.new
|
13
|
+
|
14
|
+
# Are we currently accepting new connections?
|
15
|
+
@accept_new = true
|
16
|
+
|
17
|
+
@thread = Thread.start(&method(:thread_main))
|
18
|
+
end
|
19
|
+
|
20
|
+
def close
|
21
|
+
@shutdown = true
|
22
|
+
|
23
|
+
@thread.join
|
24
|
+
@thread = nil
|
25
|
+
|
26
|
+
# Since the thread is stopped now, we can be sure no new connections are
|
27
|
+
# accepted. This is why we access the collection without locking.
|
28
|
+
@connections.each do |connection|
|
29
|
+
connection.close
|
30
|
+
end
|
31
|
+
@ins.close
|
32
|
+
end
|
33
|
+
|
34
|
+
def block
|
35
|
+
@accept_new = false
|
36
|
+
end
|
37
|
+
def allow
|
38
|
+
@accept_new = true
|
39
|
+
end
|
40
|
+
|
41
|
+
def drop_all
|
42
|
+
# Copy the connections and then empty the collection
|
43
|
+
connections = @connections_m.synchronize {
|
44
|
+
@connections.tap {
|
45
|
+
@connections = [] } }
|
46
|
+
|
47
|
+
connections.each do |conn|
|
48
|
+
conn.close
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Inside the background thread ----------------------------------------
|
53
|
+
|
54
|
+
def thread_main
|
55
|
+
loop do
|
56
|
+
accept_connections if @accept_new
|
57
|
+
|
58
|
+
forward_data
|
59
|
+
|
60
|
+
break if @shutdown
|
61
|
+
end
|
62
|
+
rescue Exception => ex
|
63
|
+
p [:uncaught, ex]
|
64
|
+
ex.backtrace.each do |line|
|
65
|
+
puts line
|
66
|
+
end
|
67
|
+
raise
|
68
|
+
end
|
69
|
+
|
70
|
+
class Connection
|
71
|
+
def initialize(in_sock, out_sock)
|
72
|
+
@m = Mutex.new
|
73
|
+
@in_sock, @out_sock = in_sock, out_sock
|
74
|
+
end
|
75
|
+
|
76
|
+
def close
|
77
|
+
@m.synchronize {
|
78
|
+
@in_sock.close; @in_sock = nil
|
79
|
+
@out_sock.close; @out_sock = nil }
|
80
|
+
end
|
81
|
+
|
82
|
+
def pump_synchronized(n=10)
|
83
|
+
@m.synchronize {
|
84
|
+
return unless @in_sock && @out_sock
|
85
|
+
pump(n) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def pump(n)
|
89
|
+
while n>0
|
90
|
+
available_sockets = [@in_sock, @out_sock]
|
91
|
+
ready_sockets, (*) = IO.select(available_sockets, nil, nil, 0)
|
92
|
+
|
93
|
+
break unless ready_sockets && !ready_sockets.empty?
|
94
|
+
|
95
|
+
ready_sockets.each do |socket|
|
96
|
+
buf = socket.read_nonblock(16*1024)
|
97
|
+
|
98
|
+
if socket == @in_sock
|
99
|
+
puts "--> #{buf.size}"
|
100
|
+
@out_sock.write(buf)
|
101
|
+
else
|
102
|
+
puts "<-- #{buf.size}"
|
103
|
+
@in_sock.write(buf)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
n -= 1
|
108
|
+
end
|
109
|
+
rescue Errno::EAGAIN
|
110
|
+
# Read would block, attempt later
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def accept_connections
|
115
|
+
loop do
|
116
|
+
in_sock = @ins.accept_nonblock
|
117
|
+
out_sock = TCPSocket.new(@host, @to_port)
|
118
|
+
|
119
|
+
@connections_m.synchronize {
|
120
|
+
@connections << Connection.new(in_sock, out_sock) }
|
121
|
+
end
|
122
|
+
rescue Errno::EAGAIN
|
123
|
+
# No more connections pending, stop accepting new connections
|
124
|
+
end
|
125
|
+
|
126
|
+
def forward_data
|
127
|
+
connections = @connections_m.synchronize { @connections.dup }
|
128
|
+
connections.each do |conn|
|
129
|
+
conn.pump_synchronized
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cod
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-01-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70299431388260 !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: *70299431388260
|
14
25
|
- !ruby/object:Gem::Dependency
|
15
26
|
name: rspec
|
16
|
-
requirement: &
|
27
|
+
requirement: &70299431387140 !ruby/object:Gem::Requirement
|
17
28
|
none: false
|
18
29
|
requirements:
|
19
30
|
- - ! '>='
|
@@ -21,10 +32,10 @@ dependencies:
|
|
21
32
|
version: '0'
|
22
33
|
type: :development
|
23
34
|
prerelease: false
|
24
|
-
version_requirements: *
|
35
|
+
version_requirements: *70299431387140
|
25
36
|
- !ruby/object:Gem::Dependency
|
26
37
|
name: flexmock
|
27
|
-
requirement: &
|
38
|
+
requirement: &70299431386460 !ruby/object:Gem::Requirement
|
28
39
|
none: false
|
29
40
|
requirements:
|
30
41
|
- - ! '>='
|
@@ -32,10 +43,21 @@ dependencies:
|
|
32
43
|
version: '0'
|
33
44
|
type: :development
|
34
45
|
prerelease: false
|
35
|
-
version_requirements: *
|
46
|
+
version_requirements: *70299431386460
|
36
47
|
- !ruby/object:Gem::Dependency
|
37
48
|
name: sdoc
|
38
|
-
requirement: &
|
49
|
+
requirement: &70299431385340 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70299431385340
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: guard
|
60
|
+
requirement: &70299431384600 !ruby/object:Gem::Requirement
|
39
61
|
none: false
|
40
62
|
requirements:
|
41
63
|
- - ! '>='
|
@@ -43,7 +65,18 @@ dependencies:
|
|
43
65
|
version: '0'
|
44
66
|
type: :development
|
45
67
|
prerelease: false
|
46
|
-
version_requirements: *
|
68
|
+
version_requirements: *70299431384600
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: growl
|
71
|
+
requirement: &70299431383600 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70299431383600
|
47
80
|
description:
|
48
81
|
email: kaspar.schiess@absurd.li
|
49
82
|
executables: []
|
@@ -60,8 +93,12 @@ files:
|
|
60
93
|
- lib/cod/beanstalk/serializer.rb
|
61
94
|
- lib/cod/beanstalk/service.rb
|
62
95
|
- lib/cod/beanstalk.rb
|
96
|
+
- lib/cod/bidir_pipe.rb
|
63
97
|
- lib/cod/channel.rb
|
98
|
+
- lib/cod/iopair.rb
|
64
99
|
- lib/cod/pipe.rb
|
100
|
+
- lib/cod/process.rb
|
101
|
+
- lib/cod/protocol_buffers_serializer.rb
|
65
102
|
- lib/cod/select.rb
|
66
103
|
- lib/cod/select_group.rb
|
67
104
|
- lib/cod/service.rb
|
@@ -70,12 +107,17 @@ files:
|
|
70
107
|
- lib/cod/tcp_server.rb
|
71
108
|
- lib/cod/work_queue.rb
|
72
109
|
- lib/cod.rb
|
110
|
+
- lib/tcp_proxy.rb
|
73
111
|
- examples/example_scaffold.rb
|
74
112
|
- examples/master_child.rb
|
113
|
+
- examples/netcat/README
|
114
|
+
- examples/netcat/server.rb
|
75
115
|
- examples/ping_pong/ping.rb
|
76
116
|
- examples/ping_pong/pong.rb
|
77
117
|
- examples/presence/client.rb
|
78
118
|
- examples/presence/server.rb
|
119
|
+
- examples/protocol-buffers/master_child.rb
|
120
|
+
- examples/protocol-buffers/README
|
79
121
|
- examples/queue/client.rb
|
80
122
|
- examples/queue/queue.rb
|
81
123
|
- examples/queue/README
|
@@ -98,13 +140,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
98
140
|
version: '0'
|
99
141
|
segments:
|
100
142
|
- 0
|
101
|
-
hash:
|
143
|
+
hash: 1455579630017282096
|
102
144
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
145
|
none: false
|
104
146
|
requirements:
|
105
147
|
- - ! '>='
|
106
148
|
- !ruby/object:Gem::Version
|
107
149
|
version: '0'
|
150
|
+
segments:
|
151
|
+
- 0
|
152
|
+
hash: 1455579630017282096
|
108
153
|
requirements: []
|
109
154
|
rubyforge_project:
|
110
155
|
rubygems_version: 1.8.10
|