cod 0.4.4 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.txt +4 -0
- data/README +1 -1
- data/examples/forked_server/README +15 -0
- data/examples/forked_server/forked.rb +46 -0
- data/lib/cod.rb +57 -23
- data/lib/cod/beanstalk.rb +1 -0
- data/lib/cod/beanstalk/channel.rb +13 -0
- data/lib/cod/line_serializer.rb +29 -0
- data/lib/cod/pipe.rb +57 -14
- data/lib/cod/process.rb +50 -0
- data/lib/cod/select.rb +11 -1
- data/lib/cod/tcp_client.rb +6 -0
- metadata +50 -17
data/HISTORY.txt
CHANGED
data/README
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
Demonstrates a server that uses more than one worker process to handle a
|
2
|
+
single tcp port.
|
3
|
+
|
4
|
+
This example forks one client that repeatedly makes connections to a tcp
|
5
|
+
server and then reads the servers pid from that connection. It counts what
|
6
|
+
pid has answered how many times and prints that result in the end.
|
7
|
+
|
8
|
+
The server accepts connections, writes its pid to it and then closes the
|
9
|
+
connections. It will wait at most TIMEOUT seconds for a new connection to
|
10
|
+
be made, otherwise it will terminate. This is to ensure that the example exits
|
11
|
+
cleanly and leaves no processes laying around.
|
12
|
+
|
13
|
+
You can try (for kicks) to double/triple/n-uple the number of clients that
|
14
|
+
does this counting. Server should keep up its even load distribution up to
|
15
|
+
a high number of clients.
|
@@ -0,0 +1,46 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + "/../../lib")
|
2
|
+
$:.unshift File.expand_path(File.dirname(__FILE__)) + "/../"
|
3
|
+
require 'cod'
|
4
|
+
require 'example_scaffold'
|
5
|
+
require 'pp'
|
6
|
+
require 'timeout'
|
7
|
+
|
8
|
+
def timeout_no_exception(seconds)
|
9
|
+
timeout(seconds, &Proc.new)
|
10
|
+
false
|
11
|
+
rescue Timeout::Error
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
client {
|
16
|
+
answer_map = Hash.new(0)
|
17
|
+
|
18
|
+
1000.times do
|
19
|
+
chan = Cod.tcp('localhost:32423')
|
20
|
+
chan.put :hei
|
21
|
+
|
22
|
+
pid = chan.get
|
23
|
+
answer_map[pid] += 1
|
24
|
+
|
25
|
+
chan.close
|
26
|
+
end
|
27
|
+
|
28
|
+
pp answer_map
|
29
|
+
}
|
30
|
+
|
31
|
+
server {
|
32
|
+
server = Cod.tcp_server('localhost:32423')
|
33
|
+
8.times do
|
34
|
+
fork {
|
35
|
+
loop do
|
36
|
+
break if timeout_no_exception(1) do
|
37
|
+
m, chan = server.get_ext
|
38
|
+
chan.put Process.pid
|
39
|
+
chan.close
|
40
|
+
end
|
41
|
+
end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
}
|
45
|
+
|
46
|
+
run
|
data/lib/cod.rb
CHANGED
@@ -3,42 +3,64 @@ require 'stringio'
|
|
3
3
|
# The core concept of Cod are 'channels'. (see {Cod::Channel::Base}) You can
|
4
4
|
# create such channels on top of the various transport layers. Once you have
|
5
5
|
# such a channel, you #put messages into it and you #get messages out of it.
|
6
|
-
# Messages are retrieved in FIFO manner
|
7
|
-
#
|
6
|
+
# Messages are retrieved in FIFO manner.
|
7
|
+
#
|
8
|
+
# channel.put :test1
|
9
|
+
# channel.put :test2
|
10
|
+
# channel.get # => :test1
|
8
11
|
#
|
9
12
|
# Cod also brings a few abstractions layered on top of channels: You can use
|
10
13
|
# channels to present 'services' (Cod::Service) to the network: A service is a
|
11
14
|
# simple one or two way RPC call. (one way = asynchronous)
|
12
15
|
#
|
16
|
+
# client = channel.client
|
17
|
+
# client.notify [:foo, :bar]
|
18
|
+
#
|
13
19
|
# Cod channels are serializable whereever possible. If you want to tell
|
14
20
|
# somebody where to write his answers and/or questions to, send him the
|
15
21
|
# channel! This is really powerful and used extensively in constructing the
|
16
22
|
# higher order primitives.
|
17
23
|
#
|
24
|
+
# server.put [:some_request, my_channel]
|
25
|
+
# # Server will receive my_channel and be able to contact us there.
|
26
|
+
#
|
18
27
|
# All Cod channels have a serializer. If you don't specify your own
|
19
28
|
# serializer, they will use Marshal.dump and Marshal.load. (see
|
20
29
|
# {Cod::SimpleSerializer}) This allows to send Ruby objects and not just
|
21
30
|
# strings by default. If you want to, you can of course go back to very strict
|
22
|
-
# wire formats, see {Cod::ProtocolBuffersSerializer}
|
31
|
+
# wire formats, see {Cod::ProtocolBuffersSerializer} or {Cod::LineSerializer}
|
32
|
+
# for an example of that.
|
33
|
+
#
|
34
|
+
# line_protocol_channel = Cod.pipe(Cod::LineSerializer.new)
|
35
|
+
# line_protocol_channel.put 'some_string'
|
23
36
|
#
|
24
37
|
# The goal of Cod is that you have to know only very few things about the
|
25
38
|
# network (the various transports) to be able to construct complex things. It
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
# most of the tricky stuff!
|
39
|
+
# also translates cryptic OS errors into plain text messages where it can't
|
40
|
+
# just handle them. This should give you a clear place to look at if things go
|
41
|
+
# wrong. Note that this can only be ever as good as the sum of situations Cod
|
42
|
+
# has been tested in. Contribute your observations and we'll come up with a
|
43
|
+
# way of dealing with most of the tricky stuff!
|
32
44
|
#
|
33
45
|
# @see Cod::Channel
|
46
|
+
#
|
47
|
+
# == Types of channels in this version
|
48
|
+
#
|
49
|
+
# {Cod.pipe} :: Transports via +IO.pipe+
|
50
|
+
# {Cod.tcp} :: Transports via TCP (client)
|
51
|
+
# {Cod.tcp_server} :: Transports via TCP (as a server)
|
52
|
+
# {Cod.stdio} :: Connects to +$stdin+ and +$stdout+ (+IO.pipe+)
|
53
|
+
# {Cod.process} :: Spawn a child process and connects to that process' +$stdin+ and +$stdout+ (+IO.pipe+)
|
54
|
+
# {Cod.beanstalk} :: Transports via a tube on beanstalkd
|
34
55
|
#
|
35
56
|
module Cod
|
36
57
|
# Creates a pipe connection that is visible to this process and its
|
37
58
|
# children.
|
38
59
|
#
|
39
|
-
# @
|
60
|
+
# @param serializer [#en,#de] optional serializer to use
|
61
|
+
# @return [Cod::Pipe]
|
40
62
|
#
|
41
|
-
def pipe(serializer=nil
|
63
|
+
def pipe(serializer=nil)
|
42
64
|
Cod::Pipe.new(serializer)
|
43
65
|
end
|
44
66
|
module_function :pipe
|
@@ -47,7 +69,9 @@ module Cod
|
|
47
69
|
# things up so that you communication is bidirectional. Writes go to
|
48
70
|
# #out and reads come from #in.
|
49
71
|
#
|
50
|
-
# @
|
72
|
+
# @overload bidir_pipe(serializer=nil)
|
73
|
+
# @param serializer [#en,#de] optional serializer to use
|
74
|
+
# @return [Cod::BidirPipe]
|
51
75
|
#
|
52
76
|
def bidir_pipe(serializer=nil, pipe_pair=nil)
|
53
77
|
Cod::BidirPipe.new(serializer, pipe_pair)
|
@@ -56,7 +80,9 @@ module Cod
|
|
56
80
|
|
57
81
|
# Creates a tcp connection to the destination and returns a channel for it.
|
58
82
|
#
|
59
|
-
# @
|
83
|
+
# @param destination [String] an address to connect to, like 'localhost:1234'
|
84
|
+
# @param serializer [#en,#de] optional serializer to use
|
85
|
+
# @return [Cod::TcpClient]
|
60
86
|
#
|
61
87
|
def tcp(destination, serializer=nil)
|
62
88
|
Cod::TcpClient.new(
|
@@ -67,7 +93,9 @@ module Cod
|
|
67
93
|
|
68
94
|
# Creates a tcp listener on bind_to and returns a channel for it.
|
69
95
|
#
|
70
|
-
# @
|
96
|
+
# @param bind_to [String] an address and port to bind to, in the form "host:port"
|
97
|
+
# @param serializer [#en,#de] optional serializer to use
|
98
|
+
# @return [Cod::TcpServer]
|
71
99
|
#
|
72
100
|
def tcp_server(bind_to, serializer=nil)
|
73
101
|
Cod::TcpServer.new(
|
@@ -78,7 +106,11 @@ module Cod
|
|
78
106
|
|
79
107
|
# Creates a channel based on the beanstalkd messaging queue.
|
80
108
|
#
|
81
|
-
# @
|
109
|
+
# @overload beanstalk(tube_name, server='localhost:11300')
|
110
|
+
# @param tube_name [String] name of the tube to send messages to /
|
111
|
+
# receive messages from
|
112
|
+
# @param server [String] address of the server to connect to
|
113
|
+
# @return [Cod::Beanstalk::Channel]
|
82
114
|
#
|
83
115
|
def beanstalk(tube_name, server=nil)
|
84
116
|
Cod::Beanstalk::Channel.new(tube_name, server||'localhost:11300')
|
@@ -86,12 +118,12 @@ module Cod
|
|
86
118
|
module_function :beanstalk
|
87
119
|
|
88
120
|
# Runs a command via Process.spawn, then links a channel to the commands
|
89
|
-
# stdout and stdin.
|
121
|
+
# stdout and stdin.
|
90
122
|
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
# @
|
123
|
+
# @param command [String] command to execute in a subprocess
|
124
|
+
# (using +Process.spawn+)
|
125
|
+
# @param serializer [#en,#de] serializer to use for all messages in channel
|
126
|
+
# @return [Cod::Process]
|
95
127
|
#
|
96
128
|
def process(command, serializer=nil)
|
97
129
|
Cod::Process.new(command, serializer)
|
@@ -102,7 +134,8 @@ module Cod
|
|
102
134
|
# pipes #put method will print to stdout, and the #get method will read from
|
103
135
|
# stdin.
|
104
136
|
#
|
105
|
-
# @
|
137
|
+
# @param serializer [#en,#de] optional serializer to use
|
138
|
+
# @return [Cod::Pipe]
|
106
139
|
#
|
107
140
|
def stdio(serializer=nil)
|
108
141
|
Cod::Pipe.new(serializer, [$stdin, $stdout])
|
@@ -130,8 +163,8 @@ module Cod
|
|
130
163
|
# Indicates that a standing connection was lost and must be reconnected.
|
131
164
|
#
|
132
165
|
class ConnectionLost < StandardError
|
133
|
-
def initialize
|
134
|
-
super "Connection lost."
|
166
|
+
def initialize(msg=nil)
|
167
|
+
super msg || "Connection lost."
|
135
168
|
end
|
136
169
|
end
|
137
170
|
end
|
@@ -144,6 +177,7 @@ require 'cod/iopair'
|
|
144
177
|
require 'cod/channel'
|
145
178
|
|
146
179
|
require 'cod/simple_serializer'
|
180
|
+
require 'cod/line_serializer'
|
147
181
|
|
148
182
|
require 'cod/pipe'
|
149
183
|
require 'cod/bidir_pipe'
|
data/lib/cod/beanstalk.rb
CHANGED
@@ -17,8 +17,10 @@ module Cod::Beanstalk
|
|
17
17
|
JOB_PRIORITY = 0
|
18
18
|
|
19
19
|
# Which tube this channel is connected to
|
20
|
+
# @return [String]
|
20
21
|
attr_reader :tube_name
|
21
22
|
# Beanstalkd server this channel is connected to
|
23
|
+
# @return [String]
|
22
24
|
attr_reader :server_url
|
23
25
|
|
24
26
|
def initialize(tube_name, server_url)
|
@@ -37,6 +39,11 @@ module Cod::Beanstalk
|
|
37
39
|
initialize(other.tube_name, other.server_url)
|
38
40
|
end
|
39
41
|
|
42
|
+
# Puts a job on the tube after serializing.
|
43
|
+
#
|
44
|
+
# @param msg [Object] message to send
|
45
|
+
# @return [void]
|
46
|
+
#
|
40
47
|
def put(msg)
|
41
48
|
pri = JOB_PRIORITY
|
42
49
|
delay = 0
|
@@ -47,6 +54,10 @@ module Cod::Beanstalk
|
|
47
54
|
fail "#put fails, #{answer.inspect}" unless answer == :inserted
|
48
55
|
end
|
49
56
|
|
57
|
+
# Reads a job from the tube and decodes it as a message.
|
58
|
+
#
|
59
|
+
# @return [Object]
|
60
|
+
#
|
50
61
|
def get
|
51
62
|
id, msg = bs_reserve
|
52
63
|
|
@@ -67,6 +78,8 @@ module Cod::Beanstalk
|
|
67
78
|
end
|
68
79
|
|
69
80
|
# --------------------------------------------------------- service/client
|
81
|
+
|
82
|
+
#
|
70
83
|
def service
|
71
84
|
Service.new(self)
|
72
85
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Cod
|
2
|
+
|
3
|
+
# A serializer that implements a line by line wire protocol. Only strings
|
4
|
+
# can be sent. An instance of this class can be used when constructing
|
5
|
+
# any channel, turning it into a line oriented channel speaking a clear text
|
6
|
+
# protocol.
|
7
|
+
#
|
8
|
+
class LineSerializer
|
9
|
+
# Turns a message into the wire format.
|
10
|
+
#
|
11
|
+
# @param msg [#to_s] message to send
|
12
|
+
# @return [String] buffer to be written to the wire
|
13
|
+
#
|
14
|
+
def en(msg)
|
15
|
+
msg.to_s + "\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Deserializes a message from the wire.
|
19
|
+
#
|
20
|
+
# @param io [IO] the wire
|
21
|
+
# @return [String]
|
22
|
+
#
|
23
|
+
def de(io)
|
24
|
+
msg = io.gets
|
25
|
+
return msg.chomp if msg
|
26
|
+
raise EOFError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/cod/pipe.rb
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
module Cod
|
3
3
|
# A cod channel based on IO.pipe.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
# the object id of that channel into the byte stream that is
|
7
|
-
# receiving such an object id (a machine pointer), Cod will
|
8
|
-
# reconstruct the channel that was at the origin of the id. This can
|
5
|
+
# Note that if you embed Cod::Pipe channels into your messages, Cod will
|
6
|
+
# insert the object id of that channel into the byte stream that is
|
7
|
+
# transmitted. On receiving such an object id (a machine pointer), Cod will
|
8
|
+
# try to reconstruct the channel that was at the origin of the id. This can
|
9
9
|
# obviously only work if you have such an object in your address space.
|
10
10
|
# There are multiple ways to construct such a situation. Say you want to
|
11
11
|
# send a pipe channel to one of your (forked) childs: This will work if you
|
@@ -13,18 +13,26 @@ module Cod
|
|
13
13
|
# share all objects that were available before the fork.
|
14
14
|
#
|
15
15
|
class Pipe < Channel
|
16
|
+
# The underlying IOPair.
|
17
|
+
# @private
|
16
18
|
attr_reader :pipe
|
19
|
+
|
20
|
+
# The serializer for this pipe.
|
17
21
|
attr_reader :serializer
|
18
22
|
|
19
23
|
# A few methods that a pipe split must answer to. The split itself is
|
20
24
|
# basically an array instance; these methods add some calling safety and
|
21
25
|
# convenience.
|
22
26
|
#
|
27
|
+
# @private
|
28
|
+
#
|
23
29
|
module SplitMethods # :nodoc:
|
24
30
|
def read; first; end
|
25
31
|
def write; last; end
|
26
32
|
end
|
27
|
-
|
33
|
+
|
34
|
+
# Creates a {Cod::Pipe}.
|
35
|
+
#
|
28
36
|
def initialize(serializer=nil, pipe_pair=nil)
|
29
37
|
super()
|
30
38
|
@serializer = serializer || SimpleSerializer.new
|
@@ -35,6 +43,9 @@ module Cod
|
|
35
43
|
# for the file descriptors stored in the pipe, so that a #close affects
|
36
44
|
# only one copy.
|
37
45
|
#
|
46
|
+
# @example
|
47
|
+
# pipe.dup # => anotherpipe
|
48
|
+
#
|
38
49
|
def initialize_copy(other)
|
39
50
|
super
|
40
51
|
@serializer = other.serializer
|
@@ -48,6 +59,8 @@ module Cod
|
|
48
59
|
# Returns self so that you can write for example:
|
49
60
|
# read_end = pipe.dup.readonly
|
50
61
|
#
|
62
|
+
# @private
|
63
|
+
#
|
51
64
|
def readonly
|
52
65
|
pipe.close_w
|
53
66
|
self
|
@@ -58,6 +71,8 @@ module Cod
|
|
58
71
|
# Returns self so that you can write for example:
|
59
72
|
# write_end = pipe.dup.writeonly
|
60
73
|
#
|
74
|
+
# @private
|
75
|
+
#
|
61
76
|
def writeonly
|
62
77
|
pipe.close_r
|
63
78
|
self
|
@@ -65,8 +80,10 @@ module Cod
|
|
65
80
|
|
66
81
|
# Actively splits this pipe into two ends, a read end and a write end. The
|
67
82
|
# original pipe is closed, leaving only the two ends to work with. The
|
68
|
-
# read end can only be read from (#get) and the write end can only be
|
69
|
-
# written to (#put).
|
83
|
+
# read end can only be read from ({#get}) and the write end can only be
|
84
|
+
# written to ({#put}).
|
85
|
+
#
|
86
|
+
# @return [Array<Cod::Channel>]
|
70
87
|
#
|
71
88
|
def split
|
72
89
|
[self.dup.readonly, self.dup.writeonly].tap { |split|
|
@@ -79,7 +96,10 @@ module Cod
|
|
79
96
|
# Using #put on a pipe instance will close the other pipe end. Subsequent
|
80
97
|
# #get will raise a Cod::InvalidOperation.
|
81
98
|
#
|
82
|
-
#
|
99
|
+
# @param obj [Object] message to send to the channel
|
100
|
+
# @return [void]
|
101
|
+
#
|
102
|
+
# @example
|
83
103
|
# pipe.put [:a, :message]
|
84
104
|
#
|
85
105
|
def put(obj)
|
@@ -92,25 +112,25 @@ module Cod
|
|
92
112
|
# Using #get on a pipe instance will close the other pipe end. Subsequent
|
93
113
|
# #put will receive a Cod::InvalidOperation.
|
94
114
|
#
|
95
|
-
#
|
115
|
+
# @example
|
96
116
|
# pipe.get # => obj
|
97
117
|
#
|
98
118
|
def get(opts={})
|
99
119
|
raise Cod::WriteOnlyChannel unless can_read?
|
100
120
|
pipe.close_w
|
101
121
|
|
102
|
-
|
103
|
-
ready = Cod.select(nil, self)
|
104
|
-
return deserialize_one if ready
|
105
|
-
end
|
122
|
+
return deserialize_one
|
106
123
|
rescue EOFError
|
107
|
-
|
124
|
+
raise Cod::ConnectionLost,
|
125
|
+
"All pipe ends seem to be closed. Reading from this pipe will not "+
|
108
126
|
"return any data."
|
109
127
|
end
|
110
128
|
|
111
129
|
# Closes the pipe completely. All active ends are closed. Note that you
|
112
130
|
# can call this function on a closed pipe without getting an error raised.
|
113
131
|
#
|
132
|
+
# @return [void]
|
133
|
+
#
|
114
134
|
def close
|
115
135
|
pipe.close
|
116
136
|
end
|
@@ -121,6 +141,9 @@ module Cod
|
|
121
141
|
result = Cod.select(timeout, self)
|
122
142
|
not result.nil?
|
123
143
|
end
|
144
|
+
|
145
|
+
# @private
|
146
|
+
#
|
124
147
|
def to_read_fds
|
125
148
|
r
|
126
149
|
end
|
@@ -141,29 +164,49 @@ module Cod
|
|
141
164
|
|
142
165
|
# Returns the read end of the pipe
|
143
166
|
#
|
167
|
+
# @private
|
168
|
+
#
|
144
169
|
def r
|
145
170
|
pipe.r
|
146
171
|
end
|
147
172
|
|
148
173
|
# Returns the write end of the pipe
|
149
174
|
#
|
175
|
+
# @private
|
176
|
+
#
|
150
177
|
def w
|
151
178
|
pipe.w
|
152
179
|
end
|
153
180
|
|
154
181
|
# --------------------------------------------------------- service/client
|
155
182
|
|
183
|
+
# Produces a service using this pipe as service channel.
|
184
|
+
# @see Cod::Service
|
185
|
+
#
|
186
|
+
# @return [Cod::Service]
|
187
|
+
#
|
156
188
|
def service
|
157
189
|
Service.new(self)
|
158
190
|
end
|
191
|
+
|
192
|
+
# Produces a service client. Requests are sent to this channel, and answers
|
193
|
+
# are routed back to +answers_to+.
|
194
|
+
#
|
195
|
+
# @param answers_to [Cod::Channel] Where answers should be addressed to.
|
196
|
+
# @return [Cod::Service::Client]
|
197
|
+
#
|
159
198
|
def client(answers_to)
|
160
199
|
Service::Client.new(self, answers_to)
|
161
200
|
end
|
162
201
|
|
163
202
|
# ---------------------------------------------------------- serialization
|
203
|
+
|
204
|
+
# @private
|
164
205
|
def _dump(depth) # :nodoc:
|
165
206
|
object_id.to_s
|
166
207
|
end
|
208
|
+
|
209
|
+
# @private
|
167
210
|
def self._load(string) # :nodoc:
|
168
211
|
ObjectSpace._id2ref(Integer(string))
|
169
212
|
end
|
data/lib/cod/process.rb
CHANGED
@@ -1,13 +1,33 @@
|
|
1
1
|
module Cod
|
2
|
+
|
3
|
+
# A subprocess that is being run in server mode (think git-server). Use
|
4
|
+
# {Cod}.process to obtain an instance of this. You can then call {#channel} to
|
5
|
+
# obtain a Cod channel to communicate with the $stdio server you've spawned.
|
6
|
+
#
|
7
|
+
# @example List the files in a directory
|
8
|
+
# process = Cod.process('ls', Cod::LineSerializer.new)
|
9
|
+
# process.wait
|
10
|
+
# loop do
|
11
|
+
# # Will list all entries of the current dir in turn, already chomped.
|
12
|
+
# msg = process.get rescue nil
|
13
|
+
# break unless msg
|
14
|
+
# end
|
15
|
+
#
|
2
16
|
class Process
|
17
|
+
# The pid of the process that was spawned.
|
18
|
+
# @return [Number]
|
3
19
|
attr_reader :pid
|
4
20
|
|
21
|
+
# Constructs a process object and runs the command.
|
22
|
+
#
|
23
|
+
# @see Cod#process
|
5
24
|
def initialize(command, serializer=nil)
|
6
25
|
@serializer = serializer || SimpleSerializer.new
|
7
26
|
|
8
27
|
run(command)
|
9
28
|
end
|
10
29
|
|
30
|
+
# @private
|
11
31
|
def run(command)
|
12
32
|
@pipe = Cod.bidir_pipe(@serializer)
|
13
33
|
|
@@ -16,19 +36,49 @@ module Cod
|
|
16
36
|
:out => @pipe.r.w)
|
17
37
|
end
|
18
38
|
|
39
|
+
# Returns the cod channel associated with this process. The channel will
|
40
|
+
# have the process' standard output bound to its #get (input), and the
|
41
|
+
# process' standard input will be bound to #put (output).
|
42
|
+
#
|
43
|
+
# Note that when the process exits and all communication has been read from
|
44
|
+
# the channel, it will probably raise a Cod::ConnectionLost error.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# process = Cod.process('uname', LineSerializer.new)
|
48
|
+
# process.channel.get # => {Darwin,Linux,...}
|
49
|
+
#
|
50
|
+
# @return [Cod::Pipe]
|
51
|
+
#
|
19
52
|
def channel
|
20
53
|
@pipe
|
21
54
|
end
|
22
55
|
|
56
|
+
# Stops the process unilaterally.
|
57
|
+
#
|
58
|
+
# @return [void]
|
59
|
+
#
|
23
60
|
def kill
|
61
|
+
terminate
|
24
62
|
::Process.kill :TERM, @pid
|
25
63
|
end
|
64
|
+
|
65
|
+
# Asks the process to terminate by closing its stanard input. This normally
|
66
|
+
# closes down the process, but no guarantees are made.
|
67
|
+
#
|
68
|
+
# @return [void]
|
69
|
+
#
|
26
70
|
def terminate
|
27
71
|
@pipe.w.close
|
28
72
|
end
|
29
73
|
|
74
|
+
# Waits for the process to terminate and returns its exit value. May
|
75
|
+
# return nil, in which case someone else already reaped the process.
|
76
|
+
#
|
77
|
+
# @return [Number,nil]
|
78
|
+
#
|
30
79
|
def wait
|
31
80
|
::Process.wait(@pid)
|
81
|
+
rescue Errno::ECHILD
|
32
82
|
end
|
33
83
|
end
|
34
84
|
end
|
data/lib/cod/select.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
module Cod
|
2
|
+
# A shortcurt for constructing a {Select}. See {Select#do} for more
|
3
|
+
# information.
|
4
|
+
#
|
5
|
+
# @param timeout [Number] seconds to block before giving up
|
6
|
+
# @param groups channels or io selectors to wait for
|
7
|
+
# @return [Hash,Array,Cod::Channel,IO]
|
8
|
+
#
|
2
9
|
def select(timeout, groups)
|
10
|
+
# TODO create an overload without the timeout
|
3
11
|
Select.new(timeout, groups).do
|
4
12
|
end
|
5
13
|
module_function :select
|
@@ -24,8 +32,10 @@ module Cod
|
|
24
32
|
# Performs the IO.select and returns a thinned out version of that initial
|
25
33
|
# groups, containing only FDs and channels that are ready for reading.
|
26
34
|
#
|
35
|
+
# @return [Hash,Array,Cod::Channel,IO]
|
36
|
+
#
|
27
37
|
def do
|
28
|
-
fds = groups.values { |e| to_read_fd(e) }
|
38
|
+
fds = groups.values { |e| to_read_fd(e) }.compact
|
29
39
|
|
30
40
|
# Perform select
|
31
41
|
r,w,e = IO.select(fds, nil, nil, timeout)
|
data/lib/cod/tcp_client.rb
CHANGED
@@ -131,6 +131,12 @@ module Cod
|
|
131
131
|
# with a valid client later on. (hopefully)
|
132
132
|
OtherEnd.new(params)
|
133
133
|
end
|
134
|
+
|
135
|
+
# @private
|
136
|
+
#
|
137
|
+
def to_read_fds
|
138
|
+
@connection.socket if @connection
|
139
|
+
end
|
134
140
|
private
|
135
141
|
# Checks to see in which of the three connection phases we're in. If we're
|
136
142
|
# past 1), shuts down the background worker thread.
|
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
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-06-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: rspec
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ! '>='
|
@@ -32,10 +37,15 @@ dependencies:
|
|
32
37
|
version: '0'
|
33
38
|
type: :development
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: flexmock
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
51
|
- - ! '>='
|
@@ -43,10 +53,15 @@ dependencies:
|
|
43
53
|
version: '0'
|
44
54
|
type: :development
|
45
55
|
prerelease: false
|
46
|
-
version_requirements:
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
47
62
|
- !ruby/object:Gem::Dependency
|
48
63
|
name: yard
|
49
|
-
requirement:
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
50
65
|
none: false
|
51
66
|
requirements:
|
52
67
|
- - ! '>='
|
@@ -54,10 +69,15 @@ dependencies:
|
|
54
69
|
version: '0'
|
55
70
|
type: :development
|
56
71
|
prerelease: false
|
57
|
-
version_requirements:
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
58
78
|
- !ruby/object:Gem::Dependency
|
59
79
|
name: guard
|
60
|
-
requirement:
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
61
81
|
none: false
|
62
82
|
requirements:
|
63
83
|
- - ! '>='
|
@@ -65,10 +85,15 @@ dependencies:
|
|
65
85
|
version: '0'
|
66
86
|
type: :development
|
67
87
|
prerelease: false
|
68
|
-
version_requirements:
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
69
94
|
- !ruby/object:Gem::Dependency
|
70
95
|
name: growl
|
71
|
-
requirement:
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
72
97
|
none: false
|
73
98
|
requirements:
|
74
99
|
- - ! '>='
|
@@ -76,7 +101,12 @@ dependencies:
|
|
76
101
|
version: '0'
|
77
102
|
type: :development
|
78
103
|
prerelease: false
|
79
|
-
version_requirements:
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
80
110
|
description:
|
81
111
|
email: kaspar.schiess@absurd.li
|
82
112
|
executables: []
|
@@ -95,6 +125,7 @@ files:
|
|
95
125
|
- lib/cod/bidir_pipe.rb
|
96
126
|
- lib/cod/channel.rb
|
97
127
|
- lib/cod/iopair.rb
|
128
|
+
- lib/cod/line_serializer.rb
|
98
129
|
- lib/cod/pipe.rb
|
99
130
|
- lib/cod/process.rb
|
100
131
|
- lib/cod/protocol_buffers_serializer.rb
|
@@ -110,6 +141,8 @@ files:
|
|
110
141
|
- examples/bs_ping_pong/ping.rb
|
111
142
|
- examples/bs_ping_pong/pong.rb
|
112
143
|
- examples/example_scaffold.rb
|
144
|
+
- examples/forked_server/forked.rb
|
145
|
+
- examples/forked_server/README
|
113
146
|
- examples/master_child.rb
|
114
147
|
- examples/netcat/README
|
115
148
|
- examples/netcat/server.rb
|
@@ -134,7 +167,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
134
167
|
version: '0'
|
135
168
|
segments:
|
136
169
|
- 0
|
137
|
-
hash:
|
170
|
+
hash: 4413858275826022328
|
138
171
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
172
|
none: false
|
140
173
|
requirements:
|
@@ -143,10 +176,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
143
176
|
version: '0'
|
144
177
|
segments:
|
145
178
|
- 0
|
146
|
-
hash:
|
179
|
+
hash: 4413858275826022328
|
147
180
|
requirements: []
|
148
181
|
rubyforge_project:
|
149
|
-
rubygems_version: 1.8.
|
182
|
+
rubygems_version: 1.8.24
|
150
183
|
signing_key:
|
151
184
|
specification_version: 3
|
152
185
|
summary: Really simple IPC. Pipes, TCP sockets, beanstalkd, ...
|