easy-serve 0.7 → 0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/examples/multi.rb +15 -9
- data/examples/passive.rb +11 -5
- data/examples/remote-drb.rb +3 -3
- data/examples/remote-eval-passive.rb +6 -6
- data/examples/remote-eval.rb +4 -4
- data/examples/remote-manual.rb +9 -9
- data/examples/remote-multi-server.rb +4 -4
- data/examples/remote-run.rb +4 -4
- data/examples/simple.rb +11 -5
- data/lib/easy-serve.rb +55 -111
- data/lib/easy-serve/accessible-services.rb +34 -0
- data/lib/easy-serve/remote-drb.rb +5 -5
- data/lib/easy-serve/remote-eval-mgr.rb +10 -8
- data/lib/easy-serve/remote-eval.rb +10 -10
- data/lib/easy-serve/remote-run-mgr.rb +10 -8
- data/lib/easy-serve/remote-run.rb +13 -13
- data/lib/easy-serve/remote.rb +4 -4
- data/lib/easy-serve/service.rb +125 -0
- metadata +18 -17
- data/lib/easy-serve/accessible-servers.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 03c328ab82c8bb6aebbc3b9e6e5c9a50a3895a5c
|
4
|
+
data.tar.gz: be6c2705dc58ee78cf2ce725f0bdb885dac96378
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d91ad208ac5237e50d2e03ca0d1524a2a8005753f78fa1ccf192b7486baa8ce70abca4c2b8ca7ecfaa25c2d364f1d0f5a6dcf6759fb2916557a3b9e9ca31b64f
|
7
|
+
data.tar.gz: 22d93a3d940daf5e2ecf4810896f9f13fbef3d49e8cad7f7f2b9a1a07991f1d4bb106163e55dfaf9d70f7a8dec8ed0bbc0a6b5a76b3856c74fbafa50e7e76491
|
data/README.md
CHANGED
data/examples/multi.rb
CHANGED
@@ -1,18 +1,24 @@
|
|
1
1
|
require 'easy-serve'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
if ARGV.delete("--tcp")
|
4
|
+
proto = :tcp
|
5
|
+
else
|
6
|
+
proto = :unix
|
6
7
|
end
|
7
8
|
|
8
|
-
|
9
|
+
services_file = ARGV.shift
|
10
|
+
unless services_file
|
11
|
+
abort "Usage: #$0 services_file # Run this in two or more shells"
|
12
|
+
end
|
13
|
+
|
14
|
+
EasyServe.start services_file: services_file do |ez|
|
9
15
|
log = ez.log
|
10
16
|
log.level = Logger::DEBUG
|
11
17
|
log.formatter = nil if $VERBOSE
|
12
18
|
|
13
|
-
ez.
|
14
|
-
ez.
|
15
|
-
log.debug {"starting
|
19
|
+
ez.start_services do
|
20
|
+
ez.service "simple-service", proto do |svr|
|
21
|
+
log.debug {"starting service"}
|
16
22
|
Thread.new do
|
17
23
|
loop do
|
18
24
|
Thread.new(svr.accept) do |conn|
|
@@ -26,13 +32,13 @@ EasyServe.start servers_file: servers_file do |ez|
|
|
26
32
|
end
|
27
33
|
end
|
28
34
|
|
29
|
-
ez.child "simple-
|
35
|
+
ez.child "simple-service" do |conn|
|
30
36
|
log.progname = "client with pid=#$$"
|
31
37
|
log.info conn.read
|
32
38
|
conn.write "hello from #{log.progname}"
|
33
39
|
end
|
34
40
|
|
35
|
-
ez.local "simple-
|
41
|
+
ez.local "simple-service" do |conn|
|
36
42
|
log.progname = "parent process"
|
37
43
|
log.info "PRESS RETURN TO STOP"
|
38
44
|
gets
|
data/examples/passive.rb
CHANGED
@@ -1,14 +1,20 @@
|
|
1
1
|
require 'easy-serve'
|
2
2
|
Thread.abort_on_exception = true
|
3
3
|
|
4
|
+
if ARGV.delete("--tcp")
|
5
|
+
proto = :tcp
|
6
|
+
else
|
7
|
+
proto = :unix
|
8
|
+
end
|
9
|
+
|
4
10
|
EasyServe.start do |ez|
|
5
11
|
log = ez.log
|
6
12
|
log.level = Logger::DEBUG
|
7
13
|
log.formatter = nil if $VERBOSE
|
8
|
-
log.debug {"starting
|
14
|
+
log.debug {"starting services"}
|
9
15
|
|
10
|
-
ez.
|
11
|
-
ez.
|
16
|
+
ez.start_services do
|
17
|
+
ez.service "simple-service", proto do |svr|
|
12
18
|
Thread.new do
|
13
19
|
loop do
|
14
20
|
conn = svr.accept
|
@@ -21,7 +27,7 @@ EasyServe.start do |ez|
|
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
24
|
-
ez.child "simple-
|
30
|
+
ez.child "simple-service", passive: true do |conn|
|
25
31
|
log.progname = "client 1"
|
26
32
|
log.info conn.read
|
27
33
|
conn.write "hello from #{log.progname}, pid = #$$; sleeping..."
|
@@ -31,7 +37,7 @@ EasyServe.start do |ez|
|
|
31
37
|
|
32
38
|
sleep 0.1
|
33
39
|
|
34
|
-
ez.child "simple-
|
40
|
+
ez.child "simple-service" do |conn|
|
35
41
|
log.progname = "client 2"
|
36
42
|
log.info conn.read
|
37
43
|
conn.write "hello from #{log.progname}, pid = #$$"
|
data/examples/remote-drb.rb
CHANGED
@@ -23,8 +23,8 @@ EasyServe.start do |ez|
|
|
23
23
|
log.level = Logger::INFO
|
24
24
|
log.formatter = nil if $VERBOSE
|
25
25
|
|
26
|
-
ez.
|
27
|
-
ez.
|
26
|
+
ez.start_services do
|
27
|
+
ez.service "simple-service", :tcp do |svr|
|
28
28
|
Thread.new do
|
29
29
|
loop do
|
30
30
|
Thread.new(svr.accept) do |conn|
|
@@ -41,7 +41,7 @@ EasyServe.start do |ez|
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
ez.remote "simple-
|
44
|
+
ez.remote "simple-service", host: addr_there do |conn|
|
45
45
|
# this block runs locally, but calls methods on the remote using drb
|
46
46
|
log.progname = "druby remote on #{addr_there}"
|
47
47
|
log.info "trying to read from #{conn.inspect}"
|
@@ -22,8 +22,8 @@ EasyServe.start do |ez|
|
|
22
22
|
log.level = Logger::INFO
|
23
23
|
log.formatter = nil if $VERBOSE
|
24
24
|
|
25
|
-
ez.
|
26
|
-
ez.
|
25
|
+
ez.start_services do
|
26
|
+
ez.service "simple-service", :tcp do |svr|
|
27
27
|
Thread.new do
|
28
28
|
loop do
|
29
29
|
Thread.new(svr.accept) do |conn|
|
@@ -40,7 +40,7 @@ EasyServe.start do |ez|
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
ez.remote "simple-
|
43
|
+
ez.remote "simple-service", host: addr_there, log: true, passive: true,
|
44
44
|
eval: %{
|
45
45
|
conn = conns[0]
|
46
46
|
# this code is executed on the remote host, connected by conn, not drb
|
@@ -52,17 +52,17 @@ EasyServe.start do |ez|
|
|
52
52
|
|
53
53
|
sleep
|
54
54
|
# Without passive, this sleep would prevent the distributed app from
|
55
|
-
# exiting -- the simple-
|
55
|
+
# exiting -- the simple-service above could not be safely stopped, since
|
56
56
|
# there's no guarantee that it is no longer needed. The passive
|
57
57
|
# declaration make it clear that this process can be stopped after all
|
58
|
-
# non-passive clients have finished, and then the
|
58
|
+
# non-passive clients have finished, and then the service can be stopped.
|
59
59
|
# Of course, this also means that if all other clients execute very
|
60
60
|
# quickly, this client might never get a chance to run.
|
61
61
|
}
|
62
62
|
|
63
63
|
sleep 1 # Ensure (for testing) that the above client runs.
|
64
64
|
|
65
|
-
ez.remote "simple-
|
65
|
+
ez.remote "simple-service", host: addr_there, log: true, eval: %{
|
66
66
|
conn = conns[0]
|
67
67
|
# this code is executed on the remote host, connected by conn, not drb
|
68
68
|
log.progname = "eval remote 2 on \#{host}"
|
data/examples/remote-eval.rb
CHANGED
@@ -15,7 +15,7 @@ unless addr_there
|
|
15
15
|
|
16
16
|
The 'hostname' may by any valid hostname or ssh alias.
|
17
17
|
|
18
|
-
If --tunnel is specified, use the ssh connection to tunnel the
|
18
|
+
If --tunnel is specified, use the ssh connection to tunnel the data
|
19
19
|
traffic. Otherwise, just use tcp. (Always use ssh to start the remote
|
20
20
|
process.)
|
21
21
|
|
@@ -27,9 +27,9 @@ EasyServe.start do |ez|
|
|
27
27
|
log.level = Logger::INFO
|
28
28
|
log.formatter = nil if $VERBOSE
|
29
29
|
|
30
|
-
ez.
|
30
|
+
ez.start_services do
|
31
31
|
host = tunnel ? "localhost" : nil # no need to expose port if tunnelled
|
32
|
-
ez.
|
32
|
+
ez.service "simple-service", :tcp, bind_host: host do |svr|
|
33
33
|
Thread.new do
|
34
34
|
loop do
|
35
35
|
Thread.new(svr.accept) do |conn|
|
@@ -46,7 +46,7 @@ EasyServe.start do |ez|
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
ez.remote "simple-
|
49
|
+
ez.remote "simple-service", host: addr_there, tunnel: tunnel, log: true,
|
50
50
|
eval: %{
|
51
51
|
conn = conns[0]
|
52
52
|
# this code is executed on the remote host, connected by conn, not drb
|
data/examples/remote-manual.rb
CHANGED
@@ -1,22 +1,22 @@
|
|
1
1
|
require 'easy-serve'
|
2
2
|
|
3
|
-
|
4
|
-
unless
|
3
|
+
services_file = ARGV.shift
|
4
|
+
unless services_file
|
5
5
|
abort <<-END
|
6
|
-
Usage: #$0
|
7
|
-
For the client, copy the generated
|
6
|
+
Usage: #$0 services_file
|
7
|
+
For the client, copy the generated services_file to the client host, and
|
8
8
|
run with the same command.
|
9
9
|
END
|
10
10
|
end
|
11
11
|
|
12
|
-
EasyServe.start
|
12
|
+
EasyServe.start services_file: services_file do |ez|
|
13
13
|
log = ez.log
|
14
14
|
log.level = Logger::DEBUG
|
15
15
|
log.formatter = nil if $VERBOSE
|
16
16
|
|
17
|
-
ez.
|
18
|
-
ez.
|
19
|
-
log.debug {"starting
|
17
|
+
ez.start_services do
|
18
|
+
ez.service "simple-service", :tcp do |svr|
|
19
|
+
log.debug {"starting service"}
|
20
20
|
Thread.new do
|
21
21
|
loop do
|
22
22
|
Thread.new(svr.accept) do |conn|
|
@@ -30,7 +30,7 @@ EasyServe.start servers_file: servers_file do |ez|
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
ez.local "simple-
|
33
|
+
ez.local "simple-service" do |conn|
|
34
34
|
log.progname = "parent process"
|
35
35
|
log.info conn.read
|
36
36
|
conn.write "hello from #{log.progname}"
|
@@ -15,7 +15,7 @@ unless address_there
|
|
15
15
|
|
16
16
|
The 'hostname' may by any valid hostname or ssh alias.
|
17
17
|
|
18
|
-
If --tunnel is specified, use the ssh connection to tunnel the
|
18
|
+
If --tunnel is specified, use the ssh connection to tunnel the data
|
19
19
|
traffic. Otherwise, just use tcp. (Always use ssh to start the remote
|
20
20
|
process.)
|
21
21
|
|
@@ -27,10 +27,10 @@ EasyServe.start do |ez|
|
|
27
27
|
log.level = Logger::INFO
|
28
28
|
log.formatter = nil if $VERBOSE
|
29
29
|
|
30
|
-
ez.
|
30
|
+
ez.start_services do
|
31
31
|
host = tunnel ? "localhost" : nil # no need to expose port if tunnelled
|
32
32
|
|
33
|
-
ez.
|
33
|
+
ez.service "adder", :tcp, bind_host: host do |svr|
|
34
34
|
Thread.new do
|
35
35
|
loop do
|
36
36
|
Thread.new(svr.accept) do |conn|
|
@@ -56,7 +56,7 @@ EasyServe.start do |ez|
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
ez.
|
59
|
+
ez.service "multiplier", :tcp, bind_host: host do |svr|
|
60
60
|
Thread.new do
|
61
61
|
loop do
|
62
62
|
Thread.new(svr.accept) do |conn|
|
data/examples/remote-run.rb
CHANGED
@@ -15,7 +15,7 @@ unless addr_there
|
|
15
15
|
|
16
16
|
The 'hostname' may by any valid hostname or ssh alias.
|
17
17
|
|
18
|
-
If --tunnel is specified, use the ssh connection to tunnel the
|
18
|
+
If --tunnel is specified, use the ssh connection to tunnel the data
|
19
19
|
traffic. Otherwise, just use tcp. (Always use ssh to start the remote
|
20
20
|
process.)
|
21
21
|
|
@@ -31,9 +31,9 @@ EasyServe.start do |ez|
|
|
31
31
|
log.level = Logger::INFO
|
32
32
|
log.formatter = nil if $VERBOSE
|
33
33
|
|
34
|
-
ez.
|
34
|
+
ez.start_services do
|
35
35
|
host = tunnel ? "localhost" : nil # no need to expose port if tunnelled
|
36
|
-
ez.
|
36
|
+
ez.service "simple-service", :tcp, bind_host: host do |svr|
|
37
37
|
Thread.new do
|
38
38
|
loop do
|
39
39
|
Thread.new(svr.accept) do |conn|
|
@@ -50,7 +50,7 @@ EasyServe.start do |ez|
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
ez.remote "simple-
|
53
|
+
ez.remote "simple-service", host: addr_there, tunnel: tunnel,
|
54
54
|
dir: "/tmp",
|
55
55
|
file: "remote-run-script.rb",
|
56
56
|
# 'file' passed to load, so can be rel to dir or ruby's $LOAD_PATH
|
data/examples/simple.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
require 'easy-serve'
|
2
2
|
|
3
|
+
if ARGV.delete("--tcp")
|
4
|
+
proto = :tcp
|
5
|
+
else
|
6
|
+
proto = :unix
|
7
|
+
end
|
8
|
+
|
3
9
|
EasyServe.start do |ez|
|
4
10
|
log = ez.log
|
5
11
|
log.level = Logger::DEBUG
|
6
12
|
log.formatter = nil if $VERBOSE
|
7
|
-
log.debug {"starting
|
13
|
+
log.debug {"starting services"}
|
8
14
|
|
9
|
-
ez.
|
10
|
-
ez.
|
15
|
+
ez.start_services do
|
16
|
+
ez.service "simple-service", proto do |svr|
|
11
17
|
Thread.new do
|
12
18
|
loop do
|
13
19
|
conn = svr.accept
|
@@ -20,13 +26,13 @@ EasyServe.start do |ez|
|
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
23
|
-
ez.child "simple-
|
29
|
+
ez.child "simple-service" do |conn|
|
24
30
|
log.progname = "client 1"
|
25
31
|
log.info conn.read
|
26
32
|
conn.write "hello from #{log.progname}"
|
27
33
|
end
|
28
34
|
|
29
|
-
ez.local "simple-
|
35
|
+
ez.local "simple-service" do |conn|
|
30
36
|
log.progname = "parent process"
|
31
37
|
log.info conn.read
|
32
38
|
conn.write "hello from #{log.progname}"
|
data/lib/easy-serve.rb
CHANGED
@@ -3,16 +3,10 @@ require 'socket'
|
|
3
3
|
require 'yaml'
|
4
4
|
require 'fileutils'
|
5
5
|
|
6
|
-
|
7
|
-
VERSION = "0.7"
|
6
|
+
require 'easy-serve/service'
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def initialize name, pid, addr
|
13
|
-
@name, @pid, @addr = name, pid, addr
|
14
|
-
end
|
15
|
-
end
|
8
|
+
class EasyServe
|
9
|
+
VERSION = "0.8"
|
16
10
|
|
17
11
|
class EasyFormatter < Logger::Formatter
|
18
12
|
Format = "%s: %s: %s\n"
|
@@ -35,10 +29,10 @@ class EasyServe
|
|
35
29
|
end
|
36
30
|
|
37
31
|
attr_accessor :log
|
38
|
-
attr_accessor :
|
32
|
+
attr_accessor :services
|
39
33
|
attr_reader :children
|
40
34
|
attr_reader :passive_children
|
41
|
-
attr_reader :
|
35
|
+
attr_reader :services_file
|
42
36
|
attr_reader :interactive
|
43
37
|
|
44
38
|
def self.start(log: default_logger, **opts)
|
@@ -52,37 +46,37 @@ class EasyServe
|
|
52
46
|
end
|
53
47
|
|
54
48
|
def initialize **opts
|
55
|
-
@
|
49
|
+
@services_file = opts[:services_file]
|
56
50
|
@interactive = opts[:interactive]
|
57
51
|
@log = opts[:log] || self.class.null_logger
|
58
52
|
@children = [] # pid
|
59
53
|
@passive_children = [] # pid
|
60
54
|
@owner = false
|
61
55
|
@tmpdir = nil
|
62
|
-
@
|
56
|
+
@services = opts[:services] # name => service
|
63
57
|
|
64
|
-
unless
|
65
|
-
if
|
66
|
-
@
|
58
|
+
unless services
|
59
|
+
if services_file
|
60
|
+
@services =
|
67
61
|
begin
|
68
|
-
|
62
|
+
load_service_table
|
69
63
|
rescue Errno::ENOENT
|
70
|
-
|
64
|
+
init_service_table
|
71
65
|
end
|
72
66
|
else
|
73
|
-
|
67
|
+
init_service_table
|
74
68
|
end
|
75
69
|
end
|
76
70
|
end
|
77
71
|
|
78
|
-
def
|
79
|
-
File.open(
|
72
|
+
def load_service_table
|
73
|
+
File.open(services_file) do |f|
|
80
74
|
YAML.load(f)
|
81
75
|
end
|
82
76
|
end
|
83
77
|
|
84
|
-
def
|
85
|
-
@
|
78
|
+
def init_service_table
|
79
|
+
@services ||= begin
|
86
80
|
@owner = true
|
87
81
|
{}
|
88
82
|
end
|
@@ -113,20 +107,16 @@ class EasyServe
|
|
113
107
|
end
|
114
108
|
|
115
109
|
if @owner
|
116
|
-
|
110
|
+
services.each do |name, service|
|
117
111
|
log.info "stopping #{name}"
|
118
|
-
|
119
|
-
Process.waitpid server.pid
|
120
|
-
if server.addr.kind_of? String
|
121
|
-
FileUtils.remove_entry server.addr
|
122
|
-
end
|
112
|
+
service.cleanup
|
123
113
|
end
|
124
114
|
|
125
|
-
if
|
115
|
+
if services_file
|
126
116
|
begin
|
127
|
-
FileUtils.rm
|
117
|
+
FileUtils.rm services_file
|
128
118
|
rescue Errno::ENOENT
|
129
|
-
log.warn "
|
119
|
+
log.warn "services file #{services_file.inspect} was deleted already"
|
130
120
|
end
|
131
121
|
end
|
132
122
|
end
|
@@ -134,14 +124,14 @@ class EasyServe
|
|
134
124
|
clean_tmpdir
|
135
125
|
end
|
136
126
|
|
137
|
-
def
|
127
|
+
def start_services
|
138
128
|
if @owner
|
139
|
-
log.debug {"starting
|
129
|
+
log.debug {"starting services"}
|
140
130
|
yield
|
141
131
|
|
142
|
-
if
|
143
|
-
File.open(
|
144
|
-
YAML.dump(
|
132
|
+
if services_file
|
133
|
+
File.open(services_file, "w") do |f|
|
134
|
+
YAML.dump(services, f)
|
145
135
|
end
|
146
136
|
end
|
147
137
|
end
|
@@ -166,7 +156,7 @@ class EasyServe
|
|
166
156
|
end
|
167
157
|
end
|
168
158
|
|
169
|
-
def
|
159
|
+
def self.bump_socket_filename name
|
170
160
|
name =~ /-\d+\z/ ? name.succ : name + "-0"
|
171
161
|
end
|
172
162
|
|
@@ -186,83 +176,49 @@ class EasyServe
|
|
186
176
|
end
|
187
177
|
end
|
188
178
|
|
189
|
-
|
190
|
-
server_class, *server_addr =
|
191
|
-
case proto
|
192
|
-
when /unix/i; [UNIXServer, choose_socket_filename(name, base: host)]
|
193
|
-
when /tcp/i; [TCPServer, host || host_name, port || 0]
|
194
|
-
else raise ArgumentError, "Unknown socket protocol: #{proto.inspect}"
|
195
|
-
end
|
179
|
+
MAX_TRIES = 10
|
196
180
|
|
197
|
-
|
181
|
+
def service name, proto = nil, **opts
|
182
|
+
proto ||= opts.delete(:proto) || :unix
|
183
|
+
case proto
|
184
|
+
when :unix
|
185
|
+
opts[:path] ||= choose_socket_filename(name, base: opts[:base])
|
186
|
+
|
187
|
+
when :tcp
|
188
|
+
opts[:connect_host] ||=
|
189
|
+
case opts[:bind_host]
|
190
|
+
when nil, "0.0.0.0", /\A<any>\z/i
|
191
|
+
host_name ## maybe local connectors should use "localhost" ?
|
192
|
+
when "localhost", "127.0.0.1"
|
193
|
+
"localhost"
|
194
|
+
end
|
195
|
+
end
|
198
196
|
|
197
|
+
service = Service.for(name, proto, **opts)
|
198
|
+
rd, wr = IO.pipe
|
199
199
|
pid = fork do
|
200
200
|
rd.close
|
201
201
|
log.progname = name
|
202
202
|
log.info "starting"
|
203
|
-
|
204
|
-
svr =
|
203
|
+
|
204
|
+
svr = service.serve(max_tries: MAX_TRIES, log: log)
|
205
205
|
yield svr if block_given?
|
206
206
|
no_interrupt_if_interactive
|
207
207
|
|
208
|
-
|
209
|
-
case proto
|
210
|
-
when /unix/i; svr.addr[1]
|
211
|
-
when /tcp/i; svr.addr(false).values_at(2,1)
|
212
|
-
end
|
213
|
-
Marshal.dump addr, wr
|
208
|
+
Marshal.dump service, wr
|
214
209
|
wr.close
|
215
210
|
sleep
|
216
211
|
end
|
217
212
|
|
218
213
|
wr.close
|
219
|
-
|
214
|
+
services[name] = Marshal.load rd
|
220
215
|
rd.close
|
221
|
-
servers[name] = Server.new(name, pid, addr)
|
222
|
-
end
|
223
|
-
|
224
|
-
MAX_TRIES = 10
|
225
|
-
|
226
|
-
def server_for server_class, *server_addr
|
227
|
-
tries = 0
|
228
|
-
begin
|
229
|
-
server_class.new(*server_addr)
|
230
|
-
rescue Errno::EADDRINUSE => ex
|
231
|
-
if server_class == UNIXServer
|
232
|
-
if tries < MAX_TRIES
|
233
|
-
tries += 1
|
234
|
-
server_addr[0] = inc_socket_filename(server_addr[0])
|
235
|
-
log.warn {
|
236
|
-
"#{ex} -- trying again at path #{server_addr}, #{tries}/#{MAX_TRIES} times."
|
237
|
-
}
|
238
|
-
retry
|
239
|
-
end
|
240
|
-
elsif server_class == TCPServer
|
241
|
-
port = Integer(server_addr[1])
|
242
|
-
if port and tries < MAX_TRIES
|
243
|
-
tries += 1
|
244
|
-
port += 1
|
245
|
-
server_addr[1] = port
|
246
|
-
log.warn {
|
247
|
-
"#{ex} -- trying again at port #{port}, #{tries}/#{MAX_TRIES} times."
|
248
|
-
}
|
249
|
-
retry
|
250
|
-
end
|
251
|
-
else
|
252
|
-
raise ArgumentError, "unknown server_class: #{server_class.inspect}"
|
253
|
-
end
|
254
|
-
raise
|
255
|
-
|
256
|
-
rescue => ex
|
257
|
-
ex.message << "; addr=#{server_addr.inspect}"
|
258
|
-
raise
|
259
|
-
end
|
260
216
|
end
|
261
217
|
|
262
218
|
# A passive client child may be stopped after all active clients exit.
|
263
|
-
def child *
|
219
|
+
def child *service_names, passive: false
|
264
220
|
c = fork do
|
265
|
-
conns =
|
221
|
+
conns = service_names.map {|sn| services[sn].connect}
|
266
222
|
yield(*conns) if block_given?
|
267
223
|
no_interrupt_if_interactive
|
268
224
|
end
|
@@ -275,8 +231,8 @@ class EasyServe
|
|
275
231
|
child *args, &block
|
276
232
|
end
|
277
233
|
|
278
|
-
def local *
|
279
|
-
conns =
|
234
|
+
def local *service_names
|
235
|
+
conns = service_names.map {|sn| services[sn].connect}
|
280
236
|
yield(*conns) if block_given?
|
281
237
|
ensure
|
282
238
|
conns and conns.each do |conn|
|
@@ -285,20 +241,8 @@ class EasyServe
|
|
285
241
|
log.info "stopped local client"
|
286
242
|
end
|
287
243
|
|
288
|
-
def socket_for *addr
|
289
|
-
socket_class =
|
290
|
-
case addr.size
|
291
|
-
when 1; UNIXSocket
|
292
|
-
else TCPSocket
|
293
|
-
end
|
294
|
-
socket_class.new(*addr)
|
295
|
-
rescue => ex
|
296
|
-
ex.message << "; addr=#{addr.inspect}"
|
297
|
-
raise
|
298
|
-
end
|
299
|
-
|
300
244
|
# ^C in the irb session (parent process) should not kill the
|
301
|
-
#
|
245
|
+
# service (child process)
|
302
246
|
def no_interrupt_if_interactive
|
303
247
|
trap("INT") {} if interactive
|
304
248
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class EasyServe
|
2
|
+
# Returns list of services that are accessible from +host+, setting
|
3
|
+
# up an ssh tunnel if specified. Note that OpenSSH 6.0 or later is required
|
4
|
+
# for the tunnel option.
|
5
|
+
def accessible_services host, tunnel: false
|
6
|
+
tcp_svs = services.values.grep(TCPService)
|
7
|
+
return tcp_svs unless tunnel and host != "localhost" and host != "127.0.0.1"
|
8
|
+
|
9
|
+
tcp_svs.map do |service|
|
10
|
+
service_host =
|
11
|
+
case service.bind_host
|
12
|
+
when nil, "localhost", "127.0.0.1", "0.0.0.0", /\A<any>\z/i
|
13
|
+
"localhost"
|
14
|
+
else
|
15
|
+
service.bind_host
|
16
|
+
end
|
17
|
+
|
18
|
+
fwd = "0:#{service_host}:#{service.port}"
|
19
|
+
out = `ssh -O forward -R #{fwd} #{host}`
|
20
|
+
|
21
|
+
begin
|
22
|
+
remote_port = Integer(out)
|
23
|
+
rescue
|
24
|
+
log.error "Unable to set up dynamic ssh port forwarding. " +
|
25
|
+
"Please check if ssh -v is at least 6.0."
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
|
29
|
+
at_exit {system "ssh -O cancel -R #{fwd} #{host}"}
|
30
|
+
|
31
|
+
TCPService.new service.name, connect_host: "localhost", port: remote_port
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -6,7 +6,7 @@ class EasyServe
|
|
6
6
|
# It's up to you to start another thread inside the code block if you
|
7
7
|
# want more concurrency. This is for convenience when testing (cases in which
|
8
8
|
# concurrency needs to be controlled explicitly).
|
9
|
-
def remote_drb *
|
9
|
+
def remote_drb *service_names, host: nil
|
10
10
|
## remote logfile option?
|
11
11
|
|
12
12
|
DRb.start_service("druby://#{host_name}:0", nil)
|
@@ -24,17 +24,17 @@ class EasyServe
|
|
24
24
|
require 'yaml'
|
25
25
|
require 'easy-serve'
|
26
26
|
|
27
|
-
|
28
|
-
|
27
|
+
service_names = #{service_names.inspect}
|
28
|
+
services = YAML.load(#{YAML.dump(services).inspect})
|
29
29
|
log_level = #{log.level}
|
30
30
|
host_uri = #{host_uri.inspect}
|
31
31
|
|
32
|
-
EasyServe.start
|
32
|
+
EasyServe.start services: services do |ez|
|
33
33
|
log = ez.log
|
34
34
|
log.level = log_level
|
35
35
|
log.formatter = nil if $VERBOSE
|
36
36
|
|
37
|
-
ez.local *
|
37
|
+
ez.local *service_names do |*conns|
|
38
38
|
begin
|
39
39
|
DRb.start_service(host_uri, {conns: conns})
|
40
40
|
puts DRb.uri
|
@@ -3,12 +3,14 @@ require 'easy-serve'
|
|
3
3
|
|
4
4
|
def EasyServe.manage_remote_eval_client msg
|
5
5
|
$VERBOSE = msg["verbose"]
|
6
|
-
|
7
|
-
msg.values_at(*%w{
|
6
|
+
service_names, services_list, log_level, eval_string, host =
|
7
|
+
msg.values_at(*%w{service_names services_list log_level eval_string host})
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
services = {}
|
10
|
+
service_names = Marshal.load(service_names)
|
11
|
+
services_list = Marshal.load(services_list)
|
12
|
+
services_list.each do |service|
|
13
|
+
services[service.name] = service
|
12
14
|
end
|
13
15
|
|
14
16
|
log_args = msg["log"]
|
@@ -22,12 +24,12 @@ def EasyServe.manage_remote_eval_client msg
|
|
22
24
|
EasyServe.null_logger
|
23
25
|
end
|
24
26
|
|
25
|
-
EasyServe.start
|
27
|
+
EasyServe.start services: services, log: log do |ez|
|
26
28
|
log = ez.log
|
27
29
|
log.level = log_level
|
28
30
|
log.formatter = nil if $VERBOSE
|
29
31
|
|
30
|
-
ez.local *
|
32
|
+
ez.local *service_names do |*conns|
|
31
33
|
begin
|
32
34
|
pr = eval "proc do |conns, host, log| #{eval_string}; end"
|
33
35
|
pr[conns, host, log]
|
@@ -48,7 +50,7 @@ def EasyServe.handle_remote_eval_messages
|
|
48
50
|
unpacker = MessagePack::Unpacker.new($stdin)
|
49
51
|
unpacker.each do |msg|
|
50
52
|
case
|
51
|
-
when msg["
|
53
|
+
when msg["service_names"]
|
52
54
|
Thread.new {manage_remote_eval_client(msg); exit}
|
53
55
|
when msg["exit"]
|
54
56
|
puts "exiting"
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'msgpack'
|
2
|
-
require 'easy-serve/accessible-
|
2
|
+
require 'easy-serve/accessible-services'
|
3
3
|
|
4
4
|
class EasyServe
|
5
5
|
# useful simple cases in testing and in production, but long eval strings
|
@@ -13,7 +13,7 @@ class EasyServe
|
|
13
13
|
#
|
14
14
|
# 2. Log back over ssh: pass log: true.
|
15
15
|
#
|
16
|
-
def remote_eval *
|
16
|
+
def remote_eval *service_names,
|
17
17
|
host: nil, passive: false, tunnel: false, **opts
|
18
18
|
child_pid = fork do
|
19
19
|
log.progname = "remote_eval #{host}"
|
@@ -26,17 +26,17 @@ class EasyServe
|
|
26
26
|
"w+" do |ssh|
|
27
27
|
|
28
28
|
ssh.sync = true
|
29
|
-
|
29
|
+
services_list = accessible_services(host, tunnel: tunnel)
|
30
30
|
|
31
31
|
MessagePack.pack(
|
32
32
|
{
|
33
|
-
verbose:
|
34
|
-
|
35
|
-
|
36
|
-
log_level:
|
37
|
-
eval_string:
|
38
|
-
host:
|
39
|
-
log:
|
33
|
+
verbose: $VERBOSE,
|
34
|
+
service_names: Marshal.dump(service_names),
|
35
|
+
services_list: Marshal.dump(services_list),
|
36
|
+
log_level: log.level,
|
37
|
+
eval_string: opts[:eval],
|
38
|
+
host: host,
|
39
|
+
log: opts[:log]
|
40
40
|
},
|
41
41
|
ssh)
|
42
42
|
|
@@ -3,15 +3,17 @@ require 'easy-serve'
|
|
3
3
|
|
4
4
|
def EasyServe.manage_remote_run_client msg
|
5
5
|
$VERBOSE = msg["verbose"]
|
6
|
-
|
6
|
+
service_names, services_list, log_level, host, dir, file, class_name, args =
|
7
7
|
msg.values_at(*%w{
|
8
|
-
|
8
|
+
service_names services_list log_level host
|
9
9
|
dir file class_name args
|
10
10
|
})
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
services = {}
|
13
|
+
service_names = Marshal.load(service_names)
|
14
|
+
services_list = Marshal.load(services_list)
|
15
|
+
services_list.each do |service|
|
16
|
+
services[service.name] = service
|
15
17
|
end
|
16
18
|
|
17
19
|
### opt for tmpdir and send files to it via ssh
|
@@ -29,12 +31,12 @@ def EasyServe.manage_remote_run_client msg
|
|
29
31
|
EasyServe.null_logger
|
30
32
|
end
|
31
33
|
|
32
|
-
EasyServe.start
|
34
|
+
EasyServe.start services: services, log: log do |ez|
|
33
35
|
log = ez.log
|
34
36
|
log.level = log_level
|
35
37
|
log.formatter = nil if $VERBOSE
|
36
38
|
|
37
|
-
ez.local *
|
39
|
+
ez.local *service_names do |*conns|
|
38
40
|
begin
|
39
41
|
cl = Object.const_get(class_name)
|
40
42
|
ro = cl.new(conns, host, log, *args)
|
@@ -56,7 +58,7 @@ def EasyServe.handle_remote_run_messages
|
|
56
58
|
unpacker = MessagePack::Unpacker.new($stdin)
|
57
59
|
unpacker.each do |msg|
|
58
60
|
case
|
59
|
-
when msg["
|
61
|
+
when msg["service_names"]
|
60
62
|
Thread.new {manage_remote_run_client(msg); exit}
|
61
63
|
when msg["exit"]
|
62
64
|
puts "exiting"
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'msgpack'
|
2
|
-
require 'easy-serve/accessible-
|
2
|
+
require 'easy-serve/accessible-services'
|
3
3
|
|
4
4
|
class EasyServe
|
5
5
|
# useful in production, though it requires remote lib files to be set up.
|
@@ -12,7 +12,7 @@ class EasyServe
|
|
12
12
|
#
|
13
13
|
# 2. Log back over ssh: pass log: true.
|
14
14
|
#
|
15
|
-
def remote_run *
|
15
|
+
def remote_run *service_names, host: nil, passive: false, tunnel: false, **opts
|
16
16
|
child_pid = fork do
|
17
17
|
log.progname = "remote_run #{host}"
|
18
18
|
|
@@ -24,20 +24,20 @@ class EasyServe
|
|
24
24
|
"w+" do |ssh|
|
25
25
|
|
26
26
|
ssh.sync = true
|
27
|
-
|
27
|
+
services_list = accessible_services(host, tunnel: tunnel)
|
28
28
|
|
29
29
|
MessagePack.pack(
|
30
30
|
{
|
31
|
-
verbose:
|
32
|
-
|
33
|
-
|
34
|
-
log_level:
|
35
|
-
host:
|
36
|
-
dir:
|
37
|
-
file:
|
38
|
-
class_name:
|
39
|
-
args:
|
40
|
-
log:
|
31
|
+
verbose: $VERBOSE,
|
32
|
+
service_names: Marshal.dump(service_names),
|
33
|
+
services_list: Marshal.dump(services_list),
|
34
|
+
log_level: log.level,
|
35
|
+
host: host,
|
36
|
+
dir: opts[:dir],
|
37
|
+
file: opts[:file],
|
38
|
+
class_name: opts[:class_name],
|
39
|
+
args: opts[:args],
|
40
|
+
log: opts[:log]
|
41
41
|
},
|
42
42
|
ssh)
|
43
43
|
|
data/lib/easy-serve/remote.rb
CHANGED
@@ -3,20 +3,20 @@ require 'easy-serve'
|
|
3
3
|
class EasyServe
|
4
4
|
class RemoteError < RuntimeError; end
|
5
5
|
|
6
|
-
def remote *
|
6
|
+
def remote *service_names, host: nil, **opts
|
7
7
|
raise ArgumentError, "no host specified" unless host
|
8
8
|
|
9
9
|
if opts[:eval]
|
10
10
|
require 'easy-serve/remote-eval'
|
11
|
-
remote_eval(*
|
11
|
+
remote_eval(*service_names, host: host, **opts)
|
12
12
|
|
13
13
|
elsif opts[:file]
|
14
14
|
require 'easy-serve/remote-run'
|
15
|
-
remote_run(*
|
15
|
+
remote_run(*service_names, host: host, **opts)
|
16
16
|
|
17
17
|
elsif block_given?
|
18
18
|
require 'easy-serve/remote-drb'
|
19
|
-
remote_drb(*
|
19
|
+
remote_drb(*service_names, host: host, **opts, &Proc.new)
|
20
20
|
|
21
21
|
else
|
22
22
|
raise ArgumentError, "cannot select remote mode based on arguments"
|
@@ -0,0 +1,125 @@
|
|
1
|
+
class EasyServe
|
2
|
+
# Refers to a named service. Use the #serve method to start the service.
|
3
|
+
# Encapsulates current location and identity, including pid and address. A
|
4
|
+
# Service object can be serialized to a remote process so it can #connect to
|
5
|
+
# the service.
|
6
|
+
class Service
|
7
|
+
attr_reader :name
|
8
|
+
attr_reader :pid
|
9
|
+
|
10
|
+
SERVICE_CLASS = {}
|
11
|
+
|
12
|
+
def self.for name, proto, **opts
|
13
|
+
sc = SERVICE_CLASS[proto] or
|
14
|
+
raise ArgumentError, "Unknown socket protocol: #{proto.inspect}"
|
15
|
+
sc.new name, **opts
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize name
|
19
|
+
@name = name
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the client socket.
|
23
|
+
def connect
|
24
|
+
try_connect
|
25
|
+
rescue => ex
|
26
|
+
ex.message << "; #{inspect}"
|
27
|
+
raise
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the server socket.
|
31
|
+
def serve max_tries: 1, log: (raise ArgumentError, "missing log")
|
32
|
+
tries = 1
|
33
|
+
begin
|
34
|
+
try_serve.tap do
|
35
|
+
@pid = Process.pid
|
36
|
+
end
|
37
|
+
|
38
|
+
rescue Errno::EADDRINUSE => ex
|
39
|
+
raise if tries >= max_tries
|
40
|
+
log.warn {"#{ex}; #{inspect} (#{tries}/#{max_tries} tries)"}
|
41
|
+
tries += 1; bump!
|
42
|
+
log.info {"Trying: #{inspect}"}
|
43
|
+
retry
|
44
|
+
end
|
45
|
+
rescue => ex
|
46
|
+
ex.message << "; #{inspect}"
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
|
50
|
+
def cleanup
|
51
|
+
Process.kill "TERM", pid
|
52
|
+
Process.waitpid pid
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class UNIXService < Service
|
57
|
+
SERVICE_CLASS[:unix] = self
|
58
|
+
|
59
|
+
attr_reader :path
|
60
|
+
|
61
|
+
def initialize name, path: nil
|
62
|
+
super name
|
63
|
+
@path = path
|
64
|
+
end
|
65
|
+
|
66
|
+
def serve max_tries: 1, log: log
|
67
|
+
super.tap do |svr|
|
68
|
+
found_path = svr.addr[1]
|
69
|
+
log.debug "#{inspect} is listening at #{found_path}"
|
70
|
+
|
71
|
+
if found_path != path
|
72
|
+
log.error "Unexpected path: #{found_path} != #{path}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def cleanup
|
78
|
+
super
|
79
|
+
FileUtils.remove_entry path if path
|
80
|
+
end
|
81
|
+
|
82
|
+
def try_connect
|
83
|
+
UNIXSocket.new(path)
|
84
|
+
end
|
85
|
+
|
86
|
+
def try_serve
|
87
|
+
UNIXServer.new(path)
|
88
|
+
end
|
89
|
+
|
90
|
+
def bump!
|
91
|
+
@path = EasyServe.bump_socket_filename(path)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class TCPService < Service
|
96
|
+
SERVICE_CLASS[:tcp] = self
|
97
|
+
|
98
|
+
attr_reader :bind_host, :connect_host, :port
|
99
|
+
|
100
|
+
def initialize name, bind_host: nil, connect_host: nil, port: 0
|
101
|
+
super name
|
102
|
+
@bind_host, @connect_host, @port = bind_host, connect_host, port
|
103
|
+
end
|
104
|
+
|
105
|
+
def serve max_tries: 1, log: log
|
106
|
+
super.tap do |svr|
|
107
|
+
found_addr = svr.addr(false).values_at(2,1)
|
108
|
+
log.debug "#{inspect} is listening at #{found_addr.join(":")}"
|
109
|
+
@port = found_addr[1]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def try_connect
|
114
|
+
TCPSocket.new(connect_host, port)
|
115
|
+
end
|
116
|
+
|
117
|
+
def try_serve
|
118
|
+
TCPServer.new(bind_host, port)
|
119
|
+
end
|
120
|
+
|
121
|
+
def bump!
|
122
|
+
@port += 1 unless port == 0 # should not happen
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: easy-serve
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.8'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel VanderWerf
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-12-
|
11
|
+
date: 2013-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|
@@ -24,8 +24,8 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
description: Framework for starting tcp/unix
|
28
|
-
parent process and on remote hosts.
|
27
|
+
description: Framework for starting tcp/unix services and connected clients under
|
28
|
+
one parent process and on remote hosts.
|
29
29
|
email: vjoel@users.sourceforge.net
|
30
30
|
executables: []
|
31
31
|
extensions: []
|
@@ -35,24 +35,25 @@ extra_rdoc_files:
|
|
35
35
|
files:
|
36
36
|
- README.md
|
37
37
|
- COPYING
|
38
|
-
- lib/easy-serve
|
38
|
+
- lib/easy-serve.rb
|
39
|
+
- lib/easy-serve/remote-eval-mgr.rb
|
39
40
|
- lib/easy-serve/remote-run.rb
|
40
41
|
- lib/easy-serve/remote-drb.rb
|
41
|
-
- lib/easy-serve/
|
42
|
-
- lib/easy-serve/
|
43
|
-
- lib/easy-serve/remote-eval-mgr.rb
|
42
|
+
- lib/easy-serve/remote.rb
|
43
|
+
- lib/easy-serve/accessible-services.rb
|
44
44
|
- lib/easy-serve/remote-eval.rb
|
45
|
-
- lib/easy-serve.rb
|
46
|
-
-
|
47
|
-
- examples/
|
48
|
-
- examples/
|
45
|
+
- lib/easy-serve/remote-run-mgr.rb
|
46
|
+
- lib/easy-serve/service.rb
|
47
|
+
- examples/simple.rb
|
48
|
+
- examples/remote-manual.rb
|
49
|
+
- examples/multi.rb
|
49
50
|
- examples/remote-eval-passive.rb
|
51
|
+
- examples/remote-run.rb
|
50
52
|
- examples/remote-drb.rb
|
53
|
+
- examples/remote-multi-server.rb
|
51
54
|
- examples/remote-run-script.rb
|
52
|
-
- examples/multi.rb
|
53
|
-
- examples/remote-manual.rb
|
54
|
-
- examples/simple.rb
|
55
55
|
- examples/remote-eval.rb
|
56
|
+
- examples/passive.rb
|
56
57
|
homepage: https://github.com/vjoel/easy-serve
|
57
58
|
licenses:
|
58
59
|
- BSD
|
@@ -83,7 +84,7 @@ rubyforge_project:
|
|
83
84
|
rubygems_version: 2.1.11
|
84
85
|
signing_key:
|
85
86
|
specification_version: 4
|
86
|
-
summary: Framework for starting tcp/unix
|
87
|
-
process and on remote hosts
|
87
|
+
summary: Framework for starting tcp/unix services and connected clients under one
|
88
|
+
parent process and on remote hosts
|
88
89
|
test_files: []
|
89
90
|
has_rdoc:
|
@@ -1,29 +0,0 @@
|
|
1
|
-
class EasyServe
|
2
|
-
# Returns list of [name, pid, addr] that are accessible from host, setting
|
3
|
-
# up ssh tunnel if specified. Note that OpenSSH 6.0 or later is required
|
4
|
-
# for the tunnel option.
|
5
|
-
def accessible_servers host, tunnel: false
|
6
|
-
if tunnel and host != "localhost" and host != "127.0.0.1"
|
7
|
-
servers.map do |n, s|
|
8
|
-
_, local_port = s.addr
|
9
|
-
fwd = "0:localhost:#{local_port}"
|
10
|
-
out = `ssh -O forward -R #{fwd} #{host}`
|
11
|
-
|
12
|
-
begin
|
13
|
-
remote_port = Integer(out)
|
14
|
-
rescue
|
15
|
-
log.error "Unable to set up dynamic ssh port forwarding. " +
|
16
|
-
"Please check if ssh -v is at least 6.0."
|
17
|
-
raise
|
18
|
-
end
|
19
|
-
|
20
|
-
at_exit {system "ssh -O cancel -R #{fwd} #{host}"}
|
21
|
-
|
22
|
-
[s.name, s.pid, ["localhost", remote_port]]
|
23
|
-
end
|
24
|
-
|
25
|
-
else
|
26
|
-
servers.map {|n, s| [s.name, s.pid, s.addr]}
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|