rippled
SociDB.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012-2015 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #if defined(__clang__)
21 #pragma clang diagnostic push
22 #pragma clang diagnostic ignored "-Wdeprecated"
23 #endif
24 
25 #include <ripple/basics/ByteUtilities.h>
26 #include <ripple/basics/contract.h>
27 #include <ripple/core/Config.h>
28 #include <ripple/core/ConfigSections.h>
29 #include <ripple/core/DatabaseCon.h>
30 #include <ripple/core/SociDB.h>
31 #include <boost/filesystem.hpp>
32 #include <memory>
33 #include <soci/sqlite3/soci-sqlite3.h>
34 
35 namespace ripple {
36 
37 static auto checkpointPageCount = 1000;
38 
39 namespace detail {
40 
43  std::string const& name,
44  std::string const& dir,
45  std::string const& ext)
46 {
47  if (name.empty())
48  {
49  Throw<std::runtime_error>(
50  "Sqlite databases must specify a dir and a name. Name: " + name +
51  " Dir: " + dir);
52  }
53  boost::filesystem::path file(dir);
54  if (is_directory(file))
55  file /= name + ext;
56  return file.string();
57 }
58 
60 getSociInit(BasicConfig const& config, std::string const& dbName)
61 {
62  auto const& section = config.section("sqdb");
63  auto const backendName = get(section, "backend", "sqlite");
64 
65  if (backendName != "sqlite")
66  Throw<std::runtime_error>("Unsupported soci backend: " + backendName);
67 
68  auto const path = config.legacy("database_path");
69  auto const ext =
70  dbName == "validators" || dbName == "peerfinder" ? ".sqlite" : ".db";
71  return detail::getSociSqliteInit(dbName, path, ext);
72 }
73 
74 } // namespace detail
75 
76 DBConfig::DBConfig(std::string const& dbPath) : connectionString_(dbPath)
77 {
78 }
79 
80 DBConfig::DBConfig(BasicConfig const& config, std::string const& dbName)
81  : DBConfig(detail::getSociInit(config, dbName))
82 {
83 }
84 
87 {
88  return connectionString_;
89 }
90 
91 void
92 DBConfig::open(soci::session& s) const
93 {
94  s.open(soci::sqlite3, connectionString());
95 }
96 
97 void
98 open(soci::session& s, BasicConfig const& config, std::string const& dbName)
99 {
100  DBConfig(config, dbName).open(s);
101 }
102 
103 void
105  soci::session& s,
106  std::string const& beName,
107  std::string const& connectionString)
108 {
109  if (beName == "sqlite")
110  s.open(soci::sqlite3, connectionString);
111  else
112  Throw<std::runtime_error>("Unsupported soci backend: " + beName);
113 }
114 
115 static sqlite_api::sqlite3*
116 getConnection(soci::session& s)
117 {
118  sqlite_api::sqlite3* result = nullptr;
119  auto be = s.get_backend();
120  if (auto b = dynamic_cast<soci::sqlite3_session_backend*>(be))
121  result = b->conn_;
122 
123  if (!result)
124  Throw<std::logic_error>("Didn't get a database connection.");
125 
126  return result;
127 }
128 
130 getKBUsedAll(soci::session& s)
131 {
132  if (!getConnection(s))
133  Throw<std::logic_error>("No connection found.");
134  return static_cast<size_t>(
135  sqlite_api::sqlite3_memory_used() / kilobytes(1));
136 }
137 
139 getKBUsedDB(soci::session& s)
140 {
141  // This function will have to be customized when other backends are added
142  if (auto conn = getConnection(s))
143  {
144  int cur = 0, hiw = 0;
145  sqlite_api::sqlite3_db_status(
146  conn, SQLITE_DBSTATUS_CACHE_USED, &cur, &hiw, 0);
147  return cur / kilobytes(1);
148  }
149  Throw<std::logic_error>("");
150  return 0; // Silence compiler warning.
151 }
152 
153 void
154 convert(soci::blob& from, std::vector<std::uint8_t>& to)
155 {
156  to.resize(from.get_len());
157  if (to.empty())
158  return;
159  from.read(0, reinterpret_cast<char*>(&to[0]), from.get_len());
160 }
161 
162 void
163 convert(soci::blob& from, std::string& to)
164 {
166  convert(from, tmp);
167  to.assign(tmp.begin(), tmp.end());
168 }
169 
170 void
171 convert(std::vector<std::uint8_t> const& from, soci::blob& to)
172 {
173  if (!from.empty())
174  to.write(0, reinterpret_cast<char const*>(&from[0]), from.size());
175  else
176  to.trim(0);
177 }
178 
179 void
180 convert(std::string const& from, soci::blob& to)
181 {
182  if (!from.empty())
183  to.write(0, from.data(), from.size());
184  else
185  to.trim(0);
186 }
187 
188 namespace {
189 
199 class WALCheckpointer : public Checkpointer
200 {
201 public:
202  WALCheckpointer(
203  std::uintptr_t id,
205  JobQueue& q,
206  Logs& logs)
207  : id_(id)
208  , session_(std::move(session))
209  , jobQueue_(q)
210  , j_(logs.journal("WALCheckpointer"))
211  {
212  if (auto [conn, keepAlive] = getConnection(); conn)
213  {
214  (void)keepAlive;
215  sqlite_api::sqlite3_wal_hook(
216  conn, &sqliteWALHook, reinterpret_cast<void*>(id_));
217  }
218  }
219 
221  getConnection() const
222  {
223  if (auto p = session_.lock())
224  {
225  return {ripple::getConnection(*p), p};
226  }
227  return {nullptr, std::shared_ptr<soci::session>{}};
228  }
229 
231  id() const override
232  {
233  return id_;
234  }
235 
236  ~WALCheckpointer() override = default;
237 
238  void
239  schedule() override
240  {
241  {
242  std::lock_guard lock(mutex_);
243  if (running_)
244  return;
245  running_ = true;
246  }
247 
248  // If the Job is not added to the JobQueue then we're not running_.
249  if (!jobQueue_.addJob(
250  jtWAL,
251  "WAL",
252  // If the owning DatabaseCon is destroyed, no need to checkpoint
253  // or keep the checkpointer alive so use a weak_ptr to this.
254  // There is a separate check in `checkpoint` for a valid
255  // connection in the rare case when the DatabaseCon is destroyed
256  // after locking this weak_ptr
257  [wp = std::weak_ptr<Checkpointer>{shared_from_this()}]() {
258  if (auto self = wp.lock())
259  self->checkpoint();
260  }))
261  {
262  std::lock_guard lock(mutex_);
263  running_ = false;
264  }
265  }
266 
267  void
268  checkpoint() override
269  {
270  auto [conn, keepAlive] = getConnection();
271  (void)keepAlive;
272  if (!conn)
273  return;
274 
275  int log = 0, ckpt = 0;
276  int ret = sqlite3_wal_checkpoint_v2(
277  conn, nullptr, SQLITE_CHECKPOINT_PASSIVE, &log, &ckpt);
278 
279  auto fname = sqlite3_db_filename(conn, "main");
280  if (ret != SQLITE_OK)
281  {
282  auto jm = (ret == SQLITE_LOCKED) ? j_.trace() : j_.warn();
283  JLOG(jm) << "WAL(" << fname << "): error " << ret;
284  }
285  else
286  {
287  JLOG(j_.trace()) << "WAL(" << fname << "): frames=" << log
288  << ", written=" << ckpt;
289  }
290 
291  std::lock_guard lock(mutex_);
292  running_ = false;
293  }
294 
295 protected:
296  std::uintptr_t const id_;
297  // session is owned by the DatabaseCon parent that holds the checkpointer.
298  // It is possible (tho rare) for the DatabaseCon class to be destoryed
299  // before the checkpointer.
301  std::mutex mutex_;
302  JobQueue& jobQueue_;
303 
304  bool running_ = false;
305  beast::Journal const j_;
306 
307  static int
308  sqliteWALHook(
309  void* cpId,
310  sqlite_api::sqlite3* conn,
311  const char* dbName,
312  int walSize)
313  {
314  if (walSize >= checkpointPageCount)
315  {
316  if (auto checkpointer =
317  checkpointerFromId(reinterpret_cast<std::uintptr_t>(cpId)))
318  {
319  checkpointer->schedule();
320  }
321  else
322  {
323  sqlite_api::sqlite3_wal_hook(conn, nullptr, nullptr);
324  }
325  }
326  return SQLITE_OK;
327  }
328 };
329 
330 } // namespace
331 
334  std::uintptr_t id,
336  JobQueue& queue,
337  Logs& logs)
338 {
339  return std::make_shared<WALCheckpointer>(
340  id, std::move(session), queue, logs);
341 }
342 
343 } // namespace ripple
344 
345 #if defined(__clang__)
346 #pragma clang diagnostic pop
347 #endif
std::vector::resize
T resize(T... args)
std::lock
T lock(T... args)
std::string
STL class.
std::shared_ptr< soci::session >
ripple::Logs
Manages partitions for logging.
Definition: Log.h:48
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:309
ripple::detail::getSociInit
std::string getSociInit(BasicConfig const &config, std::string const &dbName)
Definition: SociDB.cpp:60
ripple::DBConfig::connectionString
std::string connectionString() const
Definition: SociDB.cpp:86
std::pair
ripple::convert
void convert(soci::blob &from, std::vector< std::uint8_t > &to)
Definition: SociDB.cpp:154
ripple::DBConfig::open
void open(soci::session &s) const
Definition: SociDB.cpp:92
std::vector
STL class.
std::vector::size
T size(T... args)
ripple::checkpointerFromId
std::shared_ptr< Checkpointer > checkpointerFromId(std::uintptr_t id)
Definition: DatabaseCon.cpp:79
ripple::getKBUsedDB
std::uint32_t getKBUsedDB(soci::session &s)
Definition: SociDB.cpp:139
beast::Journal::warn
Stream warn() const
Definition: Journal.h:327
std::lock_guard
STL class.
ripple::kilobytes
constexpr auto kilobytes(T value) noexcept
Definition: ByteUtilities.h:27
ripple::DBConfig
DBConfig is used when a client wants to delay opening a soci::session after parsing the config parame...
Definition: SociDB.h:57
ripple::getKBUsedAll
std::uint32_t getKBUsedAll(soci::session &s)
Definition: SociDB.cpp:130
std::log
T log(T... args)
ripple::BasicConfig::legacy
void legacy(std::string const &section, std::string value)
Set a value that is not a key/value pair.
Definition: BasicConfig.cpp:164
ripple::DBConfig::DBConfig
DBConfig(std::string const &dbPath)
Definition: SociDB.cpp:76
ripple::detail::getSociSqliteInit
std::string getSociSqliteInit(std::string const &name, std::string const &dir, std::string const &ext)
Definition: SociDB.cpp:42
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint32_t
ripple::checkpointPageCount
static auto checkpointPageCount
Definition: SociDB.cpp:37
memory
ripple::JobQueue
A pool of threads to perform work.
Definition: JobQueue.h:55
std::weak_ptr
STL class.
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
std::vector::begin
T begin(T... args)
std
STL namespace.
ripple::jtWAL
@ jtWAL
Definition: Job.h:71
std::string::empty
T empty(T... args)
std::string::assign
T assign(T... args)
std::mutex
STL class.
std::vector::end
T end(T... args)
std::string::data
T data(T... args)
ripple::BasicConfig
Holds unparsed configuration information.
Definition: BasicConfig.h:215
ripple::makeCheckpointer
std::shared_ptr< Checkpointer > makeCheckpointer(std::uintptr_t id, std::weak_ptr< soci::session > session, JobQueue &queue, Logs &logs)
Returns a new checkpointer which makes checkpoints of a soci database every checkpointPageCount pages...
Definition: SociDB.cpp:333
ripple::DBConfig::connectionString_
std::string connectionString_
Definition: SociDB.h:59
ripple::get
T & get(EitherAmount &amt)
Definition: AmountSpec.h:118
ripple::open
void open(soci::session &s, BasicConfig const &config, std::string const &dbName)
Open a soci session.
Definition: SociDB.cpp:98
ripple::BasicConfig::section
Section & section(std::string const &name)
Returns the section with the given name.
Definition: BasicConfig.cpp:127
ripple::getConnection
static sqlite_api::sqlite3 * getConnection(soci::session &s)
Definition: SociDB.cpp:116