Can NSOperation have a lower qualityOfService than NSOperationQueue?

1.9k views Asked by At

The doc of NSOperationQueue.qualityOfService says:

This property specifies the service level applied to operation objects added to the queue. If the operation object has an explicit service level set, that value is used instead.

It doesn't mention anything about the relative quality between operation and queue so any combination should work.

However, GCD has some corresponding features whose docs say:

DISPATCH_BLOCK_ENFORCE_QOS_CLASS

Flag indicating that execution of a dispatch block object submitted to a queue should prefer the QOS class assigned to the block (resp. associated with the block at the time of submission) over the QOS class assigned to the queue, as long as doing so will not result in a lower QOS class.

If NSOperation and NSOperationQueue are built on top of GCD — which I believe is true — it seems the restriction should also apply to them.

So if I add an NSOperation with a lower qualityOfService to an NSOperationQueue will it run with its own lower qualityOfService or the queue's higher qualityOfService?

3

There are 3 answers

5
an0 On BEST ANSWER

Thanks @das for pointing me to Foundation Release Notes for OS X v10.10.

Ignoring the operation dependency part, rules of QoS between NSOperation and NSOperationQueue are exactly the same as that between dispatch block and dispatch queue — though @das points out "the QoS implementation of NSOperationQueue is not built on top of the GCD QoS block primitives" the rules are actually the same, that is to say, NSOperation's QoS(if lower) will be raise to NSOperationQueue's QoS. So my initial guess is correct. The doc of NSOperationQueue.qualityOfService is wrong:

If the operation object has an explicit service level set, that value is used instead.

Here are the detailed explanation of QoS rules of NSOperation and NSOperationQueue:

NSOperationQueue qualityOfService

NSOperationQueue has a new qualityOfService property.

You can change the qualityOfService property at any time.

When an operation is added to a queue, the queue's qualityOfService value at that time may affect the effective QOS that the operation will be run at:

  • If the queue property has not been set, the operation is unaffected.
  • If the queue property has been set to NSQualityOfServiceDefault, the operation is unaffected.
  • If the queue property is set to another value, the operation is promoted to the queue's qualityOfService if its promotion QOS is not already at least that level.

If the qualityOfService property of a queue is changed while there are operations in the queue, the effective QOS of operations in the queue will be affected, just as the operations were when added to the queue. Thus, when the qualityOfService property of a queue is changed, all operations in the queue, running or not, have their effective QOS raised up to that level (if they were lower), and future additions to the queue are raised to that level (when they are lower). If the qualityOfService property is lowered from one level to another, only future additions will be affected by that new lower value. Operations' promotion or effective QOS is never lowered by the setting of, or the set value of, a queue's qualityOfService.

When an operation is added to a queue, the operation's effective QOS values at that time may affect the effective QOS of the operations which are already in the queue ("ahead of it"):

  • The operations already ahead in the queue of the newly added operation, running or not, are promoted to the effective QOS of the operation being added.

Thus, if a high QOS operation is added to a queue, operations already in the queue are raised up to that level (if they were lower). Operations added after that high-QOS operation are not affected by its presence in the queue.

The meaning and interaction of operation promotion QOS and effective QOS is discussed in the section on NSOperation qualityOfService.

NSOperationQueues do not infer any QOS from any execution context.

If the (dispatch_queue_t) underlyingQueue property of an NSOperationQueue is set, qualityOfService property values of NSOperationQueues and NSOperations have no effect. The effective QOS of operations run by that queue is determined by the state of the dispatch_queue_t.

NSOperation qualityOfService

NSOperation has a new qualityOfService property.

You can change the qualityOfService property at any time.

There are various real and virtual QOS values that relate to how an operation runs:

  • The qualityOfService property value
  • An inferred QOS
  • The promotion QOSes
  • The effective QOS

When an operation object is created, an inferred QOS value is computed from the execution context:

  • If either:
    • the operation is being created in the execution context of another operation (already running on that thread); or
    • the operation is being created in the execution context of a certain NSProcessInfo API; then the nearest one of those to the current activation frame of the call stack of the current thread is used as the inferred QOS of the new operation:
    • that operation's effective QOS at the time it started running;
    • the NSProcessInfo API's values are mapped to a QOS value.
  • If the operation is being created on the main thread, the inferred QOS is NSQualityOfServiceUserInitiated.
  • Otherwise, the current thread's QOS (which may be none) is read and used as the inferred QOS of the new operation.

An operation can be promoted (have promotion QOSes applied to it) in several contexts:

  • When the operation is added to a queue, or when the qualityOfService property of the queue the operation is in is changed
    • (as discussed in the NSOperationQueue section)
  • When a different operation is added to a queue that the operation (in question) is already in
    • (as discussed in the NSOperationQueue section)
  • When a different later (after the operation in question) operation in the same queue has its effective QOS raised
    • the effective QOS of the other operation promotes the operation
  • When a different operation (a dependee) becomes dependent upon the operation in question
    • the effective QOS of the dependee promotes the operation
  • When a dependee operation has its effective QOS raised
    • the new effective QOS of the dependee promotes the operation
  • When the operation is waited upon, with the -waitUntilFinished method, or indirectly when the operation's queue's -waitUntilAllOperationsAreFinished method, is used
    • if the waiting thread is the main thread, the promotion QOS is taken to be NSQualityOfServiceUserInteractive;
    • otherwise if the waiting is done in the execution context of another operation, its effective QOS promotes the operation;
    • otherwise the QOS of the current thread promotes the operation.

These are all collectively called the promotion QOSes; or for the MAX() of all of them, just promotion QOS.

These various values are put together into the effective QOS. The effective QOS is the MAX() of all of these QOS values: {the inferred QOS, the promotion QOSes, the qualityOfService property value}, with these qualifications:

  • If the operation's qualityOfService property has been explicitly set to anything, even NSQualityOfServiceDefault, the inferred QOS is ignored.
  • If the operation's qualityOfService property has not been explicitly set to anything, it is ignored (as if no value exists).
  • All QOS values of NSQualityOfServiceDefault are ignored.
  • If there are no QOS values after all that ignoring, the effective QOS is NSQualityOfServiceDefault.

Thus, for example, if an operation is waited upon, its effective QOS may be raised by the waiting context, which may recursively raised all of its dependent operations and all operations ahead of it in the queue (and so on recursively outward in the tree of these relationships).

An operation's qualityOfService property value has no effect if the operation is started manually, rather than being put in an NSOperationQueue, unless the code which is starting it reads out that value and makes some appropriate use of it; that is outside the purview of Foundation.

2
das On

The QoS implementation of NSOperationQueue is not built on top of the GCD QoS block primitives.

The NSOperationQueue behaviors are documented in detail in the Foundation release notes.

For GCD queues, the only time QoS is lowered when executing a block with assigned or propagated QoS is when the queue the block was submitted to has unspecified QoS (queue was created without a QoS attribute and is not targeting a non-default global queue). In that case, the queue is initially serviced by a worker thread at QoS class default and lowering is allowed.

See the documentation in the dispatch/block.h header or the GCD WWDC sessions from 2015 and 2014 for more details.

1
ipmcc On

spindump's timeline mode will show you the QoS of the sampled process's threads. You might have to contrive things a little to get spindump to tell you what's going on, and give you some visibility into how the QoS promotion stuff works. The idea of QoS boosting is to prevent priority inversions, so a user-initiated operation won't get starved out.