8

I've been stuck on this for months. I have removed some minor details from the function but nothing major. I have this https cloud function that ends a session and then uses endTime and startTime to calculate bill which is then returned to the client.

startTime is retrived from the realtime firebase database (which the session starter function put there).

My code snippet:

exports.endSession = functions.https.onRequest(async (req, res) => {
    console.log("endSession() called.")
    if(req.method == 'GET'){
        bid = req.query.bid
        session_cost = req.query.sessioncost
    }else{
        bid = req.body.bid
        session_cost = req.body.sessioncost
    }

start_time_ref = admin.database().ref("/online_sessions/").child(bid).child("start_time")
start_time_snapshot = await start_time_ref.once('value')

console.log("start_time_snapshot: "+start_time_snapshot.val())

start_time_snapshot = moment(start_time_snapshot.val(), 'dddd MMMM Do YYYY HH:mm:ss Z');
endDateTime = getDateTime()

console.log("startTime: " + start_time_snapshot.toString())
console.log("endTime: " + endDateTime.toString())
hour_difference = getHourDifference(start_time_snapshot, endDateTime)

bill = ride_cost * Math.ceil(hour_difference)
console.log("bill: "+bill)

var s_phone
sSessionlinks_ref = admin.database().ref('/sSessionlinks/')
sSessionlinks_snapshot = await sSessionlinks_ref.once('value')

sSessionlinks_snapshot.forEach((sid)=>{
    if(sid.val() == bid){
        s_phone = sid.key
    }
})

s_fcm_token_ref = admin.database().ref("/s/").child(s_phone).child("FCM")
s_fcm_token_snapshot = await s_fcm_token_ref.once('value')

try{ // telling another client that session has ended.
    await admin.messaging().send({
        data: {
            type: "sessionCompleted",
            bill: bill.toString()
        },
        token: s_fcm_token_snapshot.val() 
    })
}catch(error){

}

//deleting this session from online sessions
online_session_ref = admin.database().ref('/online_sessions/').child(bid)
await online_session_ref.remove()

//puting this session as available
available_session_ref = admin.database().ref('/available_sessions/')
json = {}
json[bid] = s_phone
await available_session_ref.update(json) // session made available


res.status(200).send(bill.toString())
// here it *sometimes* returns 304 and then restarts but since i've already removed online_session_ref I cannot get startTime again because its removed with online_sessions so it fails.
    // return
})

When its first called. It does all the calculations correctly but responds with a 304. So (I think the client) resends the request and the function is called again but since session is destroyed so it cannot calculate startTime.

Why is it that when its first called, even though all the calculations happen correctly it returns a 304 and not a 200? This problem doesn't happen all the time. It usually happens when this function is called after a long time but I'm not certain with that. I don't know what causes this.

Helper functions I've used:

function getHourDifference(s, e){
    return moment.duration(e.diff(s)).asHours()
}


function getDateTime(){
    d = moment.utc().utcOffset('+0530')
    return d
}

When function end first time the text payload is Function execution took 794 ms, finished with status code 304

When it runs the second time (where it cannot get startTime cause its been removed in first run. There shouldn't be a second run in the first place.), the payload text is Function execution took 234 ms, finished with status code 200 (its 200 but return NaN becuase it cannot do calculation without startTime.

EDIT: As some of you asked me to tell how the the function is being called:

Its being called from and android app using Volley. The parameters are assured to not be null. The code segment to call that function is:

    // Setting the button's listeners.
    endSessionButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

                progressDialog = new SweetAlertDialog(getContext(), SweetAlertDialog.PROGRESS_TYPE);
                progressDialog.getProgressHelper().setBarColor(Color.parseColor("#A5DC86"));
                progressDialog.setTitleText("Ending session...");

                AlertDialog endDialog = new AlertDialog.Builder(getContext()).create();
                endDialog.setTitle("End Session?");

                Log.e("sessioncost", String.valueOf(session_cost));

                endDialog.setButton(Dialog.BUTTON_POSITIVE, "Yes", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        progressDialog.show();
                        // Instantiate the RequestQueue.
                        RequestQueue queue = Volley.newRequestQueue(getContext());

                        String url = "https://us-central1-something-something.cloudfunctions.net/endSession?bid=" + bid + "&sessioncost=" + session_cost;
                        Log.e("end sesion button", url);
                        // Request a string response from the provided URL.
                        StringRequest endSessionRequest = new StringRequest(Request.Method.GET, url,
                                new Response.Listener<String>() {
                                    @Override
                                    public void onResponse(final String response) {
                                        progressDialog.dismiss();
                                        Toast.makeText(getContext(), "Session Completed", Toast.LENGTH_LONG).show();

                                        progressDialog = new SweetAlertDialog(getContext(), SweetAlertDialog.SUCCESS_TYPE);
                                        progressDialog.getProgressHelper().setBarColor(Color.parseColor("#A5DC86"));
                                        progressDialog.setTitleText("Session Completed: Bill");
                                        progressDialog.setContentText("Please pay ?" + response + " to s.");
                                        progressDialog.setCancelable(false);
                                        progressDialog.show();

                                        changeState('1');

                                        bill_in_paise = Float.parseFloat(response) * 100;
                                        Log.e("bill", bill_in_paise.toString());
                                        progressDialog.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
                                            @Override
                                            public void onClick(SweetAlertDialog sweetAlertDialog) {

                                                sweetAlertDialog.dismiss();
                                                Intent intent = new Intent(getContext(), PaymentActivity.class);
                                                intent.putExtra("amt", bill_in_paise.toString());
                                                startActivityForResult(intent, REQUEST_CODE);
                                            }
                                        });


                                    }
                                }, new Response.ErrorListener() {
                            @Override
                            public void onErrorResponse(VolleyError error) {
                                Toast.makeText(getContext(), error.toString(), Toast.LENGTH_LONG).show();
                            }// onErrorResnponse - END
                        });

                        // Add the request to the RequestQueue. Cuz volley is asyc af.
                        queue.add(endSessionRequest);
                        // VOLLEY REQUEST - END
                    }
                });
                endDialog.setButton(Dialog.BUTTON_NEGATIVE, "No", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(getContext(), "Session not cancelled.  " + which, Toast.LENGTH_LONG).show();
                    }
                });

                endDialog.show();


            }

        }
    }); // endSessionButton onclick - end

UPDATE: @tuledev helped fix the 304 with a work around but the problem is still here. Even when the status code is 200 the cloud function is somehow called again and I get a NaN bill. At this point I don't know what's causing this.

ParmuTownley
  • 957
  • 2
  • 14
  • 34
  • 1
    Please edit the question to explain exactly how the client calls this function. Include any relevant code. – Doug Stevenson Mar 08 '19 at 18:27
  • @DougStevenson I have updated the question with the client code. I'm sorry I didn't have internet for 2 days. – ParmuTownley Mar 11 '19 at 14:29
  • Have you googled for an answer? https://stackoverflow.com/questions/50238968/firebase-node-updating-two-refs-results-in-304-errors – Sergej Mar 12 '19 at 07:13
  • Are you sure your code hit the `res.status(200)` line? @ParamdeepSinghObheroi I think your code maybe throw an exception somewhere in the first time. And then can't hit `res.status(200)` – tuledev Mar 12 '19 at 07:46
  • @Sergej he's using async, so doesn't need to return `Promise` – tuledev Mar 12 '19 at 07:47
  • @tuledev yes I'm sure. – ParmuTownley Mar 12 '19 at 07:48
  • @ParamdeepSinghObheroi read this https://stackoverflow.com/questions/51196873/scraping-using-google-cloud-function-it-finished-with-status-code-304. I don't know why "When its first called. It does all the calculations correctly but responds with a 304. So (I think the client) resends the request and the function is called again but since session is destroyed so it cannot calculate startTime." I tried to take a look at my console. I can confirm **the same result cause 304**. – tuledev Mar 12 '19 at 09:05
  • @tuledev interesting. The answer says that 304 happens when the response is same as what it was before. I've been testing the code with bill always as 1050. Maybe I should try othet values. But even if that is true, it still shouldn't break if the response is same cuz most people would use the same session most times. – ParmuTownley Mar 12 '19 at 10:31
  • @ParamdeepSinghObheroi I tested it. And the answer is right in my case. But I don't understand your issue, what break?. 304 doesn't cause the response become invalid, it's still successful. I have some running api for sure. – tuledev Mar 12 '19 at 10:57
  • @tuledev by break I mean, the client wouldn't get the right answer. Because if the client gets a 304, it re-requests and gets a NaN because the session information was destroyed in the first time. – ParmuTownley Mar 12 '19 at 11:02
  • @ParamdeepSinghObheroi Are you sure the client get 304? In my case, the client get `200` the one be sent by `res.status(200).send` – tuledev Mar 12 '19 at 11:12
  • @tuledev I'm basing this claim by the google console logs. Also, this doesn't happen everytime. If I do it extensively it doesn't happen. If I wait for like a day before testing then it does. – ParmuTownley Mar 12 '19 at 13:34
  • @ParamdeepSinghObheroi Maybe 304 doesn't cause the break, I think. The function logs in firebase console can show `finished with status code 304`, but the client will receive status 200 as `res.status(200).send` – tuledev Mar 12 '19 at 13:44
  • @tuledev so what could cause the resend and NaN? I've spent over a month on this problem and this is what I've come the problem to be after much investigation. Its hard because it's not easy to reproduce. – ParmuTownley Mar 12 '19 at 13:46
  • @ParamdeepSinghObheroi "When its first called. It does all the calculations correctly but responds with a 304. So (I think the client) resends the request".Did your the client resend request because of 304?. In my project, firebase console showed `finished with status code 304` hundred times. But my client always got `status 200` as a normal successful case. – tuledev Mar 12 '19 at 13:52
  • How did you know "So (I think the client) resends the request" because of 304 from firebase console log website?. I mean, maybe you are thinking in the wrong direction. – tuledev Mar 12 '19 at 13:53
  • @tuledev > are you sure the client resends request because of 304 - no I'm not sure. – ParmuTownley Mar 12 '19 at 13:53
  • @tuledev its entirety possible my reasoning is wrong. But since it's hard to reproduce that's what I've come up with. – ParmuTownley Mar 12 '19 at 13:55
  • @tuledev read this: https://httpstatuses.com/304. Last line says 304 response cannot contain a body. Maybe this is where I'm getting the NaN? – ParmuTownley Mar 12 '19 at 13:59
  • @tuledev also I can be sure the function is called twice because the the "s" which is serving client to paying client gets the "session completed" notification twice. (Its the fcm send line) – ParmuTownley Mar 12 '19 at 14:09
  • @ParamdeepSinghObheroi I think 304 will tell the client to use the cached value. Because it return the same value as the previous request. I tried with firebase, and the client gets 200 and data body, but firebase console shows 304 – tuledev Mar 12 '19 at 14:21
  • @tuledev if the client is asked to use cached value then why should the client do another request? And if the value is cached then why would I get a NaN? – ParmuTownley Mar 12 '19 at 14:24
  • The client do another request, this is what you have to looking for. Are you sure the client send another request because of 1st fail? I read your client code, and didn't found it. – tuledev Mar 12 '19 at 14:27
  • Oh, you are using Java for Android. I will take a search – tuledev Mar 12 '19 at 14:37
  • https://stackoverflow.com/questions/33606529/volley-cache-returns-null-when-receiving-304 – tuledev Mar 12 '19 at 14:47
  • Maybe there're something wrong with volley and 304. Can you try to response more info, so firebase won't use 304. Like add endDateTime to response. @ParamdeepSinghObheroi – tuledev Mar 12 '19 at 14:50
  • @ParamdeepSinghObheroi Volley actually can resend request? ,https://stackoverflow.com/questions/17481964/android-volley-to-handle-redirect – tuledev Mar 12 '19 at 14:55
  • @tuledev interesting. Maybe volley is the problem afterall. The idea of returning endTime as well to avoid 304 seems good so I'll try it as soon as I get home. Though I would still want the 304 problem to fixed cause I feel there's more places and future dev where this could happen more. I would still look for a more elegant solution. – ParmuTownley Mar 12 '19 at 14:55
  • @ParamdeepSinghObheroi return endTime just a work around if it works. Please show me the solution if you find it. Thank you. – tuledev Mar 12 '19 at 15:20
  • 1
    @tuledev so after a day of testing your work around, it seems it works. Returning endTime with bill no longer causes the error. You can post your work around as a solution. If I don't get a solution I'll accept it when the bounty ends. – ParmuTownley Mar 13 '19 at 17:44
  • @ParamdeepSinghObheroi return endTime maybe isn't a good work around solution. You should try to use `id` that is generated from firebase admin for each bill. Then return `{ id, bill }`. – tuledev Mar 14 '19 at 03:30
  • @tuledev UPDATE: the problem is still here. Even when the status code is 200 the cloud function is somehow called again and I get a NaN bill. At this point I don't know what's causing this. – ParmuTownley Mar 15 '19 at 09:43
  • @ParamdeepSinghObheroi haha. You should check more the client. Create a mock service, or something, try to reproduce the bug first – tuledev Mar 15 '19 at 09:58

1 Answers1

4

The 304 status comes because the response is the same as the previous. Firebase cloud responses 304 and the client will get the cached data.

For preventing 304 status, we can return the value + uid of the bill, or something make the response diff from the previous.

As OP and I discussed, the 304 is solved but the problem's still here. So I think the problem comes from the client side.

Hope somebody can HELP him.

Edit: OP here, I changed the client code to using okhttp instead of volley and after testing for 2 days everything seems fine. Tuledev's answer did fix 304 but even after 200 the problem persisted. Just use okhttp instead of volley.

tuledev
  • 10,177
  • 4
  • 29
  • 49