Using Dispatch Internals for Testing

After the release of my ErrorAssertions package that allows you to write tests around places your code calls fatalError() and friends, I turned my attention to another assertion method in Swift: Dispatch’s dispatchPrecondition(condition:) method. If I were successful, I’d be able to write this in the context of an XCTest test method:

expectDispatchPreconditionFailure {
    let queue = DispatchQueue(label: "com.slaunchaman.testqueue")
    queue.async {
        dispatchPrecondition(.notOnQueue(queue))
    }
}

This is a contrived example, but the long and short of it is that this allows you to test your error cases that would otherwise crash the test suite. Unfortunately, digging into the implementation of dispatchPrecondition(condition:), we find ourselves in dispatch_assert_queue(), and a failure there calls more internal methods to crash.

A good implementation of my code would be able to evaluate a DispatchPredicate without crashing. A tweak of dispatch_assert_queue() might look like this:

bool
dispatch_assert_queue_evaluate(dispatch_queue_t dq)
{
	unsigned long metatype = dx_metatype(dq);
	if (unlikely(metatype != _DISPATCH_LANE_TYPE &&
			metatype != _DISPATCH_WORKLOOP_TYPE)) {
		DISPATCH_CLIENT_CRASH(metatype, "invalid queue passed to "
				"dispatch_assert_queue()");
	}
	uint64_t dq_state = os_atomic_load2o(dq, dq_state, relaxed);
	if (likely(_dq_state_drain_locked_by_self(dq_state))) {
		return true;
	}
	if (likely(_dispatch_thread_frame_find_queue(dq))) {
		return true;
	}
	return false;
}

In fact, you could rewrite dispatch_assert_queue() to take advantage of this method, but I don’t know if the additional overhead would be a problem for such low-level code.

So, if you’ve gotten this far, thanks! Here’s the real question. Is it possible to access the two conditions in the code above—_dq_state_drain_locked_by_self(dq_state) and _dispatch_thread_frame_find_queue(dq)—from Swift? I have a naïve implementation that evaluates a DispatchPredicate in my WIP branch:

func currentQueueName() -> String? {
    let name = __dispatch_queue_get_label(nil)
    return String(cString: name, encoding: .utf8)
}

@available(macOS 10.12, iOS 10, watchOS 3, tvOS 10, *)
extension DispatchPredicate {
    
    func evaluate() -> Bool {
        switch self {
        case .onQueue(let queue):
            return currentQueueName() == queue.label
        case .notOnQueue(let queue):
            return currentQueueName() != queue.label
        default:
            fatalError("Haven't implemented this yet.")
        }
    }
    
}

This is obviously problematic in several ways.

Second question: If I were to submit a patch to swift-corelibs-libdispatch that exposed the evaluation of these conditions, would that be a desirable and acceptable solution to this problem?

I've resorted to the same trick in my tests: Dispatch precondition false positive?

1 Like