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
-repeat doesn't stop after being disposed of #94
Comments
Ugh. So this is a pretty annoying one. Since the repeat and replay happen on the same thread, it gets stuck replaying and repeating before It might be a bit more obvious in this example: RACSubscribable *subscribable = [RACSubscribable createSubscribable:^ RACDisposable * (id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendCompleted];
return nil;
}];
__block RACDisposable *disposable = [[subscribable repeat] subscribeNext:^(id _) {
NSLog(@"disposable: %@", disposable);
[disposable dispose];
}]; Disposable will always be We had a similar problem with the old implementation of generators. I'm not really sure how to solve this generally. I think Rx solves this by saying, you can never know what scheduler results might be delivered on, unless you manually specify it. That way they can split the repeat and replay so that the disposable's returned before the repeat actually happens. |
Maybe we could add a subscription variant that gives the disposable to the block? Seems like that might be generally useful anyways. |
If I understand you right, the tricky thing is that we don't have the important disposable until after the subscribable's |
Generally speaking, are subscribers supposed to be able to dispose their own subscription? |
Absolutely, they really have to to be able to end potentially unending subscribables. |
What about timeliness? Is it enough if they're only able to dispose the subscription eventually and not immediately? The general pattern used for implementing a lot of the methods on I was wondering if that behaviour was to be considered a bug too. |
Yes, I think that's a good summary of the heart of this bug. |
Oh ok, because I fixed So it's just a band-aid, not a real fix. (https://gist.github.com/4019491) |
I think a generalized version of that is the best fix. But Which takes us back to the Rx position of saying you can't depend on the queue it's in unless specified. |
It's not generally safe to asynchronously dispatch to the current GCD queue, because it could be a temporary user-created queue that's going away. The block will still get executed (because GCD makes such a guarantee), but could result in crazy unpredictable behavior to the user. |
Right, GCD does say queued blocks retain the queue they're queued on. You could implement a policy of "if you call RAC methods from a |
The "calling scheduler" is a nebulous concept, though. For an operation queue, that's |
Exactly, that's why it's so awkward. We can still make guarantees about |
What I mean by "calling scheduler" is something like The implementation could mix The user would lose the ability to create new schedulers with arbitrary scheduling backing because of this. Since all the scheduling of subscribables goes through I'm not against explicitly specifying the scheduler on which to deliver, I just wouldn't want to have to specify it multiple times in a chain because each link of the chain can potentially reschedule on a different one. |
@Coneko I think there are still problems with that idea, because it'd be possible to get into a case where one thread or queue is technically associated with multiple schedulers. This is mostly an issue with concurrent GCD queues. For example, an operation queue |
Yes, the general idea isn't very robust, so the implementation has a lot of gotchas, but in your example the I agree it's it's not perfect, and as I mentioned before it makes implementing custom |
That's a good point about target queues. I'm curious if some mix of deferred scheduling, operation queue scheduling, etc. could still result in an incorrect |
I'd be backing my idea a lot more if unit testing these threading shenanigans reliably were possible. |
I found out today, while working on #138, that This makes it just as broken as |
I understood |
I think that means we'll end up in a lot of cases where |
I think the sanest way to resolve this would be to change the subscription API a bit. For example, if return [RACSignal createSignal:^(id<RACSubscriber> subscriber, BOOL *stop) {
while (YES) {
if (*stop) break;
[subscriber sendNext:RACUnit.defaultUnit];
}
[subscriber sendCompleted];
return nil;
}]; |
I think that's a very good idea. Regardless of how the thing with the schedulers evolves, having to do all that dispatching/scheduling just to implement even something as simple as |
@jspahrsummers I'm not sure how that'd solve the problem. You'd get stuck in the loop before you'd get access to the address of |
With an API like that available, you could also implement stuff like: - (void)subscribeNext:(void (^)(id value, BOOL *stop))next error:(void (^)(NSError *))error completed:(void (^)(void))completed; I could take a pass at it. I'm pretty sure it would solve this issue. |
But now we'd have two different ways of stopping a signal: with a disposable or with |
This API is uglier, but the new scheduler work feels like a hack to work around the timing of how disposables are created. Why not just solve when they're created/made available? The idea of setting a - (RACDisposable *)subscribeNext:(void (^)(id, RACDisposable *))next; |
I don't think adding new parameters to the subscription methods is right. Subscriptions should work correctly regardless of how they're disposed of, you can't give the user two ways of disposing them, and then have them behave differently. Rather, change the I guess that would go back to the scheduler fix anyway. Still, it would hide the scheduling complexity in the internal implementation, leaving more elegant APIs both for the user that creates the signal and the user that consumes it. |
I don't see how any of these ideas would work. You can't create/return a disposable without subscribing, and as soon as you subscribe you're going down the rabbit hole of infinite subscriptions. The re-subscribe has to be deferred. I don't see any other way. |
Maybe it was a mistake to bring up concrete APIs before really explaining my thought process. The key I'm trying to communicate is this: you can return a disposable without subscribing, as long as:
That's what the |
What I meant before was in fact returning the disposable immediately, but subscribing deferred to ensure the caller received the disposable in the meanwhile and was able to dispose it if needed. @jspahrsummers : I agree it would work, I just think the API would be really ugly if all the methods that currently return |
Again, this seems to just make the API uglier for a problem that can be solved other ways. |
Alright, well, I'm willing to give deferred subscription a shot. I just think it's going to be really surprising sometimes; for example, what happens to |
@jspahrsummers That's exactly what |
👍 |
In order to solve issue #94, it's necessary for the +iterativeScheduler to enqueue the block, return full control to the currently-scheduled block, and then let that block complete. Otherwise, we may still not get the disposable we need.
👍 |
Noted in #93:
Loops indefinitely.
The text was updated successfully, but these errors were encountered: