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:
木头云 2025-11-30 10:55:24 +00:00
parent b9dd75ccd9
commit 7726742157

View File

@ -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) {
if (id == nullptr) {
return 0; 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;
} }
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 {
// Should match the size we allocated in acquire
ii->size_ = ii->size_; // Keep user-requested size
}
ii->mem_ = mem; ii->mem_ = mem;
ii->size_ = static_cast<std::size_t>(mem_info.RegionSize);
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 {