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

Dispose viewConnectable synchronously #190

Merged
merged 2 commits into from
Apr 22, 2022

Conversation

kmcbride
Copy link
Collaborator

Asynchronously disposing the controller view connection can lead to unexpected behavior (and ultimately crashes) in certain scenarios. It also differs from how other types (effect handler, event source, loop) are disposed: synchronously on the loop queue.

This contrived example demonstrates how the current implementation will lead to a crash:

// scenario: executing on main queue, ConnectableClass previously attached via .connectView()
loopController.start()

// queues work for next loop pass
DispatchQueue.main.async {
    // crashes in ConnectableClass .connect() because it executes before the queued dispose
    loopController.start()
}

// stops loop but queues ConnectableClass connection dispose on the acceptQueue (main)
loopController.stop()

As I wasn't involved in the original design it's possible I'm missing some important context, but I've attempted to annotate my reasoning for the changes as review comments.

@codecov
Copy link

codecov bot commented Apr 15, 2022

Codecov Report

Merging #190 (3ef5151) into master (fd1fb15) will decrease coverage by 0.39%.
The diff coverage is 59.09%.

@@            Coverage Diff             @@
##           master     #190      +/-   ##
==========================================
- Coverage   95.37%   94.97%   -0.40%     
==========================================
  Files          42       42              
  Lines        1427     1414      -13     
==========================================
- Hits         1361     1343      -18     
- Misses         66       71       +5     
Flag Coverage Δ
macspm 94.97% <59.09%> (-0.40%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
MobiusCore/Source/Lock.swift 100.00% <ø> (ø)
MobiusCore/Source/MobiusController.swift 96.05% <50.00%> (-1.98%) ⬇️
...iusCore/Source/AsyncDispatchQueueConnectable.swift 84.61% <66.66%> (-9.14%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fd1fb15...3ef5151. Read the comment docs.

guard status != .pendingDispose else { return }
protectedConsumer.read { consumer in
guard let consumer = consumer else {
MobiusHooks.errorHandler("cannot consume value after dispose", #file, #line)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The previous code had an issue with retaining the consumer reference, which would lead to a somewhat nebulous crash when its unowned loop reference was accessed by emitting an event after disposal. This code is functionally equivalent, but the cause of the error should be clearer now.

Comment on lines -105 to -107
guard state.running else {
MobiusHooks.errorHandler("\(Self.debugTag): cannot accept events when stopped", #file, #line)
}
Copy link
Collaborator Author

@kmcbride kmcbride Apr 15, 2022

Choose a reason for hiding this comment

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

Due to this closure being created and owned by the loop—and the lifetime of the loop being tied to the controller's running state—this doesn't appear to be a valid scenario, unless the loop reference was somehow leaking.

@kmcbride kmcbride force-pushed the sync-dispose-connectable branch from 43ce033 to dfd370a Compare April 15, 2022 15:42
Copy link
Contributor

@rastersize rastersize left a comment

Choose a reason for hiding this comment

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

This makes sense to me. Might still be good to get some of the original Mobius.swift folks to look it over as you mentioned.

@kmcbride
Copy link
Collaborator Author

@JensAyton would you mind taking a look? I think you probably have the most insight into the existing implementation.

@kmcbride kmcbride merged commit 80c0e10 into spotify:master Apr 22, 2022
@kmcbride kmcbride deleted the sync-dispose-connectable branch April 22, 2022 16:08
kmcbride added a commit to kmcbride/Mobius.swift that referenced this pull request Apr 30, 2022
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