Getting cal cancelled error in firebase cloud functions

Viewed 523

Firebase cloud functions logs "Error: 1 CANCELLED: Call cancelled" sometimes. am trying to send the push notifications one day before the timestamp, everything works till getting the fcm tokens but not able to send notification here is my code.

const NotificationLive_dayBefore = async () => {
try {
    const path = models.payments.firebasePath;
    let imageURL = null;
    let tokens = [];
    let subscribedUsers = await getSubscribedUsers().catch(e => { console.log(e) });
    if (subscribedUsers && subscribedUsers.length > 0) {
        for(const subscriber of subscribedUsers){
            const userDoc = db
                .collection('Payments').doc('v1').collection('users')
                .doc(subscriber)
                .collection('subscriptions').where('deleted_at', '==', null)
                .get() // <------------here is the error according to logs
                .then(async (snapshot) => {
                    if (!snapshot.empty) {
                        console.log('snapshot.empty', snapshot.empty);
                        for(const doc of snapshot.docs ){
                            // let friend_doc_id = doc.data().friend_doc_id
                            console.log("Friend id", doc.id);
                            
                            if (doc.exists) {
                                let oneDay = new Date().getTime() + (24 * 60 * 60 * 1000);
                                let oneDayMinus1 = new Date().getTime() + (23 * 60 * 60 * 1000);
                                console.log(oneDayMinus1);
                                console.log(oneDay);
                                try {
                                    let liveLesson = await db
                                        .collection('Lessons').doc('v1').collection('friends')
                                        .doc(doc.id)
                                        .collection('live')
                                        .where('start_time', '>', new Date(oneDayMinus1))
                                        .where('start_time', '<', new Date(oneDay))
                                        .where('deleted_at', '==', null)
                                        .get();

                                    for( const liveSnap of liveLesson.docs){
                                        console.log("liveSnapid", '=>', liveSnap.data());
                                        console.log('Private lesson exists', liveSnap.exists);
                                        if (liveSnap.exists) {
                                            // time is equal send notification
                                            console.log("Subscriber ID", subscriber);
                                            const Users = db
                                                .collection(models.notification.firebasePath)
                                                .where('deleted_at', '==', null)
                                                .where('__name__', '==', subscriber)
                                                .get()
                                                .then(async (UserSnapshot) => {
                                                    for( const userdoc of UserSnapshot.docs){
                                                      
                                                        const userdocument = userdoc.data();
                                                        if (userdocument.fcm_token) {
                                                           tokens = userdocument.fcm_token;
                                                        }
                                                        console.log('tokens',tokens);
                                                        if (tokens.length>0) {
                                                            try {
                                                                let live_lessons_images = await db
                                                                    .collection('Accounts').doc('v1').collection('friends')
                                                                    .doc(doc.id)
                                                                    .get();
                                                            
                                                                if (!live_lessons_images.data().image_url) {
                                                                    imageURL = null;
                                                                    console.log("Image not found");
                                                                } else {
                                                                    imageURL = live_lessons_images.data().image_url;
                                                                }
                                                            } catch (error) {
                                                                console.log('That did not go well.', error)
                                                            }
                                                            console.log("notification sent live lesson 24 hr before");
                                                            await sendNotificationNew(
                                                                "test",
                                                                test notification,
                                                                imageURL,
                                                                tokens
                                                            ).catch(error => { console.error("promise 1", error) });; //send notification to users as a remainder for live lesson before one day
                                                        }
                                                    }
                                                }).catch(error => { console.error("promise 1", error) });
                                        }
                                    }
                                } catch (error) {
                                    console.log('That did not go well.', error)
                                }
                            } else {
                                console.log("friend_doc_id is not present");
                            }
                        }
                    }
                }).catch(error => { console.error("promise error", error) });
        } //end foreach of subscribed user
    }
    return Promise.all('success');
}
catch (err) {
    // Handle error here
    // This will return error from api
    console.log("Exceptions: ", err);
}}

and my sendNotificationNew function is

const sendNotificationNew = async (title, body, image, tokens) => {
    console.log("title", title);
  if (tokens.length > 0) {
    if(tokens.length>1){
      tokens = tokens[tokens.length-1];
    }
    try {
      let message = {
        notification: {
          title: title,
          body: body
        },
        token: tokens
      };
     
      if (image) {
        message.notification.image = image;
      }
      admin.messaging().send(message)
        .then((response) => {
          // Response is a message ID string.
          console.log('Successfully sent message:', response);
        })
        .catch((error) => {
          console.log('Error sending message:', error);
          console.log('Error sending message:', tokens);
        });

    } catch (err) {
      console.log(err);
    }
  } else {
    console.log("Token not available");
  }
};

i was able to see the logs printing "title" of the notification, but not "Successfully sent message" or "Error sending message:".

Error message i got

Error: 1 CANCELLED: Call cancelled
at Object.callErrorFromStatus (/workspace/node_modules/@grpc/grpc-js/build/src/call.js:31)
at Object.onReceiveStatus (/workspace/node_modules/@grpc/grpc-js/build/src/client.js:327)
at Object.onReceiveStatus (/workspace/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:299)
at (/workspace/node_modules/@grpc/grpc-js/build/src/call-stream.js:145)
at processTicksAndRejections (internal/process/task_queues.js:79)

And this

Error: 9 FAILED_PRECONDITION: The requested snapshot version is too old.
at Object.callErrorFromStatus (/workspace/node_modules/@grpc/grpc-js/build/src/call.js:31)
at Object.onReceiveStatus (/workspace/node_modules/@grpc/grpc-js/build/src/client.js:327)
at Object.onReceiveStatus (/workspace/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:299)
at (/workspace/node_modules/@grpc/grpc-js/build/src/call-stream.js:145)
at processTicksAndRejections (internal/process/task_queues.js:79)
2 Answers

I resolved this issue by adding indexes in the firestore and proper catch blocks to all the promises to avoid unhandled rejection errors.

3rd Edit: Finally I've found the solution to this problem. A same error occurred in 2 cloud functions and I solved both of them by following the same process.

I'm not deleting the previous wrong answers, which didn't work for me, so that you also could know what won't work.

This error occurs because the Firestore has hit a limit. I was trying to do lots of writes and updates in Firestore in parallel.

Example: Here all the functions are called all together. This is very fast as all the tasks are done in parallel. And recommended by Firebase.

But this might hit a limit in firestore and give error.

   exports.botsCompletingLectures = functions.region('asia-south1')
        .database.ref('triggerCloudFunctions/botsCompletingLectures')
  .onUpdate(async(change, context)=>{

  const promises = [];

  promises.push(doSomeTaskInFirestore());
  promises.push(doAnotherTaskInFirestore());
  promises.push(doSomeMoreTaskInFirestore());


  function doSomeTaskInFirestore(){
  //Write to lots of documents in a collection
  }

  async function doAnotherTaskInFirestore(){
  //Update lots of documents in firestore
  }

  async function doSomeMoreTaskInFirestore(){
  //Do more tasks in firestore      
  }

  return Promise.all(promises);
  
  });

Solution: Here only one task will execute at a time and so it will take little more time. But will not give any error.

   exports.botsCompletingLectures = functions.region('asia-south1')
        .database.ref('triggerCloudFunctions/botsCompletingLectures')
  .onUpdate(async(change, context)=>{



  await doSomeTaskInFirestore()
  await doAnotherTaskInFirestore()
  return doSomeMoreTaskInFirestore()


  function doSomeTaskInFirestore(){
  //Write to lots of documents in a collection
  }

  async function doAnotherTaskInFirestore(){
  //Update lots of documents in firestore
  }

  async function doSomeMoreTaskInFirestore(){
  //Do more tasks in firestore      
  }

  
  
  });

2nd Edit: The Cloud Function worked well for 4-5 days and then started giving errors again.

So this time I've given up on trying to fix it and instead enabled Retry on failure in Cloud Functions.

In my case as the Cloud Function is running correctly for some days and gives error on other days, due to some temperory problems, like network issue or cold start, so I can enable it here.

We should not enable it, if the error is permanent for eg a Bug in the code, else the function will keep on retrying for 7 days.

You can learn about Enabling Retry in Cloud Functions from this video. https://www.youtube.com/watch?v=Pwsy8XR7HNE

1st Edit: The error appeared again the next day

So after reading and searching about it, I found out this problem occurs because of Cold Starting of a function after a long time and some Network problem and memory leak and most of the people (including me), who are getting this error, are getting it in PubSub Cloud Function and while doing some tasks in Firestore.

So I used a workaround. I don't know if it is recommended or not, but I'm tired of these errors, so I just did it.

I created a PubSub Cloud Function that updates a field value in Realtime Database. And this update in the field will trigger another function, that will do the task in firestore.

  exports.triggerTheMainFunction = functions.pubsub.schedule('40 11 * * *').onRun(async(context)=> {

  return admin.database().ref()
        .child('triggerOtherFunction')
        .child('doSomeTaskInFirestore')
        .set(admin.database.ServerValue.increment(1))
        .catch((error)=>{
  console.log('Error incrementing the value', error);
              });
  });

And converted the Actual Function for Firestore from PubSub to Event Triggered. And since then I'm not getting any errors.

 exports.doSomeTaskInCloudFirestore = functions
        .database.ref('triggerOtherFunction/doSomeTaskInFirestore')
        .onUpdate(async(change, context)=>{


  //Do the task that was needed to be done in firestore.

  });

If I get any errors in future, then I'll update this answer.

First Answer

I also got a similar error. I don't know what was causing the problem. But I solved it by installing the latest version.

enter image description here

So first I saved a copy of index.js on desktop and reinstalled everything.

I was using NodeJS 14 version. So I uninstalled it from control panel and downloaded the nodeJS 16 version from the nodejs website. And installed it.

ran in terminal.

  npm install -g npm

then

  npm install npm@latest -g

Then

  firebase init

then

  npm install -g firebase-tools

And then redeployed the same cloud function, without making any changes. And test run the function. And the error disappeared.

Related