-
Notifications
You must be signed in to change notification settings - Fork 189
v4: Refactor tiny_tds
to avoid sharing DBPROCESS
#595
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR removes lazy-loading functionality from tiny_tds
by refactoring the execute
, do
, and insert
methods to be part of the Client
class instead of the Result
class. The Result
class is now a pure Ruby object (PORO) that holds pre-computed results data, addressing several critical issues including thread safety problems, garbage collection segfaults, and connection state management issues.
- Moves query execution methods (
execute
,do
,insert
) fromResult
toClient
class - Removes all C code from the
Result
class, making it a pure Ruby object - Changes
execute
method to use keyword arguments instead of options hash
Reviewed Changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
File | Description |
---|---|
test/thread_test.rb | Updates test calls to use new do method instead of execute().do pattern |
test/test_helper.rb | Converts helper method calls from chained pattern to direct do /execute calls |
test/schema_test.rb | Updates test to use new insert method on client |
test/result_test.rb | Major refactor removing lazy-loading tests and updating to new eager-loading API |
test/client_test.rb | Adds new tests for insert method and updates timeout tests |
lib/tiny_tds/result.rb | Simplified to pure Ruby class with just attr_readers and enumerable interface |
lib/tiny_tds/client.rb | Removes query_options instance variable as execution is now handled in C |
ext/tiny_tds/tiny_tds_ext.h | Removes result.h include as Result class no longer has C implementation |
ext/tiny_tds/tiny_tds_ext.c | Removes result initialization call |
ext/tiny_tds/result.h | Empty file - C Result implementation removed |
ext/tiny_tds/result.c | Empty file - C Result implementation removed |
ext/tiny_tds/client.c | Major refactor adding result processing, value casting, and new method implementations |
README.md | Updates documentation to reflect new API and removal of lazy-loading |
CHANGELOG.md | Documents breaking changes in new version |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
_(@client.sqlsent?).must_equal false | ||
_(@client.canceled?).must_equal true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The client state assertions are inconsistent with the new eager-loading behavior. After execute
completes, both sqlsent?
and canceled?
should be false
since the query is fully processed, but line 149 expects canceled?
to be true
.
Copilot uses AI. Check for mistakes.
_(@client.sqlsent?).must_equal false | ||
_(@client.canceled?).must_equal true | ||
assert result.cancel, "must be safe to call again" | ||
# With each and no block. | ||
@client.execute(@query1).each | ||
_(@client.sqlsent?).must_equal false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After each
completes with the new eager-loading implementation, canceled?
should be true
(as results are automatically canceled after processing), but sqlsent?
should be false
. The assertion on line 158 incorrectly expects sqlsent?
to be false
when it should remain consistent with the canceled state.
_(@client.sqlsent?).must_equal false | |
_(@client.sqlsent?).must_equal true |
Copilot uses AI. Check for mistakes.
ext/tiny_tds/client.c
Outdated
#ifdef _WIN32 | ||
#define LONG_LONG_FORMAT "I64d" | ||
#else | ||
#define LONG_LONG_FORMAT "lld" | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the standard PRId64 macro from inttypes.h instead of platform-specific format strings for better portability and maintainability.
Copilot uses AI. Check for mistakes.
tiny_tds
to avoid sharing DBPROCESS
tiny_tds
to avoid sharing DBPROCESS
Background
In order for
tiny_tds
to communicate with a MSSQL server usingFreeTDS
, they provide aDBPROCESS
struct to do so in C land. The interaction withDBPROCESS
require to follow an exact sequence:dbopen
.dbcmd
.dbsqlsend
.dbsqlok
.dbresults
.dbnextrow
or cancel the running results (dbcancel
)dbclose
if not cancelled.Our
insert
anddo
method, currently implemented on theResult
class, perform the entire sequence. However, withexecute
, this is intentionally not done to allow lazy-loading of results from the server. This can lead to errors, some intended, others not:You get an error message if you try to make another query without requesting all results first (intended error). Although it can have unintended side-effects. For example, if you call
find
onResult
, it will abort theeach
early, therefore not all results are consumed and you cannot start a new query - you have to initialise a newClient
.There is a scenario with threads where you force a crash, see the following Ruby code:
This will result in the following crash:
DBPROCESS
as well as some metadata of ours (likedbsqlsent
) is part of the client instance in C land. If the garbage collector decided to sweep away theClient
instance, the results can no longer be consumed. A reproduction of this is provided in Segementation Fault when reading from a closed connection #435.This will yield a segmentation fault, since the
Client
instance is unreachable from the point of view of the garbage collector, and it gets deallocated.DBPROCESS
as well as the metadata. See Segementation Fault when reading from a closed connection #435 for the reproduction script.Proposed solution
The proposed solution in this PR removes the lazy-loading functionality of
tiny_tds
.insert
,do
andexecute
are moved to theClient
class. The C code for theResult
class is removed entirely, thus leading theClient
class to have sole control over all C data structures.Result
is now a PORO holding the results rows as well as couple of metadata, likefields
.Point 2 from the list above is only partially solved - you won't get the coredump anymore, but still a
DBPROCESS dead
alert by FreeTDS. My proposal in #563 also addressed this by just cancelling the running query. But now, since we no longer have the sharing ofDBPROCESS
, I think we can add a check and raise an exception if a query is already in progress (sqlsent == 1
). Realistically, this could only happen if you do some funny stuff with Threads as I did in point 2.