Replies: 2 comments 1 reply
-
Hello @smithi01,
Yes, it is a programmer error to deallocate a semaphore that a task is waiting for. Nothing could make it continue, and this would create a memory leak.
Cancellation does not resume anything. Signals do. Imagine two tasks that are actually blocked on
If In the first case, you do not want to destroy the semaphore, right? In the second case, it is ambiguous: you don't know if other tasks are waiting or not, and if you can destroy the semaphore. Checking I don't have any solution that pops out of my mind at the present time. Probably some kind of counter is necessary? What do you think? |
Beta Was this translation helpful? Give feedback.
-
Again, thanks for the thoughts. I have implemented as follows, which seems to work well. I can't use defer because the defer block doesn't allow async code. I have to make sure I call await signalDictionarySemaphore at every return point. private let downloadSemaphore = AsyncSemaphore(value: 8)
let semaphoreDictionaryLock = AsyncSemaphore(value: 1)
func getDictionarySemaphore(for url: URL) async -> AsyncSemaphore {
await semaphoreDictionaryLock.wait()
defer { semaphoreDictionaryLock.signal() }
if let semaphore = urlSemaphores[url] {
return semaphore
} else {
let semaphore = AsyncSemaphore(value: 1)
urlSemaphores[url] = semaphore
return semaphore
}
}
func signalDictionarySemaphore(_ semaphore: AsyncSemaphore, for url: URL) async {
await semaphoreDictionaryLock.wait()
defer { semaphoreDictionaryLock.signal() }
let othersWaiting = semaphore.signal()
if !othersWaiting {
urlSemaphores[url] = nil
} else {
}
}
private func fetchData(from url: URL, checking cache: DataCache, transform: (DownloadResponse) -> Data?) async -> Data? {
// See if there is a task in progress to download from the URL
let semaphore = await getDictionarySemaphore(for: url)
await semaphore.wait()
if Task.isCancelled {
await signalDictionarySemaphore(semaphore, for: url)
return nil
}
// Check the cache
if let data = await cache.value(forUrl: url) {
guard data != DataCache.noData else {
await signalDictionarySemaphore(semaphore, for: url)
return nil
}
await signalDictionarySemaphore(semaphore, for: url)
return data
}
if Task.isCancelled {
await signalDictionarySemaphore(semaphore, for: url)
return nil
}
// Try to download
// Limit concurrent downloads
await downloadSemaphore.wait()
defer {
downloadSemaphore.signal()
}
if Task.isCancelled {
await signalDictionarySemaphore(semaphore, for: url)
return nil
}
do {
let response = try await downloadData(from: url)
// Rest of function
''' |
Beta Was this translation helpful? Give feedback.
-
Hi. I am finding Semaphore useful in so many ways in my apps. Thank you Gwendal. In one part of my app I need to download a large number of images from URLs supplied externally. URLs in the list can often be duplicates and I cache the results. I use a dictionary of AsyncSemaphore to avoid downloading from the same URL simultaneously. All the code here is within an image service actor.
I have reviewed the code in the package, which is well commented (thank you). I want to be sure that I don't deallocate the semaphore while the suspensions array is not empty. If this happens, it looks like my production code would crash.
I use code as follows, which I think is safe. Note I use another semaphore to limit total simultaneous downloads to 8.
Assuming this is ok, my question comes because I want to improve handling fetchImage being cancelled. However, I am unclear about using waitUnlessCancelled() for a semaphore in the dictionary. The semaphore seems to effectively get signalled (value += 1) when cancelled, but I don't see where in the code the next suspension is popped and told to resume in this circumstance. I also have no return value like I do with signal(), so I don't know if I can set urlSemaphores[url] to nil.
Sometimes when I have these sort of issues, it means I am heading down the wrong track with my implementation. I would be most grateful for any comments by the community.
Cheers,
Ian
Beta Was this translation helpful? Give feedback.
All reactions