On Tue, Jun 22, 2010 at 09:48:29AM -0400, Sam Carleton scratched on the wall: > On Tue, Jun 22, 2010 at 9:15 AM, Pavel Ivanov <paiva...@gmail.com> wrote: > > > > The idea is that the copy and nulling happens very quickly where the > > > sqlite3_close is more expensive, do the copy/null very quickly so that if > > > another thread calls Close during the first threads execution of > > > sqlite3_close, the second thread finds m_db null and will not call > > > sqlite3_close. > > > > Wow, if you really relied on that then you did too few of > > mutli-threaded programming.
> Yea, I know it isn't as safe as it should be, but in this case it is one > class per thread. Why I am doing it, I don't know, old habit that I just > cannot seem to break. In light of your next statement, I think I will > change it;) The practice is justified, as "get it done quickly" has nothing to do with threads. It is a defensive programming style that is based off invariants: <http://en.wikipedia.org/wiki/Invariant_%28computer_science%29>. In this case, the one absolute thing that must happen in ::Close() is that m_db needs to get set to NULL. It would be a good idea to also close and properly destroy the database connection, but that isn't as critical. If the database connection isn't actually closed, the application will leak memory and resources. That's bad, but there is a good chance the application will continue to run-- likely until the natural termination of the process, which will clean everything up anyways. However, if m_db is not set to NULL there is, as we saw, a good chance the application will just crash outright. Most people consider this worse, especially if it happens on a customer's machine (the common case for most code execution). So the very first thing the function does is set m_db to NULL. It then uses a locally scoped copy of the database handle to actually try to get some work done (like call sqlite3_close()). But since this variable is locally scoped, we know it will go out of scope at the end of this function. This means that, one way or another, no matter how the function exits, neither the class nor any other variable in the application will have a stale reference to the database handle. This greatly reduces the possibility of a crash. In fact, the only stale pointer that ever exists is the locally scoped copy, which has a very short and confined lifetime. If the function does something wrong, the database connection might not actually be closed, but that's true regardless of how you structure the function. In a function this small it might seem a bit silly, but consider a 100 line function that has the "m_db = NULL;" statement at the bottom. All it takes is someone to come back to code three months later, do a quick scan, and add a "return;" half way through the function to deal with a hopeless error condition (this being called from a distructor makes any serious action difficult). The same general ideas apply to called client functions (e.g. stuff like sqlite3_close()) that might throw an exception, kill the thread, or otherwise disrupt the normal code flow. To defend against this, we do the important stuff "quickly," before any other code (and, in specific, external functions) has a chance to screw something up-- not because of concurrency, but because we don't trust the code (and the functions it calls) that may run next. You can say that this just points to bigger structure and error handling issues, and you might be right. This is just a safety net, not an end-all solution. But this is still a really good habit, especially for situations similar to COM code that is likely wrappering other code or libraries not under the control of the developer writing this code. This style provides a line of defense against poorly written libraries, as well as future-proofing against libraries that may be changed or updated independently of the wrapper code. There are those that might say crashing is better than silently failing, as it is more "in your face" and demands attention (and a fix). That's only true while in development, however, where there is a chance to notice it, analyze it, and fix it. A customer crash is almost always a worst-case situation. Even in-development, if you're using tools like Valgrind, the any issue with program logic or flow control is likely to show up. A Valgrind report should put up just as big a red flag as a crash-- and is likely to be easier to debug. The biggest problem with invariant style programming is that, quite frankly, it is very hard. You also need very strict and specific requirements for each function. Unless you've been doing it for some time, it might seem odd that setting the pointer to NULL is an absolute requirement of the ::Close() method, while actually closing the database connection is not. You can learn patterns, but unless you understand the why and how of the situation, it is of limited value and just looks funny. In short, it is a situation of "smart programming as a defense against poor programming (and poor environments)." Unfortunately, if there is a need to defend against poor programming, that's just as likely to be true here as it is there, meaning that the invariant style scales poorly across a larger development environment (read: one that inevitably has below-average programmers). It also adds enough complexity that a net positive gain in stability and reliability is somewhat questionable. Invariants are a powerful tool, and they have a strong place in computer science to write verifiable algorithm proofs. But like any powerful tool, they're easy to misuse. Their day-to-day practical value on a large scale programming project is still, IMHO, an unanswered question. -j -- Jay A. Kreibich < J A Y @ K R E I B I.C H > "Intelligence is like underwear: it is important that you have it, but showing it to the wrong people has the tendency to make them feel uncomfortable." -- Angela Johnson _______________________________________________ sqlite-users mailing list sqlite-users@sqlite.org http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-users