I have a limit of 40 URL Session calls to my API per minute.
I have timed the number of calls in any 60s and when 40 calls have been reached I introduced sleep(x). Where x is 60 - seconds remaining before new minute start. This works fine and the calls don’t go over 40 in any given minute. However the limit is still exceeded as there might be more calls towards the end of the minute and more at the beginning of the next 60s count. Resulting in an API error.
I could add a:
usleep(x)
Where x would be 60/40 in milliseconds. However as some large data returns take much longer than simple queries that are instant. This would increase the overall download time significantly.
Is there a way to track the actual rate to see by how much to slow the function down?
Might not be the neatest approach, but it works perfectly. Simply storing the time of each call and comparing it to see if new calls can be made and if not, the delay required.
Using previously suggested approach of delay before each API call of 60/40 = 1.5s (Minute / CallsPerMinute), as each call takes a different time to produce response, total time taken to make 500 calls was 15min 22s. Using the below approach time taken: 11min 52s as no unnecessary delay has been introduced.
Call before each API Request:
API.calls.addCall()
Call in function before executing new API task:
let limit = API.calls.isOverLimit()
if limit.isOver {
sleep(limit.waitTime)
}
Background Support Code:
var globalApiCalls: [Date] = []
public class API {
let limitePerMinute = 40 // Set API limit per minute
let margin = 2 // Margin in case you issue more than one request at a time
static let calls = API()
func addCall() {
globalApiCalls.append(Date())
}
func isOverLimit() -> (isOver: Bool, waitTime: UInt32)
{
let callInLast60s = globalApiCalls.filter({ $0 > date60sAgo() })
if callInLast60s.count > limitePerMinute - margin {
if let firstCallInSequence = callInLast60s.sorted(by: { $0 > $1 }).dropLast(2).last {
let seconds = Date().timeIntervalSince1970 - firstCallInSequence.timeIntervalSince1970
if seconds < 60 { return (true, UInt32(60 + margin) - UInt32(seconds.rounded(.up))) }
}
}
return (false, 0)
}
private func date60sAgo() -> Date
{
var dayComponent = DateComponents(); dayComponent.second = -60
return Calendar.current.date(byAdding: dayComponent, to: Date())!
}
}
Instead of using sleep have a counter. You can do this with a Semaphore (it is a counter for threads, on x amount of threads allowed at a time).
So if you only allow 40 threads at a time you will never have more. New threads will be blocked. This is much more efficient than calling sleep because it will interactively account for long calls and short calls.
The trick here is that you would call a function like this every sixty second. That would make a new semaphore every minute that would only allow 40 calls. Each semaphore would not affect one another but only it's own threads.
func uploadImages() {
let uploadQueue = DispatchQueue.global(qos: .userInitiated)
let uploadGroup = DispatchGroup()
let uploadSemaphore = DispatchSemaphore(value: 40)
uploadQueue.async(group: uploadGroup) { [weak self] in
guard let self = self else { return }
for (_, image) in images.enumerated() {
uploadGroup.enter()
uploadSemaphore.wait()
self.callAPIUploadImage(image: image) { (success, error) in
uploadGroup.leave()
uploadSemaphore.signal()
}
}
}
uploadGroup.notify(queue: .main) {
// completion
}
}
Related
I’m testing code that uses an actor, and I’d like to test that I’m properly handling concurrent access and reentrancy. One of my usual approaches would be to use DispatchQueue.concurrentPerform to fire off a bunch of requests from different threads, and ensure my values resolve as expected. However, since the actor uses structured concurrency, I’m not sure how to actually wait for the tasks to complete.
What I’d like to do is something like:
let iterationCount = 100
let allTasksComplete = expectation(description: "allTasksComplete")
allTasksComplete.expectedFulfillmentCount = iterationCount
DispatchQueue.concurrentPerform(iterations: iterationCount) { _ in
Task {
// Do some async work here, and assert
allTasksComplete.fulfill()
}
}
wait(for: [allTasksComplete], timeout: 1.0)
However the timeout for the allTasksComplete expectation expires every time, regardless of whether the iteration count is 1 or 100, and regardless of the length of the timeout. I’m assuming this has something to do with the fact that mixing structured and DispatchQueue-style concurrency is a no-no?
How can I properly test concurrent access — specifically how can I guarantee that the actor is accessed from different threads, and wait for the test to complete until all expectations are fulfilled?
A few observations:
When testing Swift concurrency, we no longer need to rely upon expectations. We can just mark our tests as async methods. See Asynchronous Tests and Expectations. Here is an async test adapted from that example:
func testDownloadWebDataWithConcurrency() async throws {
let url = try XCTUnwrap(URL(string: "https://apple.com"), "Expected valid URL.")
let (_, response) = try await URLSession.shared.data(from: url)
let httpResponse = try XCTUnwrap(response as? HTTPURLResponse, "Expected an HTTPURLResponse.")
XCTAssertEqual(httpResponse.statusCode, 200, "Expected a 200 OK response.")
}
FWIW, while we can now use async tests when testing Swift concurrency, we still can use expectations:
func testWithExpectation() {
let iterations = 100
let experiment = ExperimentActor()
let e = self.expectation(description: #function)
e.expectedFulfillmentCount = iterations
for i in 0 ..< iterations {
Task.detached {
let result = await experiment.reentrantCalculation(i)
let success = await experiment.isAcceptable(result)
XCTAssert(success, "Incorrect value")
e.fulfill()
}
}
wait(for: [e], timeout: 10)
}
You said:
However the timeout for the allTasksComplete expectation expires every time, regardless of whether the iteration count is 1 or 100, and regardless of the length of the timeout.
We cannot comment without seeing a reproducible example of the code replaced with the comment “Do some async work here, and assert”. We do not need to see your actual implementation, but rather construct the simplest possible example that manifests the behavior you describe. See How to create a Minimal, Reproducible Example.
I personally suspect that you have some other, unrelated deadlock. E.g., given that concurrentPerform blocks the thread from which you call it, maybe you are doing something that requires the blocked thread. Also, be careful with Task { ... }, which runs the task on the current actor, so if you are doing something slow and synchronous inside there, that could cause problems. We might use detached tasks, instead.
In short, we cannot diagnose the issue without a Minimal, Reproducible Example.
As a more general observation, one should be wary about mixing GCD (or semaphores or long-lived locks or whatever) with Swift concurrency, because the latter uses a cooperative thread pool, which relies upon assumptions about its threads being able to make forward progress. But if you have GCD API blocking threads, those assumptions may no longer be valid. It may not the source of the problem here, but I mention it as a cautionary note.
As an aside, concurrentPerform (which constrains the degree of parallelism) only makes sense if the work being executed runs synchronously. Using concurrentPerform to launch a series of asynchronous tasks will not constrain the concurrency at all. (The cooperative thread pool may, but concurrentPerform will not.)
So, for example, if we wanted to test a bunch of calculations in parallel, rather than concurrentPerform, we might use a TaskGroup:
func testWithStructuredConcurrency() async {
let iterations = 100
let experiment = ExperimentActor()
await withTaskGroup(of: Void.self) { group in
for i in 0 ..< iterations {
group.addTask {
let result = await experiment.reentrantCalculation(i)
let success = await experiment.isAcceptable(result)
XCTAssert(success, "Incorrect value")
}
}
}
let count = await experiment.count
XCTAssertEqual(count, iterations)
}
Now if you wanted to verify concurrent execution within an app, normally I would just profile the app (not unit tests) with Instruments, and either watch intervals in the “Points of Interest” tool or look at the new “Swift Tasks” tool described in WWDC 2022’s Visualize and optimize Swift concurrency video. E.g., here I have launched forty tasks and I can see that my device runs six at a time:
See Alternative to DTSendSignalFlag to identify key events in Instruments? for references about the “Points of Interest” tool.
If you really wanted to write a unit test to confirm concurrency, you could theoretically keep track of your own counters, e.g.,
final class MyAppTests: XCTestCase {
func testWithStructuredConcurrency() async {
let iterations = 100
let experiment = ExperimentActor()
await withTaskGroup(of: Void.self) { group in
for i in 0 ..< iterations {
group.addTask {
let result = await experiment.reentrantCalculation(i)
let success = await experiment.isAcceptable(result)
XCTAssert(success, "Incorrect value")
}
}
}
let count = await experiment.count
XCTAssertEqual(count, iterations, "Correct count")
let degreeOfConcurrency = await experiment.maxDegreeOfConcurrency
XCTAssertGreaterThan(degreeOfConcurrency, 1, "No concurrency")
}
}
Where:
actor ExperimentActor {
var degreeOfConcurrency = 0
var maxDegreeOfConcurrency = 0
var count = 0
/// Calculate pi with Leibniz series
///
/// Note: I am awaiting a detached task so that I can manifest actor reentrancy.
func reentrantCalculation(_ index: Int, decimalPlaces: Int = 8) async -> Double {
let task = Task.detached {
logger.log("starting \(index)") // I wouldn’t generally log in a unit test, but it’s a quick visual confirmation that I’m enjoying parallel execution
await self.increaseConcurrencyCount()
let threshold = pow(0.1, Double(decimalPlaces))
var isPositive = true
var denominator: Double = 1
var result: Double = 0
var increment: Double
repeat {
increment = 4 / denominator
if isPositive {
result += increment
} else {
result -= increment
}
isPositive.toggle()
denominator += 2
} while increment >= threshold
logger.log("finished \(index)")
await self.decreaseConcurrencyCount()
return result
}
count += 1
return await task.value
}
func increaseConcurrencyCount() {
degreeOfConcurrency += 1
if degreeOfConcurrency > maxDegreeOfConcurrency { maxDegreeOfConcurrency = degreeOfConcurrency}
}
func decreaseConcurrencyCount() {
degreeOfConcurrency -= 1
}
func isAcceptable(_ result: Double) -> Bool {
return abs(.pi - result) < 0.0001
}
}
Please note that if testing/running on simulator, the cooperative thread pool is somewhat constrained, not exhibiting the same degree of concurrency as you will see on an actual device.
Also note that if you are testing whether a particular test is exhibiting parallel execution, you might want to disable the parallel execution of tests, themselves, so that other tests do not tie up your cores, preventing any given particular test from enjoying parallel execution.
I'm fetching some data from an endpoint, every time I requested for 10 items. These items contains a URL that should be scraping, which slow down the process a bit.
Due to some bug probably, the received data from scraping is not the same number of the original data, which something i will deal with later. So I don't know when all data is ready.
Now, I want to make sure when I get all the data, then refresh the collection view. the only way that I can think of it, is using the timestamp of data and if the timestamp is not updated for about a sec, means, I have all the data that I need than I can refresh collection view. Not the best way of course
I added a timestamp variable
private lazy var fetchedTimestamp = Date().nanosecondsSince1970
then, whenever the data is recived I updated this timestamp with the the current time
Than, I use DispatchQueue.main.asyncAfter to check the timestamp on sec later and if the the timestamp one sec later is 1 sec after the last fetchedTimestampupdate, means the data hasn't been updated for about a sec, so I know it's ready to reload
KingfisherManager.shared.retrieveImage(with: imageURL) { result in
if let image = try? result.get().image {
scraper.setBaseImage(with: image)
DispatchQueue.main.async { [weak self] in
guard let this = self else { return }
this.feedsCollectionView.feeds.append(scraper)
this.fetchedTimestamp = Date().nanosecondsSince1970
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
let currentTime = Date().nanosecondsSince1970
if currentTime >= this.fetchedTimestamp + Int64(1e+9) {
this.feedsCollectionView.reload()
}
})
}
}
}
Is there any way to handle it better? Thank you so much
I'm using Amadeus API for flight search. Amadeus API requires that requests must not be more frequent than 1/100ms. I use the following code to limit the request frequency
for depDate in depDates{
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
AmadeusHelper.sharedInstance().searchAFlight(depAirport: depAirport, arrAirport: arrAirport, depDate: depDate, airlineCode: airlineCode, flightNumber: flightNum) { result in
switch result{
case .failure(let err):
print(err)
case .success(let flightOffer):
if let json = flightOffer.get(){
flightCandidate.offers?.append(json)
}
}
}
}
}
The above for-loop runs 4 loops. The searchAFlight function is essentially a wrapper of HTTP request. asyncAfter function will delay each request by 1 second. I thought this would be enough. However, I still get tooManyRequests error. When I was debugging, I followed the code step by step, which should took much longer to send out the requests. The requests should spread out across several minutes, long enough to satisfy the 100ms interval. Did I do something wrong here? Thanks.
===========Update=========
Based on Eric's comments, I changed the code to the following
var timeNow = DispatchTime.now() // each request is delayed with an increasing interval
for (index, depDate) in depDates.enumerated(){
DispatchQueue.main.asyncAfter(deadline: timeNow + Double(1*index)) {
AmadeusHelper.sharedInstance().searchAFlight(depAirport: depAirport, arrAirport: arrAirport, depDate: depDate, airlineCode: airlineCode, flightNumber: flightNum) { result in
switch result{
case .failure(let err):
print(err)
case .success(let flightOffer):
if let json = flightOffer.get(){
flightCandidate.offers?.append(json)
}
}
}
}
}
Unfortunately, I still get tooManyRequest error.
Your first attempt was:
for depDate in depDates {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { ... }
}
That will not work because you are scheduling all of those iterations to start one second from now, not one second from each other.
You then attempted:
for (index, depDate) in depDates.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index)) { ... }
}
That is likely closer to what you want, but is subject to “timer coalescing”, a feature where the OS will start grouping/coalescing dispatched blocks together. This is a great power saving feature, but will circumvent the desire to have delays between the requests. Also, you’re going to have troubles if you ever want to cancel some of these subsequent requests (without complicating the code a bit, at least).
The simplest solution is to adopt a recursive pattern, where you trigger the next request in the completion handler of the prior one.
func searchFlight(at index: Int = 0) {
guard index < depDates.count else { return }
let depDate = depDates[index]
AmadeusHelper.sharedInstance().searchAFlight(depDate: depDate, ...) { result in
defer {
if index < (depDates.count - 1) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
self?.searchFlight(at: index + 1)
}
}
}
switch result { ... }
}
}
There’s no coalescing of these calls. Also, this has the virtue that the delay for a subsequent request will be based upon when the prior one finished, not after the prior one was issued (which can be an issue if scheduling a bunch of these requests up-front).
That having been said, I would advise that you direct this inquiry to Amadeus. I wouldn’t be surprised if this 100ms limitation was introduced to prevent people from mining their database through their API. I also wouldn’t be surprised if they have other, undocumented, techniques for identifying excessive requests (e.g. number of requests per hour, per 24 hours, etc.). The question of “why am I receiving tooManyRequest error” is best directed to them.
Assuming I have this kind of function and DispatchQueue logic. Assume that when synchLatest() gets called it fires "Code Block 1" twice.
How is supposed to be that, during a loop, the execution of "Code Block 1" which is only a retrieve of a string from the grpc response and a store in UserDefaults take me 1.7 seconds and the second it gets executed during the loop it takes 5 seconds?
let synchQueue = DispatchQueue(label: "com.dmapp.synchQueue", qos: .default, attributes: .concurrent)
let synchProcessQueue = DispatchQueue(label: "com.dmapp.processQueue", qos: .default, attributes: .concurrent)
func synchLatest() {
while(someconditions) {
synchQueue.async {
...
let response = try grpcCall.receive()
...
synchProcessQueue.async {
....
measure("Code Block 1", {
if response.data.nickname != "" {
// Store in UserDefaults
}
})
....
}
}
}
}
#discardableResult
static func measure<A>(name: String = "", _ block: () -> A) -> A {
let startTime = CACurrentMediaTime()
let result = block()
let timeElapsed = CACurrentMediaTime() - startTime
print("Time: \(name) - \(timeElapsed)")
return result
}
Am I measuring code execution time here in the wrong way?
CACurrentMediaTime is a fine way of measuring time. It’s low overhead and doesn’t suffer the problems of Date or CFAbsoluteTimeGetCurrent, which “do not guarantee monotonically increasing results.”
A few general guidelines when benchmarking include:
let the app reach quiescence before starting the benchmarking;
do not run independent tests concurrently with respect to each other (because they may be contending for the same resources);
repeat it many times; and
change the order that the benchmarks are done in order to eliminate noise from the measurements.
If you’re looking for an alternative to manually using CACurrentMediaTime, another approach is to use signposts and points of interest, which not only will capture the amount of time it takes, but would you let you filter your instruments timeline to the relevant period of time (and you might be able to diagnose the source of the discrepancy).
I need to export data from a dictionary of 12 thousand items to Cloudkit. I tried to use the convenience API but I keep hitting the rate limit while trying to save to the public database. I then tried the Operation API and I got a similar error. My question is: how to save a very large amount of data to cloudkit without hitting the limits?
According to the docs for the CKErrorLimitExceeded error code, you should
Try refactoring your request into multiple smaller batches.
So if your CKModifyRecordsOperation operation results in the CKErrorLimitExceeded error, you can just create two CKModifyRecordsOperation objects, each with half the data from the failed operation. If you do that recursively (so any of the split operations could also fail with the limit exceeded error, splitting again in two) then you should eventually get a number of CKModifyRecordsOperation objects that have a small enough number of records to avoid the error.
If you have your own server, you could try the CloudKit Web Service API.
In iOS 10, the maximum permitted record count per operation is 400.
/// The system allowed maximum record modifications count.
///
/// If excute a CKModifyRecordsOperation with more than 400 record modifications, system will return a CKErrorLimitExceeded error.
private let maximumRecordModificationsLimit = 400
private func modifyRecords(recordsToSave: [CKRecord], recordIDsToDelete: [CKRecordID], previousRetryAfterSeconds: TimeInterval = 0, completion: ((Bool) -> Void)? = nil) {
guard !recordsToSave.isEmpty || !recordIDsToDelete.isEmpty else {
completion?(true)
return
}
func handleLimitExceeded() {
let recordsToSaveFirstSplit = recordsToSave[0 ..< recordsToSave.count / 2]
let recordsToSaveSecondSplit = recordsToSave[recordsToSave.count / 2 ..< recordsToSave.count]
let recordIDsToDeleteFirstSplit = recordIDsToDelete[0 ..< recordIDsToDelete.count / 2]
let recordIDsToDeleteSecondSplit = recordIDsToDelete[recordIDsToDelete.count / 2 ..< recordIDsToDelete.count]
self.modifyRecords(recordsToSave: Array(recordsToSaveFirstSplit), recordIDsToDelete: Array(recordIDsToDeleteFirstSplit))
self.modifyRecords(recordsToSave: Array(recordsToSaveSecondSplit), recordIDsToDelete: Array(recordIDsToDeleteSecondSplit), completion: completion)
}
if recordsToSave.count + recordIDsToDelete.count > maximumRecordModificationsLimit {
handleLimitExceeded()
return
}
// run CKModifyRecordsOperation at here
}