Skip to content

Fixed error handling to prevent crashing ruby in 0.6.x #133

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

Merged
merged 2 commits into from
Aug 31, 2013
Merged

Fixed error handling to prevent crashing ruby in 0.6.x #133

merged 2 commits into from
Aug 31, 2013

Conversation

wbond
Copy link
Contributor

@wbond wbond commented Aug 20, 2013

Observed errors included:

  • Segmentation fault
  • cfp consistency error
  • object allocation during garbage collection phase

These errors manifested themselves most consistently when a SQL error
occurred while using threads, but would also happen occasionally without
threads being involved.

With TinyTds 0.6.0 changes were made to make the C code properly release
the Global VM Lock (GVL) so that other threads could run while FreeTDS
was waiting for results from the server (blocked on I/O). While this
lock is released it is not possible to safely call the ruby C API.

The way that FreeTDS deals with error messages is to define a handler to
process the message when it is raised. The issue is that the error
handler that TinyTds uses invokes the ruby C API to throw an exception.
This accidental use of the ruby C API while TinyTds did not have the GVL
caused various catastrophic conditions in the ruby VM. These conditions
manifested themselves as the various error messages listed above.

To fix these errors, the userdata struct is used to track if the client
is currently "nonblocking" and thus does not have the GVL. In those
situations, any errors are saved into a new tinytds_errordata struct
that saves all necessary information. Once the GVL is re-obtained,
TinyTds uses the information in the tinytds_errordata struct to throw
an exception.

To further complicate the issue, FreeTDS will send multiple messages/
errors if the error is severe enough. Normally, when exceptions are
raised immediately, the developer will properly receive the first,
detailed, message. However, when TinyTds does not have the GVL and the
error is stored for later use, FreeTDS has the opportunity to send more
messages, causing the first one to be overwritten with something more
generic. Because of this, as soon as the first message is captured, all
further messages are ignored until TinyTds::Client.execute() is called
again.

wbond added 2 commits August 5, 2013 15:18
Observed errors included:

 - Segmentation fault
 - cfp consistency error
 - object allocation during garbage collection phase

These errors manifested themselves most consistently when a SQL error
occurred while using threads, but would also happen occasionally without
threads being involved.

With TinyTds 0.6.0 changes were made to make the C code properly release
the Global VM Lock (GVL) so that other threads could run while FreeTDS
was waiting for results from the server (blocked on I/O). While this
lock is released it is not possible to safely call the ruby C API.

The way that FreeTDS deals with error messages is to define a handler to
process the message when it is raised. The issue is that the error
handler that TinyTds uses invokes the ruby C API to throw an exception.
This accidental use of the ruby C API while TinyTds did not have the GVL
caused various catastrophic conditions in the ruby VM. These conditions
manifested themselves as the various error messages listed above.

To fix these errors, the userdata struct is used to track if the client
is currently "nonblocking" and thus does not have the GVL. In those
situations, any errors are saved into a new tinytds_errordata struct
that saves all necessary information. Once the GVL is re-obtained,
TinyTds uses the information in the tinytds_errordata struct to throw
an exception.

To further complicate the issue, FreeTDS will send multiple messages/
errors if the error is severe enough. Normally, when exceptions are
raised immediately, the developer will properly receive the first,
detailed, message. However, when TinyTds does not have the GVL and the
error is stored for later use, FreeTDS has the opportunity to send more
messages, causing the first one to be overwritten with something more
generic. Because of this, as soon as the first message is captured, all
further messages are ignored until TinyTds::Client.execute() is called
again.
@metaskills
Copy link
Member

Wow, what a bunch of nice work. I'll get this looked over real soon while I am doing the work for the updated windows binaries.

metaskills added a commit that referenced this pull request Aug 31, 2013
Fixed error handling to prevent crashing ruby in 0.6.x
@metaskills metaskills merged commit 4473f7a into rails-sqlserver:master Aug 31, 2013
@metaskills
Copy link
Member

Nothing but net! All tests green. Will get this out in the next version soon!

@betelgeuse
Copy link

Please consider releasing 0.6.2 so people get this fix.

@pjb3
Copy link

pjb3 commented Feb 27, 2014

I can confirm this fixed seg faults for me as well, would also like to see a release that includes this fix

@metaskills
Copy link
Member

Awesome, I promise to get to this soon. Just been wrapped up in finally launching a personal project.

@aharpervc aharpervc deleted the thread_crash_on_error branch January 5, 2017 18:01
bvogelzang added a commit to bvogelzang/tiny_tds that referenced this pull request Jul 22, 2020
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 would be stored and the actual runtime error would be discarded as non-important. The result is that while a runtime error is reported in the database, a TinyTds error is not thrown and only the info message is handled. A subset of this problem is that only one info message can be captured while in this 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.
bvogelzang added a commit to bvogelzang/tiny_tds that referenced this pull request Jul 23, 2020
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.
bvogelzang added a commit to bvogelzang/tiny_tds that referenced this pull request Oct 28, 2020
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.
bvogelzang added a commit to bvogelzang/tiny_tds that referenced this pull request Dec 18, 2020
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.
bvogelzang added a commit to bvogelzang/tiny_tds that referenced this pull request Jan 19, 2021
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.
aharpervc pushed a commit to aharpervc/tiny_tds that referenced this pull request Mar 22, 2021
- 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.
bvogelzang added a commit to bvogelzang/tiny_tds that referenced this pull request Apr 13, 2021
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.
bvogelzang added a commit to bvogelzang/tiny_tds that referenced this pull request May 5, 2021
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.
bvogelzang added a commit that referenced this pull request May 5, 2021
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 #133.
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.

4 participants