Switch to atomic for setting autocreated objects.
- Update semaphore comment to new scope.
- Use an atomic swap to avoid needing to use the semaphore.
This means the semaphore is create only when extension are auto created (less
memory usage).
diff --git a/objectivec/GPBMessage.m b/objectivec/GPBMessage.m
index 7b1263f..20ae9ae 100644
--- a/objectivec/GPBMessage.m
+++ b/objectivec/GPBMessage.m
@@ -71,6 +71,8 @@
@package
GPBUnknownFieldSet *unknownFields_;
NSMutableDictionary *extensionMap_;
+ // Readonly access to autocreatedExtensionMap_ is protected via
+ // readOnlySemaphore_.
NSMutableDictionary *autocreatedExtensionMap_;
// If the object was autocreated, we remember the creator so that if we get
@@ -79,10 +81,10 @@
GPBFieldDescriptor *autocreatorField_;
GPBExtensionDescriptor *autocreatorExtension_;
- // A lock to provide mutual exclusion from internal data that can be modified
- // by *read* operations such as getters (autocreation of message fields and
- // message extensions, not setting of values). Used to guarantee thread safety
- // for concurrent reads on the message.
+ // Message can only be mutated from one thread. But some *readonly* operations
+ // modifify internal state because they autocreate things. The
+ // autocreatedExtensionMap_ is one such structure. Access during readonly
+ // operations is protected via this semaphore.
// NOTE: OSSpinLock may seem like a good fit here but Apple engineers have
// pointed out that they are vulnerable to live locking on iOS in cases of
// priority inversion:
@@ -583,19 +585,30 @@
// This is like GPBGetObjectIvarWithField(), but for arrays, it should
// only be used to wire the method into the class.
static id GetArrayIvarWithField(GPBMessage *self, GPBFieldDescriptor *field) {
- id array = GPBGetObjectIvarWithFieldNoAutocreate(self, field);
- if (!array) {
- // Check again after getting the lock.
- GPBPrepareReadOnlySemaphore(self);
- dispatch_semaphore_wait(self->readOnlySemaphore_, DISPATCH_TIME_FOREVER);
- array = GPBGetObjectIvarWithFieldNoAutocreate(self, field);
- if (!array) {
- array = CreateArrayForField(field, self);
- GPBSetAutocreatedRetainedObjectIvarWithField(self, field, array);
- }
- dispatch_semaphore_signal(self->readOnlySemaphore_);
+ uint8_t *storage = (uint8_t *)self->messageStorage_;
+ _Atomic(id) *typePtr = (_Atomic(id) *)&storage[field->description_->offset];
+ id array = atomic_load(typePtr);
+ if (array) {
+ return array;
}
- return array;
+
+ id expected = nil;
+ id autocreated = CreateArrayForField(field, self);
+ if (atomic_compare_exchange_strong(typePtr, &expected, autocreated)) {
+ // Value was set, return it.
+ return autocreated;
+ }
+
+ // Some other thread set it, release the one created and return what got set.
+ if (GPBFieldDataTypeIsObject(field)) {
+ GPBAutocreatedArray *autoArray = autocreated;
+ autoArray->_autocreator = nil;
+ } else {
+ GPBInt32Array *gpbArray = autocreated;
+ gpbArray->_autocreator = nil;
+ }
+ [autocreated release];
+ return expected;
}
static id GetOrCreateMapIvarWithField(GPBMessage *self,
@@ -613,19 +626,31 @@
// This is like GPBGetObjectIvarWithField(), but for maps, it should
// only be used to wire the method into the class.
static id GetMapIvarWithField(GPBMessage *self, GPBFieldDescriptor *field) {
- id dict = GPBGetObjectIvarWithFieldNoAutocreate(self, field);
- if (!dict) {
- // Check again after getting the lock.
- GPBPrepareReadOnlySemaphore(self);
- dispatch_semaphore_wait(self->readOnlySemaphore_, DISPATCH_TIME_FOREVER);
- dict = GPBGetObjectIvarWithFieldNoAutocreate(self, field);
- if (!dict) {
- dict = CreateMapForField(field, self);
- GPBSetAutocreatedRetainedObjectIvarWithField(self, field, dict);
- }
- dispatch_semaphore_signal(self->readOnlySemaphore_);
+ uint8_t *storage = (uint8_t *)self->messageStorage_;
+ _Atomic(id) *typePtr = (_Atomic(id) *)&storage[field->description_->offset];
+ id dict = atomic_load(typePtr);
+ if (dict) {
+ return dict;
}
- return dict;
+
+ id expected = nil;
+ id autocreated = CreateMapForField(field, self);
+ if (atomic_compare_exchange_strong(typePtr, &expected, autocreated)) {
+ // Value was set, return it.
+ return autocreated;
+ }
+
+ // Some other thread set it, release the one created and return what got set.
+ if ((field.mapKeyDataType == GPBDataTypeString) &&
+ GPBFieldDataTypeIsObject(field)) {
+ GPBAutocreatedDictionary *autoDict = autocreated;
+ autoDict->_autocreator = nil;
+ } else {
+ GPBInt32Int32Dictionary *gpbDict = autocreated;
+ gpbDict->_autocreator = nil;
+ }
+ [autocreated release];
+ return expected;
}
#endif // !defined(__clang_analyzer__)
@@ -3337,30 +3362,34 @@
id GPBGetObjectIvarWithField(GPBMessage *self, GPBFieldDescriptor *field) {
NSCAssert(!GPBFieldIsMapOrArray(field), @"Shouldn't get here");
- if (GPBGetHasIvarField(self, field)) {
- uint8_t *storage = (uint8_t *)self->messageStorage_;
- id *typePtr = (id *)&storage[field->description_->offset];
- return *typePtr;
- }
- // Not set...
-
- // Non messages (string/data), get their default.
if (!GPBFieldDataTypeIsMessage(field)) {
+ if (GPBGetHasIvarField(self, field)) {
+ uint8_t *storage = (uint8_t *)self->messageStorage_;
+ id *typePtr = (id *)&storage[field->description_->offset];
+ return *typePtr;
+ }
+ // Not set...non messages (string/data), get their default.
return field.defaultValue.valueMessage;
}
- GPBPrepareReadOnlySemaphore(self);
- dispatch_semaphore_wait(self->readOnlySemaphore_, DISPATCH_TIME_FOREVER);
- GPBMessage *result = GPBGetObjectIvarWithFieldNoAutocreate(self, field);
- if (!result) {
- // For non repeated messages, create the object, set it and return it.
- // This object will not initially be visible via GPBGetHasIvar, so
- // we save its creator so it can become visible if it's mutated later.
- result = GPBCreateMessageWithAutocreator(field.msgClass, self, field);
- GPBSetAutocreatedRetainedObjectIvarWithField(self, field, result);
+ uint8_t *storage = (uint8_t *)self->messageStorage_;
+ _Atomic(id) *typePtr = (_Atomic(id) *)&storage[field->description_->offset];
+ id msg = atomic_load(typePtr);
+ if (msg) {
+ return msg;
}
- dispatch_semaphore_signal(self->readOnlySemaphore_);
- return result;
+
+ id expected = nil;
+ id autocreated = GPBCreateMessageWithAutocreator(field.msgClass, self, field);
+ if (atomic_compare_exchange_strong(typePtr, &expected, autocreated)) {
+ // Value was set, return it.
+ return autocreated;
+ }
+
+ // Some other thread set it, release the one created and return what got set.
+ GPBClearMessageAutocreator(autocreated);
+ [autocreated release];
+ return expected;
}
#pragma clang diagnostic pop