Skip to content

Commit

Permalink
Add autorelease pools to the Darwin KVS implementation. (project-chip…
Browse files Browse the repository at this point in the history
…#35752)

KVS can be used from outside the Matter queue in our example apps, and we were
leaking things that never got released due to the lack of an autorelease pool
around these KVS operations.

Many thanks to Vivien Nicolas for catching this.
  • Loading branch information
bzbarsky-apple authored Sep 24, 2024
1 parent 792895d commit c98a785
Showing 1 changed file with 138 additions and 130 deletions.
268 changes: 138 additions & 130 deletions src/platform/Darwin/KeyValueStoreManagerImpl.mm
Original file line number Diff line number Diff line change
Expand Up @@ -147,179 +147,187 @@ - (instancetype)initWithContext:(nonnull NSManagedObjectContext *)context key:(n

CHIP_ERROR KeyValueStoreManagerImpl::Init(const char * fileName)
{
if (mInitialized) {
return CHIP_NO_ERROR;
}
@autoreleasepool {
if (mInitialized) {
return CHIP_NO_ERROR;
}

ReturnErrorCodeIf(gContext != nullptr, CHIP_ERROR_INCORRECT_STATE);
ReturnErrorCodeIf(fileName == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(fileName[0] == '\0', CHIP_ERROR_INVALID_ARGUMENT);

NSURL * url = nullptr;
NSString * filepath = [NSString stringWithUTF8String:fileName];
ReturnErrorCodeIf(filepath == nil, CHIP_ERROR_INVALID_ARGUMENT);

// relative paths are relative to Documents folder
if (![filepath hasPrefix:@"/"]) {
NSURL * documentsDirectory = [NSFileManager.defaultManager URLForDirectory:NSDocumentDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:nil];
if (documentsDirectory == nullptr) {
ChipLogError(DeviceLayer, "Failed to get documents directory.");
return CHIP_ERROR_INTERNAL;
ReturnErrorCodeIf(gContext != nullptr, CHIP_ERROR_INCORRECT_STATE);
ReturnErrorCodeIf(fileName == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(fileName[0] == '\0', CHIP_ERROR_INVALID_ARGUMENT);

NSURL * url = nullptr;
NSString * filepath = [NSString stringWithUTF8String:fileName];
ReturnErrorCodeIf(filepath == nil, CHIP_ERROR_INVALID_ARGUMENT);

// relative paths are relative to Documents folder
if (![filepath hasPrefix:@"/"]) {
NSURL * documentsDirectory = [NSFileManager.defaultManager URLForDirectory:NSDocumentDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:nil];
if (documentsDirectory == nullptr) {
ChipLogError(DeviceLayer, "Failed to get documents directory.");
return CHIP_ERROR_INTERNAL;
}
ChipLogProgress(
DeviceLayer, "Found user documents directory: %s", [[documentsDirectory absoluteString] UTF8String]);

url = [NSURL URLWithString:filepath relativeToURL:documentsDirectory];
} else {
url = [NSURL fileURLWithPath:filepath];
}
ChipLogProgress(
DeviceLayer, "Found user documents directory: %s", [[documentsDirectory absoluteString] UTF8String]);
ReturnErrorCodeIf(url == nullptr, CHIP_ERROR_NO_MEMORY);

url = [NSURL URLWithString:filepath relativeToURL:documentsDirectory];
} else {
url = [NSURL fileURLWithPath:filepath];
}
ReturnErrorCodeIf(url == nullptr, CHIP_ERROR_NO_MEMORY);
ChipLogProgress(DeviceLayer, "KVS will be written to: %s", [[url absoluteString] UTF8String]);

ChipLogProgress(DeviceLayer, "KVS will be written to: %s", [[url absoluteString] UTF8String]);
NSManagedObjectModel * model = CreateManagedObjectModel();
ReturnErrorCodeIf(model == nullptr, CHIP_ERROR_NO_MEMORY);

NSManagedObjectModel * model = CreateManagedObjectModel();
ReturnErrorCodeIf(model == nullptr, CHIP_ERROR_NO_MEMORY);
// setup persistent store coordinator

// setup persistent store coordinator
NSPersistentStoreCoordinator * coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];

NSPersistentStoreCoordinator * coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSError * error = nil;
if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]) {
ChipLogError(DeviceLayer, "Invalid store. Attempting to clear: %s", error.localizedDescription.UTF8String);
if (![[NSFileManager defaultManager] removeItemAtURL:url error:&error]) {
ChipLogError(DeviceLayer, "Failed to delete item: %s", error.localizedDescription.UTF8String);
}

NSError * error = nil;
if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]) {
ChipLogError(DeviceLayer, "Invalid store. Attempting to clear: %s", error.localizedDescription.UTF8String);
if (![[NSFileManager defaultManager] removeItemAtURL:url error:&error]) {
ChipLogError(DeviceLayer, "Failed to delete item: %s", error.localizedDescription.UTF8String);
if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:url
options:nil
error:&error]) {
ChipLogError(DeviceLayer, "Failed to initialize clear KVS storage: %s", error.localizedDescription.UTF8String);
chipDie();
}
}

if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:url
options:nil
error:&error]) {
ChipLogError(DeviceLayer, "Failed to initialize clear KVS storage: %s", error.localizedDescription.UTF8String);
chipDie();
}
}
// create Managed Object context
gContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[gContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[gContext setPersistentStoreCoordinator:coordinator];

// create Managed Object context
gContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[gContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[gContext setPersistentStoreCoordinator:coordinator];

mInitialized = true;
return CHIP_NO_ERROR;
mInitialized = true;
return CHIP_NO_ERROR;
} // @autoreleasepool
}

CHIP_ERROR KeyValueStoreManagerImpl::_Get(
const char * key, void * value, size_t value_size, size_t * read_bytes_size, size_t offset)
{
ReturnErrorCodeIf(key == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(offset != 0, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(gContext == nullptr, CHIP_ERROR_UNINITIALIZED);

KeyValueItem * item = FindItemForKey([[NSString alloc] initWithUTF8String:key], nil, true);
if (!item) {
return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}
@autoreleasepool {
ReturnErrorCodeIf(key == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(offset != 0, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(gContext == nullptr, CHIP_ERROR_UNINITIALIZED);

KeyValueItem * item = FindItemForKey([[NSString alloc] initWithUTF8String:key], nil, true);
if (!item) {
return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}

__block NSData * itemValue = nil;
// can only access this object on the managed queue
[gContext performBlockAndWait:^{
itemValue = item.value;
}];
__block NSData * itemValue = nil;
// can only access this object on the managed queue
[gContext performBlockAndWait:^{
itemValue = item.value;
}];

if (read_bytes_size != nullptr) {
*read_bytes_size = itemValue.length;
}
if (read_bytes_size != nullptr) {
*read_bytes_size = itemValue.length;
}

if (value != nullptr) {
memcpy(value, itemValue.bytes, std::min<size_t>((itemValue.length), value_size));
if (value != nullptr) {
memcpy(value, itemValue.bytes, std::min<size_t>((itemValue.length), value_size));
#if CHIP_CONFIG_DARWIN_STORAGE_VERBOSE_LOGGING
fprintf(stderr, "GETTING VALUE FOR: '%s': ", key);
for (size_t i = 0; i < std::min<size_t>((itemValue.length), value_size); ++i) {
fprintf(stderr, "%02x ", static_cast<uint8_t *>(value)[i]);
}
fprintf(stderr, "\n");
fprintf(stderr, "GETTING VALUE FOR: '%s': ", key);
for (size_t i = 0; i < std::min<size_t>((itemValue.length), value_size); ++i) {
fprintf(stderr, "%02x ", static_cast<uint8_t *>(value)[i]);
}
fprintf(stderr, "\n");
#endif
}
}

if (itemValue.length > value_size) {
return CHIP_ERROR_BUFFER_TOO_SMALL;
}
if (itemValue.length > value_size) {
return CHIP_ERROR_BUFFER_TOO_SMALL;
}

return CHIP_NO_ERROR;
return CHIP_NO_ERROR;
} // @autoreleasepool
}

CHIP_ERROR KeyValueStoreManagerImpl::_Delete(const char * key)
{
ReturnErrorCodeIf(key == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(gContext == nullptr, CHIP_ERROR_UNINITIALIZED);
@autoreleasepool {
ReturnErrorCodeIf(key == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(gContext == nullptr, CHIP_ERROR_UNINITIALIZED);

KeyValueItem * item = FindItemForKey([[NSString alloc] initWithUTF8String:key], nil);
if (!item) {
return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}
KeyValueItem * item = FindItemForKey([[NSString alloc] initWithUTF8String:key], nil);
if (!item) {
return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}

__block BOOL success = NO;
__block NSError * error = nil;
[gContext performBlockAndWait:^{
[gContext deleteObject:item];
success = [gContext save:&error];
}];
__block BOOL success = NO;
__block NSError * error = nil;
[gContext performBlockAndWait:^{
[gContext deleteObject:item];
success = [gContext save:&error];
}];

if (!success) {
ChipLogError(DeviceLayer, "Error saving context: %s", error.localizedDescription.UTF8String);
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
if (!success) {
ChipLogError(DeviceLayer, "Error saving context: %s", error.localizedDescription.UTF8String);
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}

return CHIP_NO_ERROR;
return CHIP_NO_ERROR;
} // @autoreleasepool
}

CHIP_ERROR KeyValueStoreManagerImpl::_Put(const char * key, const void * value, size_t value_size)
{
ReturnErrorCodeIf(key == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(gContext == nullptr, CHIP_ERROR_UNINITIALIZED);

NSData * data = [[NSData alloc] initWithBytes:value length:value_size];

NSString * itemKey = [[NSString alloc] initWithUTF8String:key];
ReturnErrorCodeIf(itemKey == nil, CHIP_ERROR_INVALID_ARGUMENT);
@autoreleasepool {
ReturnErrorCodeIf(key == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorCodeIf(gContext == nullptr, CHIP_ERROR_UNINITIALIZED);

NSData * data = [[NSData alloc] initWithBytes:value length:value_size];

NSString * itemKey = [[NSString alloc] initWithUTF8String:key];
ReturnErrorCodeIf(itemKey == nil, CHIP_ERROR_INVALID_ARGUMENT);

KeyValueItem * item = FindItemForKey(itemKey, nil);
if (!item) {
[gContext performBlockAndWait:^{
[gContext insertObject:[[KeyValueItem alloc] initWithContext:gContext key:itemKey value:data]];
}];
} else {
[gContext performBlockAndWait:^{
item.value = data;
}];
}

KeyValueItem * item = FindItemForKey(itemKey, nil);
if (!item) {
__block BOOL success = NO;
__block NSError * error = nil;
[gContext performBlockAndWait:^{
[gContext insertObject:[[KeyValueItem alloc] initWithContext:gContext key:itemKey value:data]];
success = [gContext save:&error];
}];
} else {
[gContext performBlockAndWait:^{
item.value = data;
}];
}

__block BOOL success = NO;
__block NSError * error = nil;
[gContext performBlockAndWait:^{
success = [gContext save:&error];
}];

if (!success) {
ChipLogError(DeviceLayer, "Error saving context: %s", error.localizedDescription.UTF8String);
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
if (!success) {
ChipLogError(DeviceLayer, "Error saving context: %s", error.localizedDescription.UTF8String);
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}

#if CHIP_CONFIG_DARWIN_STORAGE_VERBOSE_LOGGING
fprintf(stderr, "PUT VALUE FOR: '%s': ", key);
for (size_t i = 0; i < value_size; ++i) {
fprintf(stderr, "%02x ", static_cast<const uint8_t *>(value)[i]);
}
fprintf(stderr, "\n");
fprintf(stderr, "PUT VALUE FOR: '%s': ", key);
for (size_t i = 0; i < value_size; ++i) {
fprintf(stderr, "%02x ", static_cast<const uint8_t *>(value)[i]);
}
fprintf(stderr, "\n");
#endif

return CHIP_NO_ERROR;
return CHIP_NO_ERROR;
} // @autoreleasepool
}

} // namespace PersistedStorage
Expand Down

0 comments on commit c98a785

Please sign in to comment.