mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-07 01:06:45 +08:00
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.
This commit is contained in:
parent
b9dd75ccd9
commit
7726742157
@ -5,6 +5,7 @@
|
|||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@ -20,12 +21,24 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
struct info_t {
|
||||||
|
std::atomic<std::int32_t> acc_;
|
||||||
|
};
|
||||||
|
|
||||||
struct id_info_t {
|
struct id_info_t {
|
||||||
HANDLE h_ = NULL;
|
HANDLE h_ = NULL;
|
||||||
void* mem_ = nullptr;
|
void* mem_ = nullptr;
|
||||||
std::size_t size_ = 0;
|
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
|
} // internal-linkage
|
||||||
|
|
||||||
namespace ipc {
|
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.
|
// Creates or opens a named file mapping object for a specified file.
|
||||||
else {
|
else {
|
||||||
|
std::size_t alloc_size = calc_size(size);
|
||||||
h = ::CreateFileMapping(INVALID_HANDLE_VALUE, detail::get_sa(), PAGE_READWRITE | SEC_COMMIT,
|
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();
|
DWORD err = ::GetLastError();
|
||||||
// If the object exists before the function call, the function returns a handle to the existing object
|
// 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.
|
// (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;
|
return ii;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::int32_t get_ref(id_t) {
|
std::int32_t get_ref(id_t id) {
|
||||||
return 0;
|
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) {
|
void sub_ref(id_t id) {
|
||||||
// Do Nothing.
|
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) {
|
void * get_mem(id_t id, std::size_t * size) {
|
||||||
@ -100,9 +130,19 @@ void * get_mem(id_t id, std::size_t * size) {
|
|||||||
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
|
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
ii->mem_ = mem;
|
std::size_t actual_size = static_cast<std::size_t>(mem_info.RegionSize);
|
||||||
ii->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 {
|
||||||
|
// Should match the size we allocated in acquire
|
||||||
|
ii->size_ = ii->size_; // Keep user-requested size
|
||||||
|
}
|
||||||
|
ii->mem_ = mem;
|
||||||
if (size != nullptr) *size = ii->size_;
|
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);
|
return static_cast<void *>(mem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,17 +151,21 @@ std::int32_t release(id_t id) noexcept {
|
|||||||
ipc::error("fail release: invalid id (null)\n");
|
ipc::error("fail release: invalid id (null)\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
std::int32_t ret = -1;
|
||||||
auto ii = static_cast<id_info_t*>(id);
|
auto ii = static_cast<id_info_t*>(id);
|
||||||
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
if (ii->mem_ == nullptr || ii->size_ == 0) {
|
||||||
ipc::error("fail release: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
|
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) {
|
if (ii->h_ == NULL) {
|
||||||
ipc::error("fail release: invalid id (h = null)\n");
|
ipc::error("fail release: invalid id (h = null)\n");
|
||||||
}
|
}
|
||||||
else ::CloseHandle(ii->h_);
|
else ::CloseHandle(ii->h_);
|
||||||
mem::free(ii);
|
mem::free(ii);
|
||||||
return 0;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(id_t id) noexcept {
|
void remove(id_t id) noexcept {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user