Skip to content
This repository has been archived by the owner on Feb 19, 2020. It is now read-only.

Fix/metrics manager trackevent threadsafe #471

Merged
merged 6 commits into from
Oct 20, 2017

Conversation

ElektrojungeAtWork
Copy link
Contributor

Hopefully a more thorough approach to making BITChannel thread-safe.

I'm combining mutexes and atomic compare and swap based on https://github.com/bitstadium/HockeySDK-iOS/pulls and bitstadium/HockeySDK-Mac#106.

From testing, this works great. I'm happy for as many pairs of eyes as possible.

char* jsonStream = NULL;
char *newEmptyString = NULL;
do {
jsonStream = *string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swap this line with the one below it to minimize the opportunities for a race condition which forces the loop to run extra times.


// Reset both, the async-signal-safe buffer and item counter. Swaps jsonStream and BITSafeJsonEventsString.
if (OSAtomicCompareAndSwapPtr(jsonStream, newEmptyString, (void*)string)) {
self.dataItemCount = 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not sufficient, because self.dataItemCount is being guarded by @synchronized() in other places - Add @synchronized(self){ } around the reset of self.dataItemCount to make the setter call atomic.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Gwynne since this method is called from the mainQueue and the dataItemsOperations queue.

// Enqueue item.
NSDictionary *dict = [strongSelf dictionaryForTelemetryData:item];
[strongSelf appendDictionaryToEventBuffer:dict];
if (strongSelf.dataItemCount >= strongSelf.maxBatchSize) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need an @synchronized(self) here as well to guard the access to the data item count.

previousBuffer = *eventBuffer;

// Concatenate old string with new JSON string and add a comma.
asprintf(&newBuffer, "%s%.*s\n", previousBuffer, (int)MIN(string.length, (NSUInteger)INT_MAX), string.UTF8String);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not safe - you're still accessing a previousBuffer which may have been changed out by another thread. You have to compare and swap it out first before using its value.

Copy link
Collaborator

@iamclement iamclement left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a thoughtful code. I am a bit out of context so my comment may be inaccurate.

return;
}

NSData *bundle = [NSData dataWithBytes:BITSafeJsonEventsString length:strlen(BITSafeJsonEventsString)];
NSData *bundle = [NSData dataWithBytes:jsonStream length:strlen(jsonStream)];
[self.persistence persistBundle:bundle];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is persistence thread safe too? Meaning can save 2 bundles at the same time without conflicts?


// Reset both, the async-signal-safe buffer and item counter. Swaps jsonStream and BITSafeJsonEventsString.
if (OSAtomicCompareAndSwapPtr(jsonStream, newEmptyString, (void*)string)) {
self.dataItemCount = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Gwynne since this method is called from the mainQueue and the dataItemsOperations queue.

Copy link

@gwynne gwynne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

[self invalidateTimer];
if (!BITSafeJsonEventsString || strlen(BITSafeJsonEventsString) == 0) {
char* jsonStream = NULL;
char *newEmptyString = NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 One of the asterisks here is off 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx @lumaxis! I fixed that one.

@ElektrojungeAtWork
Copy link
Contributor Author

I discussed this PR with @gwynne to a great length. While my approach above doesn't 100% guarantee safety, it's a step in the right direction.
Ultimately, we need to move this implementation to a linked-list implementation similar to what we have in Mobile Center but this requires several days of work.

Please let me know if you have objections, otherwise I'll merge the PR.

…ToEventBuffer itself is not threadsafe, it needs to be called inside a @synchronized block which it usually is.
@ElektrojungeAtWork
Copy link
Contributor Author

I talked to Gwynne about this to a great deal and bottom line is that this PR is as best as we can do without refactoring the whole buffer logic into something that uses a proper, threadsafe linked list (similar to what we use for Mobile Center).
That said, this PR improves thread safety a lot, so let me know if you have concerns about merging this.

@ElektrojungeAtWork ElektrojungeAtWork merged commit 3a3f1ad into develop Oct 20, 2017
@ElektrojungeAtWork ElektrojungeAtWork deleted the fix/MetricsManager-trackevent-threadsafe branch October 20, 2017 18:47
char *previousBuffer = NULL;
char *newEmptyString = NULL;
do {
newEmptyString = strdup("");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory leak 😟

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merged the PR that fix... but I'm pretty sure we had fixed this in a prev. PR months ago. =(

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants