function until($limit) 
  {
    $until = function() use ($limit) {
      if (is_int($limit)) {
        if ($this->attempts < $limit) {
          return true;
        } else {
          return false;
        }

      } else {
        if ($limit($this->lastException, $this->attempts)) {
          return true;
        } else {
          return false;
        }

      }
    };  

    $defaultResult = function() {
      if (is_callable($this->defaultResult)) {
        return call_user_func_array($this->defaultResult, [ $this->lastException, $this->attempts ]);
      } else {
        return $this->defaultResult;
      }
    };

    do {
      $this->attempts++;

      if ($this->attempts > 1) {
        if ($timeout = $this->handleWait($this->lastException)) {
          // DEBUG: echo "sleep($timeout)\n";
          sleep($timeout);
        }
      }

      try {
        // try to execute the wrapped process
        $result = call_user_func_array($this->operation, [ $this->attempts ]);
        // this might throw an Exception too:
        $this->handleSuccess($result);
        // it didn't throw an exception, so we're done!
        return $result;

      } catch (\Exception $e) {
        $this->lastException = $e;
        // let's filter the exception:
        if (!$this->handleException($e)) {
          // filtering the exception resulted in false, so we're done:
          $this->handleError($e);
          // just return the default result
          return $defaultResult();
        }

      }

    } while ($until());

    // we fell through to here, so we invoke handleError
    // DEBUG: echo "Fell through at: ".time()."\n";
    $this->handleError($this->lastException, $this->attempts);
    // and we just return whatever the default value of result was
    return $defaultResult();
  }