Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/test_on_macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ jobs:
puts "duckdb library #{actual}"
'

# A UDF callback that strands the dispatcher wedges the VM with the GVL
# held, so the run never returns. Bound the step: the suite takes ~1 minute.
- name: test with Ruby ${{ matrix.ruby }}
timeout-minutes: 3
env:
MYSQL_TEST: 1
DYLD_LIBRARY_PATH: ${{ github.workspace }}/libduckdb-osx-universal
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test_on_ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ jobs:
puts "duckdb library #{actual}"
'

# A UDF callback that strands the dispatcher wedges the VM with the GVL
# held, so the run never returns. Bound the step: the suite takes ~1 minute.
- name: test with Ruby ${{ matrix.ruby }}
timeout-minutes: 3
env:
MYSQL_TEST: 1
run: |
Expand Down
28 changes: 9 additions & 19 deletions test/duckdb_test/function_error_message_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,15 @@ def register_scalar(name, &block)
end)
end

# Bounds a query that stops returning once the dispatcher is stranded, so a
# regression fails the run instead of blocking it forever.
def within_dispatch_timeout(timeout = 60, &block)
thread = Thread.new do
Thread.current.report_on_exception = false
block.call
end
return thread.value if thread.join(timeout)

thread.kill

flunk 'UDF callback dispatch is stranded'
end

# A regression here strands the dispatcher, and the queries below then never
# return. Nothing in Ruby can bound that: the stranded state holds the GVL,
# so Thread#join timeouts never fire and even SIGTERM is not delivered. The
# bound lives in CI instead, as timeout-minutes on the test step.
def test_scalar_error_message_with_a_null_byte_is_reported_to_duckdb
register_scalar('nul_boom') { |v| raise "bad token: \0#{v}" }

error = assert_raises(DuckDB::Error) do
within_dispatch_timeout { @con.query('SELECT nul_boom(a) FROM t') }
@con.query('SELECT nul_boom(a) FROM t')
end

assert_match(/bad token:/, error.message)
Expand All @@ -64,7 +54,7 @@ def test_scalar_error_message_that_cannot_be_read_is_reported_to_duckdb
register_scalar('unreadable_boom') { |_v| raise MessageRaises }

assert_raises(DuckDB::Error) do
within_dispatch_timeout { @con.query('SELECT unreadable_boom(a) FROM t') }
@con.query('SELECT unreadable_boom(a) FROM t')
end
end

Expand All @@ -82,7 +72,7 @@ def test_aggregate_error_message_with_a_null_byte_is_reported_to_duckdb
)

error = assert_raises(DuckDB::Error) do
within_dispatch_timeout { @con.query('SELECT nul_boom_agg(a) FROM t') }
@con.query('SELECT nul_boom_agg(a) FROM t')
end

assert_match(/bad token:/, error.message)
Expand All @@ -95,10 +85,10 @@ def test_udf_dispatch_survives_an_error_message_duckdb_cannot_be_given
# Deliberately loose: the reporting path is asserted above, and a regression
# there must not fail this test before it reaches the recovery check.
assert_raises(StandardError) do
within_dispatch_timeout { @con.query('SELECT nul_boom2(a) FROM t') }
@con.query('SELECT nul_boom2(a) FROM t')
end

result = within_dispatch_timeout { @con.query('SELECT echo(a) FROM t LIMIT 1').to_a }
result = @con.query('SELECT echo(a) FROM t LIMIT 1').to_a

assert_equal [['echo0']], result
end
Expand Down
Loading