Skip to content

Commit 7726742

Browse files
committed
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.
1 parent b9dd75c commit 7726742

File tree

1 file changed

+188
-144
lines changed

1 file changed

+188
-144
lines changed
Lines changed: 188 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,188 @@
1-
2-
#if defined(__MINGW32__)
3-
#include <windows.h>
4-
#else
5-
#include <Windows.h>
6-
#endif
7-
8-
#include <string>
9-
#include <utility>
10-
11-
#include "libipc/shm.h"
12-
#include "libipc/def.h"
13-
#include "libipc/pool_alloc.h"
14-
15-
#include "libipc/utility/log.h"
16-
#include "libipc/memory/resource.h"
17-
18-
#include "to_tchar.h"
19-
#include "get_sa.h"
20-
21-
namespace {
22-
23-
struct id_info_t {
24-
HANDLE h_ = NULL;
25-
void* mem_ = nullptr;
26-
std::size_t size_ = 0;
27-
};
28-
29-
} // internal-linkage
30-
31-
namespace ipc {
32-
namespace shm {
33-
34-
id_t acquire(char const * name, std::size_t size, unsigned mode) {
35-
if (!is_valid_string(name)) {
36-
ipc::error("fail acquire: name is empty\n");
37-
return nullptr;
38-
}
39-
HANDLE h;
40-
auto fmt_name = ipc::detail::to_tchar(name);
41-
// Opens a named file mapping object.
42-
if (mode == open) {
43-
h = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, fmt_name.c_str());
44-
if (h == NULL) {
45-
ipc::error("fail OpenFileMapping[%d]: %s\n", static_cast<int>(::GetLastError()), name);
46-
return nullptr;
47-
}
48-
}
49-
// Creates or opens a named file mapping object for a specified file.
50-
else {
51-
h = ::CreateFileMapping(INVALID_HANDLE_VALUE, detail::get_sa(), PAGE_READWRITE | SEC_COMMIT,
52-
0, static_cast<DWORD>(size), fmt_name.c_str());
53-
DWORD err = ::GetLastError();
54-
// If the object exists before the function call, the function returns a handle to the existing object
55-
// (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS.
56-
if ((mode == create) && (err == ERROR_ALREADY_EXISTS)) {
57-
if (h != NULL) ::CloseHandle(h);
58-
h = NULL;
59-
}
60-
if (h == NULL) {
61-
ipc::error("fail CreateFileMapping[%d]: %s\n", static_cast<int>(err), name);
62-
return nullptr;
63-
}
64-
}
65-
auto ii = mem::alloc<id_info_t>();
66-
ii->h_ = h;
67-
ii->size_ = size;
68-
return ii;
69-
}
70-
71-
std::int32_t get_ref(id_t) {
72-
return 0;
73-
}
74-
75-
void sub_ref(id_t) {
76-
// Do Nothing.
77-
}
78-
79-
void * get_mem(id_t id, std::size_t * size) {
80-
if (id == nullptr) {
81-
ipc::error("fail get_mem: invalid id (null)\n");
82-
return nullptr;
83-
}
84-
auto ii = static_cast<id_info_t*>(id);
85-
if (ii->mem_ != nullptr) {
86-
if (size != nullptr) *size = ii->size_;
87-
return ii->mem_;
88-
}
89-
if (ii->h_ == NULL) {
90-
ipc::error("fail to_mem: invalid id (h = null)\n");
91-
return nullptr;
92-
}
93-
LPVOID mem = ::MapViewOfFile(ii->h_, FILE_MAP_ALL_ACCESS, 0, 0, 0);
94-
if (mem == NULL) {
95-
ipc::error("fail MapViewOfFile[%d]\n", static_cast<int>(::GetLastError()));
96-
return nullptr;
97-
}
98-
MEMORY_BASIC_INFORMATION mem_info;
99-
if (::VirtualQuery(mem, &mem_info, sizeof(mem_info)) == 0) {
100-
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
101-
return nullptr;
102-
}
103-
ii->mem_ = mem;
104-
ii->size_ = static_cast<std::size_t>(mem_info.RegionSize);
105-
if (size != nullptr) *size = ii->size_;
106-
return static_cast<void *>(mem);
107-
}
108-
109-
std::int32_t release(id_t id) noexcept {
110-
if (id == nullptr) {
111-
ipc::error("fail release: invalid id (null)\n");
112-
return -1;
113-
}
114-
auto ii = static_cast<id_info_t*>(id);
115-
if (ii->mem_ == nullptr || ii->size_ == 0) {
116-
ipc::error("fail release: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
117-
}
118-
else ::UnmapViewOfFile(static_cast<LPCVOID>(ii->mem_));
119-
if (ii->h_ == NULL) {
120-
ipc::error("fail release: invalid id (h = null)\n");
121-
}
122-
else ::CloseHandle(ii->h_);
123-
mem::free(ii);
124-
return 0;
125-
}
126-
127-
void remove(id_t id) noexcept {
128-
if (id == nullptr) {
129-
ipc::error("fail release: invalid id (null)\n");
130-
return;
131-
}
132-
release(id);
133-
}
134-
135-
void remove(char const * name) noexcept {
136-
if (!is_valid_string(name)) {
137-
ipc::error("fail remove: name is empty\n");
138-
return;
139-
}
140-
// Do Nothing.
141-
}
142-
143-
} // namespace shm
144-
} // namespace ipc
1+
2+
#if defined(__MINGW32__)
3+
#include <windows.h>
4+
#else
5+
#include <Windows.h>
6+
#endif
7+
8+
#include <atomic>
9+
#include <string>
10+
#include <utility>
11+
12+
#include "libipc/shm.h"
13+
#include "libipc/def.h"
14+
#include "libipc/pool_alloc.h"
15+
16+
#include "libipc/utility/log.h"
17+
#include "libipc/memory/resource.h"
18+
19+
#include "to_tchar.h"
20+
#include "get_sa.h"
21+
22+
namespace {
23+
24+
struct info_t {
25+
std::atomic<std::int32_t> acc_;
26+
};
27+
28+
struct id_info_t {
29+
HANDLE h_ = NULL;
30+
void* mem_ = nullptr;
31+
std::size_t size_ = 0;
32+
};
33+
34+
constexpr std::size_t calc_size(std::size_t size) {
35+
return ((((size - 1) / alignof(info_t)) + 1) * alignof(info_t)) + sizeof(info_t);
36+
}
37+
38+
inline auto& acc_of(void* mem, std::size_t size) {
39+
return reinterpret_cast<info_t*>(static_cast<ipc::byte_t*>(mem) + size - sizeof(info_t))->acc_;
40+
}
41+
42+
} // internal-linkage
43+
44+
namespace ipc {
45+
namespace shm {
46+
47+
id_t acquire(char const * name, std::size_t size, unsigned mode) {
48+
if (!is_valid_string(name)) {
49+
ipc::error("fail acquire: name is empty\n");
50+
return nullptr;
51+
}
52+
HANDLE h;
53+
auto fmt_name = ipc::detail::to_tchar(name);
54+
// Opens a named file mapping object.
55+
if (mode == open) {
56+
h = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, fmt_name.c_str());
57+
if (h == NULL) {
58+
ipc::error("fail OpenFileMapping[%d]: %s\n", static_cast<int>(::GetLastError()), name);
59+
return nullptr;
60+
}
61+
}
62+
// Creates or opens a named file mapping object for a specified file.
63+
else {
64+
std::size_t alloc_size = calc_size(size);
65+
h = ::CreateFileMapping(INVALID_HANDLE_VALUE, detail::get_sa(), PAGE_READWRITE | SEC_COMMIT,
66+
0, static_cast<DWORD>(alloc_size), fmt_name.c_str());
67+
DWORD err = ::GetLastError();
68+
// If the object exists before the function call, the function returns a handle to the existing object
69+
// (with its current size, not the specified size), and GetLastError returns ERROR_ALREADY_EXISTS.
70+
if ((mode == create) && (err == ERROR_ALREADY_EXISTS)) {
71+
if (h != NULL) ::CloseHandle(h);
72+
h = NULL;
73+
}
74+
if (h == NULL) {
75+
ipc::error("fail CreateFileMapping[%d]: %s\n", static_cast<int>(err), name);
76+
return nullptr;
77+
}
78+
}
79+
auto ii = mem::alloc<id_info_t>();
80+
ii->h_ = h;
81+
ii->size_ = size;
82+
return ii;
83+
}
84+
85+
std::int32_t get_ref(id_t id) {
86+
if (id == nullptr) {
87+
return 0;
88+
}
89+
auto ii = static_cast<id_info_t*>(id);
90+
if (ii->mem_ == nullptr || ii->size_ == 0) {
91+
return 0;
92+
}
93+
return acc_of(ii->mem_, calc_size(ii->size_)).load(std::memory_order_acquire);
94+
}
95+
96+
void sub_ref(id_t id) {
97+
if (id == nullptr) {
98+
ipc::error("fail sub_ref: invalid id (null)\n");
99+
return;
100+
}
101+
auto ii = static_cast<id_info_t*>(id);
102+
if (ii->mem_ == nullptr || ii->size_ == 0) {
103+
ipc::error("fail sub_ref: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
104+
return;
105+
}
106+
acc_of(ii->mem_, calc_size(ii->size_)).fetch_sub(1, std::memory_order_acq_rel);
107+
}
108+
109+
void * get_mem(id_t id, std::size_t * size) {
110+
if (id == nullptr) {
111+
ipc::error("fail get_mem: invalid id (null)\n");
112+
return nullptr;
113+
}
114+
auto ii = static_cast<id_info_t*>(id);
115+
if (ii->mem_ != nullptr) {
116+
if (size != nullptr) *size = ii->size_;
117+
return ii->mem_;
118+
}
119+
if (ii->h_ == NULL) {
120+
ipc::error("fail to_mem: invalid id (h = null)\n");
121+
return nullptr;
122+
}
123+
LPVOID mem = ::MapViewOfFile(ii->h_, FILE_MAP_ALL_ACCESS, 0, 0, 0);
124+
if (mem == NULL) {
125+
ipc::error("fail MapViewOfFile[%d]\n", static_cast<int>(::GetLastError()));
126+
return nullptr;
127+
}
128+
MEMORY_BASIC_INFORMATION mem_info;
129+
if (::VirtualQuery(mem, &mem_info, sizeof(mem_info)) == 0) {
130+
ipc::error("fail VirtualQuery[%d]\n", static_cast<int>(::GetLastError()));
131+
return nullptr;
132+
}
133+
std::size_t actual_size = static_cast<std::size_t>(mem_info.RegionSize);
134+
if (ii->size_ == 0) {
135+
// Opening existing shared memory
136+
ii->size_ = actual_size - sizeof(info_t);
137+
}
138+
else {
139+
// Should match the size we allocated in acquire
140+
ii->size_ = ii->size_; // Keep user-requested size
141+
}
142+
ii->mem_ = mem;
143+
if (size != nullptr) *size = ii->size_;
144+
// Initialize or increment reference counter
145+
acc_of(mem, calc_size(ii->size_)).fetch_add(1, std::memory_order_release);
146+
return static_cast<void *>(mem);
147+
}
148+
149+
std::int32_t release(id_t id) noexcept {
150+
if (id == nullptr) {
151+
ipc::error("fail release: invalid id (null)\n");
152+
return -1;
153+
}
154+
std::int32_t ret = -1;
155+
auto ii = static_cast<id_info_t*>(id);
156+
if (ii->mem_ == nullptr || ii->size_ == 0) {
157+
ipc::error("fail release: invalid id (mem = %p, size = %zd)\n", ii->mem_, ii->size_);
158+
}
159+
else {
160+
ret = acc_of(ii->mem_, calc_size(ii->size_)).fetch_sub(1, std::memory_order_acq_rel);
161+
::UnmapViewOfFile(static_cast<LPCVOID>(ii->mem_));
162+
}
163+
if (ii->h_ == NULL) {
164+
ipc::error("fail release: invalid id (h = null)\n");
165+
}
166+
else ::CloseHandle(ii->h_);
167+
mem::free(ii);
168+
return ret;
169+
}
170+
171+
void remove(id_t id) noexcept {
172+
if (id == nullptr) {
173+
ipc::error("fail release: invalid id (null)\n");
174+
return;
175+
}
176+
release(id);
177+
}
178+
179+
void remove(char const * name) noexcept {
180+
if (!is_valid_string(name)) {
181+
ipc::error("fail remove: name is empty\n");
182+
return;
183+
}
184+
// Do Nothing.
185+
}
186+
187+
} // namespace shm
188+
} // namespace ipc

0 commit comments

Comments
 (0)