-
Notifications
You must be signed in to change notification settings - Fork 188
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
Fixed error handling to prevent crashing ruby in 0.6.x #133
Conversation
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.
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. |
Fixed error handling to prevent crashing ruby in 0.6.x
Nothing but net! All tests green. Will get this out in the next version soon! |
Please consider releasing 0.6.2 so people get this fix. |
I can confirm this fixed seg faults for me as well, would also like to see a release that includes this fix |
Awesome, I promise to get to this soon. Just been wrapped up in finally launching a personal project. |
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.
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.
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.
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.
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.
- 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.
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.
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.
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.
Observed errors included:
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.