switchman 3.0.14 → 3.0.17
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.
- 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
|