switchman 3.0.1 → 4.2.5

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +16 -15
  3. data/db/migrate/20180828183945_add_default_shard_index.rb +2 -2
  4. data/db/migrate/20180828192111_add_timestamps_to_shards.rb +1 -1
  5. data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
  6. data/lib/switchman/action_controller/caching.rb +2 -2
  7. data/lib/switchman/active_record/abstract_adapter.rb +11 -18
  8. data/lib/switchman/active_record/associations.rb +315 -0
  9. data/lib/switchman/active_record/attribute_methods.rb +191 -79
  10. data/lib/switchman/active_record/base.rb +204 -50
  11. data/lib/switchman/active_record/calculations.rb +93 -50
  12. data/lib/switchman/active_record/connection_handler.rb +18 -0
  13. data/lib/switchman/active_record/connection_pool.rb +47 -34
  14. data/lib/switchman/active_record/database_configurations.rb +32 -6
  15. data/lib/switchman/active_record/finder_methods.rb +22 -16
  16. data/lib/switchman/active_record/log_subscriber.rb +3 -6
  17. data/lib/switchman/active_record/migration.rb +42 -14
  18. data/lib/switchman/active_record/model_schema.rb +1 -1
  19. data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
  20. data/lib/switchman/active_record/persistence.rb +37 -2
  21. data/lib/switchman/active_record/postgresql_adapter.rb +39 -20
  22. data/lib/switchman/active_record/predicate_builder.rb +2 -2
  23. data/lib/switchman/active_record/query_cache.rb +26 -17
  24. data/lib/switchman/active_record/query_methods.rb +252 -135
  25. data/lib/switchman/active_record/reflection.rb +10 -3
  26. data/lib/switchman/active_record/relation.rb +154 -32
  27. data/lib/switchman/active_record/spawn_methods.rb +3 -7
  28. data/lib/switchman/active_record/statement_cache.rb +13 -9
  29. data/lib/switchman/active_record/table_definition.rb +1 -1
  30. data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
  31. data/lib/switchman/active_record/test_fixtures.rb +89 -0
  32. data/lib/switchman/active_support/cache.rb +25 -4
  33. data/lib/switchman/arel.rb +20 -7
  34. data/lib/switchman/call_super.rb +2 -2
  35. data/lib/switchman/database_server.rb +123 -83
  36. data/lib/switchman/default_shard.rb +14 -5
  37. data/lib/switchman/engine.rb +85 -131
  38. data/lib/switchman/environment.rb +2 -2
  39. data/lib/switchman/errors.rb +17 -2
  40. data/lib/switchman/guard_rail/relation.rb +7 -10
  41. data/lib/switchman/guard_rail.rb +5 -0
  42. data/lib/switchman/parallel.rb +68 -0
  43. data/lib/switchman/r_spec_helper.rb +17 -28
  44. data/lib/switchman/rails.rb +1 -4
  45. data/{app/models → lib}/switchman/shard.rb +229 -246
  46. data/lib/switchman/sharded_instrumenter.rb +9 -3
  47. data/lib/switchman/shared_schema_cache.rb +11 -0
  48. data/lib/switchman/standard_error.rb +15 -12
  49. data/lib/switchman/test_helper.rb +3 -3
  50. data/{app/models → lib}/switchman/unsharded_record.rb +1 -1
  51. data/lib/switchman/version.rb +1 -1
  52. data/lib/switchman.rb +46 -12
  53. data/lib/tasks/switchman.rake +101 -54
  54. metadata +34 -176
  55. data/lib/switchman/active_record/association.rb +0 -206
  56. data/lib/switchman/open4.rb +0 -80
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.1
4
+ version: 4.2.5
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: 2021-05-24 00:00:00.000000000 Z
13
+ date: 2026-05-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -18,222 +18,74 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: '6.1'
21
+ version: '7.1'
22
22
  - - "<"
23
23
  - !ruby/object:Gem::Version
24
- version: '6.2'
24
+ version: '8.1'
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
28
28
  requirements:
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
- version: '6.1'
31
+ version: '7.1'
32
32
  - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: '6.2'
34
+ version: '8.1'
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: guardrail
37
37
  requirement: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: 3.0.0
41
+ version: 3.1.0
42
42
  type: :runtime
43
43
  prerelease: false
44
44
  version_requirements: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: 3.0.0
48
+ version: 3.1.0
49
49
  - !ruby/object:Gem::Dependency
50
- name: open4
51
- requirement: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - "~>"
54
- - !ruby/object:Gem::Version
55
- version: 1.3.0
56
- type: :runtime
57
- prerelease: false
58
- version_requirements: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - "~>"
61
- - !ruby/object:Gem::Version
62
- version: 1.3.0
63
- - !ruby/object:Gem::Dependency
64
- name: railties
50
+ name: parallel
65
51
  requirement: !ruby/object:Gem::Requirement
66
52
  requirements:
67
53
  - - ">="
68
54
  - !ruby/object:Gem::Version
69
- version: '6.1'
55
+ version: '1.22'
70
56
  - - "<"
71
57
  - !ruby/object:Gem::Version
72
- version: '6.2'
58
+ version: '3.0'
73
59
  type: :runtime
74
60
  prerelease: false
75
61
  version_requirements: !ruby/object:Gem::Requirement
76
62
  requirements:
77
63
  - - ">="
78
64
  - !ruby/object:Gem::Version
79
- version: '6.1'
65
+ version: '1.22'
80
66
  - - "<"
81
67
  - !ruby/object:Gem::Version
82
- version: '6.2'
68
+ version: '3.0'
83
69
  - !ruby/object:Gem::Dependency
84
- name: appraisal
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '2.1'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '2.1'
97
- - !ruby/object:Gem::Dependency
98
- name: byebug
70
+ name: railties
99
71
  requirement: !ruby/object:Gem::Requirement
100
72
  requirements:
101
73
  - - ">="
102
74
  - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: pg
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '1.2'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '1.2'
125
- - !ruby/object:Gem::Dependency
126
- name: pry
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
75
+ version: '7.1'
76
+ - - "<"
130
77
  - !ruby/object:Gem::Version
131
- version: '0'
132
- type: :development
78
+ version: '8.1'
79
+ type: :runtime
133
80
  prerelease: false
134
81
  version_requirements: !ruby/object:Gem::Requirement
135
82
  requirements:
136
83
  - - ">="
137
84
  - !ruby/object:Gem::Version
138
- version: '0'
139
- - !ruby/object:Gem::Dependency
140
- name: rake
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: '13.0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: '13.0'
153
- - !ruby/object:Gem::Dependency
154
- name: rspec-mocks
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - "~>"
158
- - !ruby/object:Gem::Version
159
- version: '3.5'
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - "~>"
165
- - !ruby/object:Gem::Version
166
- version: '3.5'
167
- - !ruby/object:Gem::Dependency
168
- name: rspec-rails
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: '4.0'
174
- type: :development
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - "~>"
179
- - !ruby/object:Gem::Version
180
- version: '4.0'
181
- - !ruby/object:Gem::Dependency
182
- name: rubocop
183
- requirement: !ruby/object:Gem::Requirement
184
- requirements:
185
- - - "~>"
186
- - !ruby/object:Gem::Version
187
- version: '1.10'
188
- type: :development
189
- prerelease: false
190
- version_requirements: !ruby/object:Gem::Requirement
191
- requirements:
192
- - - "~>"
193
- - !ruby/object:Gem::Version
194
- version: '1.10'
195
- - !ruby/object:Gem::Dependency
196
- name: rubocop-rake
197
- requirement: !ruby/object:Gem::Requirement
198
- requirements:
199
- - - "~>"
200
- - !ruby/object:Gem::Version
201
- version: '0.5'
202
- type: :development
203
- prerelease: false
204
- version_requirements: !ruby/object:Gem::Requirement
205
- requirements:
206
- - - "~>"
207
- - !ruby/object:Gem::Version
208
- version: '0.5'
209
- - !ruby/object:Gem::Dependency
210
- name: rubocop-rspec
211
- requirement: !ruby/object:Gem::Requirement
212
- requirements:
213
- - - "~>"
214
- - !ruby/object:Gem::Version
215
- version: '2.2'
216
- type: :development
217
- prerelease: false
218
- version_requirements: !ruby/object:Gem::Requirement
219
- requirements:
220
- - - "~>"
221
- - !ruby/object:Gem::Version
222
- version: '2.2'
223
- - !ruby/object:Gem::Dependency
224
- name: simplecov
225
- requirement: !ruby/object:Gem::Requirement
226
- requirements:
227
- - - "~>"
228
- - !ruby/object:Gem::Version
229
- version: '0.15'
230
- type: :development
231
- prerelease: false
232
- version_requirements: !ruby/object:Gem::Requirement
233
- requirements:
234
- - - "~>"
85
+ version: '7.1'
86
+ - - "<"
235
87
  - !ruby/object:Gem::Version
236
- version: '0.15'
88
+ version: '8.1'
237
89
  description: Sharding
238
90
  email:
239
91
  - cody@instructure.com
@@ -242,8 +94,6 @@ extensions: []
242
94
  extra_rdoc_files: []
243
95
  files:
244
96
  - Rakefile
245
- - app/models/switchman/shard.rb
246
- - app/models/switchman/unsharded_record.rb
247
97
  - db/migrate/20130328212039_create_switchman_shards.rb
248
98
  - db/migrate/20130328224244_create_default_shard.rb
249
99
  - db/migrate/20161206323434_add_back_default_string_limits_switchman.rb
@@ -253,10 +103,11 @@ files:
253
103
  - lib/switchman.rb
254
104
  - lib/switchman/action_controller/caching.rb
255
105
  - lib/switchman/active_record/abstract_adapter.rb
256
- - lib/switchman/active_record/association.rb
106
+ - lib/switchman/active_record/associations.rb
257
107
  - lib/switchman/active_record/attribute_methods.rb
258
108
  - lib/switchman/active_record/base.rb
259
109
  - lib/switchman/active_record/calculations.rb
110
+ - lib/switchman/active_record/connection_handler.rb
260
111
  - lib/switchman/active_record/connection_pool.rb
261
112
  - lib/switchman/active_record/database_configurations.rb
262
113
  - lib/switchman/active_record/database_configurations/database_config.rb
@@ -264,6 +115,7 @@ files:
264
115
  - lib/switchman/active_record/log_subscriber.rb
265
116
  - lib/switchman/active_record/migration.rb
266
117
  - lib/switchman/active_record/model_schema.rb
118
+ - lib/switchman/active_record/pending_migration_connection.rb
267
119
  - lib/switchman/active_record/persistence.rb
268
120
  - lib/switchman/active_record/postgresql_adapter.rb
269
121
  - lib/switchman/active_record/predicate_builder.rb
@@ -275,6 +127,7 @@ files:
275
127
  - lib/switchman/active_record/statement_cache.rb
276
128
  - lib/switchman/active_record/table_definition.rb
277
129
  - lib/switchman/active_record/tasks/database_tasks.rb
130
+ - lib/switchman/active_record/test_fixtures.rb
278
131
  - lib/switchman/active_record/type_caster.rb
279
132
  - lib/switchman/active_support/cache.rb
280
133
  - lib/switchman/arel.rb
@@ -286,18 +139,23 @@ files:
286
139
  - lib/switchman/errors.rb
287
140
  - lib/switchman/guard_rail.rb
288
141
  - lib/switchman/guard_rail/relation.rb
289
- - lib/switchman/open4.rb
142
+ - lib/switchman/parallel.rb
290
143
  - lib/switchman/r_spec_helper.rb
291
144
  - lib/switchman/rails.rb
145
+ - lib/switchman/shard.rb
292
146
  - lib/switchman/sharded_instrumenter.rb
147
+ - lib/switchman/shared_schema_cache.rb
293
148
  - lib/switchman/standard_error.rb
294
149
  - lib/switchman/test_helper.rb
150
+ - lib/switchman/unsharded_record.rb
295
151
  - lib/switchman/version.rb
296
152
  - lib/tasks/switchman.rake
297
153
  homepage: http://www.instructure.com/
298
154
  licenses:
299
155
  - MIT
300
- metadata: {}
156
+ metadata:
157
+ rubygems_mfa_required: 'true'
158
+ source_code_uri: https://github.com/instructure/switchman
301
159
  post_install_message:
302
160
  rdoc_options: []
303
161
  require_paths:
@@ -306,14 +164,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
306
164
  requirements:
307
165
  - - ">="
308
166
  - !ruby/object:Gem::Version
309
- version: '2.6'
167
+ version: '3.2'
310
168
  required_rubygems_version: !ruby/object:Gem::Requirement
311
169
  requirements:
312
170
  - - ">="
313
171
  - !ruby/object:Gem::Version
314
172
  version: '0'
315
173
  requirements: []
316
- rubygems_version: 3.2.15
174
+ rubygems_version: 3.4.19
317
175
  signing_key:
318
176
  specification_version: 4
319
177
  summary: Rails sharding magic
@@ -1,206 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Switchman
4
- module ActiveRecord
5
- module Association
6
- def shard
7
- reflection.shard(owner)
8
- end
9
-
10
- def build_record(*args)
11
- shard.activate { super }
12
- end
13
-
14
- def load_target
15
- shard.activate { super }
16
- end
17
-
18
- def scope
19
- shard_value = @reflection.options[:multishard] ? @owner : shard
20
- @owner.shard.activate { super.shard(shard_value, :association) }
21
- end
22
- end
23
-
24
- module CollectionAssociation
25
- def find_target
26
- shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
27
- # activate both the owner and the target's shard category, so that Reflection#join_id_for,
28
- # when called for the owner, will be returned relative to shard the query will execute on
29
- Shard.with_each_shard(shards, [klass.connection_classes, owner.class.connection_classes].uniq) do
30
- super
31
- end
32
- end
33
-
34
- def _create_record(*)
35
- shard.activate { super }
36
- end
37
- end
38
-
39
- module BelongsToAssociation
40
- def replace_keys(record, force: false)
41
- if record&.class&.sharded_column?(reflection.association_primary_key(record.class))
42
- foreign_id = record[reflection.association_primary_key(record.class)]
43
- owner[reflection.foreign_key] = Shard.relative_id_for(foreign_id, record.shard, owner.shard)
44
- else
45
- super
46
- end
47
- end
48
-
49
- def shard
50
- if @owner.class.sharded_column?(@reflection.foreign_key) &&
51
- (foreign_id = @owner[@reflection.foreign_key])
52
- Shard.shard_for(foreign_id, @owner.shard)
53
- else
54
- super
55
- end
56
- end
57
- end
58
-
59
- module ForeignAssociation
60
- # significant change:
61
- # * transpose the key to the correct shard
62
- def set_owner_attributes(record) # rubocop:disable Naming/AccessorMethodName
63
- return if options[:through]
64
-
65
- key = owner._read_attribute(reflection.join_foreign_key)
66
- key = Shard.relative_id_for(key, owner.shard, shard)
67
- record._write_attribute(reflection.join_primary_key, key)
68
-
69
- record._write_attribute(reflection.type, owner.class.polymorphic_name) if reflection.type
70
- end
71
- end
72
-
73
- module Extension
74
- def self.build(_model, _reflection); end
75
-
76
- def self.valid_options
77
- [:multishard]
78
- end
79
- end
80
-
81
- ::ActiveRecord::Associations::Builder::Association.extensions << Extension
82
-
83
- module Preloader
84
- module Association
85
- # Copypasta from Activerecord but with added global_id_for goodness.
86
- def records_for(ids)
87
- scope.where(association_key_name => ids).load do |record|
88
- global_key = if record.class.connection_classes == UnshardedRecord
89
- convert_key(record[association_key_name])
90
- else
91
- Shard.global_id_for(record[association_key_name], record.shard)
92
- end
93
- owner = owners_by_key[convert_key(global_key)].first
94
- association = owner.association(reflection.name)
95
- association.set_inverse_instance(record)
96
- end
97
- end
98
-
99
- # significant changes:
100
- # * partition_by_shard the records_for call
101
- # * re-globalize the fetched owner id before looking up in the map
102
- def load_records
103
- # owners can be duplicated when a relation has a collection association join
104
- # #compare_by_identity makes such owners different hash keys
105
- @records_by_owner = {}.compare_by_identity
106
-
107
- if owner_keys.empty?
108
- raw_records = []
109
- else
110
- # determine the shard to search for each owner
111
- if reflection.macro == :belongs_to
112
- # for belongs_to, it's the shard of the foreign_key
113
- partition_proc = lambda do |owner|
114
- if owner.class.sharded_column?(owner_key_name)
115
- Shard.shard_for(owner[owner_key_name], owner.shard)
116
- else
117
- Shard.current
118
- end
119
- end
120
- elsif !reflection.options[:multishard]
121
- # for non-multishard associations, it's *just* the owner's shard
122
- partition_proc = ->(owner) { owner.shard }
123
- end
124
-
125
- raw_records = Shard.partition_by_shard(owners, partition_proc) do |partitioned_owners|
126
- relative_owner_keys = partitioned_owners.map do |owner|
127
- key = owner[owner_key_name]
128
- if key && owner.class.sharded_column?(owner_key_name)
129
- key = Shard.relative_id_for(key, owner.shard,
130
- Shard.current(klass.connection_classes))
131
- end
132
- convert_key(key)
133
- end
134
- relative_owner_keys.compact!
135
- relative_owner_keys.uniq!
136
- records_for(relative_owner_keys)
137
- end
138
- end
139
-
140
- @preloaded_records = raw_records.select do |record|
141
- assignments = false
142
-
143
- owner_key = record[association_key_name]
144
- if owner_key && record.class.sharded_column?(association_key_name)
145
- owner_key = Shard.global_id_for(owner_key,
146
- record.shard)
147
- end
148
-
149
- owners_by_key[convert_key(owner_key)].each do |owner|
150
- entries = (@records_by_owner[owner] ||= [])
151
-
152
- if reflection.collection? || entries.empty?
153
- entries << record
154
- assignments = true
155
- end
156
- end
157
-
158
- assignments
159
- end
160
- end
161
-
162
- # significant change: globalize keys on sharded columns
163
- def owners_by_key
164
- @owners_by_key ||= owners.each_with_object({}) do |owner, result|
165
- key = owner[owner_key_name]
166
- key = Shard.global_id_for(key, owner.shard) if key && owner.class.sharded_column?(owner_key_name)
167
- key = convert_key(key)
168
- (result[key] ||= []) << owner if key
169
- end
170
- end
171
-
172
- # significant change: don't cache scope (since it could be for different shards)
173
- def scope
174
- build_scope
175
- end
176
- end
177
- end
178
-
179
- module CollectionProxy
180
- def initialize(*args)
181
- super
182
- self.shard_value = scope.shard_value
183
- self.shard_source_value = :association
184
- end
185
-
186
- def shard(*args)
187
- scope.shard(*args)
188
- end
189
- end
190
-
191
- module AutosaveAssociation
192
- def record_changed?(reflection, record, key)
193
- record.new_record? ||
194
- (record.has_attribute?(reflection.foreign_key) && record.send(reflection.foreign_key) != key) || # have to use send instead of [] because sharding
195
- record.attribute_changed?(reflection.foreign_key)
196
- end
197
-
198
- def save_belongs_to_association(reflection)
199
- # this seems counter-intuitive, but the autosave code will assign to attribute bypassing switchman,
200
- # after reading the id attribute _without_ bypassing switchman. So we need Shard.current for the
201
- # category of the associated record to match Shard.current for the category of self
202
- shard.activate(connection_classes_for_reflection(reflection)) { super }
203
- end
204
- end
205
- end
206
- end
@@ -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