67ac7f6625
<UserStory> 1954: Implement Service Refactoring </UserStory> UserStory #1954 <Changes> 1. Refactored notification handling to persist notifications directly in the datastore instead of maintaining notification collections within User objects. 2. Removed recipient User pointer dependencies from Notification and retained recipient user identification through recipientUserId. 3. Implemented generic observer persistence support in DataStore with shared helper methods for loading and saving observer subscriptions. 4. Added datastore-backed observer management for ServiceManagementService, PaymentManagementService, and InventoryManagementService. 5. Updated attach() and detach() operations to load, modify, and persist observer subscriptions using shared memory mappings. 6. Refactored sendNotification() implementations to create and persist Notification records directly to the datastore for subscribed observers. 7. Updated UserManagementService notification retrieval and deletion logic to operate on datastore notification records filtered by recipient user ID. 8. Removed notification ownership and observer-specific notification APIs from User and Observer classes. 9. Added configurable shared memory growth factor support and updated mapping expansion logic to use centralized configuration values. 10. Removed obsolete NotificationManagementService implementation and updated project configuration references. 11. Added DataStoreLockGuard integration for observer and notification persistence operations to ensure synchronized datastore access. </Changes> <Test> N/A </Test> <Review> Sreeja Reghukumar, please review </Review>
386 lines
9.8 KiB
C++
386 lines
9.8 KiB
C++
/*
|
|
File: SharedMemory.cpp
|
|
Description: Implements shared memory utilities for managing
|
|
Windows file mapping operations. Provides functions
|
|
to create, open, resize, and close mappings, as well
|
|
as access headers, records, and ensure synchronization
|
|
across processes.
|
|
Author: Trenser
|
|
Created: 11-June-2026
|
|
*/
|
|
|
|
#include "SharedMemory.h"
|
|
#include "Windows.h"
|
|
#include "Config.h"
|
|
|
|
/*
|
|
Function: invalidateMapping
|
|
Description: Releases all mapping resources and resets the mapping to an invalid state.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- None
|
|
*/
|
|
static void invalidateMapping(MappingInfo& mapping)
|
|
{
|
|
if (mapping.mappedView != nullptr)
|
|
{
|
|
UnmapViewOfFile(mapping.mappedView);
|
|
mapping.mappedView = nullptr;
|
|
}
|
|
if (mapping.mappingHandle != NULL)
|
|
{
|
|
CloseHandle(mapping.mappingHandle);
|
|
mapping.mappingHandle = NULL;
|
|
}
|
|
if (mapping.fileHandle != INVALID_HANDLE_VALUE)
|
|
{
|
|
CloseHandle(mapping.fileHandle);
|
|
mapping.fileHandle = INVALID_HANDLE_VALUE;
|
|
}
|
|
mapping.mappedCapacity = 0;
|
|
}
|
|
|
|
/*
|
|
Function: createOrOpenMapping
|
|
Description: Creates or opens a file mapping and maps it into the process address space.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- bool: True if the mapping was successfully created/opened, otherwise false
|
|
*/
|
|
bool SharedMemory::createOrOpenMapping(MappingInfo& mapping)
|
|
{
|
|
if (mapping.recordSize == 0)
|
|
{
|
|
return false;
|
|
}
|
|
mapping.fileHandle =
|
|
CreateFileA(
|
|
mapping.fileName.c_str(),
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
if (mapping.fileHandle == INVALID_HANDLE_VALUE)
|
|
{
|
|
return false;
|
|
}
|
|
LARGE_INTEGER fileSize;
|
|
if (!GetFileSizeEx(mapping.fileHandle, &fileSize))
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
bool isNewFile = (fileSize.QuadPart == 0);
|
|
const size_t initialCapacity = config::file::INITIAL_CAPACITY;
|
|
if (isNewFile)
|
|
{
|
|
LARGE_INTEGER newSize;
|
|
newSize.QuadPart = sizeof(FileHeader) + initialCapacity * mapping.recordSize;
|
|
if (!SetFilePointerEx(mapping.fileHandle, newSize, NULL, FILE_BEGIN))
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
if (!SetEndOfFile(mapping.fileHandle))
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
}
|
|
mapping.mappingHandle =
|
|
CreateFileMappingA(
|
|
mapping.fileHandle,
|
|
NULL,
|
|
PAGE_READWRITE,
|
|
0,
|
|
0,
|
|
NULL);
|
|
if (mapping.mappingHandle == NULL)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappedView =
|
|
MapViewOfFile(
|
|
mapping.mappingHandle,
|
|
FILE_MAP_ALL_ACCESS,
|
|
0,
|
|
0,
|
|
0);
|
|
if (mapping.mappedView == nullptr)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
FileHeader* header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
if (isNewFile)
|
|
{
|
|
header->recordCount = 0;
|
|
header->capacity = initialCapacity;
|
|
}
|
|
mapping.mappedCapacity = header->capacity;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Function: closeMapping
|
|
Description: Unmaps and closes all resources associated with a file mapping.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping to close
|
|
Returns:
|
|
- None
|
|
*/
|
|
void SharedMemory::closeMapping(MappingInfo& mapping)
|
|
{
|
|
invalidateMapping(mapping);
|
|
}
|
|
|
|
/*
|
|
Function: getHeader
|
|
Description: Returns the file header stored at the beginning of the mapped memory region.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- FileHeader*: Pointer to the file header, or nullptr if the mapping is not valid
|
|
*/
|
|
FileHeader* SharedMemory::getHeader(MappingInfo& mapping)
|
|
{
|
|
if (mapping.mappedView == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return reinterpret_cast<FileHeader*>(mapping.mappedView);
|
|
}
|
|
|
|
/*
|
|
Function: getRecordAddress
|
|
Description: Returns the address of a record at the specified index within the mapped memory region.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
- index: size_t, record index
|
|
Returns:
|
|
- void*: Pointer to the record, or nullptr if the mapping is not valid
|
|
*/
|
|
void* SharedMemory::getRecordAddress(MappingInfo& mapping, size_t index)
|
|
{
|
|
if (mapping.mappedView == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return reinterpret_cast<char*>(mapping.mappedView) + sizeof(FileHeader) + index * mapping.recordSize;
|
|
}
|
|
|
|
/*
|
|
Function: getRecordCount
|
|
Description: Returns the number of records currently stored in the mapping.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- size_t: Number of records in the mapping
|
|
*/
|
|
size_t SharedMemory::getRecordCount(MappingInfo& mapping)
|
|
{
|
|
FileHeader* header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
return 0;
|
|
}
|
|
return header->recordCount;
|
|
}
|
|
|
|
/*
|
|
Function: setRecordCount
|
|
Description: Updates the number of records stored in the mapping.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
- count: size_t, new record count
|
|
Returns:
|
|
- None
|
|
*/
|
|
void SharedMemory::setRecordCount(MappingInfo& mapping, size_t count)
|
|
{
|
|
FileHeader* header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
header->recordCount = count;
|
|
}
|
|
|
|
/*
|
|
Function: getCapacity
|
|
Description: Returns the current capacity of the mapping.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- size_t: Maximum number of records that can be stored in the mapping
|
|
*/
|
|
size_t SharedMemory::getCapacity(MappingInfo& mapping)
|
|
{
|
|
FileHeader* header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
return 0;
|
|
}
|
|
return header->capacity;
|
|
}
|
|
|
|
/*
|
|
Function: resizeMapping
|
|
Description: Resizes the file mapping to the specified capacity.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
- newCapacity: size_t, new mapping capacity
|
|
Returns:
|
|
- bool: True if the resize succeeded, otherwise false
|
|
*/
|
|
bool SharedMemory::resizeMapping(MappingInfo& mapping, size_t newCapacity)
|
|
{
|
|
FileHeader* header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
header->capacity = newCapacity;
|
|
if (!FlushViewOfFile(mapping.mappedView, sizeof(FileHeader)))
|
|
{
|
|
return false;
|
|
}
|
|
if (!UnmapViewOfFile(mapping.mappedView))
|
|
{
|
|
return false;
|
|
}
|
|
mapping.mappedView = nullptr;
|
|
CloseHandle(mapping.mappingHandle);
|
|
mapping.mappingHandle = NULL;
|
|
LARGE_INTEGER newSize;
|
|
newSize.QuadPart = sizeof(FileHeader) + newCapacity * mapping.recordSize;
|
|
if (!SetFilePointerEx(mapping.fileHandle, newSize, NULL, FILE_BEGIN))
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
if (!SetEndOfFile(mapping.fileHandle))
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappingHandle =
|
|
CreateFileMappingA(
|
|
mapping.fileHandle,
|
|
NULL,
|
|
PAGE_READWRITE,
|
|
0,
|
|
0,
|
|
NULL);
|
|
if (mapping.mappingHandle == NULL)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappedView =
|
|
MapViewOfFile(
|
|
mapping.mappingHandle,
|
|
FILE_MAP_ALL_ACCESS,
|
|
0,
|
|
0,
|
|
0);
|
|
if (mapping.mappedView == nullptr)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappedCapacity = newCapacity;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Function: ensureCapacityForInsert
|
|
Description: Ensures that the mapping has space for at least one additional record, growing it if necessary.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- bool: True if capacity is available, otherwise false
|
|
*/
|
|
bool SharedMemory::ensureCapacityForInsert(MappingInfo& mapping)
|
|
{
|
|
size_t recordCount = getRecordCount(mapping);
|
|
size_t capacity = getCapacity(mapping);
|
|
if (recordCount < capacity)
|
|
{
|
|
return true;
|
|
}
|
|
return resizeMapping(mapping, capacity * config::file::GROWTH_FACTOR);
|
|
}
|
|
|
|
/*
|
|
Function: ensureLatestMapping
|
|
Description: Remaps the file if another process has resized it.
|
|
Parameters:
|
|
- mapping: MappingInfo&, mapping information and handles
|
|
Returns:
|
|
- bool: True if the mapping is valid and up to date, otherwise false
|
|
*/
|
|
bool SharedMemory::ensureLatestMapping(MappingInfo& mapping)
|
|
{
|
|
FileHeader* header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
if (header->capacity == mapping.mappedCapacity)
|
|
{
|
|
return true;
|
|
}
|
|
if (!UnmapViewOfFile(mapping.mappedView))
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappedView = nullptr;
|
|
CloseHandle(mapping.mappingHandle);
|
|
mapping.mappingHandle = NULL;
|
|
mapping.mappingHandle =
|
|
CreateFileMappingA(
|
|
mapping.fileHandle,
|
|
NULL,
|
|
PAGE_READWRITE,
|
|
0,
|
|
0,
|
|
NULL);
|
|
if (mapping.mappingHandle == NULL)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappedView =
|
|
MapViewOfFile(
|
|
mapping.mappingHandle,
|
|
FILE_MAP_ALL_ACCESS,
|
|
0,
|
|
0,
|
|
0);
|
|
if (mapping.mappedView == nullptr)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
header = getHeader(mapping);
|
|
if (header == nullptr)
|
|
{
|
|
invalidateMapping(mapping);
|
|
return false;
|
|
}
|
|
mapping.mappedCapacity = header->capacity;
|
|
return true;
|
|
} |