-
Notifications
You must be signed in to change notification settings - Fork 188
Report stored procedure errors when using info messages #475
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
Report stored procedure errors when using info messages #475
Conversation
This looks really good, and I also see that CI has passed, which is a good sign. I think this can probably be merged after 2.1.3 is released. |
Idea: can something be done to flush the error array, and if so, would that address this bug: #445 |
I can't think of a way to adjust this in a way that would ultimately solve the timeout issue. See my commit message here: master...bvogelzang:network-timeout-handling. In cases of network failure I think the problem is:
To really fix that issue we'd need some way to detect the difference between a timeout error on the happy path (with working network) vs. the bad path (with network failure). Otherwise we can't branch accordingly. It would be nice if we could detect if |
5b1964a
to
aa66cdc
Compare
308a5c5
to
7338020
Compare
@metaskills, @larskanis, @coderjoe, @wpolicarpo any chance for getting a review on this PR, #483, and #481 |
7338020
to
72b49f9
Compare
These changes are now available to test in 2.1.4.pre |
Never mind, this must have been an issue on my own system. Verifying that I'm running 2.1.4.pre from rubygems, the example script does work now. That's with freetds v1.1.24 and v1.00.112. I suspect that there might be an older version of freetds that has a problem, but given that recent versions work this PR seems okay to merge to me. |
While testing #481 I noticed that only
example script (basically the same as the other PR): require 'tiny_tds'
def w(*args)
puts [Thread.current.object_id, *args].join ' '
end
t = Thread.new do
i = 0
loop do
i += 1
w i
sleep(1)
end
end
c = TinyTds::Client.new({
host: 'localhost',
username: 'sa',
password: 'Testing123@@',
timeout: 5,
login_timeout: 5,
message_handler: ->(e) { w e },
})
w TinyTds::VERSION
sql = <<~SQL
create or alter procedure tinytds_TestPrintWithError
as
begin
print 'print 1' -- nope
raiserror('raiserror 1', 1, 1) -- nope
-- raiserror('raiserror 2', 1, 1) with nowait -- yep, and uncommenting this will show all 3 messages
waitfor delay '00:01:00'
end
SQL
w c.execute(sql).to_a
w c.execute('exec tinytds_TestPrintWithError').to_a
t.exit
uncommented:
|
I'm not exactly sure. This really just comes down to the order of when freetds notifies tiny_tds of the info message. With
Yes, we can definitely do this. These info messages are being pushed to our nonblocking_errors array which means we have access to them in for (short int i = 0; i < userdata->nonblocking_errors_length; i++) {
tinytds_errordata error = userdata->nonblocking_errors[i];
// look ahead to drain any info messages ahead of raising an error
if (!error.is_message) {
for (short int j = i; j < userdata->nonblocking_errors_length; j++) {
tinytds_errordata msg_error = userdata->nonblocking_errors[j];
if (msg_error.is_message) {
rb_tinytds_raise_error(client,
msg_error.is_message,
msg_error.cancel,
msg_error.error,
msg_error.source,
msg_error.severity,
msg_error.dberr,
msg_error.oserr
);
}
}
}
rb_tinytds_raise_error(client,
error.is_message,
error.cancel,
error.error,
error.source,
error.severity,
error.dberr,
error.oserr
);
} I'm trying to come up with a reason we wouldn't want to do this but can't really think of a downside.
Yes, we probably should. |
These changes look good to me. We've been testing 2.1.4-pre2 in production for a week or so and this change seems to have the intended outcome. Can you squash/edit your commits down to whatever's appropriate for master history before merging? |
30f1b7c
to
f136f57
Compare
Capturing errors while in a non-blocking state was originally structured to capture a single error. This was intentional in order to avoid capturing more generic info messages that FreeTDS might send before the Global VM Lock was obtained. In most circumstances this is what we want. However, now that we capture info messages it is possible that a info message will be stored and the actual runtime error will be discarded as non-important. The result is that while a runtime error is reported in the database, a TinyTds error is never thrown and only the info message is handled. A subset of this problem is that only one info message can be captured while in non-blocking mode which prevents stored procedures from reporting multiple info messages to TinyTds. To fix this issue, the reported messages are stored within a dynamic array of tinytds_errordata structs, then processed normally once the GVL is obtained. Given the fact that we don't know the number of messages that will be sent, we dynamically manage and re-allocate memory for the nonblocking_errors array as needed. We can't use the ruby C API because it is not safe to call while in a non-blocking state as shown by rails-sqlserver#133.
f136f57
to
b64f2d1
Compare
Summary
Capturing errors while in a non-blocking state was originally structured to capture a single error. This was intentional in order to avoid capturing more generic info messages that FreeTDS might send before the Global VM Lock was obtained. In most circumstances this is what we want. However, now that we capture info messages it is possible that a info message will be stored and the actual runtime error will be mistakenly discarded as non-important. The result is that while a runtime error is reported in the database, a
TinyTds
error is never thrown and only the info message is handled. A subset of this problem is that only one info message can be captured while in non-blocking mode which prevents stored procedures from reporting multiple info messages toTinyTds
.To fix this issue, the reported messages are stored within a dynamic array of
tinytds_errordata
structs, then processed normally once the GVL is obtained.Given the fact that we don't know the number of messages that will be sent, we dynamically manage and re-allocate memory for the
nonblocking_errors
array as needed. We can't use the ruby C API because it is not safe to call while in a non-blocking state as shown by #133.Example
Given this procedure:
Result:
Notes