A Watchdog Timer in GCD

by Ed on July 10, 2010

A good way to help track down performance issues is through the use of a watchdog timer. They help you by pointing out the places in your code that are taking longer than they should. When the watchdog fires, it can do whatever action is appropriate for your application. I’m sure all iOS developers are aware of the more extreme watchdog timer that has existed since the beginning of iOS which would kill your application if it took too long to shut down. Our watchdog example will be far more benign :-).

We’ll approach this by giving you an object you can create before some critical section and release after it’s over. You’ll use it like so:

MyWatchdogTimer *timer = [[MyWatchdogTimer alloc] initWithTimeout:2.0];

// some piece of code that might take a while, but should never take
// more than 2 seconds.
[self myPotentiallyLengthyTask]

[timer invalidate];
[timer release];

If the timer fires, you know the operation took too long. If it didn’t all is well, you release it and we’re done.

The Class

The object class is pretty simple:

@interface MyWatchdogTimer {
@private
    dispatch_source_t     _timer;
}

- (id)initWithTimeout:(NSTimeInterval)timeout;
- (void)invalidate;

@end

As you can see, we’re merely storing our dispatch source we’ll be using as a timer.

When doing asynchronous tasks, it’s very common to use an invalidation idiom for shutting the task down. This pattern can be seen in run loop sources, as well as GCD itself (dispatch_cancel). This ensures the task will be torn down at a time that is appropriate, i.e. it can shut down cleanly.

The Implementation

@implementation MyWatchdogTimer

- (id)initWithTimeout:(NSTimeInterval)timeout {
    self = [super init];
    if (self) {            
        dispatch_queue_t queue = dispatch_get_global_queue(
                                    DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        // create our timer source
        _timer = dispatch_source_create(
                           DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
                           queue);

        // set the time to fire (we're only going to fire once,
        // so just fill in the initial time).
        dispatch_source_set_timer(_timer,
               dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC),
               DISPATCH_TIME_FOREVER, 0);

        // Hey, let's actually do something when the timer fires!
        dispatch_source_set_event_handler(_timer, ^{
            NSLog(@"WATCHDOG: task took longer than %f seconds",
                    timeout);
            // ensure we never fire again
            dispatch_source_cancel(_timer);
        });

        // now that our timer is all set to go, start it
        dispatch_resume(_timer);
    }
    return self;
}

- (void)dealloc {
    dispatch_source_cancel(_timer);
    dispatch_release(_timer);
    [super dealloc];
}

- (void)invalidate {
    _dispatch_source_cancel(_timer);
}

@end

That’s it. Let’s go over what we’re doing here.

Our strategy is to install the timer onto a concurrent queue. We want it to run at the same time as our current thread, no? So we get a reference to the standard priority concurrent queue.

Then we create our timer source, using dispatch_source_create. In this case, we’re creating a timer (there are a few different source types), so we pass DISPATCH_SOURCE_TYPE_TIMER as the first parameter. The other parameters are unused for timers.

Now that we have the timer, we need to set the start time. It’s specified in nanoseconds, so we need to take our input time and multiply it accordingly. We use dispatch_time so that we can specify a time from now. The third parameter is the interval, but since we’re only going to fire it once, we just set it to forever. You can also use 0 since we explicitly cancel it when we fire.

Next, we set our event handler. This is how you tell the source what to do when the timer fires. In our case, we merely log, but you could do whatever you like. You could also create a version of this that took a block passed into the init method and execute that when the timer fires. That would be pretty handy if you want to do specialized things for each watchdog you create.

Note that we cancel the timer when it fires. This is to ensure it will never fire again.

And finally, we start the timer running with dispatch_resume. All sources are created in the suspended state, allowing you to set them up properly before they are started.

In our dealloc, we merely cancel the timer and release it. It does no harm if it’s already been cancelled. This covers the case where the watchdog doesn’t fire, we want to kill it and move on.

In invalidate, all we do is cancel the source. This will ensure it does not fire. It’s possible you might invalidate it just when it’s firing, but that’s OK. You were just on the cusp of the timeout, and you should have been faster!

If you don’t like to have to invalidate and then release the object, you could always create an invalidateAndRelease method to save some typing.

Timers In General

This is one specific use of a timer in GCD, clearly you can use them in many other cases where you want things to execute concurrently. If you needed to return results to your main thread, you can just use dispatch_async to execute the code on the main queue (dispatch_get_main_queue() as we’ve seen in previous posts.

If you just want a timer to run on the main thread, you can certainly install a GCD timer on the main queue, or you could just use NSTimer. Whatever floats your… you know.

If nothing else, this might give you ideas on how you could use dispatch timers in your application.

That’s it, enjoy!

{ 7 comments… read them below or add one }

Simon Strandgaard July 11, 2010 at 4:25 am

Dunno if iPhone have posix signals. In mac programs, I use SIGALRM. To start the watchdog I just do alarm(5);. To cancel the watchdog I just do alarm(0); However alarm() only takes seconds as it’s argument.

Reply

Steve Weller July 11, 2010 at 5:50 am

Also see this GCD timer implementation by Mike Ash:

http://www.mikeash.com/pyblog/friday-qa-2010-07-02-background-timers.html

Reply

sc January 16, 2013 at 12:23 pm

Thanks for writing the GCD intros; they’re the clearest I’ve found!

Should there be a call to dispatch_retain(timer) in initWithTimeout()?

Thanks!

Reply

Ed January 16, 2013 at 12:40 pm

Shouldn’t need one since we create the timer there and it will start with a retain count of 1.

Reply

sc January 16, 2013 at 3:45 pm

timer = dispatchsourcecreate(
DISPATCH
SOURCETYPETIMER, 0, 0,
queue);

Gotcha; I’m not familiar with how mem mgmt works when calling c functions.

s

Reply

Sandeep January 28, 2013 at 5:57 am

It seems to me like there is a little typo. The place where you set the source timer, the code should be like;

dispatchsourcesettimer(timer, dispatchtime(DISPATCHTIMENOW, DISPATCHTIMEFOREVER), timeout * NSECPERSEC, 0);

But, your code above assumes the interval to be DISPATCHTIMEFOREVER. Please check it.

Reply

Ed January 29, 2013 at 11:29 am

I want the timer to fire at a certain time, but effectively never repeat. That’s why it’s the way it is.

Reply

Leave a Comment

{ 5 trackbacks }

Previous post:

Next post: