Skip to content

Commit c8c346d

Browse files
felixbuenemannsodabrew
authored andcommitted
Fix fragile specs (#1041)
* Use monotonic time if possible to guard against clock skew * Relax time comparisons to make test failures under load less likely * Fix memory corruption segfaults on macOS due to mixing Threads and Timeout Without the timeout thread safety fix, the specs always crash in the "threaded queries should be supported" spec for various reasons on macOS 10.14.4 with ruby 2.6.1 / MySQL 8.0.15. Some observed errors: malloc: *** error for object 0x7fcbba90ef80: pointer being freed was not allocated malloc: *** set a breakpoint in malloc_error_break to debug lib/mysql2/client.rb:131:in `_query': Bad file descriptor (Errno::EBADF) Now specs run fine, even during Prime95 load test on i9-8950HK (6c/12t).
1 parent d7c91ff commit c8c346d

File tree

2 files changed

+23
-9
lines changed

2 files changed

+23
-9
lines changed

spec/mysql2/client_spec.rb

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -607,29 +607,29 @@ def run_gc
607607
# XXX this test is not deterministic (because Unix signal handling is not)
608608
# and may fail on a loaded system
609609
it "should run signal handlers while waiting for a response" do
610-
kill_time = 0.1
611-
query_time = 2 * kill_time
610+
kill_time = 0.25
611+
query_time = 4 * kill_time
612612

613613
mark = {}
614614

615615
begin
616-
trap(:USR1) { mark.store(:USR1, Time.now) }
616+
trap(:USR1) { mark.store(:USR1, clock_time) }
617617
pid = fork do
618618
sleep kill_time # wait for client query to start
619619
Process.kill(:USR1, Process.ppid)
620620
sleep # wait for explicit kill to prevent GC disconnect
621621
end
622-
mark.store(:QUERY_START, Time.now)
622+
mark.store(:QUERY_START, clock_time)
623623
@client.query("SELECT SLEEP(#{query_time})")
624-
mark.store(:QUERY_END, Time.now)
624+
mark.store(:QUERY_END, clock_time)
625625
ensure
626626
Process.kill(:TERM, pid)
627627
Process.waitpid2(pid)
628628
trap(:USR1, 'DEFAULT')
629629
end
630630

631631
# the query ran uninterrupted
632-
expect(mark.fetch(:QUERY_END) - mark.fetch(:QUERY_START)).to be_within(0.02).of(query_time)
632+
expect(mark.fetch(:QUERY_END) - mark.fetch(:QUERY_START)).to be_within(0.1).of(query_time)
633633
# signals fired while the query was running
634634
expect(mark.fetch(:USR1)).to be_between(mark.fetch(:QUERY_START), mark.fetch(:QUERY_END))
635635
end
@@ -694,6 +694,7 @@ def run_gc
694694
sleep_time = 0.5
695695

696696
# Note that each thread opens its own database connection
697+
start = clock_time
697698
threads = Array.new(5) do
698699
Thread.new do
699700
new_client do |client|
@@ -702,10 +703,12 @@ def run_gc
702703
Thread.current.object_id
703704
end
704705
end
706+
values = threads.map(&:value)
707+
stop = clock_time
705708

706-
# This timeout demonstrates that the threads are sleeping concurrently:
707-
# In the serial case, the timeout would fire and the test would fail
708-
values = Timeout.timeout(sleep_time * 1.1) { threads.map(&:value) }
709+
# This check demonstrates that the threads are sleeping concurrently:
710+
# In the serial case, the difference would be a multiple of sleep time
711+
expect(stop - start).to be_within(0.1).of(sleep_time)
709712

710713
expect(values).to match_array(threads.map(&:object_id))
711714
end

spec/spec_helper.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ def num_classes
4040
# rubocop:enable Lint/UnifiedInteger
4141
end
4242

43+
# Use monotonic time if possible (ruby >= 2.1.0)
44+
if defined?(Process::CLOCK_MONOTONIC)
45+
def clock_time
46+
Process.clock_gettime Process::CLOCK_MONOTONIC
47+
end
48+
else
49+
def clock_time
50+
Time.now.to_f
51+
end
52+
end
53+
4354
config.before :each do
4455
@client = new_client
4556
end

0 commit comments

Comments
 (0)