firebase Cloud function transaction working sporadically - firebase

I have the cloud function like so:
exports.updateNewsCount = functions.database.ref('/channels/{channelId}/news/{newsId}/')
.onWrite (event => {
const channelId = event.params.channelId;
const newsId = event.params.newsId;
let CntRef = admin.database().ref('/channelDetails/' + channelId + '/newsCnt');
if (event.data.exists() && !event.data.previous.exists()){
return CntRef.transaction(function(current){
if (current){
console.log ('current is not null');
return (current || 0) + 1;
}
else {
console.log('current is null');
return current;
}
},function(error, b, d){
if (error)
console.log(error);
else
console.log ('error is null');
if (b)
console.log('boolean is true');
else
console.log('boolean is false');
if (d)
console.log('snapshot is ' + d);
else
console.log ('snapshot is null');
}).then(()=>{});
} else if (!event.data.exists() && event.data.previous.exists()){
return CntRef.transaction(function(current){
if (current)
return (current || 1) - 1;
else
return current;
}, function(error, b, d){if (error) console.log(error); if (d) console.log(d);}).then(()=>{});
}
});
It fires consistently as I can see the log entries. However, the newsCnt field is not updated as expected. Sometimes it gets updated and sometimes not!!! What am I doing wrong here?

You should expect that a transaction be called potentially multiple times, the first time with null. That's the way transactions work. Please read the documentation here.
In particular note the following callout in that section:
Note: Because your update function is called multiple times, it must
be able to handle null data. Even if there is existing data in your
remote database, it may not be locally cached when the transaction
function is run, resulting in null for the initial value.

Related

Firebase cloud functions not catching with fast database updates

Hello I am trying to build a multiplayer game.
I have got a working queue:
.ref('Multiplayer/Queue/{queueCategory}/Players/{playerid}')
.onCreate((snapshot, context) => {
const root = snapshot.ref.root;
var category = context.params.queueCategory;
const gameDir = "Multiplayer/Active/" + category;
var gameID = snapshot.ref.root.child(gameDir).push().key;
root.child("Multiplayer/Queue/" + category + "/Players").once("value").then(players => {
var secondplayer: DataSnapshot | null = null;
functions.logger.log(players.numChildren());
players.forEach(player => {
if(player.val() === "placeholder" && player.key !== context.params.playerid) {
secondplayer = player;
}
});
functions.logger.log(secondplayer);
if(secondplayer === null) return null;
root.child("Multiplayer/Queue/" + category + "/Players").transaction(function (matchmaking) {
//check if player joined differnet game
if (matchmaking === null || matchmaking === undefined || secondplayer === null || matchmaking[context.params.playerid] !== "placeholder" || matchmaking[secondplayer?.key || 1] !== "placeholder") return matchmaking;
matchmaking[context.params.playerid] = gameDir + "/" + gameID + "/player1";
matchmaking[secondplayer.key || -1] = gameDir + "/" +gameID + "/player2";
return matchmaking;
}).then(result => {
var playerval: string = result.snapshot.child(context.params.playerid).val();
var pPath = playerval.split('/');
pPath.pop();
playerval = pPath.join('/');
functions.logger.log("playervalue: " + playerval);
functions.logger.log("gamedir: " + gameDir + gameID);
if(playerval !== gameDir + "/" + gameID) return;
var game = {
gamestate: "init",
category: category,
Players: {
"player1": "",
"player2": ""
}
}
root.child(gameDir + "/" + gameID).set(game).then(snap => {
return null;
}).catch(error => {
console.log(error);
})
}).catch(error => {
console.log(error);
})
return null;
}).catch(error => {
console.log(error);
})
});
This script pairs up players and changes the value of the player in queue to the new gameroom
dir
. Everything works, except when to many players join the queue at once (
breaks down at roughly 1player/sec). I suspect the problem is in this part of the code:
var secondplayer: DataSnapshot | null = null;
functions.logger.log(players.numChildren());
players.forEach(player => {
if(player.val() === "placeholder" && player.key !== context.params.playerid) {
secondplayer = player;
}
});
functions.logger.log(secondplayer);
if(secondplayer === null) return null;
If to many players join the second player will be overlapping with other instances of the functions and ultimately the will terminate after the second player value has been set.
How can I fix this?
Please help me
Cloud functions do not guarantee order of execution. There is a Firecast explaining parallel execution. You can take a look at transactions but your use case doesn't seem to be straightforward as incrementing or decrementing a value.
Cloud functions (any serverless functions) may not be the best choice for all the cases.
You will have to make sure the user is deleted from the node immediately once is player is matched. Using transactions you can match and remove 2 players immediately. So when a third user who was about to match as well will be added to queue instead of in the queue.
You may need queues running on your server (if the reads and writes are so high speed) or refactor the logic that matches your players. Cloud Compute may be a better choice for this. You may be able to use something like OpenMatch with that.
I remember stacking up all the users trying to match in an array and then running a cron job every 2 seconds to make pairs of them. Then I had used realtime database to emit changes to relevant users. Although this may not not be possible in cloud functions as each functions runs independently of each other.

socket.io get value that promise returns instead of promise { pending }

in socket.io i am trying to check if a user exist easily enough to where i can just call
if(checkUserExist(uid) == 'true'){
success();
}else{
failure();
};
so i figured out i need to use promises because the function i use to get info from the database is async so i do this
function checkUserExist(uid){
return new Promise(resolve => {
webUser.findOne({ _id: uid }, function(err, uid) {
if(uid){
console.log("USER EXISTS")
resolve('true')
}if(!uid){
console.log("USER NO REAL")
resolve('false')
}
})
});
and when i'm trying to use the function like this
socket.on('getAgents',function(uid){
console.log(checkUserExist(uid))
if(checkUserExist(uid) == 'true'){
console.log('user does exist getting agents')
agentList.find({}, function(err, docs) {
docs.forEach(function(d) {
socket.emit('newAgent', d.agentName)
});
});
}else if(checkUserExist(uid) == 'false'){
console.log('invalid uid ' + uid)
socket.emit('serverError', 'Invalid UID '+ uid)
}
})
the returned value is Promise { <pending> }
i am not sure what to do i thought that it was a simple enough task but obviously i don't yet know how to do it. is there anybody out there that can help me out.
promises is a fairly new concept to me and i still don't fully understand how they work should maybe use a library like promisify?
Thanks a ton :)
So, checkUserExist() returns a promise, not a value. That's how you coded it!
And, since the value is obtained asynchronously, you can't return the value directly anyway. See the canonical answer on that issue for more explanation on that topic.
To make your code work properly, you would have to actually use the promise that your function returns:
socket.on('getAgents',function(uid){
console.log(checkUserExist(uid))
checkUserExist(uid).then(result => {
if (result) {
agentList.find({}, function(err, docs) {
if (err) {
// decide what to do if there's a DB error here
return;
}
docs.forEach(function(d) {
socket.emit('newAgent', d.agentName)
});
});
} else {
socket.emit('serverError', 'Invalid UID ' + uid)
}
}).catch(err => {
socket.emit('serverError', 'Error processing UID ' + uid);
});
});

Firebase Cloud Functions wait for variables

I wanna write img_thumb_url to firebase database. In func1A, my code i wrote below where I expect it can breakthrough the while loop when those global variable room and msg_key are not null but what I found it always undefined although those variables become defined with string value when func2B successfully trigggered.
...
while(1){
if(room!=null && msg_key!=null){
console.log('room is ', room, '. msg_key is ', msg_key);
console.log('break from the loop');
break;
}
}
admin.initializeApp();
return admin.database().ref('/msgs/' + room + '/chat/' + msg_key + '/attachment/photo/thumbnail/url2').set(img_thumb_url);
})
But the path above ' '/msgs/' + room + '/chat/' + msg_key + '/attachment/photo/thumbnail/url2 ' depends on variable room and msg_key retrieved from other function func2B which is here,
exports.func2B = functions.database.ref('/msgs/{roomName}/chat/{pushid}')
.onCreate((snapshot, context) => {
// Grab the current value of what was written to the Realtime Database.
const msg = snapshot.val();
if (msg.attachPhotoUrl == null) {
console.log('No photo attachment found');
return null;
}
if (msg_key != null) {
console.log('msg_key no longer null');
return null;
}
console.log('writing url2 for thumbnail in firebase database...');
msg_key = msg.key;
room = msg.roomName;
console.log('room is ',room);
console.log('msg_key is ',msg_key);
console.log('img_thumb_url: ',img_thumb_url);
return ....
});
I'm not sure whether this is the proper way of method but I don't think it is as simple as that. Please help me how to resolve. How can i get that assigned value variable room and msg_key in func2B to func1A?

async.parallel stops working on huge list of array

i'm still new with async module. Basically, I want to do the batch update operation from array using async.parallel, like this :
var _ = require('lodash');
var async = require('async');
var list = req.body.dataArr; //the array
var asyncTasks = [];
_.each(hotelList,function(value,key){
asyncTasks.push(function(callback){
Hotel.find({hotelId:value.hotelId}).exec(function(err,hotels){
if(err){
console.log(err);
}
if(hotels.length ==0){
//hotel not found
console.log('Hotel not found for ID :'+value.hotelId);
return;
}
hotels[0].countryCode = value.countryCode;
hotels[0].city = value.city;
hotels[0].save(function(err){
if(err){
console.log('saving failed ... Reason : '+err);
return;
}
console.log('saving successful');
});
});
});
});
async.parallelLimit(asyncTasks,function(){
//async.parallelLimit(asyncTasks,10, function(){
console.log('finish call asyncTask');
res.json({status:'success'});
});
The problem is, when i run it using all data in array (there's more then 100.000 indexes), it only stop without any message eventough i have waited for several minutes, but when i try to limit the array to only 10 using parallelLimit, it only do 10 update operation like this :
saving successful
Is there something wrong in how I'm using async? Sorry if my english is bad.
Your asyncTasks functions need to call their callback parameters when they've finished their work so that parallel or parallelLimit knows when they're done.
_.each(hotelList,function(value,key){
asyncTasks.push(function(callback){
Hotel.find({hotelId:value.hotelId}).exec(function(err,hotels){
if(err){
console.log(err);
return callback(err);
}
if(hotels.length ==0){
//hotel not found
console.log('Hotel not found for ID :'+value.hotelId);
return callback(Error('Not found'));
}
hotels[0].countryCode = value.countryCode;
hotels[0].city = value.city;
hotels[0].save(function(err){
if(err){
console.log('saving failed ... Reason : '+err);
return callback(err);
}
console.log('saving successful');
callback();
});
});
});
});

How do I do a parameter based publication in Meteor and remove old subscription document?

I want to implement a parameter based publication in Meteor but I am running into some problems.
Here is what I have.
As the user types the keyup event that subscribes to publication and passes the value of the input.
'keyup #customerSearch': function(event, template){
var keyword = template.find('#customerSearch').value;
if(keyword){
if(keyword.length >= 3){
Meteor.subscribe('sessioncustomers', keyword);
}
}
}
The publication uses this keyword to return the records.
Meteor.publish("sessioncustomers", function(keyword){
if(keyword ){
if(keyword.length >= 3){
query.name = new RegExp(regExpQuoted(keyword), 'i' );
Customers.find(query);
} else {
return null;
}
}else{
return null;
}
});
The problem.
It works and documents are received except when the client changes the keyword or rather as the keywords changes the publication publishes additional documents that match the keywords but the client collection never removes the old documents.
How do I get the old documents that no longer match out of the client collection?
I thought that because the parameters of the subscription had changed that the non-matching documents would be unsubscribed and only the new matching documents would be subscribed.
In your keyup callback you need to "unsubscribe" to the previous publication,
otherwise you'll keep the old documents.
var sessionCustomersHandler = false;
'keyup #customerSearch': function(event, template) {
var keyword = template.find('#customerSearch').value;
if (keyword && keyword.length >= 3)
var newSessionCustomersHandler = Meteor.subscribe('sessioncustomers', keyword);
if (sessionCustomersHandler)
sessionCustomersHandler.stop();
sessionCustomersHandler = newSessionCustomersHandler;
}
Moreover, don't forget to check(keyword, String) in your publish function, for security.
Meteor.publish("sessioncustomers", function(keyword){
check(keyword, String)
if (keyword.length >= 3)
return Customers.find({
name: new RegExp(regExpQuoted(keyword), 'i' )
});
});
Make a local unnamed client collection
this.SessionCustomers = new Meteor.Collection(null);
Call a server method to get the results you want. Make the callback clear (remove all) and then insert to that local collection.
return Meteor.call('sessioncustomers', query, function(err, data) {
if (err) {
return console.log(err.message);
} else {
SessionCustomers.remove({});
var item, _i, _len;
for (_i = 0, _len = data.length; _i < _len; _i++) {
item = array[_i];
SessionCustomers.insert(item);
}
}
});

Resources