Skip to content

Register C globals holding Ruby objects with the GC - #611

Merged
andyundso merged 1 commit into
rails-sqlserver:masterfrom
4shark:register-globals-with-gc
Oct 2, 2026
Merged

andyundso merged 1 commit into
rails-sqlserver:masterfrom
4shark:register-globals-with-gc

Conversation

@plribeiro3000

Copy link
Copy Markdown
Contributor

Summary

  • tiny_tds_ext.c, client.c and result.c keep TinyTds, TinyTds::Error, TinyTds::Client, TinyTds::Result, Kernel and Date in C globals that were never registered with the GC.
  • After compaction cTinyTdsError can point to a moved object, so the next FreeTDS message or error handled by rb_tinytds_raise_error crashes the process with a segmentation fault instead of raising TinyTds::Error.
  • Each of them is now registered with rb_global_variable, the same way opt_escape_regex, opt_escape_dblquote and the opt_* values in result.c already are.

Test

test/gc_compaction_test.rb runs the minimal repro from #608 in a subprocess: it forces compaction with GC.verify_compaction_references and connects to a closed port. It needs no SQL Server and is skipped where compaction is unsupported.

Verified locally on macOS, Ruby 4.0.6, FreeTDS 1.5.4:

  • without the patch: the subprocess dies with SIGSEGV (rb_exc_new -> unexpected_type)
  • with the patch: TinyTds::Error is raised and the test passes

bundle exec rake format leaves the C files unchanged.

Fixes #608

@aharpervc

Copy link
Copy Markdown
Contributor

ci failed,

GcCompactionTest::after GC compaction
  test_0001_raises TinyTds::Error instead of crashing when a connection fails FAIL (0.20s)
        expected TinyTds::Error to be raised, got #<Process::Status: pid 2511 exit 1>:
        <internal:gc>:230:in `verify_compaction_references': unknown keyword: :expand_heap (ArgumentError)
        	from -e:3:in `<main>'
        /home/runner/work/tiny_tds/tiny_tds/test/gc_compaction_test.rb:26:in `block (2 levels) in <class:GcCompactionTest>'

Does this need to be conditional for ruby 3.2+?

@plribeiro3000

Copy link
Copy Markdown
Contributor Author

Yes, good catch @aharpervc. expand_heap: only exists from Ruby 3.2 on; 3.0 and 3.1 call it double_heap:. The test now picks the keyword based on RUBY_VERSION (7ab12b9).

@plribeiro3000
plribeiro3000 force-pushed the register-globals-with-gc branch from 7ab12b9 to 2fa810b Compare September 30, 2026 22:27
Comment thread ext/tiny_tds/tiny_tds_ext.c Outdated
Comment on lines +11 to +14
mTinyTds = rb_define_module("TinyTds");
cTinyTdsError = rb_const_get(mTinyTds, rb_intern("Error"));
rb_global_variable(&mTinyTds);
rb_global_variable(&cTinyTdsError);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small thing here: can you call rb_global_variable directly after the assignment of mTinyTds?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, each rb_global_variable now comes right after the assignment it registers, so mTinyTds is registered before cTinyTdsError is looked up. This also matches how client.c and result.c already do it. Amended into df658bc.

The extension keeps TinyTds::Error, TinyTds, TinyTds::Client, TinyTds::Result, Kernel and Date in C globals that were never registered with the GC. After compaction cTinyTdsError could point to a moved object, so the next FreeTDS message or error handled by rb_tinytds_raise_error crashed the process with a segmentation fault instead of raising TinyTds::Error.

Register each of them with rb_global_variable, the same way opt_escape_regex and the opt_* values already are, and add a regression test that forces compaction before a failing connection.

Fixes rails-sqlserver#608
@plribeiro3000
plribeiro3000 force-pushed the register-globals-with-gc branch from 2fa810b to df658bc Compare October 1, 2026 20:43
@andyundso
andyundso merged commit 74a2232 into rails-sqlserver:master Oct 2, 2026
115 checks passed
@plribeiro3000
plribeiro3000 deleted the register-globals-with-gc branch October 5, 2026 10:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Segfault in connect under multithreaded use: cTinyTdsError/mTinyTds not registered with the Ruby GC

4 participants