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

[SR-8572] OperationQueue.isSuspended = true suspends the underlying DispatchQueue #3644

Closed
swift-ci opened this issue Aug 17, 2018 · 4 comments

Comments

@swift-ci
Copy link
Contributor

Previous ID SR-8572
Radar None
Original Reporter morlovich (JIRA User)
Type Bug
Status Resolved
Resolution Done
Environment

Ubuntu 16.04 / Swift 4.2 (but also earlier versions)

Additional Detail from JIRA
Votes 0
Component/s Foundation
Labels Bug
Assignee morlovich (JIRA)
Priority Medium

md5: cfbe7022ce2b9cf366f599f22d732b9b

Issue Description:

The current implementation of OperationQueue suspends the underlying DispatchQueue when the user calls OperationQueue.isSuspended = true (unlike on Darwin, which only suspends the given OperationQueue, but not the underlying DispatchQueue).

Consider the following code:

import Foundation
let rootQueue = DispatchQueue(label: "org.swift.queue")
let delegateQueue = OperationQueue()
delegateQueue.qualityOfService = .default
delegateQueue.maxConcurrentOperationCount = 1
delegateQueue.underlyingQueue = rootQueue
delegateQueue.isSuspended = true

// Should run
var doneAsync = false
rootQueue.async {
print("on request queue")
doneAsync = true
delegateQueue.isSuspended = false
}

// Should not run until block above runs and resumes this OperationQueue
var doneOperation = false
delegateQueue.addOperation {
print("on internal queue")
doneOperation = true
}

repeat {
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.01))
} while doneAsync == false || doneOperation == false

print("DONE")

On Darwin, this completes without any issues. On Ubuntu, Swift 4.x (and probably 3.x), this hangs.

I discovered this issue while porting Alamofire to run on Linux. Alamofire is relying on this type of a pattern, but because of this bug, requests never complete.

The problem lies around line 506 here: https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/Operation.swift

queue.suspend()/queue.resume()

I am not very familiar with the code in Operation.swift, but I think the fix is straightforward.
1] Stop calling suspend/resume
2] In addOperations() call, prepare DispatchWorkItem() as it is currently, but if the state is suspended, put these objects in an array of pending items instead of scheduling on the _underlyingQueue
3] When the state is changed from suspended to resumed -> immediately put pending DispatchWorkItems() on the underlying queue.

I would really like to see this fixed in Swift 4.2 release.

@phauslerdgrove-oss (JIRA User)@parkera you guys were the original authors of this code. What do you think of the proposed fix?

Also CC'ing @jshier as the author of Alamofire 🙂

@jshier
Copy link
Contributor

jshier commented Aug 17, 2018

Yes, this would be a huge problem for Alamofire 5, as the root queue is now shared amongst all requests by default, which only works because of the Darwin behavior. We could refactor to replicate the behavior using raw DispatchQueues, but I'd rather keep the simple solution of an OperationQueue on top of the DispatchQueue.

As an aside, can anyone tell me if a DispatchQueue initialized with a targetQueue will suspend the target if it's suspended? That could be another solution if it doesn't.

@swift-ci
Copy link
Contributor Author

Comment by Maksim Orlovich (JIRA)

Hi @jshier. Thanks for your input wrt this bug. I'm not sure of the behavior of DispatchQueue's on Linux when it comes to the target DispatchQueue's. I'll try and experiment and post my findings here. I suspect it will match the Darwin behavior as https://github.com/apple/swift-corelibs-libdispatch appears to be based on Apple's libdispatch (https://opensource.apple.com/source/libdispatch/)...

By the way, here's the initial support for Alamofire 5 on Linux:
https://github.com/maksimorlovich/alamofire/tree/alamofire5-linux

I had to stub out unsupported features like ServerTrust evaluators and URLTaskSessionMetrics, but for the most part, things seem to work. I've got about 200 unit tests passing out of the box, however, this bug and https://bugs.swift.org/browse/SR-8542 are causing problems...

@swift-ci
Copy link
Contributor Author

Comment by Maksim Orlovich (JIRA)

@jshier I have tried the experiment with DispatchQueue's targets and behavior on Ubuntu is the same as on Darwin. Here's what I tried:

import Foundation
import Dispatch

let dq1 = DispatchQueue(label: "org.swift.queue1")
let dq2 = DispatchQueue(label: "org.swift.queue2", target: dq1)

//dq1.suspend()
//dq2.suspend()

var done1 = false
var done2 = false

dq1.async {
print("on DQ1")
done1 = true
}

dq2.async {
print("on DQ2")
done2 = true
}

dq1.resume()

repeat {
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.01))
} while done1 == false || done2 == false

print("DONE")

Calling dq1.suspend() appears to suspend both queues (neither statement is printed). However, calling dq2.suspend() appears to suspend only DQ2 and dq1 continues to run. This behavior is the same on both Ubuntu and on Darwin.

@swift-ci
Copy link
Contributor Author

Comment by Maksim Orlovich (JIRA)

Fixed in Swift 4.2 release and in master branch.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@shahmishal shahmishal transferred this issue from apple/swift May 5, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants