switchman 2.0.13 → 2.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de9d4ea9f0368bf1bfe15fdb8dd6bef040a8ec107aed386eaa81f8fa444513df
4
- data.tar.gz: 71b7905b40d59a4767576552c8f5d16a2b3bdaec6d910f81f18d803a9c916256
3
+ metadata.gz: 62fa2df72b999208e937ba7df468f122159e573d3e838cf86df601592e64fb0c
4
+ data.tar.gz: 20e83c56b8043a8d5e9fbc433db4b1a26a6ec7291389c59463a47ea2ed0b0b87
5
5
  SHA512:
6
- metadata.gz: bac07aa4f15521f123a1d95f676448d649c71eddd86fdfbbb57504f59d0c71f9ce69618aa7d1e25e93fdc278708cb2a7385d111417f23a8fc0eb1e9b6f6eefb3
7
- data.tar.gz: 1c091615da88fe2f54de51de1e740336007fd13d66d0cac0b977d0e2770a6dfacd6a85bd12b689a0c610b1de5daeab4512743f0c50199e0f610806cc2f4dd629
6
+ metadata.gz: 7c2cdc6f074ddb755b8a80dad67a2828a8f52d2c034f9ec7bdf473d2e2241cf21b80969cc3f6d0dcc33b6713f299ea976370800640a54839d0895cd41c29f01a
7
+ data.tar.gz: 68651c29f71fad57c1e6bbc02b35c6e607a98bc708045841b547c07de4f0ce63048d4a22b22c561d4692a728adc8740fd139ef4d88e5e777cf593e1de679ca9b
@@ -18,7 +18,7 @@ module Switchman
18
18
  :unsharded => [Shard]
19
19
  }
20
20
  private_constant :CATEGORIES
21
- @connection_specification_name = @shard_category = :unsharded
21
+ @connection_specification_name = 'unsharded'
22
22
 
23
23
  if defined?(::ProtectedAttributes)
24
24
  attr_accessible :default, :name, :database_server
@@ -283,17 +283,28 @@ module Switchman
283
283
 
284
284
  with_each_shard(subscope, categories, options) { yield }
285
285
  exception_pipe.last.close
286
- rescue => e
286
+ rescue Exception => e
287
287
  begin
288
288
  dumped = Marshal.dump(e)
289
+ dumped = nil if dumped.length > 64 * 1024
289
290
  rescue
291
+ dumped = nil
292
+ end
293
+
294
+ if dumped.nil?
290
295
  # couldn't dump the exception; create a copy with just
291
296
  # the message and the backtrace
292
297
  e2 = e.class.new(e.message)
293
- e2.set_backtrace(e.backtrace)
298
+ backtrace = e.backtrace
299
+ # truncate excessively long backtraces
300
+ if backtrace.length > 50
301
+ backtrace = backtrace[0...25] + ['...'] + backtrace[-25..-1]
302
+ end
303
+ e2.set_backtrace(backtrace)
294
304
  e2.instance_variable_set(:@active_shards, e.instance_variable_get(:@active_shards))
295
305
  dumped = Marshal.dump(e2)
296
306
  end
307
+
297
308
  exception_pipe.last.set_encoding(dumped.encoding)
298
309
  exception_pipe.last.write(dumped)
299
310
  exception_pipe.last.flush
@@ -38,11 +38,15 @@ module Switchman
38
38
  def define_method_global_attribute(attr_name)
39
39
  if sharded_column?(attr_name)
40
40
  generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
41
- def __temp__
41
+ def __temp_global_attribute__
42
+ raw_value = original_#{attr_name}
43
+ return nil if raw_value.nil?
44
+ return raw_value if raw_value > Shard::IDS_PER_SHARD
45
+
42
46
  Shard.global_id_for(original_#{attr_name}, shard)
43
47
  end
44
- alias_method 'global_#{attr_name}', :__temp__
45
- undef_method :__temp__
48
+ alias_method 'global_#{attr_name}', :__temp_global_attribute__
49
+ undef_method :__temp_global_attribute__
46
50
  RUBY
47
51
  else
48
52
  define_method_unsharded_column(attr_name, 'global')
@@ -52,11 +56,13 @@ module Switchman
52
56
  def define_method_local_attribute(attr_name)
53
57
  if sharded_column?(attr_name)
54
58
  generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
55
- def __temp__
56
- Shard.local_id_for(original_#{attr_name}).first
59
+ def __temp_local_attribute__
60
+ raw_value = original_#{attr_name}
61
+ return nil if raw_value.nil?
62
+ return raw_value % Shard::IDS_PER_SHARD
57
63
  end
58
- alias_method 'local_#{attr_name}', :__temp__
59
- undef_method :__temp__
64
+ alias_method 'local_#{attr_name}', :__temp_local_attribute__
65
+ undef_method :__temp_local_attribute__
60
66
  RUBY
61
67
  else
62
68
  define_method_unsharded_column(attr_name, 'local')
@@ -94,18 +100,33 @@ module Switchman
94
100
  # rename the original method to original_
95
101
  alias_method 'original_#{attr_name}', '#{attr_name}'
96
102
  # and replace with one that transposes the id
97
- def __temp__
98
- Shard.relative_id_for(original_#{attr_name}, shard, Shard.current(#{shard_category_code_for_reflection(reflection)}))
103
+ def __temp_relative_attribute__
104
+ raw_value = original_#{attr_name}
105
+ return nil if raw_value.nil?
106
+
107
+ abs_raw_value = raw_value.abs
108
+ current_shard = Shard.current(#{shard_category_code_for_reflection(reflection)})
109
+ same_shard = shard == current_shard
110
+ return raw_value if same_shard && abs_raw_value < Shard::IDS_PER_SHARD
111
+
112
+ value_shard_id = abs_raw_value / Shard::IDS_PER_SHARD
113
+ # this is a stupid case when someone stuffed a global id for the current shard in instead
114
+ # of a local id
115
+ return raw_value % Shard::IDS_PER_SHARD if value_shard_id == current_shard.id
116
+ return raw_value if !same_shard && abs_raw_value > Shard::IDS_PER_SHARD
117
+ return shard.global_id_for(raw_value) if !same_shard && abs_raw_value < Shard::IDS_PER_SHARD
118
+
119
+ Shard.relative_id_for(raw_value, shard, current_shard)
99
120
  end
100
- alias_method '#{attr_name}', :__temp__
101
- undef_method :__temp__
121
+ alias_method '#{attr_name}', :__temp_relative_attribute__
122
+ undef_method :__temp_relative_attribute__
102
123
 
103
124
  alias_method 'original_#{attr_name}=', '#{attr_name}='
104
- def __temp__(new_value)
125
+ def __temp_relative_attribute_assignment__(new_value)
105
126
  self.original_#{attr_name} = Shard.relative_id_for(new_value, Shard.current(#{shard_category_code_for_reflection(reflection)}), shard)
106
127
  end
107
- alias_method '#{attr_name}=', :__temp__
108
- undef_method :__temp__
128
+ alias_method '#{attr_name}=', :__temp_relative_attribute_assignment__
129
+ undef_method :__temp_relative_attribute_assignment__
109
130
  RUBY
110
131
  else
111
132
  define_method_unsharded_column(attr_name, 'global')
@@ -115,11 +136,11 @@ module Switchman
115
136
  def define_method_unsharded_column(attr_name, prefix)
116
137
  return if columns_hash["#{prefix}_#{attr_name}"]
117
138
  generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
118
- def __temp__
139
+ def __temp_unsharded_attribute__
119
140
  raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
120
141
  end
121
- alias_method '#{prefix}_#{attr_name}', :__temp__
122
- undef_method :__temp__
142
+ alias_method '#{prefix}_#{attr_name}', :__temp_unsharded_attribute__
143
+ undef_method :__temp_unsharded_attribute__
123
144
  RUBY
124
145
  end
125
146
  end
@@ -112,8 +112,20 @@ module Switchman
112
112
  end
113
113
 
114
114
  def remove_connection(spec_name)
115
+ # also remove pools based on the same spec name that are for shard category purposes
116
+ # can't just use delete_if, because it's a Concurrent::Map, not a Hash
117
+ owner_to_pool.keys.each do |k|
118
+ next if k == spec_name
119
+
120
+ v = owner_to_pool[k]
121
+ owner_to_pool.delete(k) if v.is_a?(ConnectionPoolProxy) && v.default_pool.spec.name == spec_name
122
+ end
123
+
124
+ # unwrap the pool from inside a ConnectionPoolProxy
115
125
  pool = owner_to_pool[spec_name]
116
126
  owner_to_pool[spec_name] = pool.default_pool if pool.is_a?(ConnectionPoolProxy)
127
+
128
+ # now let Rails do its thing with the data type it expects
117
129
  super
118
130
  end
119
131
 
@@ -128,15 +140,21 @@ module Switchman
128
140
  else
129
141
  ancestor_pool.spec
130
142
  end
131
- pool = establish_connection(spec.to_hash)
132
- pool.instance_variable_set(:@schema_cache, ancestor_pool.schema_cache) if ancestor_pool.schema_cache
133
- pool
134
- elsif spec_name != "primary"
143
+ # avoid copying "duplicate" pools that implement shard categories.
144
+ # they'll have a spec.name of primary, but a spec_name of something else, like unsharded
145
+ if spec.name == spec_name
146
+ pool = establish_connection(spec.to_hash)
147
+ pool.instance_variable_set(:@schema_cache, ancestor_pool.schema_cache) if ancestor_pool.schema_cache
148
+ next pool
149
+ end
150
+ end
151
+
152
+ if spec_name != "primary"
135
153
  primary_pool = retrieve_connection_pool("primary")
136
154
  if primary_pool.is_a?(ConnectionPoolProxy)
137
155
  pool = ConnectionPoolProxy.new(spec_name.to_sym, primary_pool.default_pool, @shard_connection_pools)
138
156
  pool.schema_cache.copy_references(primary_pool.schema_cache)
139
- pool
157
+ owner_to_pool[spec_name] = pool
140
158
  else
141
159
  primary_pool
142
160
  end
@@ -28,15 +28,26 @@ module Switchman
28
28
  # this technically belongs on AssociationReflection, but we put it on
29
29
  # ThroughReflection as well, instead of delegating to its internal
30
30
  # HasManyAssociation, losing its proper `klass`
31
- def association_scope_cache(conn, owner, &block)
32
- key = conn.prepared_statements
33
- if polymorphic?
34
- key = [key, owner._read_attribute(@foreign_type)]
31
+ if ::Rails.version < '6.0.4'
32
+ def association_scope_cache(conn, owner, &block)
33
+ key = conn.prepared_statements
34
+ if polymorphic?
35
+ key = [key, owner._read_attribute(@foreign_type)]
36
+ end
37
+ key = [key, shard(owner).id].flatten
38
+ @association_scope_cache[key] ||= @scope_lock.synchronize {
39
+ @association_scope_cache[key] ||= (::Rails.version >= "5.2" ? ::ActiveRecord::StatementCache.create(conn, &block) : block.call)
40
+ }
41
+ end
42
+ else
43
+ def association_scope_cache(klass, owner, &block)
44
+ key = self
45
+ if polymorphic?
46
+ key = [key, owner._read_attribute(@foreign_type)]
47
+ end
48
+ key = [key, shard(owner).id].flatten
49
+ klass.cached_find_by_statement(key, &block)
35
50
  end
36
- key = [key, shard(owner).id].flatten
37
- @association_scope_cache[key] ||= @scope_lock.synchronize {
38
- @association_scope_cache[key] ||= (::Rails.version >= "5.2" ? ::ActiveRecord::StatementCache.create(conn, &block) : block.call)
39
- }
40
51
  end
41
52
  end
42
53
 
@@ -154,7 +154,7 @@ module Switchman
154
154
  end
155
155
  end
156
156
  spec = ::ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(
157
- category,
157
+ default_pool.spec.name,
158
158
  config,
159
159
  "#{config[:adapter]}_connection"
160
160
  )
@@ -17,13 +17,7 @@ module Switchman
17
17
  Shard.default(reload: true)
18
18
  end
19
19
 
20
- # can't auto-create a new shard on the default shard's db server if the
21
- # default shard is split across multiple db servers
22
- if ::ActiveRecord::Base.connection_handler.connection_pool_list.length > 1
23
- server1 = DatabaseServer.create(Shard.default.database_server.config)
24
- else
25
- server1 = Shard.default.database_server
26
- end
20
+ server1 = Shard.default.database_server
27
21
  server2 = DatabaseServer.create(Shard.default.database_server.config.merge(server2: true))
28
22
 
29
23
  if server1 == Shard.default.database_server && server1.config[:shard1] && server1.config[:shard2]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Switchman
4
- VERSION = "2.0.13"
4
+ VERSION = "2.1.3"
5
5
  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: 2.0.13
4
+ version: 2.1.3
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-06-11 00:00:00.000000000 Z
13
+ date: 2021-07-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: railties
@@ -272,7 +272,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
272
272
  - !ruby/object:Gem::Version
273
273
  version: '0'
274
274
  requirements: []
275
- rubygems_version: 3.2.15
275
+ rubygems_version: 3.2.24
276
276
  signing_key:
277
277
  specification_version: 4
278
278
  summary: Rails sharding magic