-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Generalize DispatchQueues to Dispatcher-protocol objects #888
Conversation
Nice patch, and very thoroughly PR’d.
Probably not true, since I'll merge this and try it on a few projects to see if it just works.
I’m cool with it this way. It's already pretty crowded in the other files.
The
Is ambiguous (sadly) despite the normal version returning But since Dispatcher is our own class we don't need the namespacer, I understand it is consistent, but since the original purposes is disambiguation I think we should stick with that rationale. Possibly we should just bite the bullet and make this PMK7, this would allow us to be more ruthless with making this system the default and provide some convenience overloads so dispatch works well (since most people will use that). Mainly I just worry about corner case issues that may result for our innocent and unsuspecting PMK6 users who didn't want this feature (yet). |
Any clue on the Linux 3.x failures on Travis? |
OK, great, I'll proceed on this. Work list:
I'm leaving for vacation soon and won't get to this until I return; that will be around July 18. I'm looking forward to the results of your spot checks. |
I'm ready to start working on this again. Max, did you have a chance to run this version against any existing codebases? |
I think we should make this a version 7 feature, along with #896, thus there's no risks. Then it's easy for us all to test an alpha release of that and offer feedback. |
So we should merge into a branch like |
Sounds good. I'll get back with an updated branch to replace this PR in a week or two. |
82d6e4a
to
f5e7e4c
Compare
.travis.yml
Outdated
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.0 TEST=1'} | ||
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=DEVELOPMENT-SNAPSHOT-2018-06-20-a'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just pushed this to master #909
Sounds like you're up to date on this, but just to be explicit: the issue with Linux testing related to an oddity of older Linux Swift compilers in Swift 3 compatibility mode. As outlined above, this whole approach depends on Swift enforcing a fewest-defaulted-arguments rule for disambiguating overloaded functions. This always worked fine for both Swift 3 and Swift 4 in all versions of Xcode. But for some reason, there was a recent span of versions of the Linux Swift codebase that were not applying this rule in Swift 3 mode. That appears to be fixed in more recent builds. TL;DR: Unless you care strongly about 1) Swift 3 compatibility 2) on Linux 3) consistently across every historical Linux compiler version, the Travis errors are essentially spurious and can be ignored/reconfigured out. |
0e105ba
to
4ff9962
Compare
It’s weird but I can’t change the branch this merges into, can you change it to |
Sure, I can work on this tomorrow. |
f32e06d
to
fbe14e6
Compare
mxcl#972) * Fixed bug where pendingPromiseDeallocated event was incorrectly reported as a waitOnMainThread event. Added test which should have caught this bug in the first place. * Kick Travis CI
Sorry it's taken me a while longer to get to this - I spent some time wrestling with Travis CI. I will be more available over the next couple of months. I have a branch that rebases onto the current v7, but I didn't realize this PR was an active reference into my clone and I moved the head pointer in a way that github didn't like, so it closed the PR. I'm reopening now and I'm going to see if I can get that original "dispatch" branch rehabilitated, but if it doesn't work I'll submit a separate PR for "dispatch-v7". This is just a baseline. I haven't added docs or addressed any of the issues above yet. As mentioned above, this code depends on the fact that the compiler prefers overloads that default fewer arguments. Looking more into the multiple test builds, I've discovered that this really has nothing to do with the version of Swift and everything to do with the compiler. Evidently it's not part of the language specification but part of the compiler's implementation latitude. The result is that this branch is compatible with both Swift 3 and Swift 4, but you must use a relatively current compiler. That seems to be true across all platforms. Older compilers will complain about ambiguous method calls in client code and even in the internal implementation of PromiseKit. I'm not sure how much of a barrier this really presents, but if someone runs into the problem in the wild, it's going to be hard for them to figure it out unless it's a FAQ. I'm also seeing sporadic hangs in the Travis testing phases, usually in WrapTests or WhenTests. These seem to occur on the current master, too, so my working hypothesis is that it's not related to these updates. |
I think this original PR is now good again, but the dispatch-v7 branch referenced below is cleaner. |
Shall I close in favor of #975? |
Yes, I guess that's the right thing, although there's a lot of useful context above. There's no way to convert a PR to an Issue, is there? |
No, but the connection is in the other, so we can refer back. I'll close so this one doesn't distract. |
fix analyzer memory leak
This set of diffs addresses issue #878, "DispatchQueues should be generalized". It's for review; I wouldn't merge this just yet even if you like it.
Summary
This PR adds a
Dispatcher
protocol and makesDispatchQueue
s conformant. The good news is that this will work smoothly without adding ambiguity. Backward compatibility should be flawless.The bad news is that there is no way to do it without adding function overloads. The wrappers are trivial but numerous. They seem somewhat out of place in this otherwise very clean code base. For reasons of API convenience described below, I think they would need to be retained going forward. They're not just transitional.
So, all things considered, I will completely understand if you don't want to take this on.
Why wrappers are required
One of the underlying motivations to move away from
DispatchQueue
s is that they cannot be subclassed. Not just "shouldn't be subclassed" but "mechanically cannot".class MyDispatchQueue: DispatchQueue {}
gives a compiler error.If
Dispatcher
is a protocol, arguments of that type can acceptDispatchQueue
s directly. However, they cannot be made to accept forms likethen(on: .main)
because protocols cannot have static members. (Well, they can, but they just impose this requirement on implementors, like any other protocol requirement. You cannot defineDispatcher.main
on the abstract protocol type.)If
Dispatcher
is a specific type, then it can define.main
et al, but it can't accept arguments of typeDispatchQueue
, so you would still need overloads for that.Form of wrappers
Here's the general pattern. Canonical function:
Corresponding wrapper:
This pattern has a couple of nice advantages.
First, dispatch flags are properly a concern and implementation detail of
Dispatcher
objects. This pattern takes them out of the canonical API entirely while continuing to fully support them for backward compatibility.Second, the fact that wrappers have a
flags:
argument and canonical functions do not heads off any potential function overloading ambiguities. Swift strongly prefers to resolve function calls to the signature that requires the fewest defaulted arguments.In fact, this preference is so strong that a form such as
calls the canonical function directly, bypassing the wrapper entirely. Normally, a function with a protocol-type argument would be a second choice relative to a type-specific function, but the minimization of defaulted arguments has an even higher priority. (Of course,
{...}.done {...}
calls the canonical function directly as well.)Of course, if there is a
flags:
argument supplied, then the call can only be resolved in one way and there is no ambiguity.The only code paths that will use the wrappers are 1) calls with
flags:
arguments, and 2) forms such asdone(on: .main)
anddone(on: .global(qos: .background))
. The need to infer the types in #2 locks the call to the wrapper, even though the resulting type would bypass the wrapper were it supplied independently.What is conf.D?
The
Dispatcher
analog ofconf.Q
with type(map: Dispatcher, return: Dispatcher)
.conf.Q
remains in the API for compatibility. It's now a gateway toconf.D
. The only pattern that will mess things up is setting a non-DispatchQueue
value for one of theconf.D
fields and then attempting to read it back or modify it through theconf.Q
interface. You'll just getnil
. (But if you're messing around withDispatcher
andconf.D
, you've already left the realm of perfect backward compatibility.)conf.D
isn't necessarily the optimal name for this, I'll allow. Pick your poison.Why do
conf.D
and canonical functions have non-optionalDispatcher
arguments instead of arguments of typeDispatcher?
Because
Dispatcher
objects can implement dispatching however they like, there's no need foron: nil
to be a special case anymore. You can just dispatch to aCurrentThreadDispatcher
(supplied) that runs the closure directly.The
on: nil
form still works, but it goes through the wrapper path.What is
.pmkDefault
in the wrapper signature?It's an arbitrary sentinel value. Wrappers need to distinguish between explicitly-specified
DispatchQueue
values, explicitly-specifiednil
s, and the "no value specified" case. You can theoretically supplyflags:
withouton:
; this continues to work fine as long as the defaultDispatcher
is in fact aDispatchQueue
..pmkDefault
never appears in synthesized framework header files or in option-click help. It just shows up as "default".Why doesn't the
Dispatcher
protocol just mirror theDispatchQueue
API?For reference, this is the entire protocol:
It could be called
async
just for familiarity, but in fact dispatching is not required to be asynchronous, cfCurrentThreadDispatcher
. Also, existing DispatchQueueasync
functions would not satisfy this simplified signature, so an adapter function would still be required.If those functions on
DispatchQueue
are all calledasync
, it triggers ambiguity warnings and the potential for unintended infinite recursion.Dispatcher
objects are of course free to make use of the entireDispatchQueue
API if they wish. The suppliedDispatchQueueDispatcher
adapter fully supports flags and could be easily extended to support QoS overrides andDispatchGroups
.Note that wrappers generate a
DispatchQueueDispatcher
only when they have flags to propagate. For plain-vanilla cases, the underlyingDispatchQueue
is just passed directly as aDispatcher
with no overhead.Other comments and open issues
Function-level documentation comments should be fully accurate and updated. I did not change any of the separate documentation files but will do so if you are interested in pursuing this change.
Since turning off dispatching (i.e., use of
CurrentThreadDispatcher
) is primarily a debugging tool, one could argue that whenconf.*
is set to that mode, it should override any explicitly-specifiedDispatcher
orDispatchQueue
arguments and force synchronous dispatch everywhere. That is not the current behavior, though, so I have followed the existing scheme. I'm not even sure if this would work without causing deadlocks, but if you were interested in exploring it, now might be a good time.I updated the deprecations but didn't touch any Objective-C material. I also don't have a ready Linux testbed for this and am not sure what to look out for there.
There are unit tests for dispatching.
Dispatcher.dispatch(.promise) {}
behaves analogously to theDispatchQueue
version.Arguably, wrappers should be right next to their canonical versions for ease of maintenance. Currently they are all bundled into Dispatcher.swift.
Some parts of the existing code use dispatch queues, dispatch groups, and features such as barriers. That's fine, and to the extent that I understand the details, I see no reason to mess with this. It's internal implementation.
Although I think this API is pretty free from ambiguity and overload conflicts, I have seen a couple of cases where Swift reports the error "DispatchQueue is not convertible to DispatchQueue?" when it can't figure out the type information in a chain. I suppose this is no worse than the average nonsense error that Swift usually generates in such situations.