From 9fa9c308ea896e9450e9b831a7e1df72d5e1d336 Mon Sep 17 00:00:00 2001 From: gmodarelli Date: Tue, 8 May 2018 14:13:29 +0000 Subject: [PATCH 1/2] Handle read_timeout for queries executed within EventMachine --- lib/mysql2/em.rb | 8 ++++++++ spec/em/em_spec.rb | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/mysql2/em.rb b/lib/mysql2/em.rb index 329b3080e..a1f60040f 100644 --- a/lib/mysql2/em.rb +++ b/lib/mysql2/em.rb @@ -3,6 +3,8 @@ module Mysql2 module EM + class ReadTimeout < ::RuntimeError; end + class Client < ::Mysql2::Client module Watcher def initialize(client, deferable) @@ -41,6 +43,12 @@ def query(sql, opts = {}) if ::EM.reactor_running? super(sql, opts.merge(async: true)) deferable = ::EM::DefaultDeferrable.new + if @read_timeout + deferable.timeout(@read_timeout, Mysql2::EM::ReadTimeout.new) + deferable.errback do |error| + raise error if error.is_a?(Mysql2::EM::ReadTimeout) + end + end @watch = ::EM.watch(socket, Watcher, self, deferable) @watch.notify_readable = true deferable diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index c57d474dd..07f6f8c6b 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -65,6 +65,21 @@ end.to raise_error('some error') end + it "should timeout if we wait longer than :read_timeout" do + expect do + EM.run do + client = Mysql2::EM::Client.new DatabaseCredentials['root'].merge(read_timeout: 1) + defer = client.query "SELECT sleep(2)" + defer.callback do + # This _shouldn't_ be run, but it needed to prevent the specs from + # freezing if this test fails. + client.close + EM.stop_event_loop + end + end + end.to raise_error(Mysql2::EM::ReadTimeout) + end + context 'when an exception is raised by the client' do let(:client) { Mysql2::EM::Client.new DatabaseCredentials['root'] } let(:error) { StandardError.new('some error') } From 011ee54b8d21893b043bea6603f5394f2fdb3185 Mon Sep 17 00:00:00 2001 From: gmodarelli Date: Thu, 5 Jul 2018 07:19:03 +0000 Subject: [PATCH 2/2] Use defer.errback instead of raising exceptions to prevent EM from crashing --- lib/mysql2/em.rb | 3 --- spec/em/em_spec.rb | 25 ++++++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/mysql2/em.rb b/lib/mysql2/em.rb index a1f60040f..15cf10d5d 100644 --- a/lib/mysql2/em.rb +++ b/lib/mysql2/em.rb @@ -45,9 +45,6 @@ def query(sql, opts = {}) deferable = ::EM::DefaultDeferrable.new if @read_timeout deferable.timeout(@read_timeout, Mysql2::EM::ReadTimeout.new) - deferable.errback do |error| - raise error if error.is_a?(Mysql2::EM::ReadTimeout) - end end @watch = ::EM.watch(socket, Watcher, self, deferable) @watch.notify_readable = true diff --git a/spec/em/em_spec.rb b/spec/em/em_spec.rb index 07f6f8c6b..8338f7d79 100644 --- a/spec/em/em_spec.rb +++ b/spec/em/em_spec.rb @@ -66,18 +66,21 @@ end it "should timeout if we wait longer than :read_timeout" do - expect do - EM.run do - client = Mysql2::EM::Client.new DatabaseCredentials['root'].merge(read_timeout: 1) - defer = client.query "SELECT sleep(2)" - defer.callback do - # This _shouldn't_ be run, but it needed to prevent the specs from - # freezing if this test fails. - client.close - EM.stop_event_loop - end + errors = [] + EM.run do + client = Mysql2::EM::Client.new DatabaseCredentials['root'].merge(read_timeout: 1) + defer = client.query "SELECT sleep(2)" + defer.callback do + # This _shouldn't_ be run, but it needed to prevent the specs from + # freezing if this test fails. + EM.stop_event_loop end - end.to raise_error(Mysql2::EM::ReadTimeout) + defer.errback do |err| + errors << err + EM.stop_event_loop + end + end + expect(errors).to eq([Mysql2::EM::ReadTimeout.new]) end context 'when an exception is raised by the client' do