switchman 3.0.14 → 3.0.17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/models/switchman/shard.rb +25 -115
- data/lib/switchman/active_record/base.rb +27 -14
- data/lib/switchman/active_record/persistence.rb +1 -3
- data/lib/switchman/database_server.rb +10 -11
- data/lib/switchman/guard_rail/relation.rb +4 -7
- data/lib/switchman/guard_rail.rb +5 -0
- data/lib/switchman/parallel.rb +68 -0
- data/lib/switchman/r_spec_helper.rb +3 -0
- data/lib/switchman/sharded_instrumenter.rb +1 -1
- data/lib/switchman/standard_error.rb +2 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +1 -1
- data/lib/tasks/switchman.rake +1 -3
- metadata +6 -6
- data/lib/switchman/open4.rb +0 -80
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 213adc61b124f8ba48e52259c47702cb601fdc9e0ef71c452c1eeee23dc94e46
|
4
|
+
data.tar.gz: f7982e2e7cb3efce13291382b7fa49f9cb0da87cfd5939581368ac5735ed3e00
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3651c9824e8258c8854e0281ecbc081b42f3fa97524d90eb41ac701f9a5eb0bef53ea77a700c682ffa5913ea71aa7816044cfa408b051f20f4b96b86a4e5307
|
7
|
+
data.tar.gz: 8fac15d77c092790b6410bf15aaccef4c90243308970eddd29e2c9145281cde67e4d6572439b97966ecc2c5924557c7916aca8665e722530c2ead0d43dbcdab2
|
@@ -125,8 +125,7 @@ module Switchman
|
|
125
125
|
# * +classes+ - an array of classes to activate
|
126
126
|
# parallel: - true/false to execute in parallel, or an integer of how many
|
127
127
|
# sub-processes. Note that parallel invocation currently uses
|
128
|
-
# forking
|
129
|
-
# results back
|
128
|
+
# forking.
|
130
129
|
# exception: - :ignore, :raise, :defer (wait until the end and raise the first
|
131
130
|
# error), or a proc
|
132
131
|
def with_each_shard(*args, parallel: false, exception: :raise, &block)
|
@@ -159,137 +158,47 @@ module Switchman
|
|
159
158
|
return if database_servers.count.zero?
|
160
159
|
|
161
160
|
scopes = database_servers.to_h do |server|
|
162
|
-
[server, server.shards
|
161
|
+
[server, scope.merge(server.shards)]
|
163
162
|
end
|
164
163
|
else
|
165
164
|
scopes = scope.group_by(&:database_server)
|
166
165
|
end
|
167
166
|
|
168
|
-
exception_pipes = []
|
169
|
-
pids = []
|
170
|
-
out_fds = []
|
171
|
-
err_fds = []
|
172
|
-
pid_to_name_map = {}
|
173
|
-
fd_to_name_map = {}
|
174
|
-
errors = []
|
175
|
-
|
176
|
-
wait_for_output = lambda do
|
177
|
-
ready, = IO.select(out_fds + err_fds)
|
178
|
-
ready.each do |fd|
|
179
|
-
if fd.eof?
|
180
|
-
fd.close
|
181
|
-
out_fds.delete(fd)
|
182
|
-
err_fds.delete(fd)
|
183
|
-
next
|
184
|
-
end
|
185
|
-
line = fd.readline
|
186
|
-
puts "#{fd_to_name_map[fd]}: #{line}"
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
# only one process; don't bother forking
|
191
|
-
return with_each_shard(scopes.first.last, classes, exception: exception, &block) if scopes.length == 1
|
192
|
-
|
193
167
|
# clear connections prior to forking (no more queries will be executed in the parent,
|
194
168
|
# and we want them gone so that we don't accidentally use them post-fork doing something
|
195
169
|
# silly like dealloc'ing prepared statements)
|
196
170
|
::ActiveRecord::Base.clear_all_connections!
|
197
171
|
|
198
|
-
|
172
|
+
parent_process_name = `ps -ocommand= -p#{Process.pid}`.slice(/#{$0}.*/)
|
173
|
+
ret = ::Parallel.map(scopes, in_processes: scopes.length > 1 ? parallel : 0) do |server, subscope|
|
199
174
|
name = server.id
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
# was started)
|
209
|
-
# first, simplify the binary name by stripping directories,
|
210
|
-
# then truncate arguments as necessary
|
211
|
-
bin = File.basename($0) # Process.argv0 doesn't work on Ruby 2.5 (https://bugs.ruby-lang.org/issues/15887)
|
212
|
-
max_length = 128 - bin.length - name.length - 3
|
213
|
-
args = ARGV.join(' ')
|
214
|
-
args = args[0..max_length] if max_length >= 0
|
215
|
-
new_title = [bin, args, name].join(' ')
|
175
|
+
# rubocop:disable Style/GlobalStdStream
|
176
|
+
$stdout = Parallel::PrefixingIO.new(name, STDOUT)
|
177
|
+
$stderr = Parallel::PrefixingIO.new(name, STDERR)
|
178
|
+
# rubocop:enable Style/GlobalStdStream
|
179
|
+
begin
|
180
|
+
max_length = 128 - name.length - 3
|
181
|
+
short_parent_name = parent_process_name[0..max_length] if max_length >= 0
|
182
|
+
new_title = [short_parent_name, name].join(' ')
|
216
183
|
Process.setproctitle(new_title)
|
217
|
-
|
218
|
-
with_each_shard(subscope, classes, exception: exception, &block)
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
dumped = Marshal.dump(e)
|
223
|
-
dumped = nil if dumped.length > 64 * 1024
|
224
|
-
rescue
|
225
|
-
dumped = nil
|
226
|
-
end
|
227
|
-
|
228
|
-
if dumped.nil?
|
229
|
-
# couldn't dump the exception; create a copy with just
|
230
|
-
# the message and the backtrace
|
231
|
-
e2 = e.class.new(e.message)
|
232
|
-
backtrace = e.backtrace
|
233
|
-
# truncate excessively long backtraces
|
234
|
-
backtrace = backtrace[0...25] + ['...'] + backtrace[-25..] if backtrace.length > 50
|
235
|
-
e2.set_backtrace(backtrace)
|
236
|
-
e2.instance_variable_set(:@active_shards, e.instance_variable_get(:@active_shards))
|
237
|
-
dumped = Marshal.dump(e2)
|
238
|
-
end
|
239
|
-
exception_pipe.last.set_encoding(dumped.encoding)
|
240
|
-
exception_pipe.last.write(dumped)
|
241
|
-
exception_pipe.last.flush
|
242
|
-
exception_pipe.last.close
|
243
|
-
exit! 1
|
244
|
-
end)
|
245
|
-
exception_pipe.last.close
|
246
|
-
pids << pid
|
247
|
-
io_in.close # don't care about writing to stdin
|
248
|
-
out_fds << io_out
|
249
|
-
err_fds << io_err
|
250
|
-
pid_to_name_map[pid] = name
|
251
|
-
fd_to_name_map[io_out] = name
|
252
|
-
fd_to_name_map[io_err] = name
|
253
|
-
|
254
|
-
while pids.count >= parallel
|
255
|
-
while out_fds.count >= parallel
|
256
|
-
# wait for output if we've hit the parallel limit
|
257
|
-
wait_for_output.call
|
258
|
-
end
|
259
|
-
# we've gotten all the output from one fd so wait for its child process to exit
|
260
|
-
found_pid, status = Process.wait2
|
261
|
-
pids.delete(found_pid)
|
262
|
-
errors << pid_to_name_map[found_pid] if status.exitstatus != 0
|
184
|
+
Switchman.config[:on_fork_proc]&.call
|
185
|
+
with_each_shard(subscope, classes, exception: exception, &block).map { |result| Parallel::ResultWrapper.new(result) }
|
186
|
+
rescue => e
|
187
|
+
logger.error e.full_message
|
188
|
+
Parallel::QuietExceptionWrapper.new(name, ::Parallel::ExceptionWrapper.new(e))
|
263
189
|
end
|
264
|
-
|
265
|
-
found_pid, status = Process.wait2
|
266
|
-
pids.delete(found_pid)
|
267
|
-
errors << pid_to_name_map[found_pid] if status.exitstatus != 0
|
268
|
-
end
|
269
|
-
|
270
|
-
wait_for_output.call while out_fds.any? || err_fds.any?
|
271
|
-
pids.each do |pid|
|
272
|
-
_, status = Process.waitpid2(pid)
|
273
|
-
errors << pid_to_name_map[pid] if status.exitstatus != 0
|
274
|
-
end
|
275
|
-
|
276
|
-
# check for an exception; we only re-raise the first one
|
277
|
-
exception_pipes.each do |exception_pipe|
|
278
|
-
serialized_exception = exception_pipe.first.read
|
279
|
-
next if serialized_exception.empty?
|
280
|
-
|
281
|
-
ex = Marshal.load(serialized_exception) # rubocop:disable Security/MarshalLoad
|
282
|
-
raise ex
|
283
|
-
ensure
|
284
|
-
exception_pipe.first.close
|
285
|
-
end
|
190
|
+
end.flatten
|
286
191
|
|
192
|
+
errors = ret.select { |val| val.is_a?(Parallel::QuietExceptionWrapper) }
|
287
193
|
unless errors.empty?
|
194
|
+
raise errors.first.exception if errors.length == 1
|
195
|
+
|
288
196
|
raise ParallelShardExecError,
|
289
|
-
"The following
|
197
|
+
"The following database server(s) did not finish processing cleanly: #{errors.map(&:name).sort.join(', ')}",
|
198
|
+
cause: errors.first.exception
|
290
199
|
end
|
291
200
|
|
292
|
-
return
|
201
|
+
return ret.map(&:result)
|
293
202
|
end
|
294
203
|
|
295
204
|
classes ||= []
|
@@ -498,6 +407,7 @@ module Switchman
|
|
498
407
|
|
499
408
|
klass.connects_to shards: connects_to_hash
|
500
409
|
end
|
410
|
+
DatabaseServer.all.each { |db| db.guard! if db.config[:prefer_secondary] } unless @sharding_initialized
|
501
411
|
|
502
412
|
@sharding_initialized = true
|
503
413
|
end
|
@@ -28,19 +28,11 @@ module Switchman
|
|
28
28
|
if self != ::ActiveRecord::Base && current_scope
|
29
29
|
current_scope.activate do
|
30
30
|
db = Shard.current(connection_classes).database_server
|
31
|
-
|
32
|
-
super
|
33
|
-
else
|
34
|
-
db.unguard { super }
|
35
|
-
end
|
31
|
+
db.unguard { super }
|
36
32
|
end
|
37
33
|
else
|
38
34
|
db = Shard.current(connection_classes).database_server
|
39
|
-
|
40
|
-
super
|
41
|
-
else
|
42
|
-
db.unguard { super }
|
43
|
-
end
|
35
|
+
db.unguard { super }
|
44
36
|
end
|
45
37
|
end
|
46
38
|
|
@@ -68,6 +60,28 @@ module Switchman
|
|
68
60
|
end
|
69
61
|
end
|
70
62
|
|
63
|
+
def current_role_overriden?
|
64
|
+
current_role != current_role(without_overrides: true)
|
65
|
+
end
|
66
|
+
|
67
|
+
# significant change: Allow per-shard roles
|
68
|
+
def current_role(without_overrides: false)
|
69
|
+
return super() if without_overrides
|
70
|
+
|
71
|
+
sharded_role = nil
|
72
|
+
connected_to_stack.reverse_each do |hash|
|
73
|
+
shard_role = hash.dig(:shard_roles, current_shard)
|
74
|
+
if shard_role && (hash[:klasses].include?(Base) || hash[:klasses].include?(connection_classes))
|
75
|
+
sharded_role = shard_role
|
76
|
+
break
|
77
|
+
end
|
78
|
+
end
|
79
|
+
# Allow a shard-specific role to be reverted to regular inheritance
|
80
|
+
return sharded_role if sharded_role && sharded_role != :_switchman_inherit
|
81
|
+
|
82
|
+
super()
|
83
|
+
end
|
84
|
+
|
71
85
|
# significant change: _don't_ check if klasses.include?(Base)
|
72
86
|
# i.e. other sharded models don't inherit the current shard of Base
|
73
87
|
def current_shard
|
@@ -146,7 +160,8 @@ module Switchman
|
|
146
160
|
|
147
161
|
def with_transaction_returning_status
|
148
162
|
shard.activate(self.class.connection_classes) do
|
149
|
-
|
163
|
+
db = Shard.current(self.class.connection_classes).database_server
|
164
|
+
db.unguard { super }
|
150
165
|
end
|
151
166
|
end
|
152
167
|
|
@@ -167,9 +182,7 @@ module Switchman
|
|
167
182
|
|
168
183
|
def update_columns(*)
|
169
184
|
db = shard.database_server
|
170
|
-
|
171
|
-
|
172
|
-
super
|
185
|
+
db.unguard { super }
|
173
186
|
end
|
174
187
|
|
175
188
|
def id_for_database
|
@@ -129,27 +129,26 @@ module Switchman
|
|
129
129
|
end
|
130
130
|
end
|
131
131
|
|
132
|
-
def guard_rail_environment
|
133
|
-
@guard_rail_environment || ::GuardRail.environment
|
134
|
-
end
|
135
|
-
|
136
132
|
# locks this db to a specific environment, except for
|
137
133
|
# when doing writes (then it falls back to the current
|
138
134
|
# value of GuardRail.environment)
|
139
135
|
def guard!(environment = :secondary)
|
140
|
-
|
136
|
+
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment }, klasses: [::ActiveRecord::Base] }
|
141
137
|
end
|
142
138
|
|
143
139
|
def unguard!
|
144
|
-
|
140
|
+
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit }, klasses: [::ActiveRecord::Base] }
|
145
141
|
end
|
146
142
|
|
147
143
|
def unguard
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
144
|
+
return yield unless ::ActiveRecord::Base.current_role_overriden?
|
145
|
+
|
146
|
+
begin
|
147
|
+
unguard!
|
148
|
+
yield
|
149
|
+
ensure
|
150
|
+
::ActiveRecord::Base.connected_to_stack.pop
|
151
|
+
end
|
153
152
|
end
|
154
153
|
|
155
154
|
def shards
|
@@ -6,20 +6,17 @@ module Switchman
|
|
6
6
|
def exec_queries(*args)
|
7
7
|
if lock_value
|
8
8
|
db = Shard.current(connection_classes).database_server
|
9
|
-
|
9
|
+
db.unguard { super }
|
10
|
+
else
|
11
|
+
super
|
10
12
|
end
|
11
|
-
super
|
12
13
|
end
|
13
14
|
|
14
15
|
%w[update_all delete_all].each do |method|
|
15
16
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
16
17
|
def #{method}(*args)
|
17
18
|
db = Shard.current(connection_classes).database_server
|
18
|
-
|
19
|
-
db.unguard { super }
|
20
|
-
else
|
21
|
-
super
|
22
|
-
end
|
19
|
+
db.unguard { super }
|
23
20
|
end
|
24
21
|
RUBY
|
25
22
|
end
|
data/lib/switchman/guard_rail.rb
CHANGED
@@ -3,6 +3,11 @@
|
|
3
3
|
module Switchman
|
4
4
|
module GuardRail
|
5
5
|
module ClassMethods
|
6
|
+
def environment
|
7
|
+
# no overrides so we get the global role, not the role for the default shard
|
8
|
+
::ActiveRecord::Base.current_role(without_overrides: true)
|
9
|
+
end
|
10
|
+
|
6
11
|
def activate(role)
|
7
12
|
DatabaseServer.send(:reference_role, role)
|
8
13
|
super
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'parallel'
|
4
|
+
|
5
|
+
module Switchman
|
6
|
+
module Parallel
|
7
|
+
module UndumpableException
|
8
|
+
def initialize(original)
|
9
|
+
super
|
10
|
+
@active_shards = original.instance_variable_get(:@active_shards)
|
11
|
+
current_shard
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class QuietExceptionWrapper
|
16
|
+
attr_accessor :name
|
17
|
+
|
18
|
+
def initialize(name, wrapper)
|
19
|
+
@name = name
|
20
|
+
@wrapper = wrapper
|
21
|
+
end
|
22
|
+
|
23
|
+
def exception
|
24
|
+
@wrapper.exception
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class UndumpableResult
|
29
|
+
attr_reader :name
|
30
|
+
|
31
|
+
def initialize(result)
|
32
|
+
@name = result.inspect
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
"#<UndumpableResult:#{name}>"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ResultWrapper
|
41
|
+
attr_reader :result
|
42
|
+
|
43
|
+
def initialize(result)
|
44
|
+
@result =
|
45
|
+
begin
|
46
|
+
Marshal.dump(result) && result
|
47
|
+
rescue
|
48
|
+
UndumpableResult.new(result)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class PrefixingIO
|
54
|
+
delegate_missing_to :@original_io
|
55
|
+
|
56
|
+
def initialize(prefix, original_io)
|
57
|
+
@prefix = prefix
|
58
|
+
@original_io = original_io
|
59
|
+
end
|
60
|
+
|
61
|
+
def puts(*args)
|
62
|
+
args.flatten.each { |arg| @original_io.puts "#{@prefix}: #{arg}" }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
::Parallel::UndumpableException.prepend(::Switchman::Parallel::UndumpableException)
|
@@ -70,7 +70,10 @@ module Switchman
|
|
70
70
|
Shard.default(reload: true)
|
71
71
|
puts 'Done!'
|
72
72
|
|
73
|
+
main_pid = Process.pid
|
73
74
|
at_exit do
|
75
|
+
next unless main_pid == Process.pid
|
76
|
+
|
74
77
|
# preserve rspec's exit status
|
75
78
|
status = $!.is_a?(::SystemExit) ? $!.status : nil
|
76
79
|
puts 'Tearing down sharding for all specs'
|
@@ -6,13 +6,14 @@ module Switchman
|
|
6
6
|
super
|
7
7
|
# These seem to get themselves into a bad state if we try to lookup shards while processing
|
8
8
|
return if is_a?(IO::EAGAINWaitReadable)
|
9
|
+
|
9
10
|
return if Thread.current[:switchman_error_handler]
|
10
11
|
|
11
12
|
begin
|
12
13
|
Thread.current[:switchman_error_handler] = true
|
13
14
|
|
14
15
|
begin
|
15
|
-
@active_shards
|
16
|
+
@active_shards ||= Shard.active_shards if defined?(Shard)
|
16
17
|
rescue
|
17
18
|
# If we hit an error really early in boot, activerecord may not be initialized yet
|
18
19
|
end
|
data/lib/switchman/version.rb
CHANGED
data/lib/switchman.rb
CHANGED
data/lib/tasks/switchman.rake
CHANGED
@@ -86,10 +86,8 @@ module Switchman
|
|
86
86
|
nil
|
87
87
|
end
|
88
88
|
rescue => e
|
89
|
-
|
89
|
+
warn "Exception from #{e.current_shard.id}: #{e.current_shard.description}:\n#{e.full_message}" if options[:parallel] != 0
|
90
90
|
raise
|
91
|
-
|
92
|
-
# ::ActiveRecord::Base.configurations = old_configurations
|
93
91
|
end
|
94
92
|
end
|
95
93
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: switchman
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.
|
4
|
+
version: 3.0.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cody Cutrer
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2022-
|
13
|
+
date: 2022-04-05 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -47,19 +47,19 @@ dependencies:
|
|
47
47
|
- !ruby/object:Gem::Version
|
48
48
|
version: 3.0.0
|
49
49
|
- !ruby/object:Gem::Dependency
|
50
|
-
name:
|
50
|
+
name: parallel
|
51
51
|
requirement: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: 1.
|
55
|
+
version: '1.22'
|
56
56
|
type: :runtime
|
57
57
|
prerelease: false
|
58
58
|
version_requirements: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
60
|
- - "~>"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: 1.
|
62
|
+
version: '1.22'
|
63
63
|
- !ruby/object:Gem::Dependency
|
64
64
|
name: railties
|
65
65
|
requirement: !ruby/object:Gem::Requirement
|
@@ -287,7 +287,7 @@ files:
|
|
287
287
|
- lib/switchman/errors.rb
|
288
288
|
- lib/switchman/guard_rail.rb
|
289
289
|
- lib/switchman/guard_rail/relation.rb
|
290
|
-
- lib/switchman/
|
290
|
+
- lib/switchman/parallel.rb
|
291
291
|
- lib/switchman/r_spec_helper.rb
|
292
292
|
- lib/switchman/rails.rb
|
293
293
|
- lib/switchman/sharded_instrumenter.rb
|
data/lib/switchman/open4.rb
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'open4'
|
4
|
-
|
5
|
-
# This fixes a bug with exception handling,
|
6
|
-
# see https://github.com/ahoward/open4/pull/30
|
7
|
-
module Open4
|
8
|
-
def self.do_popen(b = nil, exception_propagation_at = nil, closefds=false, &cmd)
|
9
|
-
pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
|
10
|
-
|
11
|
-
verbose = $VERBOSE
|
12
|
-
begin
|
13
|
-
$VERBOSE = nil
|
14
|
-
|
15
|
-
cid = fork {
|
16
|
-
if closefds
|
17
|
-
exlist = [0, 1, 2] | [pw,pr,pe,ps].map{|p| [p.first.fileno, p.last.fileno] }.flatten
|
18
|
-
ObjectSpace.each_object(IO){|io|
|
19
|
-
io.close if (not io.closed?) and (not exlist.include? io.fileno) rescue nil
|
20
|
-
}
|
21
|
-
end
|
22
|
-
|
23
|
-
pw.last.close
|
24
|
-
STDIN.reopen pw.first
|
25
|
-
pw.first.close
|
26
|
-
|
27
|
-
pr.first.close
|
28
|
-
STDOUT.reopen pr.last
|
29
|
-
pr.last.close
|
30
|
-
|
31
|
-
pe.first.close
|
32
|
-
STDERR.reopen pe.last
|
33
|
-
pe.last.close
|
34
|
-
|
35
|
-
STDOUT.sync = STDERR.sync = true
|
36
|
-
|
37
|
-
begin
|
38
|
-
cmd.call(ps)
|
39
|
-
rescue Exception => e
|
40
|
-
begin
|
41
|
-
Marshal.dump(e, ps.last)
|
42
|
-
ps.last.flush
|
43
|
-
rescue Errno::EPIPE
|
44
|
-
raise e
|
45
|
-
end
|
46
|
-
ensure
|
47
|
-
ps.last.close unless ps.last.closed?
|
48
|
-
end
|
49
|
-
|
50
|
-
exit!
|
51
|
-
}
|
52
|
-
ensure
|
53
|
-
$VERBOSE = verbose
|
54
|
-
end
|
55
|
-
|
56
|
-
[ pw.first, pr.last, pe.last, ps.last ].each { |fd| fd.close }
|
57
|
-
|
58
|
-
Open4.propagate_exception cid, ps.first if exception_propagation_at == :init
|
59
|
-
|
60
|
-
pw.last.sync = true
|
61
|
-
|
62
|
-
pi = [ pw.last, pr.first, pe.first ]
|
63
|
-
|
64
|
-
begin
|
65
|
-
return [cid, *pi] unless b
|
66
|
-
|
67
|
-
begin
|
68
|
-
b.call(cid, *pi)
|
69
|
-
ensure
|
70
|
-
pi.each { |fd| fd.close unless fd.closed? }
|
71
|
-
end
|
72
|
-
|
73
|
-
Open4.propagate_exception cid, ps.first if exception_propagation_at == :block
|
74
|
-
|
75
|
-
Process.waitpid2(cid).last
|
76
|
-
ensure
|
77
|
-
ps.first.close unless ps.first.closed?
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|