private Future doAsyncCall()

in runtime/impl/src/main/java/com/google/apphosting/runtime/ApiProxyImpl.java [392:476]


  private Future<byte[]> doAsyncCall(
      EnvironmentImpl environment,
      String packageName,
      String methodName,
      byte[] requestBytes,
      Double requestDeadlineInSeconds) {
    TraceWriter traceWriter = environment.getTraceWriter();
    CloudTraceContext currentContext = null;
    if (traceWriter != null) {
      CloudTraceContext parentContext = CloudTrace.getCurrentContext(environment);
      currentContext = traceWriter.startApiSpan(parentContext, packageName, methodName);

      // Collects stack trace if required.
      if (TraceContextHelper.isStackTraceEnabled(currentContext)
          && environment.getTraceExceptionGenerator() != null) {
        StackTraceElement[] stackTrace =
            environment
                .getTraceExceptionGenerator()
                .getExceptionWithRequestId(new Exception(), environment.getRequestId())
                .getStackTrace();
        traceWriter.addStackTrace(currentContext, stackTrace);
      }
    }

    // Browserchannel messages are actually sent via XMPP, so this cheap hack
    // translates the packageName in production.  If these two services are
    // ever separated, this should be removed.
    if (packageName.equals("channel")) {
      packageName = "xmpp";
    }

    double deadlineInSeconds =
        deadlineOracle.getDeadline(
            packageName, environment.isOfflineRequest(), requestDeadlineInSeconds);

    APIRequest.Builder apiRequest =
        APIRequest.newBuilder()
            .setApiPackage(packageName)
            .setCall(methodName)
            .setSecurityTicket(environment.getSecurityTicket())
            .setPb(ByteString.copyFrom(requestBytes));
    if (currentContext != null) {
      apiRequest.setTraceContext(TraceContextHelper.toProto2(currentContext));
    }

    AnyRpcClientContext rpc = apiHost.newClientContext();
    long apiSlotWaitTime;
    try {
      // Get an API slot, waiting if there are already too many threads doing API calls.
      // If we do wait for t milliseconds then our deadline is decreased by t.
      apiSlotWaitTime = environment.apiRpcStarting(deadlineInSeconds);
      deadlineInSeconds -= apiSlotWaitTime / 1000.0;
      if (deadlineInSeconds < 0) {
        throw new InterruptedException("Deadline was used up while waiting for API RPC slot");
      }
    } catch (InterruptedException ex) {
      long remainingMillis = environment.getRemainingMillis();
      String msg = String.format(
          "Interrupted waiting for an API RPC slot with %d millis %s soft deadline",
          Math.abs(remainingMillis), remainingMillis > 0 ? "until" : "since");
      logger.atWarning().withCause(ex).log("%s", msg);
      if (remainingMillis <= ATTRIBUTE_TO_DEADLINE_MILLIS) {
        return createCancelledFuture(packageName, methodName, DEADLINE_REACHED_SLOT_REASON);
      } else {
        return createCancelledFuture(packageName, methodName, INTERRUPTED_SLOT_REASON);
      }
    }
    // At this point we have counted the API call against the concurrent limit, so if we get an
    // exception starting the asynchronous RPC then we must uncount the API call.
    try {
      return finishAsyncApiCallSetup(
          rpc,
          apiRequest.build(),
          currentContext,
          environment,
          packageName,
          methodName,
          deadlineInSeconds,
          apiSlotWaitTime);
    } catch (RuntimeException | Error e) {
      environment.apiRpcFinished();
      logger.atWarning().withCause(e).log("Exception in API call setup");
      return Futures.immediateFailedFuture(e);
    }
  }