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

Data race in static Kronos.Clock.annotatedNow.getter #85

Open
Usipov opened this issue Apr 19, 2021 · 2 comments
Open

Data race in static Kronos.Clock.annotatedNow.getter #85

Usipov opened this issue Apr 19, 2021 · 2 comments

Comments

@Usipov
Copy link

Usipov commented Apr 19, 2021

Screenshot 2021-04-19 at 18 57 52

Screenshot 2021-04-19 at 19 01 41


WARNING: ThreadSanitizer: data race (pid=97366)
  Read of size 8 at 0x0001164e9888 by thread T4:
    #0 static Clock.annotatedNow.getter <null>:2 (Kronos:x86_64+0x26a76ca)
    #1 static Clock.now.getter <null>:2 (Kronos:x86_64+0x26a747d)

  Previous write of size 8 at 0x0001164e9888 by main thread:
    #0 static Clock.stableTime.setter <null>:2 (Kronos:x86_64+0x26a6dcf)
    #1 closure #1 in static Clock.sync(from:samples:first:completion:) <null>:2 (Kronos:x86_64+0x26a827e)
    #2 partial apply for closure #1 in static Clock.sync(from:samples:first:completion:) <null>:2 (Kronos:x86_64+0x26a86b5)
    #3 $defer #1 () in closure #1 in closure #1 in NTPClient.query(pool:version:port:numberOfSamples:maximumServers:timeout:progress:) <null>:2 (Kronos:x86_64+0x26b7cf7)
    #4 closure #1 in closure #1 in NTPClient.query(pool:version:port:numberOfSamples:maximumServers:timeout:progress:) <null>:2 (Kronos:x86_64+0x26b783c)
    #5 partial apply for closure #1 in closure #1 in NTPClient.query(pool:version:port:numberOfSamples:maximumServers:timeout:progress:) <null>:2 (Kronos:x86_64+0x26bd6f8)
    #6 closure #1 in NTPClient.query(ip:port:version:timeout:numberOfSamples:completion:) <null>:2 (Kronos:x86_64+0x26b9af7)
    #7 partial apply for closure #1 in NTPClient.query(ip:port:version:timeout:numberOfSamples:completion:) <null>:2 (Kronos:x86_64+0x26bdb8e)
    #8 thunk for @escaping @callee_guaranteed (@guaranteed Data?, @unowned Double) -> () <null>:2 (Kronos:x86_64+0x26ba39d)
    #9 closure #1 in NTPClient.sendAsyncUDPQuery(to:port:timeout:completion:) <null>:2 (Kronos:x86_64+0x26bd0dd)
    #10 @objc closure #1 in NTPClient.sendAsyncUDPQuery(to:port:timeout:completion:) <null>:2 (Kronos:x86_64+0x26bd1f3)
    #11 __CFSocketPerformV0 <null>:2 (CoreFoundation:x86_64+0x92fd9)
    #12 start <null>:2 (libdyld.dylib:x86_64+0x1408)

  Location is global 'static Clock.stableTime' at 0x0001164e9888 (Kronos+0x000005b96888)

  Thread T4 (tid=1302869, running) is a GCD worker thread
@keith
Copy link
Member

keith commented Apr 19, 2021

So technically right now we expect you to call .now and .sync from the same thread. In our app we only ever call this from the main thread. I think we'd be happy to support this if you wanted to submit a change for it though

@maxep
Copy link

maxep commented Sep 22, 2021

Hey 👋

This issue has been reported on our side as well, see dd-sdk-ios/#588. Is it right to say that Clock.now should be invoked from the main thread? Given the current implementation, sockets are added to the CFRunLoopGetMain(), so all callback of NTPClient.query will be called on the main thread.

I think Kronos could benefit from 2 changes:

  1. Allow using the current run loop. This won't work, see this thread. But we could still allow a queue to dispatch completion blocks. Something like this:
public static func sync(from pool: String = "time.apple.com", samples: Int = 4,
                        queue: DispatchQueue = .main,
                        first: ((Date, TimeInterval) -> Void)? = nil,
                        completion: ((Date?, TimeInterval?) -> Void)? = nil)
{
    self.loadFromDefaults()

    NTPClient().query(pool: pool, numberOfSamples: samples) { offset, done, total in
        queue.async {
            if let offset = offset {
                self.stableTime = TimeFreeze(offset: offset)

                if done == 1, let now = self.now {
                    first?(now, offset)
                }
            }

            if done == total {
                completion?(self.now, offset)
            }
        }
    }
}
  1. Make TimeFreeze public so integration could create and use that object in a thread safe manner.

Would this make sense?

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

No branches or pull requests

3 participants