Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GraphIdentifier threadsafety optimization & patch interleaving race condition in Lazy resolve #523

Merged
merged 12 commits into from
Nov 2, 2022

Conversation

maxim-chipeev
Copy link
Contributor

@maxim-chipeev maxim-chipeev commented Oct 15, 2022

We encountered a bizarre error in an async context; this PR offers a solution to two problems. (A topic of note here is that the GraphIdentifier is a class. We will come back to this later).

  • GraphIdentifier dereference crash, e.g. EXC_BAD_ACCESS: KERN_INVALID_ADDRESS: Attempted to dereference garbage pointer
  • Race condition wherein an async call to Lazy.instance could acquire the lock, set the graph identifier, release the lock, then re-acquire the lock and begin resolving. In between the lock release/acquisition of one .instance call, another .instance call could update the GraphIdentifier before the first thread could call resolve and re-acquire the lock. Can results in unexpected behaviour.

Synchronization

Synchronizing a container will emit a child which ensures that any async calls to resolve(…) should happen sequentially and never concurrently (or more precisely, for the logic within).

One line here is quite interesting:
CleanShot 2022-10-15 at 13 07 49@2x

It happens within the sync context, so it should be safe. However, there is one Swinject object that also contains a graph identifier reference. That is Lazy.

CleanShot 2022-10-15 at 12 37 11@2x

CleanShot 2022-10-15 at 12 39 05@2x

A Lazy property can be held by any class. Once that class is dereferenced/deallocated so are its children, in this case the ObjectIdentifier being one of them.

Critically, we have no control over when a Lazy property (and its GraphIdentifier ref) might be released.

deinit {
    container?.lock.sync {
        // THIS AVOIDS THE DEREFERENCE CRASH
        self.graphIdentifier = nil
    }
}
  • To further my point, the above line entirely prevents the crash when added to the Lazy type.

Solution

This one fairly simple and results meets expectations for GraphIdentifier behaviour. It should be an Identifiable struct, the performance tradeoffs here are negligible: a smidge costlier to create, but faster to pass by value (requires no ARC calls).

Some debug artifacts…

CleanShot 2022-10-15 at 12 26 06@2x

CleanShot 2022-10-15 at 12 42 57@2x
CleanShot 2022-10-15 at 12 44 26@2x

@maxim-chipeev
Copy link
Contributor Author

@mpdifran @welshm Seems I can't add reviewers, but I think you might like this. 🙂

@maxim-chipeev maxim-chipeev force-pushed the maxim/instance-wrapper-sync branch from 1467514 to 7f5382a Compare October 15, 2022 18:03
Sources/GraphIdentifier.swift Outdated Show resolved Hide resolved
Sources/InstanceStorage.swift Outdated Show resolved Hide resolved
Sources/InstanceStorage.swift Outdated Show resolved Hide resolved
@mpdifran mpdifran requested review from yoichitgy and tkohout October 17, 2022 14:00
@@ -178,16 +178,7 @@ public final class Container {
}

internal func restoreObjectGraph(_ identifier: GraphIdentifier) {
Copy link
Contributor

Choose a reason for hiding this comment

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

May be worth documenting that this method is not protected with a lock because it is expected to be called within a concurrency context

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, and if it weren't for the tests we could mark this private too. Or discard the function and just set it—since this function is only called within this file at this point. I will add a comment.

@maxim-chipeev maxim-chipeev requested review from mpdifran and removed request for yoichitgy and tkohout October 17, 2022 17:38
@maxim-chipeev
Copy link
Contributor Author

maxim-chipeev commented Oct 17, 2022

what the, why did clicking the re-request button delete the other ones… 🤯

@mpdifran mpdifran requested review from tkohout and yoichitgy October 17, 2022 19:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants