3

I'm getting a "deadline-exceeded" error on the frontend when calling a firebase callable cloud function (onCall).

I know that I have to return a Promise so the function knows when to clean itself, but it is still not working.

After 60 seconds, "deadline-exceeded" is throw to the frontend but the function keeps running on the server and finish with success. All batch operations are written to the firestore.

10:37:14.782 AM
syncExchangeOperations
Function execution took 319445 ms, finished with status code: 200
10:36:57.323 AM
syncExchangeOperations
Function execution started
10:36:57.124 AM
syncExchangeOperations
Function execution took 170 ms, finished with status code: 204
10:36:56.955 AM
syncExchangeOperations
Function execution started
async function syncBinanceOperations(
  userId,
  userExchange,
  userExchangeLastOperations,
  systemExchange
) {
  try {
    const client = Binance({
      apiKey: userExchange.apiKey,
      apiSecret: userExchange.privateKey
    });

    const batch = admin.firestore().batch();
    const lastOperations = userExchangeLastOperations
      ? userExchangeLastOperations
      : false;

    const promises = [];

    promises.push(
      syncBinanceTrades(client, lastOperations, userId, systemExchange, batch)
    );
    promises.push(
      syncBinanceDeposits(client, lastOperations, userId, systemExchange, batch)
    );
    promises.push(
      syncBinanceWhitdraws(
        client,
        lastOperations,
        userId,
        systemExchange,
        batch
      )
    );
    promises.push(
      updateUserExchange(userId, userExchange.id, {
        lastSync: moment().format('x')
      })
    );

    await Promise.all(promises);
    return batch.commit();
  } catch (error) {
    return handleErrors(error);
  }
}

exports.syncExchangeOperations = functions.https.onCall(
  async (data, context) => {
    try {
      userAuthenthication(data.userId, context.auth);
      let user = await getUser(data.userId);

      if (!user.plan.benefits.syncExchanges) {
        throw 'Operação não autorizada para o plano contratado';
      }

      let userExchange = await getUserExchange(data.userId, data.exchangeId);

      let response = await Promise.all([
        getUserLastOperations(data.userId, userExchange.exchangeId),
        getSystemExchange(userExchange.exchangeId)
      ]);

      let userExchangeLastOperations = response[0];
      let systemExchange = response[1];

      switch (systemExchange.id) {
        case 'binance':
          return syncBinanceOperations(
            user.id,
            userExchange,
            userExchangeLastOperations,
            systemExchange
          );
      }
    } catch (error) {
      return handleErrors(error);
    }
  }
);

It works fine if I change this function to a HTTP request. It waits the function to finish and returns.

exports.syncExchangeOperations = functions
  .runWith(runtimeOpts)
  .https.onRequest((req, res) => {
    return cors(req, res, async () => {
      try {
        let auth = await admin.auth().verifyIdToken(req.get('Authorization').split('Bearer ')[1]);

        let userExchange = await getUserExchange(
          auth.uid,
          req.query.exchangeId
        );

        let response = await Promise.all([
          getUserLastOperations(auth.uid, userExchange.exchangeId),
          getSystemExchange(userExchange.exchangeId)
        ]);

        let userExchangeLastOperations = response[0];
        let systemExchange = response[1];

        switch (systemExchange.id) {
          case 'binance':
            await syncBinanceOperations(
              auth.uid,
              userExchange,
              userExchangeLastOperations,
              systemExchange
            );
        }
        res.status(200).send();
      } catch (error) {
        res.status(401).send(handleErrors(error));
      }
    });
  });
Camopy
  • 125
  • 1
  • 12

2 Answers2

16

The "deadline-exeeded" that you encountered is an error thrown by the Firebase Javascript library on the client (not the function itself). The Firebase docs are lacking documentation o how to use functions.runWithOptions() on a callable function. For some reason the functions().httpsCallable() has a built in timeout on the client side.

So if you use this on your Node.js function:

exports.testFunction = functions.runWith({ timeoutSeconds: 180 }).https.onCall(async (data, ctx) => {
// Your Function Code that takes more than 60second to run
});

You need to override the buit in Javascript Library timeout on the client like this:

let testFunction = firebase.functions().httpsCallable("testFunction", {timeout: 180000});

I don't know what is the purpose of the built in timeout on the client, for me it has no purpose since it doesn't even stop the execution of the function on the server. But it must be there for some internal reasons.

Notice the Node.js timeoutSeconds is in seconds and the timeout option on the client library is in milliseconds.

ESCUDIA
  • 191
  • 1
  • 5
3

"Deadline exceeded" means that the function invocation timed out from the perspective of the client. The default is 60 seconds.

Try increasing the timeout on both the client and function so that it has time to complete before the client timeout is reached. You can do this by specifying it in an HttpsCallableOptions object.

Also try returning something other than batch.commit(). Whatever that function return will be serialized and sent to the client, which could cause problems. Instead, just await batch.commit() then return something predictable, like a plain JavaScript object.

See the API documentation for information on setting the timeout:

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441