aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorRyuta Kamizono <kamipo@gmail.com>2017-11-27 11:54:59 +0900
committerGitHub <noreply@github.com>2017-11-27 11:54:59 +0900
commit0e2cd3d749dadcdb5027d48399aea02ef74815f3 (patch)
treee3a8bbc17d2b33a0c373c7aa9343079f2f9999e9 /activerecord
parentad0630f0ae2d65bc0adda4097af8009a985b6f15 (diff)
downloadrails-0e2cd3d749dadcdb5027d48399aea02ef74815f3.tar.gz
rails-0e2cd3d749dadcdb5027d48399aea02ef74815f3.tar.bz2
rails-0e2cd3d749dadcdb5027d48399aea02ef74815f3.zip
Add new error class `QueryCanceled` which will be raised when canceling statement due to user request (#31235)
This changes `StatementTimeout` to `QueryCanceled` for PostgreSQL. In MySQL, errno 1317 (`ER_QUERY_INTERRUPTED`) is only used when the query is manually cancelled. But in PostgreSQL, `QUERY_CANCELED` error code (57014) which is used `StatementTimeout` is also used when the both case. And, we can not tell which reason happened. So I decided to introduce new error class `QueryCanceled` closer to the error code name.
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/transaction_test.rb27
-rw-r--r--activerecord/test/cases/adapters/postgresql/transaction_test.rb31
6 files changed, 69 insertions, 3 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index ccd2aa90e1..40a26d98e1 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Add new error class `QueryCanceled` which will be raised
+ when canceling statement due to user request.
+
+ *Ryuta Kamizono*
+
* Add `#up_only` to database migrations for code that is only relevant when
migrating up, e.g. populating a new column.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 77ccac54bf..ede8a9c1e2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -635,6 +635,7 @@ module ActiveRecord
ER_CANNOT_ADD_FOREIGN = 1215
ER_CANNOT_CREATE_TABLE = 1005
ER_LOCK_WAIT_TIMEOUT = 1205
+ ER_QUERY_INTERRUPTED = 1317
ER_QUERY_TIMEOUT = 3024
def translate_exception(exception, message)
@@ -663,6 +664,8 @@ module ActiveRecord
LockWaitTimeout.new(message)
when ER_QUERY_TIMEOUT
StatementTimeout.new(message)
+ when ER_QUERY_INTERRUPTED
+ QueryCanceled.new(message)
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 489feadb00..7b27f6b7a0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -420,7 +420,7 @@ module ActiveRecord
when LOCK_NOT_AVAILABLE
LockWaitTimeout.new(message)
when QUERY_CANCELED
- StatementTimeout.new(message)
+ QueryCanceled.new(message)
else
super
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 89e777ffc3..efcbd44776 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -343,6 +343,10 @@ module ActiveRecord
class StatementTimeout < StatementInvalid
end
+ # QueryCanceled will be raised when canceling statement due to user request.
+ class QueryCanceled < StatementInvalid
+ end
+
# UnknownAttributeReference is raised when an unknown and potentially unsafe
# value is passed to a query method when allow_unsafe_raw_sql is set to
# :disabled. For example, passing a non column name value to a relation's
diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
index da4b73a0da..cb183cc54c 100644
--- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
@@ -116,5 +116,32 @@ module ActiveRecord
end
end
end
+
+ test "raises QueryCanceled when canceling statement due to user request" do
+ assert_raises(ActiveRecord::QueryCanceled) do
+ s = Sample.create!(value: 1)
+ latch = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch.count_down
+ sleep(0.5)
+ conn = Sample.connection
+ pid = conn.query_value("SELECT id FROM information_schema.processlist WHERE info LIKE '% FOR UPDATE'")
+ conn.execute("KILL QUERY #{pid}")
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch.wait
+ Sample.lock.find(s.id)
+ end
+ ensure
+ thread.join
+ end
+ end
+ end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
index ebba0f0c1c..c24dfeb345 100644
--- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
@@ -120,8 +120,8 @@ module ActiveRecord
end
end
- test "raises StatementTimeout when statement timeout exceeded" do
- assert_raises(ActiveRecord::StatementTimeout) do
+ test "raises QueryCanceled when statement timeout exceeded" do
+ assert_raises(ActiveRecord::QueryCanceled) do
s = Sample.create!(value: 1)
latch1 = Concurrent::CountDownLatch.new
latch2 = Concurrent::CountDownLatch.new
@@ -148,6 +148,33 @@ module ActiveRecord
end
end
+ test "raises QueryCanceled when canceling statement due to user request" do
+ assert_raises(ActiveRecord::QueryCanceled) do
+ s = Sample.create!(value: 1)
+ latch = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch.count_down
+ sleep(0.5)
+ conn = Sample.connection
+ pid = conn.query_value("SELECT pid FROM pg_stat_activity WHERE query LIKE '% FOR UPDATE'")
+ conn.execute("SELECT pg_cancel_backend(#{pid})")
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch.wait
+ Sample.lock.find(s.id)
+ end
+ ensure
+ thread.join
+ end
+ end
+ end
+
private
def with_warning_suppression