Do performs an action with a maximum number of retries. If fn returns a non-nil error, it will
be retried up to the maximum number of times and then returned. If fn returns a nil error, the retry
loop is broken. The loop is also broken if the provided context is cancelled.