Coder Social home page Coder Social logo

Comments (22)

jspahrsummers avatar jspahrsummers commented on April 19, 2024

I want to spend some time thinking about this, but it's difficult without some examples of how cancelable signals are currently used.

I know we use them for shell tasks and git operations in GHfM, but are there more widely applicable use cases?

from reactivecocoa.

joshaber avatar joshaber commented on April 19, 2024

HTTP requests could be something you'd want to cancel. A length upload or download especially. More generally, any long-running asynchronous work.

When I talked over this with @xpaulbettsx a while back, he suggested that it should just be a matter of all subscribers disposing of their disposable.

I like the simplicity of that, but practically I'm not sure how that'd work. Cancelation probably comes from one place, while you could have n subscribers and those bitches don't known 'bout its cancelation.

Maybe cancelable things could be -multicast:'d and then you'd just keep the disposable from -connect around to use that to cancel? So there wouldn't be any formal idea of cancelation, just disposal.

from reactivecocoa.

jspahrsummers avatar jspahrsummers commented on April 19, 2024

That's a little scary to me, and seems to exacerbate the "conceptually weird" part, because you still have subscriptions that are terminating without completing or erroring, except now it's even more opaque and action-at-a-distance-y.

from reactivecocoa.

jspahrsummers avatar jspahrsummers commented on April 19, 2024

How would an FRP language (like Elm) handle stuff like this?

from reactivecocoa.

joshaber avatar joshaber commented on April 19, 2024

No idea. I haven't seen a similar concept yet. That's why I'm inclined to say we shouldn't include it.

from reactivecocoa.

dannygreg avatar dannygreg commented on April 19, 2024

FWIW one of the reasons that this came to being was the probable need for clean up when cancelling an operation, so simply disposing of the signal wouldn't solve that case.

What specific example we were basing that on I'm not completely sure.

from reactivecocoa.

jspahrsummers avatar jspahrsummers commented on April 19, 2024

Cleanup can always be handled with disposables, though:

- (id<RACSignal>)fetchData {
    return [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        NSOperation *fetchOperation = …;

        fetchOperation.completionBlock = ^(NSData *data) {
            [subscriber sendNext:data];
            [subscriber sendCompleted];
        };

        return [RACDisposable disposableWithBlock:^{
            [fetchOperation cancel];
        }];
    }];
}

from reactivecocoa.

Coneko avatar Coneko commented on April 19, 2024

What does it mean to cancel a signal? Does it complete? Does it error?

It could error with NSUserCancelledError, but it would make subscribing to errors a bit more verbose if you have to handle it differently in many cases.

from reactivecocoa.

joshaber avatar joshaber commented on April 19, 2024

The question in my mind is, does it even make conceptual sense to talk about canceling a signal? It seems like a signal represents the result of the work, not the work itself. You cancel getting the result by disposing of your subscription. Canceling the work is an entirely different thing that seems separate from signals.

from reactivecocoa.

Coneko avatar Coneko commented on April 19, 2024

Yes, I see your point.
So you mean cancellation isn't something signals themselves should handle in general, but should be left up to the specific signal implementations.

I think this kind of separation between signals and "things that are related to signals but aren't signals" comes up with other FRP libraries too, in fact I have seen functions that return tuples of values, with one of the values being the signal, and the other values being related to it but used to interact with it in ways that wouldn't make sense for all signals, for example to send values to it or destroy it and so on.

// Returns a tuple where the first element is a signal that ...
// and the second element is a block that can be called to cancel the fetch operation
- (RACTuple *)fetchData {
    NSOperation *fetchOperation = nil;
    return [RACTuple tupleWithObjects:@[ [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        NSOperation *fetchOperation = ...;

        fetchOperation.completionBlock = ^(NSData *data) {
            if (fetchOperation.isCancelled) {
                [subscriber sendError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]];
                return;
            }
            [subscriber sendNext:data];
            [subscriber sendCompleted];
        };

        return [RACDisposable disposableWithBlock:^{
            [fetchOperation cancel];
        }];
    }];, ^{
        [fetchOperation cancel];
    } ]];
}

from reactivecocoa.

dannygreg avatar dannygreg commented on April 19, 2024

I'm not sure a signal does just represent the result of your work. In that I don't think I like the idea of having to keep hold of multiple references which represent the same unit of work, merely to use them for different purposes.

from reactivecocoa.

joshaber avatar joshaber commented on April 19, 2024

@dannygreg You wouldn't have to keep multiple references around. The signal would be automatically kept around as usual as a consequence of having subscribers. You'd just need to keep the block or disposable or whatever around.

from reactivecocoa.

joshaber avatar joshaber commented on April 19, 2024

@Coneko I like that idea though I hate how awkward tuples are in Objective-C. At the very least, it seems we can agree that cancelable signals should die.

from reactivecocoa.

joshaber avatar joshaber commented on April 19, 2024

So do we all agree RACCancelableSignal should 🔥?

from reactivecocoa.

jwilling avatar jwilling commented on April 19, 2024

from reactivecocoa.

dannygreg avatar dannygreg commented on April 19, 2024

Sure.

from reactivecocoa.

jspahrsummers avatar jspahrsummers commented on April 19, 2024

The question in my mind is, does it even make conceptual sense to talk about canceling a signal? It seems like a signal represents the result of the work, not the work itself. You cancel getting the result by disposing of your subscription. Canceling the work is an entirely different thing that seems separate from signals.

In a pure functional reactive world, you shouldn't really care how the work occurs or even if there is any (it could be mock data, for example). You just indicate that you don't want the results any more, and the environment infers that the operation should be canceled.

Now, it's arguably* true that there are UI cases where we need deterministic cancelation, so I think the answer is just to ensure that the semantics of disposables (with regard to canceling ongoing work) are well-defined, and then build or use methods like -takeUntil: which help control disposal.

*Honestly, I'm not even convinced a UI needs to cancel immediately. "As soon as possible" is a well-established convention for software, and there's no guarantee that the user will cancel at the right time to avoid whatever side effects there are anyways.

from reactivecocoa.

joshaber avatar joshaber commented on April 19, 2024

Lemme make sure I'm understanding you.

You're saying that disposing of the subscription should cancel. So it's up to callers to coordinate with whoever else might subscribe to the signal and tell them to dispose when cancelation should happen.

Is that roughly right?

Conceptually I might agree. I'm worried that'd be onerous for users. I guess there's no reason they'd have to do that.

Here's a concrete example we can talk through:

RACSignal *signal = [self someCancelableThing];

self.disposable = [signal subscribeNext:^(id x) {
    // do stuff
}];

// does a subscribe or 10, who knows
[self presentUIWithProgressForSignalOrSomeBullshit:signal];

[self.cancelCommand subscribeNext:^(id _) {
    [self.disposable dispose];
}];

So now users have to know something about what -presentUIWithProgressForSignalOrSomeBullshit: (or anything to which it passes the signal!) does and coordinate cancelation with it.

from reactivecocoa.

jspahrsummers avatar jspahrsummers commented on April 19, 2024

I would write the above as:

// Depends on -takeUntil: being guaranteed to dispose of the signal it subscribes to.
RACSignal *signal = [[self someCancelableThing] takeUntil:self.cancelCommand];

[signal subscribeNext:^(id x) {
    // do stuff
}];

// does a subscribe or 10, who knows
[self presentUIWithProgressForSignalOrSomeBullshit:signal];

In this way, the code which gets passed signal doesn't have to worry about cancellation, and control over when cancellation occurs is maintained at the point of creation.

from reactivecocoa.

joshaber avatar joshaber commented on April 19, 2024

@jspahrsummers Good point 👍

from reactivecocoa.

jspahrsummers avatar jspahrsummers commented on April 19, 2024

Although, looking at my code again, you would probably have to multicast signal in order to avoid a race condition with cancelCommand (if one of the hypothetical 10 subscribers only subscribes after cancelCommand has already sent).

from reactivecocoa.

jspahrsummers avatar jspahrsummers commented on April 19, 2024

I'm spending some time reading through the Rx Design Guidelines, and section 4.4 seems relevant here:

When unsubscribe is called on an observable subscription, the observable sequence will make a best effort attempt to stop all outstanding work. This means that any queued work that has not been started will not start.

Any work that is already in progress might still complete as it is not always safe to abort work that is in progress. Results from this work will not be signaled to any previously subscribed observer instances.

from reactivecocoa.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.