Closed IvanRibakov closed 3 years ago
try dev
branch. I know for sure that some memory leak was fixed either in 1.6 or after 1.6 in dev
branch
@IvanRibakov can you reproduce it in dev
branch or master
?
@fnc12 I haven't had time to try your suggestion yet. I'm working on a project that uses sqlite_orm
via Conan and modifying current build automation to source sqlite_orm
from a custom location is going to take more time than I can afford at the moment.
I do expect to be able to get back to this problem soon (by next week latest I hope) so I'd like to keep this issue open for a little while longer if you don't mind.
@fnc12 I've tried latest dev
branch code but still observing a crash due to exception being thrown from within a destructor.
Is there anything else that can be done to prevent terminate
from being called?
holy shield! Can you please provide some code to reproduce this bug?
@fnc12
Above code essentially generates high read/write load on the DB. I didn't get the same what():
message every time (sometimes it was Disk I/O), but judging by the coredump, in all cases the common cause/part of the code is sqlite_orm::internal::connection_ref::~connection_ref
+ sqlite_orm::internal::connection_holder::release
.
Here is sample output + coredump backtrace of one of those times when I managed to get exactly the same what()
message:
the reason you get this is line storage.update(model);
which performs writing. Writing can be performed only from one thread simultaneously but you run all threads in parallel. You can get the same error if you use raw libsqlite3 not sqlite_orm
with the same code. How to fix it? Either make only one thread to make writing or increase busy timeout:
storage.pragma.busy_timeout(...);
You can get the same error if you use raw libsqlite3 not sqlite_orm with the same code.
I'm not concerned about the error as such, but rather about the fact that I'm unable to handle it (I would expect to be able to try-catch my way out of it) because of the way sqlite_orm
is structured. Is that really the best that can be done in this case?
are you sure that you cannot catch it? Try to enclose storage.update(model);
inside try-catch
block
are you sure that you cannot catch it?
Yes
Try to enclose storage.update(model); inside try-catch block
I have tried, but as you can see from the coredump, after being raised in the connection_holder::release
exception does not propagate to the application code but rather is handled by the system library code which calls terminate
that application can not do anything about. As I've said in the initial issue description, this is the result of throwing exception from the destructor (and allowing it to leave unhandled). This behaviour is mentioned here as all kinds of bad things
and explained in more details here.
Ok I see. Looks like a bad thing. Anyway what would you do if you can handle this exception?
Anyway what would you do if you can handle this exception?
Log it to begin with. We're using sqlite_orm
from a REST API so returning a 5XX error code (maybe 503) to the caller would be the next step. As far as I understood from your earlier comment this is caused by concurrent writes but is not a catastrophic failure that makes DB unusable so I see no reason not to continue with application execution.
you can use storage.busy_handler(...);
function to log it
or try to increase busy_timeout
anyway
anyway I'll try to reproduce it. It is strange that exception is throw. It can be thrown from dtor only if the last connection is closed but you asked storage to open database forever then it must not happen
you can use storage.busy_handler(...); function to log it
Is there somewhere an example on customising busy_handler
? Mainly interested in the handler interface and what means there are for error detection/handling.
P.S. Am I right that using custom busy_handler
will disable the built-in timeout logic (configured via storage.busy_timeout
) so if I want to keep both (current timeout behaviour + preventing a crash) I will need to reimplement "busy timeout" logic in the busy_handler
?
There are no examples in the repo but this API forwards call to sqlite3_busy_handler
. More info here https://sqlite.org/c3ref/busy_handler.html
Going down the custom busy_handler
route raises some nasty question which make this approach look very unappealing:
busy_timeout
logicWere you able to reproduce the crash locally? Wondering if you have any thoughts on the "exception inside destructor" situation aside form "Looks like a bad thing".
how to correctly re-implement busy_timeout logic
just set busy_timeout
to value greater than time required to make storage.update
in your case
how to safely throw and catch exceptions in C++ when a layer of C code is in between exception generation and exception handling
it works very easy. C layer never throws any exception but returns error code. sqlite_orm
translates every case of non zero error code to exception making it correct to translate errors from C to C++.
Were you able to reproduce the crash locally?
I did not started yet. I'll do it soon
Wondering if you have any thoughts on the "exception inside destructor" situation aside form "Looks like a bad thing".
Looks like a thing to think about
how to correctly re-implement busy_timeout logic
just set busy_timeout to value greater than time required to make storage.update in your case
As far as I understood from SQLite documentation
Setting a new busy handler clears any previously set handler. Note that calling sqlite3_busy_timeout() or evaluating PRAGMA busy_timeout=N will change the busy handler and thus clear any previously set busy handler.
there can only be 1 busy_handler
per connection. busy_timeout
is also a busy_handler
so it can not be used at the same time as a custom busy_handler
. Hence the initial comment about the need to re-implement sleep/retry logic that is currently provided by the default busy_timeout
handler.
how to safely throw and catch exceptions in C++ when a layer of C code is in between exception generation and exception handling
it works very easy. C layer never throws any exception but returns error code. sqlite_orm translates every case of non zero error code to exception making it correct to translate errors from C to C++.
I don't see how above explanation helps. Currently the problem comes from the part of sqlite_orm
that translates every case of non zero error code to exception
. I was assuming that by using custom busy_handler
it will be possible to throw an exception and completely bypass parts of sqlite_orm
that could trigger the crash mentioned in this issue. Now that I'm saying it, I'm not sure any more if that is a good idea since maybe sqlite_orm
does some kind of resource cleanup in case of error handling which would be also skipped in this case. So if throwing exception from busy_handler
is not an option, I don't see how it can help work around the crash caused by destructor exception.
@fnc12 Any thoughts? What are the odds of this problem to be fixed in the sqlite_orm
code base?
@IvanRibakov do you have a sample code to implement this? I cannot find it
To implement what? You mean code to reproduce the issue? It's attached to this https://github.com/fnc12/sqlite_orm/issues/718#issuecomment-842211808
@IvanRibakov sorry I am blind
@IvanRibakov I've ran your code. It is very easy to reproduce your crash. It happens cause of data races. I don't understand how do you expect to handle data races cause data races are UB cases. If you want your software to act predictable then you have to escape all cases of UB. So you'd better add mutex around storage.get
and storage.update
calls. I did it and everything stopped crashing.
Why I am sure that the reason is not located inside sqlite_orm
? Cause I moved connection closing outside of d-tor and still got crashes but in different place: inside sqlite3_prepare_v2
@IvanRibakov are you there?
Cause I moved connection closing outside of d-tor and still got crashes but in different place: inside sqlite3_prepare_v2
Do you have a stack trace or any other example of what that other crash looked like?
@fnc12 Are YOU there?
@IvanRibakov sorry I was a little bit busy. I remember about your question. I'll post the code and stack trace soon
@IvanRibakov hey hey hey I am starting working on your issue!
@IvanRibakov I've fixed dtor exception throwing by moving connection closing out of dtor but it still keeps firing sqlite3 asserts.
This is a stack trace you asked for.
Also you can run this code if you switch to dtor-exception
branch.
Sorry for delay
@IvanRibakov are you there?
I don't have anything to add here.
I'm off to reviewing current persistence layer architecture and looking for a way to avoid concurrent writes. Perhaps you know a mutex implementation with following properties:
?
Try the simplest std::mutex
or std::recursive_mutex
. It is slow but effective
I'm using
sqlite_orm
v1.5
in a multi-threaded environment. When doing some load testing application crashes due to exception being thrown inside a destructor as can be seen from the backtrace above.Is there anything that can be done to prevent the application from crashing in this case?