-
Notifications
You must be signed in to change notification settings - Fork 295
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
Fix dispatchSource #1344
Fix dispatchSource #1344
Conversation
* fix constraints warning by adding leading and trailing constraints for nested stackViews * remove print statement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the steps we can follow to replicate this error? Will be helpful for folks testing/reviewing this PR.
case resumed | ||
} | ||
|
||
private var state: State = .suspended |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious why we need to manage this state ourselves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GCD timers can be somewhat sensitive. If you try and resume
/suspend
an already resumed
/suspended
timer, you will get a crash with a reason like:
BUG IN CLIENT OF LIBDISPATCH: Over-resume of an object
This tells us we have tried to resume an already resumed timer. We just need to balance the calls to suspend and resume, like the documentation states:
As a result, you must balance each call to dispatch_suspend with a matching call to dispatch_resume before event delivery resumes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, thanks for the background here. This makes sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we include that link & those details in a comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, added: 3be7fd8
|
||
class RepeatingTimerTests: XCTestCase { | ||
|
||
func testTimer_resumesAndSuspends() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick - we try to name tests like this format: test<MethodName>_{optional when<Condition>}_<ExpectedResult>
This test might be more readable as two separate tests. For ex: testResume_callsEventHandler
or testSuspend_doesNotCallEventHandler
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for let me know, updated here: d4bb6aa
/// Amount of time, in seconds. | ||
private let timeInterval: Int | ||
|
||
init(timeInterval: Int) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we've separate out this logic can we remove the timeInterval
property from BTAnalyticsService
and set the default in this class? That way the timer logic is fully contained here and we are only instantiating it in BTAnalyticsService
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeh, we can just hard code it to 15 in the RepeatingTimer class. BTAnalyticsService doesn't need to change the interval
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@richherrera wanted to make sure you saw this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By removing timeInterval
from class BTAnalyticsService
and setting a default value in this class, we're mixing the "logic" again. Imagine we want to change the time interval from 15 seconds to 30, like in Android. I would expect to make this change in BTAnalyticsService
, not RepeatingTimer
. What do you think? Any ideas? What I came up with still looks like a magic number:
init(timeInterval: Int) { | |
init(timeInterval: Int = 15) { |
Any suggestions on how to set the value 15 without it looking like a magic number
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure that I agree with this. BTAnalyticsService
only responsibility should be sending events, it should not care about how often we send events at all. On the other hand the RepeatingTimer
should control all time related logic including the interval of the timer. I don't think BTAnalyticsService
needs to care how often events are sent only that it's sending events.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I completely agree with you. I was thinking more about making this class usable in multiple places if needed, considering that in some other context, 15 seconds might be too short. But I'm happy to make the change. Do you think it would be good to put it in the initializer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets make sure to add a CHANGELOG entry for this
For more information: https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/GCDWorkQueues/GCDWorkQueues.html#//apple_ref/doc/uid/TP40008091-CH103-SW8 | ||
*/ | ||
|
||
func resume() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is great work Rich. I am wondering if we want to synchronize resume and suspends as well since analytics could be sent from different threads. We could use actor which would require awaits on function calls but we could also use serial dispatch queue. wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent suggestion. For now, I think DispatchSource
is a task that runs periodically, so we could proceed without needing actor's. Also, based on Jax's implementation, the DispatchSource
is created to run on the main thread. From what I see, the task of sending analytics can be on different threads, but both only enqueue events. The task of sending them is handled by the single instance of DispatchSource
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. I didn't know that this line let timerSource = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
ensures that the handler is run on main as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏 We want to try to get this merged & released by EOW as an LE merchant is integrating w/ App Switch next week
Summary of changes
RepeatingTimer
that usesDispatchSource
, moving all the logic from theBTAnalyticsService
class. This way, we can reuse the same logic wherever needed. This class ensures it can be properly deallocated from memory or the implementation of pause/resume functionality. Also,RepeatingTimer
aims to fix an intermittent crash.Steps to reproduce the crash:
Xcode 15.4
iOS 17.2
Simulator iPhone 14
This branch was created where the
APIClient
andPaymentClient
are instantiated almost at the same time, based on a potential implementation by the merchants. The steps to reproduce the crash are totokenize
and thencancel
(crash is very intermittent, so you'll need to run the application multiple times and follow the tokenization process repeatedly).Update: This branch includes the changes from the branch that reproduces the crash, as well as the changes from this branch.
Checklist
Authors