Skip to content

Commit 7d19e70

Browse files
committed
Merge pull request #83 from calabash/fix-more-stable-write_request
Attempt recovery from interrupted syscalls
2 parents 1510c24 + 793862b commit 7d19e70

File tree

2 files changed

+55
-4
lines changed

2 files changed

+55
-4
lines changed

lib/run_loop/core.rb

+54-3
Original file line numberDiff line numberDiff line change
@@ -721,21 +721,64 @@ def self.run(options={})
721721
Core.run_with_options(options)
722722
end
723723

724-
def self.send_command(run_loop, cmd, timeout=60)
724+
def self.send_command(run_loop, cmd, options={timeout: 60}, num_retries=0, last_error=nil)
725+
if num_retries > 3
726+
if last_error
727+
raise last_error
728+
else
729+
raise "Max retries exceeded #{num_retries} > 3. No error recorded."
730+
end
731+
end
732+
733+
if options.is_a?(Numeric)
734+
options = {timeout: options}
735+
end
725736

726737
if not cmd.is_a?(String)
727738
raise "Illegal command #{cmd} (must be a string)"
728739
end
729740

741+
if not options.is_a?(Hash)
742+
raise "Illegal options #{options} (must be a Hash (or number for compatibility))"
743+
end
744+
745+
timeout = options[:timeout] || 60
746+
logger = options[:logger]
747+
interrupt_retry_timeout = options[:interrupt_retry_timeout] || 25
730748

731-
expected_index = Core.write_request(run_loop, cmd)
749+
expected_index = run_loop[:index]
732750
result = nil
751+
begin
752+
expected_index = Core.write_request(run_loop, cmd)
753+
rescue Errno::EINTR => intr_error
754+
# Attempt recover from interrupt by attempting to read result (assuming write went OK)
755+
# or retry if attempted read result fails
756+
run_loop[:index] = expected_index # restore expected index in case it changed
757+
log_info(logger, "Core.write_request was interrupted: #{intr_error}. Attempting recovery...")
758+
sleep(1) # Arbitrary wait in hope that the system condition causing the interrupt passes
759+
if intr_error && intr_error.backtrace
760+
log_info(logger, "backtrace: #{intr_error.backtrace.join("\n")}")
761+
end
762+
log_info(logger, "Attempting read in case the request was received... Please wait (#{interrupt_retry_timeout})...")
763+
begin
764+
Timeout::timeout(interrupt_retry_timeout, TimeoutError) do
765+
result = Core.read_response(run_loop, expected_index)
766+
end
767+
# Update run_loop expected index since we succeeded in reading the index
768+
run_loop[:index] = expected_index + 1
769+
log_info(logger, "Did read response for interrupted request of index #{expected_index}... Proceeding.")
770+
return result
771+
rescue TimeoutError => _
772+
log_info(logger, "Read did not result in a response for index #{expected_index}... Retrying send_command...")
773+
return send_command(run_loop, cmd, options, num_retries+1, intr_error)
774+
end
775+
end
776+
733777

734778
begin
735779
Timeout::timeout(timeout, TimeoutError) do
736780
result = Core.read_response(run_loop, expected_index)
737781
end
738-
739782
rescue TimeoutError => _
740783
raise TimeoutError, "Time out waiting for UIAutomation run-loop for command #{cmd}. Waiting for index:#{expected_index}"
741784
end
@@ -776,4 +819,12 @@ def self.validate_script(script)
776819
script
777820
end
778821

822+
def self.log_info(device_logger, message)
823+
msg = "#{Time.now}: #{message}"
824+
if device_logger && device_logger.respond_to?(:info)
825+
logger.info(msg)
826+
else
827+
puts msg if ENV['DEBUG'] == '1'
828+
end
829+
end
779830
end

lib/run_loop/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module RunLoop
2-
VERSION = '1.1.1.pre6'
2+
VERSION = '1.1.1.pre7'
33

44
# A model of a software release version that can be used to compare two versions.
55
#

0 commit comments

Comments
 (0)