# # add_file "tests/t_database_sig_cleanup.at" # # patch "ChangeLog" # from [016732448d020c58a1245727ae00ec66f9533fdb] # to [028ab1eab281874b48bc82bfeeca036c574ca51a] # # patch "database.cc" # from [5b488d9296a1165037f850298c34720b5e4fb253] # to [034cc97a43dccb1c7be5388339af9f33fe6597e4] # # patch "database.hh" # from [dc62f8b1975f2884f63949fd701072a409ce0740] # to [db62b6b964da9632dc1b04024eff7b749bce8fe0] # # patch "keys.cc" # from [3fb812c189636b0fb652994edb0b82fde2d71f93] # to [a2f02dffc377c4d2f0f8ba613db1b0c4230d3949] # # patch "main.cc" # from [93c14a170425bd2be15e68d0f4eed476f9b77144] # to [0981328eff2cd408bb7d9b13208b16243feec637] # # patch "monotone.cc" # from [8c23f5814ae8730ac72e4c4e51c5ea7aaed31a3b] # to [c44e323fa88aa1053a8fd0060f418b1c4870123b] # # patch "sanity.cc" # from [e21f793216b8f4177d0dbcb663de917140ae3acb] # to [d81f699c8daf70322c2fe440c7a366ac13aefda6] # # patch "sanity.hh" # from [5a3b4f9635067d92301924a42621085c15a33188] # to [0e176663e0e3f7a8e26cb0c1abb194366ea7d878] # # patch "tests/t_database_sig_cleanup.at" # from [] # to [22a764be6d58c7bc266abcd21aaaed47e765cdb3] # # patch "testsuite.at" # from [3dd7d23b79312c68c41089c651f9d1d7e91f150e] # to [813048e60d51b50221c35169d8bb6ac11bdf57d8] # ======================================================================== --- ChangeLog 016732448d020c58a1245727ae00ec66f9533fdb +++ ChangeLog 028ab1eab281874b48bc82bfeeca036c574ca51a @@ -1,3 +1,14 @@ +2005-10-19 Matt Johnston + + * main.cc, database.{cc,hh}: SIGINT and SIGTERM handlers + exit gracefully, and try to ROLLBACK+close any databases to clean up + .db-journal files. Added new database::close() method to be used + rather than sqlite_close() directly + * monotone.{cc,hh}, sanity.{cc.hh}: move clean_shutdown flag to + global_sanity + * tests/t_database_sig_cleanup.at: test it + * keys.cc: don't L() private key + 2005-10-19 Matthew A. Nicholson * std_hooks.lua: Minor correction to vim warning during 3-way merge. ======================================================================== --- database.cc 5b488d9296a1165037f850298c34720b5e4fb253 +++ database.cc 034cc97a43dccb1c7be5388339af9f33fe6597e4 @@ -53,6 +53,12 @@ int const any_rows = -1; int const any_cols = -1; +namespace +{ + // track all open databases for close_all_databases() handler + set sql_contexts; +} + extern "C" { // some wrappers to ease migration const char *sqlite3_value_text_s(sqlite3_value *v); @@ -462,7 +468,7 @@ calculate_schema_id(__sql, id); - sqlite3_close(__sql); + close(); out << F("database schema version: %s") % id << endl; } @@ -475,7 +481,7 @@ open(); migrate_monotone_schema(__sql, __app); - sqlite3_close(__sql); + close(); } void @@ -537,11 +543,7 @@ // trigger destructors to finalize cached statements statement_cache.clear(); - if (__sql) - { - sqlite3_close(__sql); - __sql = 0; - } + close(); } void @@ -2429,13 +2431,33 @@ { int error; + I(!__sql); + error = sqlite3_open(filename.as_external().c_str(), &__sql); + if (__sql) + { + I(sql_contexts.find(__sql) == sql_contexts.end()); + sql_contexts.insert(__sql); + } + N(!error, (F("could not open database '%s': %s") % filename % string(sqlite3_errmsg(__sql)))); } +void +database::close() +{ + if (__sql) + { + sqlite3_close(__sql); + I(sql_contexts.find(__sql) != sql_contexts.end()); + sql_contexts.erase(__sql); + __sql = 0; + } +} + // transaction guards transaction_guard::transaction_guard(database & d) : committed(false), db(d) @@ -2455,3 +2477,22 @@ { committed = true; } + +// called to avoid foo.db-journal files hanging around if we exit cleanly +// without unwinding the stack (happens with SIGINT & SIGTERM) +void +close_all_databases() +{ + L(F("attempting to rollback and close %d databases") % sql_contexts.size()); + for (set::iterator i = sql_contexts.begin(); + i != sql_contexts.end(); i++) + { + // the ROLLBACK is required here, even though the sqlite docs + // imply that transactions are rolled back on database closure + int exec_err = sqlite3_exec(*i, "ROLLBACK", NULL, NULL, NULL); + int close_err = sqlite3_close(*i); + + L(F("exec_err = %d, close_err = %d") % exec_err % close_err); + } + sql_contexts.clear(); +} ======================================================================== --- database.hh dc62f8b1975f2884f63949fd701072a409ce0740 +++ database.hh db62b6b964da9632dc1b04024eff7b749bce8fe0 @@ -203,6 +203,7 @@ void check_filename(); void open(); + void close(); public: @@ -458,6 +459,8 @@ void commit(); }; +void +close_all_databases(); #endif // __DATABASE_HH__ ======================================================================== --- keys.cc 3fb812c189636b0fb652994edb0b82fde2d71f93 +++ keys.cc a2f02dffc377c4d2f0f8ba613db1b0c4230d3949 @@ -192,13 +192,11 @@ bool force = force_from_user; L(F("base64-decoding %d-byte private key\n") % priv().size()); - L(F("priv '%s'\n") % priv()); decode_base64(priv, decoded_key); for (int i = 0; i < 3; ++i) { get_passphrase(lua, id, phrase, false, force); L(F("have %d-byte encrypted private key\n") % decoded_key().size()); - L(F("decoded '%s'\n") % decoded_key()); shared_ptr pkcs8_key; try ======================================================================== --- main.cc 93c14a170425bd2be15e68d0f4eed476f9b77144 +++ main.cc 0981328eff2cd408bb7d9b13208b16243feec637 @@ -25,6 +25,10 @@ #include #include +#include "i18n.h" +#include "database.hh" +#include "sanity.hh" + // Microsoft + other compatible compilers such as Intel #if defined(_MSC_VER) || (defined(__MWERKS__) && __MWERKS__ >= 0x3000) #define MS_STRUCTURED_EXCEPTION_HANDLING @@ -266,6 +270,8 @@ struct sigaction old_SIGSEGV_action; struct sigaction old_SIGBUS_action; struct sigaction old_SIGABRT_action; + struct sigaction old_SIGTERM_action; + struct sigaction old_SIGINT_action; struct sigaction old_SIGPIPE_action; all_signals_action.sa_flags = 0; @@ -281,10 +287,13 @@ sigaction(SIGSEGV, &all_signals_action, &old_SIGSEGV_action); sigaction(SIGBUS , &all_signals_action, &old_SIGBUS_action); sigaction(SIGABRT, &all_signals_action, &old_SIGABRT_action); + sigaction(SIGTERM, &all_signals_action, &old_SIGTERM_action); + sigaction(SIGINT, &all_signals_action, &old_SIGINT_action); sigaction(SIGPIPE, &ignore_signals_action, &old_SIGPIPE_action); int result = 0; bool trapped_signal = false; + bool clean_signal_exit = false; char const *em = NULL; volatile int sigtype = sigsetjmp(jump_buf, 1); @@ -312,6 +321,14 @@ case SIGBUS: em = "signal: memory access violation"; break; + case SIGINT: + em = _("interrupted"); + clean_signal_exit = true; + break; + case SIGTERM: + em = _("terminated by signal"); + clean_signal_exit = true; + break; default: em = "signal: unrecognized signal"; } @@ -323,6 +340,14 @@ sigaction(SIGBUS , &old_SIGBUS_action , sigaction_ptr()); sigaction(SIGABRT, &old_SIGABRT_action, sigaction_ptr()); sigaction(SIGPIPE, &old_SIGPIPE_action, sigaction_ptr()); + + if(clean_signal_exit) + { + global_sanity.clean_shutdown = true; + ui.inform(em); + close_all_databases(); + return 1; + } if(trapped_signal) throw unix_signal_exception(em); ======================================================================== --- monotone.cc 8c23f5814ae8730ac72e4c4e51c5ea7aaed31a3b +++ monotone.cc c44e323fa88aa1053a8fd0060f418b1c4870123b @@ -125,12 +125,10 @@ // in other words, this program should *never* unexpectedly terminate // without dumping some diagnostics. -static bool clean_shutdown; - void dumper() { - if (!clean_shutdown) + if (!global_sanity.clean_shutdown) global_sanity.dump_buffer(); Botan::Init::deinitialize(); @@ -237,7 +235,6 @@ int cpp_main(int argc, char ** argv) { - clean_shutdown = false; int ret = 0; atexit(&dumper); @@ -367,12 +364,12 @@ case OPT_VERSION: print_version(); - clean_shutdown = true; + global_sanity.clean_shutdown = true; return 0; case OPT_FULL_VERSION: print_full_version(); - clean_shutdown = true; + global_sanity.clean_shutdown = true; return 0; case OPT_REVISION: @@ -578,22 +575,22 @@ poptPrintHelp(ctx(), stdout, 0); cout << endl; commands::explain_usage(u.which, cout); - clean_shutdown = true; + global_sanity.clean_shutdown = true; return 2; } } catch (informative_failure & inf) { ui.inform(inf.what); - clean_shutdown = true; + global_sanity.clean_shutdown = true; return 1; } catch (std::ios_base::failure const & ex) { - clean_shutdown = true; + global_sanity.clean_shutdown = true; return 1; } - clean_shutdown = true; + global_sanity.clean_shutdown = true; return ret; } ======================================================================== --- sanity.cc e21f793216b8f4177d0dbcb663de917140ae3acb +++ sanity.cc d81f699c8daf70322c2fe440c7a366ac13aefda6 @@ -30,7 +30,8 @@ sanity global_sanity; sanity::sanity() : - debug(false), quiet(false), relaxed(false), logbuf(0xffff), already_dumping(false) + debug(false), quiet(false), relaxed(false), logbuf(0xffff), + already_dumping(false), clean_shutdown(false) { std::string flavour; get_system_flavour(flavour); ======================================================================== --- sanity.hh 5a3b4f9635067d92301924a42621085c15a33188 +++ sanity.hh 0e176663e0e3f7a8e26cb0c1abb194366ea7d878 @@ -61,6 +61,7 @@ system_path filename; std::string gasp_dump; bool already_dumping; + bool clean_shutdown; std::vector musings; void log(boost::format const & fmt, ======================================================================== --- tests/t_database_sig_cleanup.at +++ tests/t_database_sig_cleanup.at 22a764be6d58c7bc266abcd21aaaed47e765cdb3 @@ -0,0 +1,44 @@ +# -*- Autoconf -*- + +AT_SETUP([database is closed on signal exit]) + +MONOTONE_SETUP + +# this test checks that .db-journal files aren't left lying about if the +# process is killed with SIGTERM or SIGINT + +AT_DATA(testfile, [stuff +]) +AT_CHECK(MONOTONE add testfile, [], [ignore], [ignore]) + +# a hack to make the monotone process hang around with the database locked. + +AT_DATA(wait.lua, [ +function get_passphrase(key) sleep(1000) end +]) + + +# SIGTERM first +AT_CHECK(MONOTONE --rcfile=wait.lua --branch=testbranch commit --message=blah-blah & echo $! > monotone_commit.pid, [], [ignore], [ignore]) +AT_CHECK(sleep 2, [], [ignore], [ignore]) +AT_CHECK(test -f test.db-journal, [], [ignore], [ignore]) +AT_CHECK(kill -TERM `cat monotone_commit.pid`, [], [ignore], [ignore]) +AT_CHECK(test -f test.db-journal, [1], [ignore], [ignore]) + + +# and again for SIGINT +AT_CHECK(MONOTONE --rcfile=wait.lua --branch=testbranch commit --message=blah-blah & echo $! > monotone_commit.pid, [], [ignore], [ignore]) +AT_CHECK(sleep 2, [], [ignore], [ignore]) +AT_CHECK(test -f test.db-journal, [], [ignore], [ignore]) +AT_CHECK(kill -INT `cat monotone_commit.pid`, [], [ignore], [ignore]) +AT_CHECK(test -f test.db-journal, [1], [ignore], [ignore]) + + +# should be cleaned up for SIGSEGV +AT_CHECK(MONOTONE --rcfile=wait.lua --branch=testbranch commit --message=blah-blah & echo $! > monotone_commit.pid, [], [ignore], [ignore]) +AT_CHECK(sleep 2, [], [ignore], [ignore]) +AT_CHECK(test -f test.db-journal, [], [ignore], [ignore]) +AT_CHECK(kill -SEGV `cat monotone_commit.pid`, [], [ignore], [ignore]) +AT_CHECK(test -f test.db-journal, [], [ignore], [ignore]) + +AT_CLEANUP ======================================================================== --- testsuite.at 3dd7d23b79312c68c41089c651f9d1d7e91f150e +++ testsuite.at 813048e60d51b50221c35169d8bb6ac11bdf57d8 @@ -717,3 +717,4 @@ m4_include(tests/t_add_inside_MT.at) m4_include(tests/t_annotate_renames.at) m4_include(tests/t_config_confdir.at) +m4_include(tests/t_database_sig_cleanup.at)