cod 0.4.0 → 0.4.1
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.
- 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
|