Compare commits

...

3 Commits

Author SHA1 Message Date
木头云
cf5738eb3a fix(test): replace C++17 structured bindings with C++14 compatible code
Problem:
- Two test cases in test_buffer.cpp used structured bindings (auto [a, b])
- Structured bindings are a C++17 feature
- Project requires C++14 compatibility

Solution:
- Replace 'auto [ptr, size] = buf.to_tuple()' with C++14 compatible code
- Use std::get<N>() to extract tuple elements
- Modified tests: ToTupleNonConst, ToTupleConst

Changes:
- Line 239: Use std::get<0/1>(tuple) instead of structured binding
- Line 252: Use std::get<0/1>(tuple) instead of structured binding
- Add explanatory comments for clarity

This ensures the test suite compiles with C++14 standard.
2025-11-30 11:16:03 +00:00
木头云
c31ef988c1 fix(shm): remove redundant self-assignment in shm_win.cpp
- Remove useless 'ii->size_ = ii->size_;' statement at line 140
- The user-requested size is already set in acquire() function
- Simplify else branch to just a comment for clarity
- No functional change, just code cleanup
2025-11-30 11:06:04 +00:00
木头云
7726742157 feat(shm): implement reference counting for Windows shared memory
Problem:
- Reference counting tests fail on Windows (ReleaseMemory, ReferenceCount,
  SubtractReference, HandleRef, HandleSubRef)
- get_ref() and sub_ref() were stub implementations returning 0/doing nothing
- CreateFileMapping HANDLE lacks built-in reference counting mechanism

Solution:
- Implement reference counting using std::atomic<std::int32_t> stored at
  the end of shared memory (same strategy as POSIX version)
- Add calc_size() helper to allocate extra space for atomic counter
- Add acc_of() helper to access the atomic counter at the end of memory
- Modify acquire() to allocate calc_size(size) instead of size
- Modify get_mem() to initialize counter to 1 on first mapping
- Modify release() to decrement counter and return ref count before decrement
- Implement get_ref() to return current reference count
- Implement sub_ref() to atomically decrement reference count
- Convert file from Windows (CRLF) to Unix (LF) line endings for consistency

Key Implementation Details:
1. Reference counter stored at end of shared memory (aligned to info_t)
2. First get_mem() call: fetch_add(1) initializes counter to 1
3. release() returns ref count before decrement (for semantics compatibility)
4. Memory layout: [user data][padding][atomic<int32_t> counter]
5. Uses memory_order_acquire/release/acq_rel for proper synchronization

This makes Windows implementation match POSIX behavior and ensures all
reference counting tests pass on Windows platform.
2025-11-30 10:55:24 +00:00
2 changed files with 193 additions and 146 deletions

View File

@ -5,6 +5,7 @@
#include <Windows.h>
#endif
#include <atomic>
#include <string>
#include <utility>
@ -20,12 +21,24 @@
namespace {
struct info_t {
std::atomic<std::int32_t> acc_;
};
struct id_info_t {
HANDLE h_ = NULL;
void* mem_ = nullptr;
std::size_t size_ = 0;
};
constexpr std::size_t calc_size(std::size_t size) {
return ((((size - 1) / alignof(info_t)) + 1) * alignof(info_t)) + sizeof(info_t);
}
inline auto& acc_of(void* mem, std::size_t size) {
return reinterpret_cast<info_t*>(static_cast<ipc::byte_t*>(mem) + size - sizeof(info_t))->acc_;
}
} // internal-linkage
namespace ipc {
@ -48,8 +61,9 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
}
// Creates or opens a named file mapping object for a specified file.
else {
std::size_t alloc_size = calc_size(size);
h = ::CreateFileMapping(INVALID_HANDLE_VALUE, detail::get_sa(), PAGE_READWRITE | SEC_COMMIT,
0, static_cast<DWORD>(size), fmt_name.c_str());
0, static_cast<DWORD>(alloc_size), fmt_name.c_str());
DWORD err = ::GetLastError();
// If the object exists before the function call, the function returns a handle to the existing object
// (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS.
@ -68,12 +82,28 @@ id_t acquire(char const * name, std::size_t size, unsigned mode) {
return ii;
}
std::int32_t get_ref(id_t) {
std::int32_t get_ref(id_t id) {
if (id == nullptr) {
return 0;
}
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
return 0;
}
return acc_of(ii->mem_, calc_size(ii->size_)).load(std::memory_order_acquire);
}
void sub_ref(id_t) {
// Do Nothing.
void sub_ref(id_t id) {
if (id == nullptr) {
ipc::error("fail sub_ref: invalid id (null)\n");
return;
}
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
ipc::error("fail sub_ref: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
return;
}
acc_of(ii->mem_, calc_size(ii->size_)).fetch_sub(1, std::memory_order_acq_rel);
}
void * get_mem(id_t id, std::size_t * size) {
@ -100,9 +130,16 @@ void * get_mem(id_t id, std::size_t * size) {
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
return nullptr;
}
std::size_t actual_size = static_cast<std::size_t>(mem_info.RegionSize);
if (ii->size_ == 0) {
// Opening existing shared memory
ii->size_ = actual_size - sizeof(info_t);
}
// else: Keep user-requested size (already set in acquire)
ii->mem_ = mem;
ii->size_ = static_cast<std::size_t>(mem_info.RegionSize);
if (size != nullptr) *size = ii->size_;
// Initialize or increment reference counter
acc_of(mem, calc_size(ii->size_)).fetch_add(1, std::memory_order_release);
return static_cast<void *>(mem);
}
@ -111,17 +148,21 @@ std::int32_t release(id_t id) noexcept {
ipc::error("fail release: invalid id (null)\n");
return -1;
}
std::int32_t ret = -1;
auto ii = static_cast<id_info_t*>(id);
if (ii->mem_ == nullptr || ii->size_ == 0) {
ipc::error("fail release: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
}
else ::UnmapViewOfFile(static_cast<LPCVOID>(ii->mem_));
else {
ret = acc_of(ii->mem_, calc_size(ii->size_)).fetch_sub(1, std::memory_order_acq_rel);
::UnmapViewOfFile(static_cast<LPCVOID>(ii->mem_));
}
if (ii->h_ == NULL) {
ipc::error("fail release: invalid id (h = null)\n");
}
else ::CloseHandle(ii->h_);
mem::free(ii);
return 0;
return ret;
}
void remove(id_t id) noexcept {

View File

@ -236,7 +236,10 @@ TEST_F(BufferTest, ToTupleNonConst) {
buffer buf(data, 25, DestructorTracker::destructor);
auto [ptr, size] = buf.to_tuple();
// C++14 compatible: use std::get instead of structured binding
auto tuple = buf.to_tuple();
auto ptr = std::get<0>(tuple);
auto size = std::get<1>(tuple);
EXPECT_EQ(ptr, buf.data());
EXPECT_EQ(size, buf.size());
EXPECT_EQ(size, 25u);
@ -249,7 +252,10 @@ TEST_F(BufferTest, ToTupleConst) {
const buffer buf(data, 30, DestructorTracker::destructor);
auto [ptr, size] = buf.to_tuple();
// C++14 compatible: use std::get instead of structured binding
auto tuple = buf.to_tuple();
auto ptr = std::get<0>(tuple);
auto size = std::get<1>(tuple);
EXPECT_EQ(ptr, buf.data());
EXPECT_EQ(size, buf.size());
EXPECT_EQ(size, 30u);