Getting cal cancelled error in firebase cloud functions - javascript

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)

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.
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.

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

Related

How to perform a POST request using background task for React Native apps using expo

I am trying to log my GPS coordinates in a React-Native expo application.
The background task runs successfully and consoles log the coordinates.
Now I want to send them to my server.
For example a post request to /api/mylocation?lon=123&lat=345
I tried a regular fetch method but I get a timeout error.
regular meaning fetch(url,{ method:'POST' etc}) (I know how to perform a basic fetch)
How can I solve this? I suspect it has to do with async and all that.
const LOCATION_TRACKING = 'location-tracking';
const startLocationTracking = async () => {
console.log('startLocationTracking');
await Location.startLocationUpdatesAsync(LOCATION_TRACKING, {
accuracy: Location.Accuracy.Highest,
timeInterval: 10000,
distanceInterval: 0,
});
const hasStarted = await Location.hasStartedLocationUpdatesAsync(
LOCATION_TRACKING
);
console.log('tracking started?', hasStarted);
};
TaskManager.defineTask(LOCATION_TRACKING, async ({ data, error }) => {
console.log('running task:', data, error);
// send_driver_location()
if (error) {
console.log('LOCATION_TRACKING task ERROR:', error);
return;
}
if (data) {
const { locations } = data;
let lat = locations[0].coords.latitude;
let long = locations[0].coords.longitude;
console.log(
`${new Date(Date.now()).toLocaleString()}: ${lat},${long}`
);
// add a fetch method here?
// fetch(url, {...}
}
});
Obviously, this will be helpful when I want to fetch tokens from my server for authentication purposes also.

Socket hangup due to wrong handling of promises

I have script to move data from one platform to another. The source db allows only 100 records to be fetched in a single request. So I created a routine to fetch by batches of 100 which works fine I guess.
Now I try to process each records of 100 and do the necessary transformations (which involves axios call to get certain data) and create a record in firebase firestore.
Now when I run this migration in firebase express node, I get socket hang up ECONNRESET.
I know this is caused by wrong handling of promises.
Here is what my code looks like:
import { scrollByBatches } from "../helpers/migrations/apiScroll";
import { createServiceLocation } from "../helpers/locations";
const mapServiceLocationData = async (serviceLocation: any, env: string) => {
try {
const migratedServiceLocation: any = {
isMigrated: true,
id: serviceLocation._id,
};
if (serviceLocation.list?.length) {
await Promise.all(serviceLocation.ids.map(async (id: string) => {
const { data } = await dbEndPoint.priceMultiplier({ id }); // error says socket hangup on this call
let multiplierUnit;
let serviceType;
if (data.response._id) {
multiplierUnit = data.response;
const result = await dbEndPoint.serviceType({ id: multiplierUnit.service_custom_service_type }); // error says socket hangup on this call
if (result.data.response._id) {
serviceType = result.data.response.type_text;
migratedServiceLocation.logs = [...multiplierUnit.history_list_text, ...migratedServiceLocation.logs];
}
}
}));
}
await createServiceLocation(migratedServiceLocation); // create record in destination db
} catch (error) {
console.log("Error serviceLocation: ", serviceLocation._id, JSON.stringify(error));
}
return null; // is this even necessary?
};
export const up = async () => {
try {
// get 100 docs from source db => process it.. => fetch next 100 => so on...
await scrollByBatches(dbEndPoint.serviceLocation, async (serviceLocations: any) => {
await Promise.all(
serviceLocations.map(async (serviceLocation: any) => {
await mapServiceLocationData(serviceLocation);
})
);
}, 100);
} catch (error) {
console.log("Error", JSON.stringify(error));
}
return null; // is this even necessary?
};
The error I get in firebase functions console is:
For clarity on how the fetch by batches looks like:
const iterateInBatches = async (endPoint: any, limit: number, cursor: number, callback: any, resolve: any, reject: any) => {
try {
const result = await endPoint({ limit, cursor });
const { results, remaining }: any = result.data.response;
if (remaining >= 0) {
await callback(results);
}
if ((remaining)) {
setTimeout(() => {
iterateInBatches(endPoint, limit, (cursor + limit), callback, resolve, reject);
}, 1000); // wait a second
} else {
resolve();
}
} catch (err) {
reject(err);
}
};
export const scrollByBatches = async (endPoint: any, callback: any, limit: number, cursor: number = 0) => {
return new Promise((resolve, reject) => {
iterateInBatches(endPoint, limit, cursor, callback, resolve, reject);
});
};
What am I doing wrong? I have added comments in the code sections for readability.
Thanks.
There are two cases when socket hang up gets thrown:
When you are a client
When you, as a client, send a request to a remote server, and receive no timely response. Your socket is ended which throws this error. You should catch this error and decide how to handle it: whether to retry the request, queue it for later, etc.
When you are a server/proxy
When you, as a server, perhaps a proxy server, receive a request from a client, then start acting upon it (or relay the request to the upstream server), and before you have prepared the response, the client decides to cancel/abort the request.
I would suggest a number of possibilities for you to try and test that might help you solve your issue of ECONNRESET :
If you have access to the source database, you could try looking
there for some logs or metrics. Perhaps you are overloading the
service.
Quick and dirty solution for development: Use longjohn, you get long
stack traces that will contain the async operations. Clean and
correct solution: Technically, in node, whenever you emit an 'error'
event and no one listens to it, it will throw the error. To make it
not throw, put a listener on it and handle it yourself. That way you
can log the error with more information.
You can also set NODE_DEBUG=net or use strace. They both provide you
what the node is doing internally.
You could restart your server and run the connection again, maybe
your server crashed or refused the connection most likely blocked by
the User Agent.
You could also try running this code locally, instead of in cloud
functions to see if there is a different result. It's possible that
the RSG/google network is interfering somehow.
You can also have a look at this GitHub issue and stackoverflow
thread to see the common fixes for the ECONNRESET issue and see if
those help resolve the issue.

How to make sure AMQP message is not lost in case of error in subscriber with rhea?

So I have designed a basic Publisher-Subscriber model using rhea in JS that takes an API request for saving data in DB and then publishes it to a queue.
From there a subscriber(code added below) picks it up and tries to save it in a DB. Now my issue is that this DB instance goes through a lot of changes during development period and can result in errors during insert operations.
So now when the subscriber tries to push to this DB and it results in an error, the data is lost since it was dequeued. I'm a total novice in JS so is there a way to make sure that a message isn't dequeued unless we are sure that it is saved properly without having to publish it again on error?
The code for my subscriber:
const Receiver = require("rhea");
const config = {
PORT: 5672,
host: "localhost"
};
let receiveClient;
function connectReceiver() {
const receiverConnection = Receiver.connect(config);
const receiver = receiverConnection.open_receiver("send_message");
receiver.on("connection_open", function () {
console.log("Subscriber connected through AMQP");
});
receiver.on("error", function (err) {
console.log("Error with Subscriber:", err);
});
receiver.on("message", function (element) {
if (element.message.body === 'detach') {
element.receiver.detach();
}
else if (element.message.body === 'close') {
element.receiver.close();
}
else {
//save in DB
}
}
receiveClient = receiver;
return receiveClient;
}
You can use code like this to explicitly accept the message or release it back to the sender:
try {
save_in_db(event.message);
event.delivery.accept();
} catch {
event.delivery.release();
}
See the delivery docs for more info.

Firebase response depending on Firestore Query does not work

Depending on whether there is an entry in Cloud Firestore with the correct DocumentId. However, this does not work because my function sends the status 200 before even finishing the query. So how can I get that working?
Here is my code:
access = false;
admin.firebase().collection("tuere").doc(door).collection("eintritt").get().then((snapshot) => {
snapshot.forEach((doc) => {
if(doc.id === uid){
access = true;
console.log("May open door " + uid);
}
});
}).catch((err) => {
console.log(err);
});
res.status(200).send(access);
When I open the Tab in Chrome and let it load "false" appears, but when I wait like 15 Seconds "May open door (uid)" appears in the Logs.
How can I solve this problem and how can i get my function to run faster?
You should send the HTTP response when the promise resolves, so within the then of the query promise: like that:
access = false;
admin.firebase().collection("tuere").doc(door).collection("eintritt").get()
.then((snapshot) => {
snapshot.forEach((doc) => {
if(doc.id === uid){
access = true;
console.log("May open door " + uid);
}
});
res.status(200).send(access);
}).catch((err) => {
console.log(err);
res.status(500).send(err);
});
Also, you should send an HTTP response in case of error, this is why I added res.status(500).send(err); in the catch
I would suggest you look this video from Doug Stevenson: https://www.youtube.com/watch?v=7IkUgCLr5oA
Also there is a point which surprises me: shouln't you use
admin.firestore().collection("tuere").doc(door)....
instead of
admin.firebase().collection("tuere").doc(door)
I have to look in the reference, but I have the feeling that admin.firebase() does not exist.

Firebase-admin won't write to database, gives no errors

Firebase admin isn't writing to the database.
I am instantiating the database:
var db = admin.database();
Then setting up a reference to the table I want:
var systemsRef = db.ref("systems/");
I then have a function to check if the 'system', (an encrypted hardware id), exists.
function isSystemRegistered(id){
var isTrue;
systemsRef.once('value', function(snapshot) {
isTrue = (snapshot.hasChild(id))? true : false;
});
return isTrue;
}
Which, as of yet returns false; which is true, because it doesn't exist yet. If the system doesn't exist, it writes the data.
const sysID = getSysID();
var sys.info.name = generateUniqueSystemName();
if(isSystemRegistered(sysID){
console.log("system is already registered!");
} else {
msystemsRef.set({
sysID : sys.info.name
}, function(error){
console.log('There was an error while attempting to write to database: ' + error);
});
});
}
I've experimented, and temporarily made my prototype database fully public for a few minutes, just to be sure my rules weren't the issue... They weren't: still no bueno. No writes to the database... and no errors.
I tried a different set, just be sure:
msystemsRef.set("I'm writing data", function(error) {
if (error) {
alert("Data could not be saved." + error);
} else {
alert("Data saved successfully.");
}
});
Again, I'm using an admin account, with public rules, so I should see a now I'm writing data table, just below root. Nothing...
So I switched tactics and attempted to push to the database with the canned tutorial, with my database still fully public:
systemsRef.push({sysID : sys.info.name});
And nothing... Want am I missing?
Make sure the credentials and databaseURL are correct.
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: databaseURL
});
Check if they're matching - credential from one app and databaseURL from another existing app could produce such result.
If you are not loading credential's data from file but from somewhere else, make sure it's not modified - I had an issue with newlines in private key when putting the credential's data to shell variable.
In isSystemRegistered you're returning the synchronized value of isTrue which is undefined.
You should return the promise of the .once('value') method and in the calling method attach a then() to check if it exists.
You can also use the snapshot.exists() to check on a reference for existence.
Edit:
Suggested edit:
var systemRef = admin.database('system');
function isSystemRegistered(id) {
return systemRef.child(id).once('value')
.then(function (snap) {
return snap.exists();
});
}
function writeData(aSystem) {
var sysId = generateUniqueSystemName();
return systemRef.child(sysId)
.once('value')
.then(function (snapshot) {
if (!snapshot.exists()) {
return systemRef.child(sysId).update(aSystem);
}
// Here `sysId === snapshot.key`
return systemRef.child(sysId).set(aSystem);
})
.catch(function (err) {
console.error(err);
});
}
Running on Raspberry Pi raises some more questions.
How does it connect to the internet?
How fast is the connection?
What NodeJS version do you run?
Did this run successfully on your PC?
Same issue here. I eventually realised that the database went offline before the command was even sent to firebase. This made sense because there was no error, since it never even sent the request.
Eg. .set({blah: 123}) does not immediately transmit to the server. Instead, something is placed on the node event queue to execute. If you let the database go offline too soon, it won't process the queue.
Perhaps (like me) you're calling admin.database().goOffline(); at the end of the script? If so, you may just need to defer or delay the offline method until after the transmission.
// (TYPESCRIPT)
import * as admin from 'firebase-admin';
let app = admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: "https://blahblah.firebaseio.com/",
});
admin.database().ref().push({ something: 123 }).then(() => {
console.log("push complete");
});
// delay before going offline
setTimeout(() => {
admin.database().goOffline();
process.abort();
}, 2000);

Categories

Resources