@@ -51,7 +51,12 @@ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notif
51
51
result = if binds . nil? || binds . empty?
52
52
ActiveSupport ::Dependencies . interlock . permit_concurrent_loads do
53
53
result = raw_connection . query ( sql )
54
- @affected_rows_before_warnings = raw_connection . affected_rows
54
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
55
+ # As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
56
+ # from that same connection was GCed while `#query` released the GVL.
57
+ # By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
58
+ # of hitting the bug.
59
+ @affected_rows_before_warnings = result &.size || raw_connection . affected_rows
55
60
result
56
61
end
57
62
result
@@ -66,6 +71,12 @@ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notif
66
71
ActiveSupport ::Dependencies . interlock . permit_concurrent_loads do
67
72
result = stmt . execute ( *type_casted_binds )
68
73
@affected_rows_before_warnings = stmt . affected_rows
74
+
75
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
76
+ # by eagerly closing uncached prepared statements, we also reduce the chances of
77
+ # that bug happening. It can still happen if `#execute` is used as we have no callback
78
+ # to eagerly close the statement.
79
+ result . instance_variable_set ( :@_ar_stmt_to_close , stmt ) if result && !prepare
69
80
result
70
81
end
71
82
rescue ::Mysql2 ::Error
@@ -97,17 +108,34 @@ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notif
97
108
end
98
109
end
99
110
100
- def cast_result ( result )
101
- if result . nil? || result . fields . empty?
111
+ def cast_result ( raw_result )
112
+ return ActiveRecord ::Result . empty if raw_result . nil?
113
+
114
+ fields = raw_result . fields
115
+
116
+ result = if fields . empty?
102
117
ActiveRecord ::Result . empty
103
118
else
104
- ActiveRecord ::Result . new ( result . fields , result . to_a )
119
+ ActiveRecord ::Result . new ( fields , raw_result . to_a )
105
120
end
121
+
122
+ free_raw_result ( raw_result )
123
+
124
+ result
106
125
end
107
126
108
- def affected_rows ( result )
127
+ def affected_rows ( raw_result )
128
+ free_raw_result ( raw_result ) if raw_result
129
+
109
130
@affected_rows_before_warnings
110
131
end
132
+
133
+ def free_raw_result ( raw_result )
134
+ raw_result . free
135
+ if stmt = raw_result . instance_variable_get ( :@_ar_stmt_to_close )
136
+ stmt . close
137
+ end
138
+ end
111
139
end
112
140
end
113
141
end
0 commit comments