cod 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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, ...
|