/**
  * Request that a particular client authorize the Leases contained in the LeaseSet, after which
  * the onCreateJob is queued up. If that doesn't occur within the timeout specified, queue up the
  * onFailedJob. This call does not block.
  *
  * @param set LeaseSet with requested leases - this object must be updated to contain the signed
  *     version (as well as any changed/added/removed Leases)
  * @param expirationTime ms to wait before failing
  * @param onCreateJob Job to run after the LeaseSet is authorized
  * @param onFailedJob Job to run after the timeout passes without receiving authorization
  */
 void requestLeaseSet(LeaseSet set, long expirationTime, Job onCreateJob, Job onFailedJob) {
   if (_dead) {
     if (_log.shouldLog(Log.WARN)) _log.warn("Requesting leaseSet from a dead client: " + set);
     if (onFailedJob != null) _context.jobQueue().addJob(onFailedJob);
     return;
   }
   // We can't use LeaseSet.equals() here because the dest, keys, and sig on
   // the new LeaseSet are null. So we compare leases one by one.
   // In addition, the client rewrites the expiration time of all the leases to
   // the earliest one, so we can't use Lease.equals() or Lease.getEndDate().
   // So compare by tunnel ID, and then by gateway.
   // (on the remote possibility that two gateways are using the same ID).
   // TunnelPool.locked_buildNewLeaseSet() ensures that leases are sorted,
   //  so the comparison will always work.
   int leases = set.getLeaseCount();
   // synch so _currentLeaseSet isn't changed out from under us
   synchronized (this) {
     if (_currentLeaseSet != null && _currentLeaseSet.getLeaseCount() == leases) {
       for (int i = 0; i < leases; i++) {
         if (!_currentLeaseSet.getLease(i).getTunnelId().equals(set.getLease(i).getTunnelId()))
           break;
         if (!_currentLeaseSet.getLease(i).getGateway().equals(set.getLease(i).getGateway()))
           break;
         if (i == leases - 1) {
           if (_log.shouldLog(Log.INFO)) _log.info("Requested leaseSet hasn't changed");
           if (onCreateJob != null) _context.jobQueue().addJob(onCreateJob);
           return; // no change
         }
       }
     }
   }
   if (_log.shouldLog(Log.INFO))
     _log.info("Current leaseSet " + _currentLeaseSet + "\nNew leaseSet " + set);
   LeaseRequestState state = null;
   synchronized (this) {
     state = _leaseRequest;
     if (state != null) {
       if (_log.shouldLog(Log.DEBUG)) _log.debug("Already requesting " + state);
       LeaseSet requested = state.getRequested();
       LeaseSet granted = state.getGranted();
       long ours = set.getEarliestLeaseDate();
       if (((requested != null) && (requested.getEarliestLeaseDate() > ours))
           || ((granted != null) && (granted.getEarliestLeaseDate() > ours))) {
         // theirs is newer
       } else {
         // ours is newer, so wait a few secs and retry
         _context
             .simpleScheduler()
             .addEvent(new Rerequest(set, expirationTime, onCreateJob, onFailedJob), 3 * 1000);
       }
       // fire onCreated?
       return; // already requesting
     } else {
       _leaseRequest =
           state =
               new LeaseRequestState(
                   onCreateJob, onFailedJob, _context.clock().now() + expirationTime, set);
       if (_log.shouldLog(Log.DEBUG)) _log.debug("New request: " + state);
     }
   }
   _context.jobQueue().addJob(new RequestLeaseSetJob(_context, this, state));
 }
 RequestThrottler(RouterContext ctx) {
   this.context = ctx;
   this.counter = new ObjectCounter<Hash>();
   ctx.simpleScheduler().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
 }