Skip to content
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

Delay between -repeat and -retry attempts #882

Closed
neilpa opened this issue Oct 21, 2013 · 8 comments
Closed

Delay between -repeat and -retry attempts #882

neilpa opened this issue Oct 21, 2013 · 8 comments
Labels

Comments

@neilpa
Copy link
Member

neilpa commented Oct 21, 2013

I'm trying to find a better solution for delaying -repeat and -retry than what I currently have.

@implementation RACSignal (Delayed)

- (RACSignal*) concatDelay:(NSTimeInterval)interval
{
    return [[self concat:[[RACSignal empty] delay:interval]];
        setNameWithFormat:@"[%@] -concatDelay: %f", self.name, interval]];
}

- (RACSignal*) delayedRepeat:(NSTimeInterval)interval
{
    return [[[self concatDelay:interval] repeat];
        setNameWithFormat:@"[%@] -delayedRepeat: %f", self.name, interval];
}

- (RACSignal*) delayedRetry:(NSTimeInterval)interval
{
    return [[[self concatDelay:interval] retry]
        setNameWithFormat:@"[%@] -delayedRetry: %f", self.name, interval];
}

@end

This works okay for repeat but sucks for retry since completion is delayed for the first non-failing signal.

This solution isn't flexible either since the time interval is constant. A generator RACSequence that provided the delay would be a lot better. That would make things like exponential back-off a breeze.

I've been trying to do this in terms of the existing operators. However, after looking through many of the operator implementations I'm starting to think I'll have to go lower level.

@kastiglione
Copy link
Member

Have you looked at +interval:onScheduler:? This is how I would probably do it:

- (RACSignal *)delayedRepeat:(NSTimeInterval)interval {
    RACSignal *delay = [[[RACSignal
        interval:interval onScheduler:RACScheduler.currentScheduler]
        take:1]
        ignoreElements];

    return [[self
        concat:delay]
        repeat];
}

- (RACSignal *)delayedRetry:(NSTimeInterval)interval {
    RACSignal *delay = [[[RACSignal
        interval:interval onScheduler:RACScheduler.currentScheduler]
        take:1]
        ignoreElements];

    return [[self
        catch:^(NSError *error) {
            return [delay concat:[RACSignal error:error]];
        }]
        retry];
}

@neilpa
Copy link
Member Author

neilpa commented Oct 21, 2013

You're use of catch is nice and solves the retry timing issue I mentioned. Otherwise though this isn't much different than my approach. Is there some benefit to using +interval:onScheduler: instead of +empty with -delay to create the concat-ed signal?

@kastiglione
Copy link
Member

Actually, you could similarly do -delayedRetry: with -delay::

- (RACSignal *)delayedRetry:(NSTimeInterval)interval {
    return [[self
        catch:^(NSError *error) {
            return [[[RACSignal
                empty]
                delay:interval]
                concat:[RACSignal error:error]];
        }]
        retry];
}

EDIT: Fixed a duh.

@neilpa
Copy link
Member Author

neilpa commented Oct 21, 2013

That looks better, was about to comment that you're last version didn't work

@kastiglione
Copy link
Member

To use a sequence, you might do:

- (RACSignal *)delayedRetry:(RACSequence *)intervalSequence {
    __block RACSequence *intervals = intervalSequence;

    return [[self
        catch:^(NSError *error) {
            NSTimeInterval interval = intervals.head.doubleValue;
            if (intervals.tail != nil) intervals = intervals.tail;

            return [[[RACSignal
                empty]
                delay:interval]
                concat:[RACSignal error:error]];
        }]
        retry];
}

I have not tried this.

@neilpa
Copy link
Member Author

neilpa commented Oct 21, 2013

That should work.

@neilpa
Copy link
Member Author

neilpa commented Oct 21, 2013

I was looking over RAC 3.0 and RACSignalProvider might offer a more elegant and flexible solution to this problem

- (RACSignal*) retryable:(RACSignalProvider*)provider
{
    return [[self
        catch:^(NSError *error) {
            return [provider provide:error];
        }]
        retry];
}

- (RACSignal*) delayedRetry:(NSTimeInterval)interval
{
    return [self retryable:[RACSignalProvider
        providerWithBlock:^(NSError* error) {
            return [[[RACSignal
                empty]
                delay:interval]
                concat:[RACSignal error:error]];
        }]
    ];
}

- (RACSignal*) naiveBackoff:(NSTimeInterval)initial
{
    __block NSTimeInterval backoff = initial / 2;

    return [self retryable:[RACSignalProvider
        providerWithBlock:^(NSError* error) {
            backoff *= 2;

            return [[[RACSignal
                empty]
                delay:backoff]
                concat:[RACSignal error:error]];
        }]
    ];
}

This -retryable approach also makes it easier to embed error-specific retry behavior - e.g. refreshing an expired auth token immediately if that's the failure reason vs other arbitrary network failures.

@jonsterling
Copy link
Contributor

In order to provide exponential back off, it's actually a lot more convenient in my experience to just provide a block NSUInteger -> NSTimeInterval than it is to use a sequence... Discrete functions of that sort and RACSequences are isomorphic, so unless I'm missing something, we should probably just pick the representation which is easiest to work with.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants