File tree Expand file tree Collapse file tree 6 files changed +99
-50
lines changed
lib/active_record/connection_adapters Expand file tree Collapse file tree 6 files changed +99
-50
lines changed Original file line number Diff line number Diff line change
1
+ * Properly synchronize ` Mysql2Adapter#active? ` and ` TrilogyAdapter#active? `
2
+
3
+ As well as ` disconnect! ` and ` verify! ` .
4
+
5
+ This generally isn't a big problem as connections must not be shared between
6
+ threads, but is required when running transactional tests or system tests
7
+ and could lead to a SEGV.
8
+
9
+ * Jean Boussier*
10
+
1
11
* Support ` :source_location ` tag option for query log tags
2
12
3
13
``` ruby
Original file line number Diff line number Diff line change @@ -718,13 +718,14 @@ def reconnect!(restore_transactions: false)
718
718
end
719
719
end
720
720
721
-
722
721
# Disconnects from the database if already connected. Otherwise, this
723
722
# method does nothing.
724
723
def disconnect!
725
- clear_cache! ( new_connection : true )
726
- reset_transaction
727
- @raw_connection_dirty = false
724
+ @lock . synchronize do
725
+ clear_cache! ( new_connection : true )
726
+ reset_transaction
727
+ @raw_connection_dirty = false
728
+ end
728
729
end
729
730
730
731
# Immediately forget this connection ever existed. Unlike disconnect!,
@@ -780,19 +781,17 @@ def requires_reloading?
780
781
# is no longer active, then this method will reconnect to the database.
781
782
def verify!
782
783
unless active?
783
- if @unconfigured_connection
784
- @lock . synchronize do
785
- if @unconfigured_connection
786
- @raw_connection = @unconfigured_connection
787
- @unconfigured_connection = nil
788
- configure_connection
789
- @verified = true
790
- return
791
- end
784
+ @lock . synchronize do
785
+ if @unconfigured_connection
786
+ @raw_connection = @unconfigured_connection
787
+ @unconfigured_connection = nil
788
+ configure_connection
789
+ @verified = true
790
+ return
792
791
end
793
- end
794
792
795
- reconnect! ( restore_transactions : true )
793
+ reconnect! ( restore_transactions : true )
794
+ end
796
795
end
797
796
798
797
@verified = true
Original file line number Diff line number Diff line change @@ -113,23 +113,27 @@ def connected?
113
113
end
114
114
115
115
def active?
116
- !! @ raw_connection &.ping
116
+ connected? && @lock . synchronize { @ raw_connection &.ping } || false
117
117
end
118
118
119
119
alias :reset! :reconnect!
120
120
121
121
# Disconnects from the database if already connected.
122
122
# Otherwise, this method does nothing.
123
123
def disconnect!
124
- super
125
- @raw_connection &.close
126
- @raw_connection = nil
124
+ @lock . synchronize do
125
+ super
126
+ @raw_connection &.close
127
+ @raw_connection = nil
128
+ end
127
129
end
128
130
129
131
def discard! # :nodoc:
130
- super
131
- @raw_connection &.automatic_close = false
132
- @raw_connection = nil
132
+ @lock . synchronize do
133
+ super
134
+ @raw_connection &.automatic_close = false
135
+ @raw_connection = nil
136
+ end
133
137
end
134
138
135
139
private
@@ -144,9 +148,11 @@ def connect
144
148
end
145
149
146
150
def reconnect
147
- @raw_connection &.close
148
- @raw_connection = nil
149
- connect
151
+ @lock . synchronize do
152
+ @raw_connection &.close
153
+ @raw_connection = nil
154
+ connect
155
+ end
150
156
end
151
157
152
158
def configure_connection
Original file line number Diff line number Diff line change @@ -117,26 +117,26 @@ def connected?
117
117
end
118
118
119
119
def active?
120
- connection &. ping || false
120
+ connected? && @lock . synchronize { @raw_connection &. ping } || false
121
121
rescue ::Trilogy ::Error
122
122
false
123
123
end
124
124
125
125
alias reset! reconnect!
126
126
127
127
def disconnect!
128
- super
129
- unless connection . nil?
130
- connection . close
131
- self . connection = nil
128
+ @lock . synchronize do
129
+ super
130
+ @raw_connection & .close
131
+ @raw_connection = nil
132
132
end
133
133
end
134
134
135
135
def discard!
136
- super
137
- unless connection . nil?
138
- connection . discard!
139
- self . connection = nil
136
+ @lock . synchronize do
137
+ super
138
+ @raw_connection & .discard!
139
+ @raw_connection = nil
140
140
end
141
141
end
142
142
@@ -166,23 +166,15 @@ def error_number(exception)
166
166
exception . error_code if exception . respond_to? ( :error_code )
167
167
end
168
168
169
- def connection
170
- @raw_connection
171
- end
172
-
173
- def connection = ( conn )
174
- @raw_connection = conn
175
- end
176
-
177
169
def connect
178
- self . connection = self . class . new_client ( @config )
170
+ @raw_connection = self . class . new_client ( @config )
179
171
rescue ConnectionNotEstablished => ex
180
172
raise ex . set_pool ( @pool )
181
173
end
182
174
183
175
def reconnect
184
- connection &.close
185
- self . connection = nil
176
+ @raw_connection &.close
177
+ @raw_connection = nil
186
178
connect
187
179
end
188
180
Original file line number Diff line number Diff line change @@ -809,6 +809,48 @@ def kill_connection_from_server(connection_id)
809
809
end
810
810
end
811
811
end
812
+
813
+ class AdapterThreadSafetyTest < ActiveRecord ::TestCase
814
+ setup do
815
+ @threads = [ ]
816
+ @connection = ActiveRecord ::Base . connection_pool . checkout
817
+ end
818
+
819
+ teardown do
820
+ @threads . each ( &:kill )
821
+ end
822
+
823
+ test "#active? is synchronized" do
824
+ threads ( 2 , 25 ) { @connection . select_all ( "SELECT 1" ) }
825
+ threads ( 2 , 25 ) { @connection . verify! }
826
+ threads ( 2 , 25 ) { @connection . disconnect! }
827
+
828
+ join
829
+ end
830
+
831
+ test "#verify! is synchronized" do
832
+ threads ( 2 , 25 ) { @connection . verify! }
833
+ threads ( 2 , 25 ) { @connection . disconnect! }
834
+
835
+ join
836
+ end
837
+
838
+ private
839
+ def join
840
+ @threads . shuffle . each ( &:join )
841
+ end
842
+
843
+ def threads ( count , times )
844
+ @threads += count . times . map do
845
+ Thread . new do
846
+ times . times do
847
+ yield
848
+ Thread . pass
849
+ end
850
+ end
851
+ end
852
+ end
853
+ end
812
854
end
813
855
814
856
if ActiveRecord ::Base . connection . supports_advisory_locks?
Original file line number Diff line number Diff line change @@ -95,24 +95,24 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase
95
95
end
96
96
97
97
test "#active? answers false with connection and exception" do
98
- @conn . send ( :connection ) . stub ( :ping , -> { raise ::Trilogy ::BaseError . new } ) do
98
+ @conn . instance_variable_get ( :@raw_connection ) . stub ( :ping , -> { raise ::Trilogy ::BaseError . new } ) do
99
99
assert_equal false , @conn . active?
100
100
end
101
101
end
102
102
103
103
test "#reconnect answers new connection with existing connection" do
104
- old_connection = @conn . send ( :connection )
104
+ old_connection = @conn . instance_variable_get ( :@raw_connection )
105
105
@conn . reconnect!
106
- connection = @conn . send ( :connection )
106
+ connection = @conn . instance_variable_get ( :@raw_connection )
107
107
108
108
assert_instance_of Trilogy , connection
109
109
assert_not_equal old_connection , connection
110
110
end
111
111
112
112
test "#reset answers new connection with existing connection" do
113
- old_connection = @conn . send ( :connection )
113
+ old_connection = @conn . instance_variable_get ( :@raw_connection )
114
114
@conn . reset!
115
- connection = @conn . send ( :connection )
115
+ connection = @conn . instance_variable_get ( :@raw_connection )
116
116
117
117
assert_instance_of Trilogy , connection
118
118
assert_not_equal old_connection , connection
You can’t perform that action at this time.
0 commit comments