GitHub PullRequestReviewCommentEvent at 2014-11-14 23:52:11
Repository: rippled
Actor: mtrippled
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/ripple/rippled/pulls/comments/20395160","id":20395160,"diff_hunk":"@@ -0,0 +1,819 @@\n+//------------------------------------------------------------------------------\n+/*\n+    This file is part of rippled: https://github.com/ripple/rippled\n+    Copyright (c) 2012, 2013 Ripple Labs Inc.\n+\n+    Permission to use, copy, modify, and/or distribute this software for any\n+    purpose  with  or without fee is hereby granted, provided that the above\n+    copyright notice and this permission notice appear in all copies.\n+\n+    THE  SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n+    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF\n+    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n+    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n+    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN\n+    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n+    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n+*/\n+//==============================================================================\n+\n+#ifndef RIPPLE_APP_SHAMAPSTOREIMP_H_INCLUDED\n+#define RIPPLE_APP_SHAMAPSTOREIMP_H_INCLUDED\n+\n+#include <ripple/app/misc/SHAMapStore.h>\n+#include <ripple/nodestore/impl/Tuning.h>\n+#include <ripple/nodestore/DatabaseRotating.h>\n+#include <beast/module/sqdb/sqdb.h>\n+\n+#include <iostream>\n+#include <condition_variable>\n+\n+namespace ripple {\n+\n+class SHAMapStoreImp : public SHAMapStore\n+{\n+private:\n+    struct SavedState\n+    {\n+        std::string writableDb;\n+        std::string archiveDb;\n+        LedgerIndex lastRotated;\n+    };\n+\n+    // keep this all within SHAMapStore, with forwarding functions for\n+    // RPC advisory_delete & OnlineDelete, with public interface.\n+    // There *should* be no reason to expose this class outside of SHAMapStore\n+    class SavedStateDB\n+    {\n+    public:\n+        beast::sqdb::session session_;\n+        std::mutex mutex_;\n+        beast::Journal journal_;\n+\n+        // Just instantiate without any logic in case online delete is not\n+        // configured\n+        SavedStateDB() = default;\n+\n+        // opens SQLite database and, if necessary, creates & initializes its tables.\n+        void\n+        init (std::string const& databasePath,\n+                std::string const& dbName)\n+        {\n+            boost::filesystem::path pathName = databasePath;\n+            pathName /= dbName;\n+\n+            std::lock_guard <std::mutex> l (mutex_);\n+\n+            beast::Error error (session_.open (pathName.native()));\n+            checkError (error);\n+\n+            session_.once (error) << \"PRAGMA synchronous=FULL;\";\n+            checkError (error);\n+\n+            session_.once (error) <<\n+                    \"CREATE TABLE IF NOT EXISTS DbState (\"\n+                    \"  Key                    INTEGER PRIMARY KEY,\"\n+                    \"  WritableDb             TEXT,\"\n+                    \"  ArchiveDb              TEXT,\"\n+                    \"  LastDeletedLedger      INTEGER\"\n+                    \");\"\n+                    ;\n+            checkError (error);\n+\n+            session_.once (error) <<\n+                    \"CREATE TABLE IF NOT EXISTS CanDelete (\"\n+                    \"  Key                    INTEGER PRIMARY KEY,\"\n+                    \"  CanDeleteSeq           INTEGER\"\n+                    \");\"\n+                    ;\n+\n+            std::int64_t count = 0;\n+            beast::sqdb::statement st = (session_.prepare <<\n+                    \"SELECT COUNT(Key) FROM DbState WHERE Key = 1;\"\n+                    , beast::sqdb::into (count)\n+                    );\n+            st.execute_and_fetch (error);\n+            checkError (error);\n+\n+            if (!count)\n+            {\n+                session_.once (error) <<\n+                        \"INSERT INTO DbState VALUES (1, '', '', 0);\";\n+                checkError (error);\n+            }\n+\n+            st = (session_.prepare <<\n+                    \"SELECT COUNT(Key) FROM CanDelete WHERE Key = 1;\"\n+                    , beast::sqdb::into (count)\n+                    );\n+            st.execute_and_fetch (error);\n+            checkError (error);\n+\n+            if (!count)\n+            {\n+                session_.once (error) <<\n+                        \"INSERT INTO CanDelete VALUES (1, 0);\";\n+                checkError (error);\n+            }\n+        }\n+\n+        // get/set the ledger index that we can delete up to and including\n+        LedgerIndex\n+        getCanDelete()\n+        {\n+            beast::Error error;\n+            LedgerIndex seq;\n+\n+            {\n+                std::lock_guard <std::mutex> l (mutex_);\n+\n+                session_.once (error) <<\n+                        \"SELECT CanDeleteSeq FROM CanDelete WHERE Key = 1;\"\n+                        , beast::sqdb::into (seq);\n+                ;\n+            }\n+            checkError (error);\n+\n+            return seq;\n+        }\n+\n+        void\n+        setCanDelete (LedgerIndex canDelete)\n+        {\n+            beast::Error error;\n+            {\n+                std::lock_guard <std::mutex> l (mutex_);\n+\n+                session_.once (error) <<\n+                        \"UPDATE CanDelete SET CanDeleteSeq = ? WHERE Key = 1;\"\n+                        , beast::sqdb::use (canDelete)\n+                        ;\n+            }\n+            checkError (error);\n+        }\n+\n+        SavedState\n+        getState()\n+        {\n+            beast::Error error;\n+            SavedState state;\n+\n+            {\n+                std::lock_guard <std::mutex> l (mutex_);\n+\n+                session_.once (error) <<\n+                        \"SELECT WritableDb, ArchiveDb, LastDeletedLedger\"\n+                        \" FROM DbState WHERE Key = 1;\"\n+                        , beast::sqdb::into (state.writableDb)\n+                        , beast::sqdb::into (state.archiveDb)\n+                        , beast::sqdb::into (state.lastRotated)\n+                        ;\n+            }\n+            checkError (error);\n+\n+            return state;\n+        }\n+\n+        void\n+        setState (SavedState const& state)\n+        {\n+            beast::Error error;\n+\n+            {\n+                std::lock_guard <std::mutex> l (mutex_);\n+                session_.once (error) <<\n+                        \"UPDATE DbState\"\n+                        \" SET WritableDb = ?,\"\n+                        \" ArchiveDb = ?,\"\n+                        \" LastDeletedLedger = ?\"\n+                        \" WHERE Key = 1;\"\n+                        , beast::sqdb::use (state.writableDb)\n+                        , beast::sqdb::use (state.archiveDb)\n+                        , beast::sqdb::use (state.lastRotated)\n+                        ;\n+            }\n+            checkError (error);\n+        }\n+\n+        void\n+        setLastRotated (LedgerIndex seq)\n+        {\n+            beast::Error error;\n+            {\n+                std::lock_guard <std::mutex> l (mutex_);\n+                session_.once (error) <<\n+                        \"UPDATE DbState SET LastDeletedLedger = ?\"\n+                        \" WHERE Key = 1;\"\n+                        , beast::sqdb::use (seq)\n+                        ;\n+            }\n+            checkError (error);\n+        }\n+\n+        void\n+        setBackends (std::string const& writableBackend,\n+                std::string const& archiveBackend)\n+        {\n+            beast::Error error;\n+            {\n+                std::lock_guard <std::mutex> l (mutex_);\n+                session_.once (error) <<\n+                        \"UPDATE DbState SET WritableDb = ?, ArchiveDb = ?\"\n+                        \" WHERE Key=1;\"\n+                        , beast::sqdb::use (writableBackend)\n+                        , beast::sqdb::use (archiveBackend)\n+                        ;\n+            }\n+            checkError (error);\n+        }\n+\n+        void\n+        checkError (beast::Error const& error)\n+        {\n+            if (error)\n+            {\n+                journal_.fatal << \"state database error: \" << error.code()\n+                        << \": \" << error.getReasonText();\n+                throw std::runtime_error (\"State database error.\");\n+            }\n+        }\n+    };\n+\n+    Setup setup_;\n+    NodeStore::Manager& manager_;\n+    NodeStore::Scheduler& scheduler_;\n+    beast::Journal journal_;\n+    beast::Journal nodeStoreJournal_;\n+    NodeStore::DatabaseRotating* database_;\n+    SavedStateDB state_db_;\n+    std::thread thread_;\n+    bool stop_;\n+    mutable std::condition_variable cond_;\n+    mutable std::mutex mutex_;\n+    std::string const dbName_ = \"state.db\";\n+    std::string const dbPrefix_ = \"rippledb\";\n+    Ledger::pointer validatedLedger_;\n+    std::uint64_t const checkStopInterval_ = 1000;\n+\n+public:\n+    SHAMapStoreImp (Setup const& setup,\n+            Stoppable& parent,\n+            NodeStore::Manager& manager,\n+            NodeStore::Scheduler& scheduler,\n+            beast::Journal journal,\n+            beast::Journal nodeStoreJournal)\n+        : SHAMapStore (parent)\n+        , setup_ (setup)\n+        , manager_ (manager)\n+        , scheduler_ (scheduler)\n+        , journal_ (journal)\n+        , nodeStoreJournal_ (nodeStoreJournal)\n+        , database_ (nullptr)\n+        , stop_ (false)\n+    {\n+        if (setup_.deleteInterval)\n+        {\n+            if (setup_.ledgerHistory > setup_.deleteInterval)\n+                throw std::runtime_error (\n+                        \"LEDGER_HISTORY exceeds online_delete.\");\n+\n+            state_db_.init (setup_.databasePath, dbName_);\n+\n+            dbPaths();\n+        }\n+    }\n+\n+    ~SHAMapStoreImp()\n+    {\n+        if (thread_.joinable())\n+            thread_.join();\n+    }\n+\n+    std::uint32_t\n+    clampFetchDepth (std::uint32_t fetch_depth) const override\n+    {\n+        if (setup_.deleteInterval && fetch_depth > setup_.deleteInterval)\n+            fetch_depth = setup_.deleteInterval;\n+\n+        return fetch_depth;\n+    }\n+\n+    std::unique_ptr <NodeStore::Database>\n+    makeDatabase (std::string const& name,\n+            std::int32_t readThreads) override\n+    {\n+        std::unique_ptr <NodeStore::Database> db;\n+\n+        if (setup_.deleteInterval)\n+        {\n+            SavedState state = state_db_.getState();\n+\n+            std::shared_ptr <NodeStore::Backend> writableBackend (\n+                    makeBackendRotating (state.writableDb));\n+            std::shared_ptr <NodeStore::Backend> archiveBackend (\n+                    makeBackendRotating (state.archiveDb));\n+            std::unique_ptr <NodeStore::DatabaseRotating> dbr =\n+                    makeDatabaseRotating (name, readThreads, writableBackend,\n+                    archiveBackend);\n+\n+            if (!state.writableDb.size())\n+            {\n+                state.writableDb = writableBackend->getName();\n+                state.archiveDb = archiveBackend->getName();\n+                state_db_.setState (state);\n+            }\n+\n+            database_ = dbr.get();\n+            db.reset (dynamic_cast <NodeStore::Database*>(dbr.release()));\n+        }\n+        else\n+        {\n+            db = manager_.make_Database (name, scheduler_, nodeStoreJournal_,\n+                    readThreads, setup_.nodeDatabase,\n+                    setup_.ephemeralNodeDatabase);\n+        }\n+\n+        return db;\n+    }\n+\n+    LedgerIndex\n+    setCanDelete (LedgerIndex seq) override\n+    {\n+        state_db_.setCanDelete (seq);\n+\n+        return seq;\n+    }\n+\n+    bool\n+    advisoryDelete() const override\n+    {\n+        return setup_.advisoryDelete;\n+    }\n+\n+    LedgerIndex\n+    getLastRotated() override\n+    {\n+        return state_db_.getState().lastRotated;\n+    }\n+\n+    LedgerIndex\n+    getCanDelete() override\n+    {\n+        return state_db_.getCanDelete();\n+    }\n+\n+    void\n+    onLedgerClosed (Ledger::pointer validatedLedger) override\n+    {\n+        {\n+            std::lock_guard <std::mutex> l (mutex_);\n+            validatedLedger_ = validatedLedger;\n+        }\n+        cond_.notify_one();\n+    }\n+\n+private:\n+    // callback for visitNodes\n+    bool\n+    copyNode (std::uint64_t& nodeCount,\n+            SHAMapTreeNode const& node) const\n+    {\n+        bool ret = false;\n+        // Copy a single record from node to database_\n+        database_->fetchNode (node.getNodeHash());\n+        if (! (++nodeCount % checkStopInterval_))\n+        {\n+            std::lock_guard <std::mutex> l (mutex_);\n+            if (stop_)\n+            {\n+                ret = true;\n+            }\n+        }\n+\n+        return ret;\n+    }\n+\n+    void\n+    run()\n+    {\n+        LedgerIndex lastRotated = state_db_.getState().lastRotated;\n+\n+        while (1)\n+        {\n+            Ledger::pointer validatedLedger;\n+\n+            std::unique_lock <std::mutex> l (mutex_);\n+            if (stop_)\n+            {\n+                stopped();\n+                return;\n+            }\n+            cond_.wait (l);\n+            if (validatedLedger_.get())\n+                validatedLedger = validatedLedger_;\n+            else\n+                continue;\n+            validatedLedger_.reset();\n+            l.unlock();\n+\n+            LedgerIndex validatedSeq = validatedLedger->getLedgerSeq();\n+            if (!lastRotated)\n+            {\n+                lastRotated = validatedSeq;\n+                state_db_.setLastRotated (lastRotated);\n+            }\n+            LedgerIndex canDelete =\n+                    std::numeric_limits <LedgerIndex>::max();\n+            if (setup_.advisoryDelete)\n+                canDelete = state_db_.getCanDelete();\n+\n+            if (validatedSeq >= lastRotated + setup_.deleteInterval\n+                    && canDelete >= lastRotated-1)\n+            {\n+                journal_.debug << \"rotating  validatedSeq \" << validatedSeq\n+                        << \" lastRotated \" << lastRotated << \" deleteInterval \"\n+                        << setup_.deleteInterval << \" canDelete \" << canDelete;\n+\n+                // will delete up to (not including) lastRotated)\n+                clearPrior (lastRotated);\n+                if (checkStop())\n+                    return;\n+\n+                lastRotated = validatedSeq;\n+                state_db_.setLastRotated (lastRotated);\n+\n+                std::uint64_t nodeCount = 0;\n+                validatedLedger->peekAccountStateMap()->snapShot (\n+                        false)->visitNodes (\n+                        std::bind (&SHAMapStoreImp::copyNode, this,\n+                        std::ref(nodeCount), std::placeholders::_1));\n+                journal_.debug << \"copied ledger \" << validatedSeq\n+                        << \" nodecount \" << nodeCount;\n+                if (checkStop())\n+                    return;\n+\n+                freshenCaches();\n+                journal_.debug << validatedSeq << \" freshened caches\";\n+                if (checkStop())\n+                    return;\n+\n+                std::shared_ptr <NodeStore::Backend> newBackend =\n+                        makeBackendRotating();\n+                journal_.debug << validatedSeq << \" new backend \"\n+                        << newBackend->getName();\n+                std::shared_ptr <NodeStore::Backend> oldBackend;\n+\n+                clearCaches (validatedSeq);\n+                if (checkStop())\n+                    return;\n+\n+                {\n+                    std::unique_lock <std::mutex> l =\n+                            database_->getRotateLock();\n+\n+                    clearCaches (validatedSeq);\n+\n+                    oldBackend = database_->rotateBackends (newBackend);\n+\n+                    state_db_.setBackends (\n+                            database_->getWritableBackend (true)->getName(),\n+                            database_->getArchiveBackend (true)->getName());\n+                }\n+                journal_.debug << \"finished rotation \" << validatedSeq;\n+                if (checkStop())\n+                    return;\n+\n+                // it's either this or go into each backend, set a flag\n+                // then modify destructor\n+                // it's OK to stop before deleting the old backend path because\n+                // it will get deleted on restart\n+                boost::filesystem::path oldBackendPath = oldBackend->getName();\n+                while (oldBackend.use_count() > 1)\n+                {\n+                    journal_.debug << validatedSeq\n+                            << \"while oldBackend.use_count \"\n+                            << oldBackend.use_count();\n+                    if (checkStop())\n+                        return;\n+                    std::this_thread::sleep_for (std::chrono::seconds (1));\n+                }\n+                oldBackend.reset();\n+                boost::filesystem::remove_all (oldBackendPath);\n+\n+                journal_.debug << validatedSeq << \" backends: \"\n+                        << database_->getWritableBackend()->getName() << \",\"\n+                        << database_->getArchiveBackend()->getName() << \";\"\n+                        << \" statedb: \" << state_db_.getState().writableDb\n+                        << \",\" << state_db_.getState().archiveDb;\n+\n+            journal_.debug << \"end rotating \" << validatedSeq;\n+            }\n+        }\n+    }\n+\n+    void\n+    dbPaths()\n+    {\n+        boost::filesystem::path dbPath =\n+                setup_.nodeDatabase[\"path\"].toStdString();\n+\n+        if (boost::filesystem::exists (dbPath))\n+        {\n+            if (! boost::filesystem::is_directory (dbPath))\n+            {\n+                std::cerr << \"node db path must be a directory. \"\n+                        << dbPath.native();\n+                throw std::runtime_error (\n+                        \"node db path must be a directory.\");\n+            }\n+        }\n+        else\n+        {\n+            boost::filesystem::create_directories (dbPath);\n+        }\n+\n+        SavedState state = state_db_.getState();\n+        bool writableDbExists = false;\n+        bool archiveDbExists = false;\n+\n+        for (boost::filesystem::directory_iterator it (dbPath);\n+                it != boost::filesystem::directory_iterator(); ++it)\n+        {\n+            if (! state.writableDb.compare (it->path().native()))\n+            {\n+                writableDbExists = true;\n+            }\n+            else if (! state.archiveDb.compare (it->path().native()))\n+            {\n+                archiveDbExists = true;\n+            }\n+            else if (! dbPrefix_.compare (it->path().stem().native()))\n+            {\n+                boost::filesystem::remove_all (it->path());\n+            }\n+        }\n+\n+        if ((!writableDbExists && state.writableDb.size()) ||\n+                (!archiveDbExists && state.archiveDb.size()) ||\n+                (writableDbExists != archiveDbExists) ||\n+                state.writableDb.empty() != state.archiveDb.empty())\n+        {\n+            boost::filesystem::path stateDbPathName = setup_.databasePath;\n+            stateDbPathName /= dbName_;\n+            stateDbPathName += \"*\";\n+\n+            std::cerr << \"state db error: \" << std::endl\n+                    << \"  writableDbExists \" << writableDbExists\n+                    << \" archiveDbExists \" << archiveDbExists << std::endl\n+                    << \"  writableDb '\" << state.writableDb\n+                    << \"' archiveDb '\" << state.archiveDb << \"'\"\n+                    << std::endl << std::endl\n+                    << \"To resume operation, make backups of and \"\n+                    << \"remove the files matching \"\n+                    << stateDbPathName.native()\n+                    << \" and contents of the directory \"\n+                    << setup_.nodeDatabase[\"path\"].toStdString()\n+                    << std::endl;\n+\n+            throw std::runtime_error (\"state db error\");\n+        }\n+    }\n+\n+    std::shared_ptr <NodeStore::Backend>\n+    makeBackendRotating (std::string path = std::string())\n+    {\n+        boost::filesystem::path newPath;\n+        NodeStore::Parameters parameters = setup_.nodeDatabase;\n+\n+        if (path.size())\n+        {\n+            newPath = path;\n+        }\n+        else\n+        {\n+            boost::filesystem::path p = parameters[\"path\"].toStdString();\n+            p /= dbPrefix_;\n+            p += \".%%%%\";\n+            newPath = boost::filesystem::unique_path (p);\n+        }\n+        parameters.set(\"path\", newPath.native());\n+\n+        return manager_.make_Backend (parameters, scheduler_,\n+                nodeStoreJournal_);\n+    }\n+\n+    /**\n+     * Creates a NodeStore with two\n+     * backends to allow online deletion of data.\n+     *\n+     * @param name A diagnostic label for the database.\n+     * @param scheduler The scheduler to use for performing asynchronous tasks\n+     * @param journal The logging object\n+     * @param readThreads The number of async read threads to create\n+     * @param writableBackend backend for writing\n+     * @param archiveBackend backend for archiving\n+     * @param fastBackendParameters [optional] The parameter string for the ephemeral backend.\n+     *\n+     * @return The opened database.\n+     */\n+    std::unique_ptr <NodeStore::DatabaseRotating>\n+    makeDatabaseRotating (std::string const& name,\n+            std::int32_t readThreads,\n+            std::shared_ptr <NodeStore::Backend> writableBackend,\n+            std::shared_ptr <NodeStore::Backend> archiveBackend) const\n+    {\n+        std::unique_ptr <NodeStore::Backend> fastBackend (\n+            (setup_.ephemeralNodeDatabase.size() > 0)\n+                ? manager_.make_Backend (setup_.ephemeralNodeDatabase,\n+                scheduler_, journal_) : nullptr);\n+\n+        return manager_.make_DatabaseRotating (\"NodeStore.main\", scheduler_, 4,\n+                writableBackend, archiveBackend, std::move (fastBackend),\n+                nodeStoreJournal_);\n+    }\n+\n+    bool\n+    checkStop()\n+    {\n+        bool r = false;\n+\n+        std::lock_guard <std::mutex> l (mutex_);\n+        if (stop_)\n+        {\n+            stopped();\n+            r = true;\n+        }\n+\n+        return r;\n+    }\n+\n+    template <class CacheInstance>\n+    bool\n+    freshenCache (CacheInstance& cache)\n+    {\n+        std::uint64_t check = 0;\n+\n+        for (uint256 it: cache.getKeys())\n+        {\n+            database_->fetchNode (it);\n+            if ((! ++check % checkStopInterval_) && stop_)\n+                return true;\n+        }\n+\n+        return false;\n+    }\n+\n+    /** delete from sqlite table in batches to not lock the db excessively\n+     *  pause briefly to extend access time to other users\n+     *  call with mutex object unlocked\n+     */\n+    void\n+    clearSql (Database* db,\n+            std::unique_lock <std::recursive_mutex> lock,\n+            LedgerIndex lastRotated,\n+            std::string minQuery,\n+            std::string deleteQuery)\n+    {\n+        LedgerIndex min = std::numeric_limits <LedgerIndex>::max();\n+        lock.lock();\n+        if (!db->executeSQL (minQuery) || !db->startIterRows())\n+            return;\n+        min = db->getBigInt (0);\n+        db->endIterRows ();\n+        lock.unlock();\n+\n+        std::uint32_t const batchSize = 5000;\n+        std::uint32_t pause = 1000;\n+        boost::format formattedDeleteQuery (deleteQuery);\n+\n+        journal_.debug << \"start: \" << deleteQuery << \" from \"\n+                << min << \" to \" << lastRotated;\n+        while (min < lastRotated)\n+        {\n+            min = (min + batchSize >= lastRotated) ? lastRotated :\n+                min + batchSize;\n+            lock.lock();\n+            db->executeSQL (boost::str (formattedDeleteQuery % min));\n+            lock.unlock();\n+            if (stop_)\n+                return;\n+            std::this_thread::sleep_for (std::chrono::microseconds (pause));\n+        }\n+        journal_.debug << \"finished: \" << deleteQuery;\n+    }\n+\n+    void\n+    clearCaches (LedgerIndex validatedSeq)\n+    {\n+        getApp().getLedgerMaster().clearLedgerCachePrior (validatedSeq);\n+        getApp().getFullBelowCache().clear();\n+    }\n+\n+    void\n+    freshenCaches()\n+    {\n+        if (freshenCache (database_->getPositiveCache()))\n+            return;\n+        if (freshenCache (getApp().getTreeNodeCache()))\n+            return;\n+        if (freshenCache (getApp().getMasterTransaction().getCache()))\n+            return;\n+    }\n+\n+    void\n+    clearPrior (LedgerIndex lastRotated)\n+    {\n+        getApp().getLedgerMaster().clearPriorLedgers (lastRotated);\n+        if (stop_)","path":"src/ripple/app/misc/SHAMapStoreImp.h","position":727,"original_position":727,"commit_id":"2b41be6aa7f4e8f0ab70d853460450e5216c3297","original_commit_id":"2b41be6aa7f4e8f0ab70d853460450e5216c3297","user":{"login":"mtrippled","id":7728157,"avatar_url":"https://avatars.githubusercontent.com/u/7728157?v=3","gravatar_id":"","url":"https://api.github.com/users/mtrippled","html_url":"https://github.com/mtrippled","followers_url":"https://api.github.com/users/mtrippled/followers","following_url":"https://api.github.com/users/mtrippled/following{/other_user}","gists_url":"https://api.github.com/users/mtrippled/gists{/gist_id}","starred_url":"https://api.github.com/users/mtrippled/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mtrippled/subscriptions","organizations_url":"https://api.github.com/users/mtrippled/orgs","repos_url":"https://api.github.com/users/mtrippled/repos","events_url":"https://api.github.com/users/mtrippled/events{/privacy}","received_events_url":"https://api.github.com/users/mtrippled/received_events","type":"User","site_admin":false},"body":"You're right.","created_at":"2014-11-14T23:52:11Z","updated_at":"2014-11-14T23:52:11Z","html_url":"https://github.com/ripple/rippled/pull/717#discussion_r20395160","pull_request_url":"https://api.github.com/repos/ripple/rippled/pulls/717","_links":{"self":{"href":"https://api.github.com/repos/ripple/rippled/pulls/comments/20395160"},"html":{"href":"https://github.com/ripple/rippled/pull/717#discussion_r20395160"},"pull_request":{"href":"https://api.github.com/repos/ripple/rippled/pulls/717"}}},"pull_request":{"url":"https://api.github.com/repos/ripple/rippled/pulls/717","id":24273502,"html_url":"https://github.com/ripple/rippled/pull/717","diff_url":"https://github.com/ripple/rippled/pull/717.diff","patch_url":"https://github.com/ripple/rippled/pull/717.patch","issue_url":"https://api.github.com/repos/ripple/rippled/issues/717","number":717,"state":"open","locked":false,"title":"SHAMapStore with online delete","user":{"login":"mtrippled","id":7728157,"avatar_url":"https://avatars.githubusercontent.com/u/7728157?v=3","gravatar_id":"","url":"https://api.github.com/users/mtrippled","html_url":"https://github.com/mtrippled","followers_url":"https://api.github.com/users/mtrippled/followers","following_url":"https://api.github.com/users/mtrippled/following{/other_user}","gists_url":"https://api.github.com/users/mtrippled/gists{/gist_id}","starred_url":"https://api.github.com/users/mtrippled/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mtrippled/subscriptions","organizations_url":"https://api.github.com/users/mtrippled/orgs","repos_url":"https://api.github.com/users/mtrippled/repos","events_url":"https://api.github.com/users/mtrippled/events{/privacy}","received_events_url":"https://api.github.com/users/mtrippled/received_events","type":"User","site_admin":false},"body":"@JoelKatz @nbougalis \r\n\r\nHere's a more detailed description of this effort:\r\n\r\n    SHAMapStore: Online Delete\r\n    Makes rippled configurable to support deletion of all data in its key-value\r\n    store (nodestore) and ledger and transaction SQLite databases based on\r\n    validated ledger sequence number. All records from a specified ledger\r\n    and forward shall remain available in the key-value store and SQLite, and\r\n    all data prior to that specific ledger may be deleted.\r\n    \r\n    Additionally, the administrator may require that an RPC command be\r\n    executed to enable deletion. This is to align data deletion with local\r\n    policy.\r\n","created_at":"2014-11-12T06:47:19Z","updated_at":"2014-11-14T23:52:11Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5ccbd044a2c86f3e3dceced1875727078f53945e","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/ripple/rippled/pulls/717/commits","review_comments_url":"https://api.github.com/repos/ripple/rippled/pulls/717/comments","review_comment_url":"https://api.github.com/repos/ripple/rippled/pulls/comments/{number}","comments_url":"https://api.github.com/repos/ripple/rippled/issues/717/comments","statuses_url":"https://api.github.com/repos/ripple/rippled/statuses/2b41be6aa7f4e8f0ab70d853460450e5216c3297","head":{"label":"mtrippled:od","ref":"od","sha":"2b41be6aa7f4e8f0ab70d853460450e5216c3297","user":{"login":"mtrippled","id":7728157,"avatar_url":"https://avatars.githubusercontent.com/u/7728157?v=3","gravatar_id":"","url":"https://api.github.com/users/mtrippled","html_url":"https://github.com/mtrippled","followers_url":"https://api.github.com/users/mtrippled/followers","following_url":"https://api.github.com/users/mtrippled/following{/other_user}","gists_url":"https://api.github.com/users/mtrippled/gists{/gist_id}","starred_url":"https://api.github.com/users/mtrippled/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mtrippled/subscriptions","organizations_url":"https://api.github.com/users/mtrippled/orgs","repos_url":"https://api.github.com/users/mtrippled/repos","events_url":"https://api.github.com/users/mtrippled/events{/privacy}","received_events_url":"https://api.github.com/users/mtrippled/received_events","type":"User","site_admin":false},"repo":{"id":21549222,"name":"rippled","full_name":"mtrippled/rippled","owner":{"login":"mtrippled","id":7728157,"avatar_url":"https://avatars.githubusercontent.com/u/7728157?v=3","gravatar_id":"","url":"https://api.github.com/users/mtrippled","html_url":"https://github.com/mtrippled","followers_url":"https://api.github.com/users/mtrippled/followers","following_url":"https://api.github.com/users/mtrippled/following{/other_user}","gists_url":"https://api.github.com/users/mtrippled/gists{/gist_id}","starred_url":"https://api.github.com/users/mtrippled/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mtrippled/subscriptions","organizations_url":"https://api.github.com/users/mtrippled/orgs","repos_url":"https://api.github.com/users/mtrippled/repos","events_url":"https://api.github.com/users/mtrippled/events{/privacy}","received_events_url":"https://api.github.com/users/mtrippled/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/mtrippled/rippled","description":"Ripple peer-to-peer network daemon","fork":true,"url":"https://api.github.com/repos/mtrippled/rippled","forks_url":"https://api.github.com/repos/mtrippled/rippled/forks","keys_url":"https://api.github.com/repos/mtrippled/rippled/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mtrippled/rippled/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mtrippled/rippled/teams","hooks_url":"https://api.github.com/repos/mtrippled/rippled/hooks","issue_events_url":"https://api.github.com/repos/mtrippled/rippled/issues/events{/number}","events_url":"https://api.github.com/repos/mtrippled/rippled/events","assignees_url":"https://api.github.com/repos/mtrippled/rippled/assignees{/user}","branches_url":"https://api.github.com/repos/mtrippled/rippled/branches{/branch}","tags_url":"https://api.github.com/repos/mtrippled/rippled/tags","blobs_url":"https://api.github.com/repos/mtrippled/rippled/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mtrippled/rippled/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mtrippled/rippled/git/refs{/sha}","trees_url":"https://api.github.com/repos/mtrippled/rippled/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mtrippled/rippled/statuses/{sha}","languages_url":"https://api.github.com/repos/mtrippled/rippled/languages","stargazers_url":"https://api.github.com/repos/mtrippled/rippled/stargazers","contributors_url":"https://api.github.com/repos/mtrippled/rippled/contributors","subscribers_url":"https://api.github.com/repos/mtrippled/rippled/subscribers","subscription_url":"https://api.github.com/repos/mtrippled/rippled/subscription","commits_url":"https://api.github.com/repos/mtrippled/rippled/commits{/sha}","git_commits_url":"https://api.github.com/repos/mtrippled/rippled/git/commits{/sha}","comments_url":"https://api.github.com/repos/mtrippled/rippled/comments{/number}","issue_comment_url":"https://api.github.com/repos/mtrippled/rippled/issues/comments/{number}","contents_url":"https://api.github.com/repos/mtrippled/rippled/contents/{+path}","compare_url":"https://api.github.com/repos/mtrippled/rippled/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mtrippled/rippled/merges","archive_url":"https://api.github.com/repos/mtrippled/rippled/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mtrippled/rippled/downloads","issues_url":"https://api.github.com/repos/mtrippled/rippled/issues{/number}","pulls_url":"https://api.github.com/repos/mtrippled/rippled/pulls{/number}","milestones_url":"https://api.github.com/repos/mtrippled/rippled/milestones{/number}","notifications_url":"https://api.github.com/repos/mtrippled/rippled/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mtrippled/rippled/labels{/name}","releases_url":"https://api.github.com/repos/mtrippled/rippled/releases{/id}","created_at":"2014-07-06T21:44:01Z","updated_at":"2014-07-16T08:25:08Z","pushed_at":"2014-11-13T20:21:46Z","git_url":"git://github.com/mtrippled/rippled.git","ssh_url":"git@github.com:mtrippled/rippled.git","clone_url":"https://github.com/mtrippled/rippled.git","svn_url":"https://github.com/mtrippled/rippled","homepage":"https://ripple.com","size":46488,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"develop"}},"base":{"label":"ripple:develop","ref":"develop","sha":"0829ee9234338c1f7facc304a3f27eefa1c27daa","user":{"login":"ripple","id":3165523,"avatar_url":"https://avatars.githubusercontent.com/u/3165523?v=3","gravatar_id":"","url":"https://api.github.com/users/ripple","html_url":"https://github.com/ripple","followers_url":"https://api.github.com/users/ripple/followers","following_url":"https://api.github.com/users/ripple/following{/other_user}","gists_url":"https://api.github.com/users/ripple/gists{/gist_id}","starred_url":"https://api.github.com/users/ripple/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ripple/subscriptions","organizations_url":"https://api.github.com/users/ripple/orgs","repos_url":"https://api.github.com/users/ripple/repos","events_url":"https://api.github.com/users/ripple/events{/privacy}","received_events_url":"https://api.github.com/users/ripple/received_events","type":"Organization","site_admin":false},"repo":{"id":2724167,"name":"rippled","full_name":"ripple/rippled","owner":{"login":"ripple","id":3165523,"avatar_url":"https://avatars.githubusercontent.com/u/3165523?v=3","gravatar_id":"","url":"https://api.github.com/users/ripple","html_url":"https://github.com/ripple","followers_url":"https://api.github.com/users/ripple/followers","following_url":"https://api.github.com/users/ripple/following{/other_user}","gists_url":"https://api.github.com/users/ripple/gists{/gist_id}","starred_url":"https://api.github.com/users/ripple/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ripple/subscriptions","organizations_url":"https://api.github.com/users/ripple/orgs","repos_url":"https://api.github.com/users/ripple/repos","events_url":"https://api.github.com/users/ripple/events{/privacy}","received_events_url":"https://api.github.com/users/ripple/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ripple/rippled","description":"Ripple peer-to-peer network daemon","fork":false,"url":"https://api.github.com/repos/ripple/rippled","forks_url":"https://api.github.com/repos/ripple/rippled/forks","keys_url":"https://api.github.com/repos/ripple/rippled/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ripple/rippled/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ripple/rippled/teams","hooks_url":"https://api.github.com/repos/ripple/rippled/hooks","issue_events_url":"https://api.github.com/repos/ripple/rippled/issues/events{/number}","events_url":"https://api.github.com/repos/ripple/rippled/events","assignees_url":"https://api.github.com/repos/ripple/rippled/assignees{/user}","branches_url":"https://api.github.com/repos/ripple/rippled/branches{/branch}","tags_url":"https://api.github.com/repos/ripple/rippled/tags","blobs_url":"https://api.github.com/repos/ripple/rippled/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ripple/rippled/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ripple/rippled/git/refs{/sha}","trees_url":"https://api.github.com/repos/ripple/rippled/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ripple/rippled/statuses/{sha}","languages_url":"https://api.github.com/repos/ripple/rippled/languages","stargazers_url":"https://api.github.com/repos/ripple/rippled/stargazers","contributors_url":"https://api.github.com/repos/ripple/rippled/contributors","subscribers_url":"https://api.github.com/repos/ripple/rippled/subscribers","subscription_url":"https://api.github.com/repos/ripple/rippled/subscription","commits_url":"https://api.github.com/repos/ripple/rippled/commits{/sha}","git_commits_url":"https://api.github.com/repos/ripple/rippled/git/commits{/sha}","comments_url":"https://api.github.com/repos/ripple/rippled/comments{/number}","issue_comment_url":"https://api.github.com/repos/ripple/rippled/issues/comments/{number}","contents_url":"https://api.github.com/repos/ripple/rippled/contents/{+path}","compare_url":"https://api.github.com/repos/ripple/rippled/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ripple/rippled/merges","archive_url":"https://api.github.com/repos/ripple/rippled/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ripple/rippled/downloads","issues_url":"https://api.github.com/repos/ripple/rippled/issues{/number}","pulls_url":"https://api.github.com/repos/ripple/rippled/pulls{/number}","milestones_url":"https://api.github.com/repos/ripple/rippled/milestones{/number}","notifications_url":"https://api.github.com/repos/ripple/rippled/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ripple/rippled/labels{/name}","releases_url":"https://api.github.com/repos/ripple/rippled/releases{/id}","created_at":"2011-11-07T04:40:15Z","updated_at":"2014-11-12T05:35:10Z","pushed_at":"2014-11-14T19:24:51Z","git_url":"git://github.com/ripple/rippled.git","ssh_url":"git@github.com:ripple/rippled.git","clone_url":"https://github.com/ripple/rippled.git","svn_url":"https://github.com/ripple/rippled","homepage":"https://ripple.com","size":100449,"stargazers_count":872,"watchers_count":872,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":231,"mirror_url":null,"open_issues_count":136,"forks":231,"open_issues":136,"watchers":872,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/ripple/rippled/pulls/717"},"html":{"href":"https://github.com/ripple/rippled/pull/717"},"issue":{"href":"https://api.github.com/repos/ripple/rippled/issues/717"},"comments":{"href":"https://api.github.com/repos/ripple/rippled/issues/717/comments"},"review_comments":{"href":"https://api.github.com/repos/ripple/rippled/pulls/717/comments"},"review_comment":{"href":"https://api.github.com/repos/ripple/rippled/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/ripple/rippled/pulls/717/commits"},"statuses":{"href":"https://api.github.com/repos/ripple/rippled/statuses/2b41be6aa7f4e8f0ab70d853460450e5216c3297"}}}}