Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## (unreleased)

* Do not use `dbcancel` as the `rb_thread_call_without_gvl` unblock function. An interrupt (for example SIGCHLD) during `dbsqlok`/`dbresults`/`dbnextrow` aborted the batch and returned empty results. Client `:timeout` is unchanged.

## 3.4.0

* Add Ruby 4.0 to the cross compile list
Expand Down
14 changes: 6 additions & 8 deletions ext/tiny_tds/result.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,17 @@ VALUE rb_tinytds_new_result_obj(tinytds_client_wrapper *cwrap)

// No GVL Helpers

/* Do not pass dbcancel as the unblock function. MRI invokes the UBF
whenever the waiting thread has a pending interrupt, including
process-directed signals such as SIGCHLD delivered to main.
dbcancel then aborts the SQL batch and Result#each treats FAIL as
an empty success. Client :timeout still uses dbsetinterrupt. */
#define NOGVL_DBCALL(_dbfunction, _client) ( \
(RETCODE)(intptr_t)rb_thread_call_without_gvl( \
(void *(*)(void *))_dbfunction, _client, \
(rb_unblock_function_t*)dbcancel_ubf, _client ) \
NULL, NULL ) \
)

static void dbcancel_ubf(DBPROCESS *client)
{
GET_CLIENT_USERDATA(client);
dbcancel(client);
userdata->dbcancel_sent = 1;
}

static void nogvl_setup(DBPROCESS *client)
{
GET_CLIENT_USERDATA(client);
Expand Down
18 changes: 18 additions & 0 deletions test/client_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "test_helper"
require "rbconfig"

class ClientTest < TinyTds::TestCase
describe "with valid credentials" do
Expand Down Expand Up @@ -95,6 +96,23 @@ class ClientTest < TinyTds::TestCase
assert_new_connections_work
end

it "does not cancel a query when another thread reaps a child process" do
skip if sqlserver_azure?
client = new_connection timeout: 15
reaper = Thread.new do
sleep 0.5
5.times do
Process.wait(Process.spawn(RbConfig.ruby, "-e", "nil"))
sleep 0.2
end
end
rows = client.execute("WaitFor Delay '00:00:02'; SELECT 42 AS [n]").each
reaper.join
assert_equal 1, rows.length
assert_equal 42, rows.first["n"]
close_client(client)
end

it "raises TinyTds exception with long query past :timeout option" do
client = new_connection timeout: 1
action = lambda { client.execute("WaitFor Delay '00:00:02'").do }
Expand Down
Loading