Skip to content

Commit c88c91e

Browse files
committed
Add SQLite connection pool and cgroups support with tests
1 parent be51ce1 commit c88c91e

8 files changed

Lines changed: 376 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,29 @@ if(UNIX AND EXISTS "${CMAKE_SOURCE_DIR}/src/sandbox/ruleset.cpp")
111111
endif()
112112
endif()
113113

114+
# SQLite pool test
115+
find_package(SQLite3)
116+
if (SQLite3_FOUND)
117+
message(STATUS "Found SQLite3: ${SQLite3_LIBRARIES}")
118+
add_executable(sqlite_pool_test tests/sqlite_pool_test.cpp src/services/sqlite_pool.cpp)
119+
target_include_directories(sqlite_pool_test PRIVATE src)
120+
target_link_libraries(sqlite_pool_test PRIVATE ${SQLite3_LIBRARIES})
121+
add_test(NAME sqlite_pool_test COMMAND sqlite_pool_test)
122+
set_tests_properties(sqlite_pool_test PROPERTIES LABELS "smoke;sqlite")
123+
target_compile_definitions(native_node PRIVATE -DHAVE_SQLITE3=1)
124+
target_link_libraries(native_node PRIVATE ${SQLite3_LIBRARIES})
125+
else()
126+
message(WARNING "SQLite3 not found: sqlite pool tests will be skipped")
127+
endif()
128+
129+
# cgroups v2 test
130+
if(UNIX)
131+
add_executable(cgroups_test tests/cgroups_test.cpp src/sandbox/cgroups.cpp)
132+
target_include_directories(cgroups_test PRIVATE src)
133+
add_test(NAME cgroups_test COMMAND cgroups_test)
134+
set_tests_properties(cgroups_test PROPERTIES LABELS "smoke;cgroups")
135+
endif()
136+
114137

115138
# Installation
116139
install(TARGETS native_node RUNTIME DESTINATION bin)

src/sandbox/cgroups.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include "cgroups.h"
2+
#include <string>
3+
#include <fstream>
4+
#include <iostream>
5+
#include <sys/stat.h>
6+
#include <unistd.h>
7+
#include <fcntl.h>
8+
#include <cerrno>
9+
#include <cstring>
10+
#include <sstream>
11+
12+
namespace sandbox {
13+
14+
static const std::string CGROUP_ROOT = "/sys/fs/cgroup";
15+
16+
bool is_cgroup_v2_available() {
17+
struct stat st;
18+
std::string controllers = CGROUP_ROOT + "/cgroup.controllers";
19+
if (stat(controllers.c_str(), &st) == 0) return true;
20+
return false;
21+
}
22+
23+
static bool write_file(const std::string& path, const std::string& data) {
24+
std::ofstream ofs(path);
25+
if (!ofs.is_open()) return false;
26+
ofs << data;
27+
return !ofs.fail();
28+
}
29+
30+
std::string create_transient_cgroup(const std::string& name) {
31+
if (!is_cgroup_v2_available()) return "";
32+
std::string path = CGROUP_ROOT + "/" + name;
33+
// mkdir
34+
if (mkdir(path.c_str(), 0755) != 0) {
35+
if (errno == EEXIST) return path;
36+
std::cerr << "[cgroups] mkdir failed: " << strerror(errno) << std::endl;
37+
return "";
38+
}
39+
return path;
40+
}
41+
42+
bool add_pid_to_cgroup(const std::string& cgroup_path, pid_t pid) {
43+
if (pid == 0) pid = getpid();
44+
std::string procs = cgroup_path + "/cgroup.procs";
45+
std::ostringstream ss;
46+
ss << pid << "\n";
47+
if (!write_file(procs, ss.str())) {
48+
std::cerr << "[cgroups] failed to write pid to " << procs << std::endl;
49+
return false;
50+
}
51+
return true;
52+
}
53+
54+
bool remove_transient_cgroup(const std::string& cgroup_path) {
55+
// attempt to rmdir (will fail if not empty)
56+
if (rmdir(cgroup_path.c_str()) != 0) {
57+
std::cerr << "[cgroups] rmdir failed: " << strerror(errno) << std::endl;
58+
return false;
59+
}
60+
return true;
61+
}
62+
63+
} // namespace sandbox

src/sandbox/cgroups.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
namespace sandbox {
6+
7+
// Detect whether cgroup v2 is available on this system (unified hierarchy)
8+
bool is_cgroup_v2_available();
9+
10+
// Create a transient cgroup under the given base (e.g., "/sys/fs/cgroup") with the
11+
// provided name. Returns the full path of the created cgroup on success, empty string on failure.
12+
// Note: Requires appropriate privileges to write to the cgroup filesystem.
13+
std::string create_transient_cgroup(const std::string& name);
14+
15+
// Add the current process (or PID) to the given cgroup path (full path), returns true on success.
16+
bool add_pid_to_cgroup(const std::string& cgroup_path, pid_t pid = 0);
17+
18+
// Remove the transient cgroup; best effort.
19+
bool remove_transient_cgroup(const std::string& cgroup_path);
20+
21+
} // namespace sandbox

src/services/services.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#include "services.h"
22
#include <iostream>
33
#include <atomic>
4+
#include "sqlite_pool.h"
5+
6+
static std::unique_ptr<services::SQLiteConnectionPool> g_sqlite_pool;
47

58
namespace services {
69

@@ -9,12 +12,23 @@ static std::atomic<bool> g_initialized{false};
912

1013
bool initialize() {
1114
std::cout << "[services] initializing (SQLite, MailApp, PropertyStore stubs)" << std::endl;
15+
// Initialize SQLite connection pool (WAL mode)
16+
g_sqlite_pool = std::make_unique<services::SQLiteConnectionPool>("./data/native_node.db", 4);
17+
if (!g_sqlite_pool->initialize()) {
18+
std::cerr << "[services] failed to initialize SQLite pool" << std::endl;
19+
return false;
20+
}
21+
1222
g_initialized = true;
1323
return true;
1424
}
1525

1626
void shutdown() {
1727
std::cout << "[services] shutdown" << std::endl;
28+
if (g_sqlite_pool) {
29+
g_sqlite_pool->shutdown();
30+
g_sqlite_pool.reset();
31+
}
1832
g_initialized = false;
1933
}
2034

src/services/sqlite_pool.cpp

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#include "sqlite_pool.h"
2+
#include <iostream>
3+
#include <cassert>
4+
#include <thread>
5+
6+
namespace services {
7+
8+
SQLiteConnectionPool::SQLiteConnectionPool(const std::string& db_path, size_t pool_size)
9+
: db_path_(db_path), pool_size_(pool_size) {}
10+
11+
SQLiteConnectionPool::~SQLiteConnectionPool() {
12+
shutdown();
13+
}
14+
15+
bool SQLiteConnectionPool::initialize() {
16+
std::lock_guard<std::mutex> lk(mtx_);
17+
if (initialized_) return true;
18+
19+
conns_.resize(pool_size_, nullptr);
20+
in_use_.assign(pool_size_, false);
21+
22+
for (size_t i = 0; i < pool_size_; ++i) {
23+
sqlite3* db = nullptr;
24+
int rc = sqlite3_open(db_path_.c_str(), &db);
25+
if (rc != SQLITE_OK) {
26+
std::cerr << "[sqlite_pool] failed to open db: " << sqlite3_errstr(rc) << std::endl;
27+
// close previously opened
28+
for (auto c : conns_) if (c) sqlite3_close(c);
29+
return false;
30+
}
31+
32+
// Enable WAL mode
33+
char* errmsg = nullptr;
34+
rc = sqlite3_exec(db, "PRAGMA journal_mode=WAL;", nullptr, nullptr, &errmsg);
35+
if (rc != SQLITE_OK) {
36+
std::cerr << "[sqlite_pool] failed to set WAL mode: " << (errmsg ? errmsg : "") << std::endl;
37+
if (errmsg) sqlite3_free(errmsg);
38+
sqlite3_close(db);
39+
for (auto c : conns_) if (c) sqlite3_close(c);
40+
return false;
41+
}
42+
43+
conns_[i] = db;
44+
}
45+
46+
initialized_ = true;
47+
std::cout << "[sqlite_pool] initialized with " << pool_size_ << " connections (WAL enabled)" << std::endl;
48+
return true;
49+
}
50+
51+
void SQLiteConnectionPool::shutdown() {
52+
std::lock_guard<std::mutex> lk(mtx_);
53+
for (auto c : conns_) {
54+
if (c) sqlite3_close(c);
55+
}
56+
conns_.clear();
57+
in_use_.clear();
58+
initialized_ = false;
59+
}
60+
61+
sqlite3* SQLiteConnectionPool::acquire() {
62+
std::unique_lock<std::mutex> lk(mtx_);
63+
cv_.wait(lk, [this]() {
64+
for (bool b : in_use_) if (!b) return true; return false;
65+
});
66+
for (size_t i = 0; i < conns_.size(); ++i) {
67+
if (!in_use_[i]) {
68+
in_use_[i] = true;
69+
return conns_[i];
70+
}
71+
}
72+
return nullptr; // should not reach
73+
}
74+
75+
void SQLiteConnectionPool::release(sqlite3* conn) {
76+
std::lock_guard<std::mutex> lk(mtx_);
77+
for (size_t i = 0; i < conns_.size(); ++i) {
78+
if (conns_[i] == conn) {
79+
in_use_[i] = false;
80+
cv_.notify_one();
81+
return;
82+
}
83+
}
84+
}
85+
86+
// SQLiteConnHandle
87+
SQLiteConnHandle::SQLiteConnHandle(SQLiteConnectionPool& pool, sqlite3* conn)
88+
: pool_(pool), conn_(conn) {}
89+
90+
SQLiteConnHandle::~SQLiteConnHandle() {
91+
if (conn_) pool_.release(conn_);
92+
}
93+
94+
sqlite3* SQLiteConnHandle::get() { return conn_; }
95+
96+
} // namespace services

src/services/sqlite_pool.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <memory>
5+
#include <mutex>
6+
#include <condition_variable>
7+
#include <vector>
8+
#include <sqlite3.h>
9+
10+
namespace services {
11+
12+
// A simple fixed-size SQLite connection pool that opens multiple connections
13+
// to the same database file and sets WAL mode on each connection.
14+
class SQLiteConnectionPool {
15+
public:
16+
explicit SQLiteConnectionPool(const std::string& db_path, size_t pool_size = 4);
17+
~SQLiteConnectionPool();
18+
19+
// Initialize the pool (open connections and set PRAGMA journal_mode=WAL)
20+
bool initialize();
21+
void shutdown();
22+
23+
// Acquire a sqlite3* connection from the pool. Blocks until available.
24+
sqlite3* acquire();
25+
// Release a connection back to the pool
26+
void release(sqlite3* conn);
27+
28+
private:
29+
std::string db_path_;
30+
size_t pool_size_;
31+
std::vector<sqlite3*> conns_;
32+
std::vector<bool> in_use_;
33+
std::mutex mtx_;
34+
std::condition_variable cv_;
35+
bool initialized_ = false;
36+
};
37+
38+
// RAII handle that returns connection to pool on destruction
39+
class SQLiteConnHandle {
40+
public:
41+
SQLiteConnHandle(SQLiteConnectionPool& pool, sqlite3* conn);
42+
~SQLiteConnHandle();
43+
sqlite3* get();
44+
private:
45+
SQLiteConnectionPool& pool_;
46+
sqlite3* conn_ = nullptr;
47+
};
48+
49+
} // namespace services

tests/cgroups_test.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include <iostream>
2+
#include "sandbox/cgroups.h"
3+
4+
int main() {
5+
std::cout << "cgroups_test: starting" << std::endl;
6+
if (!sandbox::is_cgroup_v2_available()) {
7+
std::cout << "cgroup v2 not available; skipping test" << std::endl;
8+
return 0;
9+
}
10+
11+
const std::string name = "native_node_test_12345";
12+
std::string path = sandbox::create_transient_cgroup(name);
13+
if (path.empty()) {
14+
std::cerr << "failed to create transient cgroup (maybe insufficient privileges)" << std::endl;
15+
return 2;
16+
}
17+
18+
if (!sandbox::add_pid_to_cgroup(path)) {
19+
std::cerr << "failed to add pid to cgroup" << std::endl;
20+
sandbox::remove_transient_cgroup(path);
21+
return 2;
22+
}
23+
24+
if (!sandbox::remove_transient_cgroup(path)) {
25+
std::cerr << "failed to remove cgroup (may need cleanup)" << std::endl;
26+
return 2;
27+
}
28+
29+
std::cout << "cgroups_test: succeeded" << std::endl;
30+
return 0;
31+
}

0 commit comments

Comments
 (0)