Why is Parse Server saveEventually with callback taking so long?

Viewed 292

I'm using Parse Server for my Android app and everything is working fine, but every time I call saveEventually on a new or old ParseObject, it is taking a really long time. Sometimes it's more than 1 minute for 1 item to return the callback.

Anyone had this problem?

Example:

orderObject.p.apply {
      put(ORDER_STATE, ORDER_STATE_FINISHED)
      put(ORDER_NEXT_DATE, orderEndDate)
}

createLog("FinishOrderSeq", "OrderActivity - saveOrder - before saveEvent")
orderObject.p.saveEventuallyEx(isOnline(this)){ e ->
     createLog("FinishOrderSeq", "OrderActivity - saveOrder - after saveEvent")
     if (e == null){
         createToast(getString(R.string.order_dialog_success), this)
         createOrderCopy(orderObject, dialog)
     } else {
         createToast(getString(R.string.order_dialog_err), this)
         changeButtonState(posBtn, true)
         changeButtonState(negBtn, true)
     }
}

fun ParseObject.saveEventuallyEx(isOnline: Boolean, callback: (ParseException?) -> Unit){
    if (isOnline){
        saveEventually{ err ->
            callback(err)
        }
    } else {
        saveEventually()
        callback(null)
    }
}

Also logs as I replaced it with saveInBackground with callback(still 30 seconds):

2020-05-28 14:53:49.805 18673-18673/? I/FinishOrderSeq: OrderActivity - saveOrder - before saveEvent
2020-05-28 14:54:15.694 18673-18673/? I/FinishOrderSeq: OrderActivity - saveOrder - after saveEvent

UPDATE: So I figured out from parse dashboard, that ParseObject is saved as record in table immediatelly, but callback from saveEventually is sent after 30sec - 2 minutes.

UPDATE 2: I also tried to use saveInBackground() if user is online (with callback). This also took 30seconds to 2 minutes for callback to return. Object was saved to parse database with all data after 100ms (checked from Parse Dashboard).

Then I thought something is wrong with ParseSDK threads, so I used save() inside Coroutine. Same problem occured here, save() took up to 2 minutes to perform.

Code with coroutine:

fun ParseObject.saveAsync(context: CoroutineContext, scope: CoroutineScope, isOnline: Boolean, callback: (ParseException?) -> Unit){
    if (isOnline){
        scope.launch {
            var ex: ParseException? = null
            try {
                save()
            } catch (e: ParseException){
                ex = e
            }
            withContext(context){
                callback(ex)
            }
        }
    }
}

There is some serious problem with callbacks in ParseSDK for Android and I don't know what can cause this. No exception no error on server side.

UPDATE 3: After deeper investigation, I found which function is taking long time to proceed.

ParseObject.State result = saveTask.getResult();

Approximately 30 seconds - 2 minutes to get into next line of code. This is lowest level of function I can get inside SDK.

Inside function save() or saveInBackground() there is this inner function in Java:

Task<Void> saveAsync(final String sessionToken, final Task<Void> toAwait) {
        if (!isDirty()) {
            return Task.forResult(null);
        }

        final ParseOperationSet operations;
        synchronized (mutex) {
            updateBeforeSave();
            validateSave();
            operations = startSave();
        }

        Task<Void> task;
        synchronized (mutex) {
            // Recursively save children

            /*
             * TODO(klimt): Why is this estimatedData and not... I mean, what if a child is
             * removed after save is called, but before the unresolved user gets resolved? It
             * won't get saved.
             */
            task = deepSaveAsync(estimatedData, sessionToken);
        }

        return task.onSuccessTask(
                TaskQueue.<Void>waitFor(toAwait)
        ).onSuccessTask(new Continuation<Void, Task<ParseObject.State>>() {
            @Override
            public Task<ParseObject.State> then(Task<Void> task) {
                final Map<String, ParseObject> fetchedObjects = collectFetchedObjects();
                ParseDecoder decoder = new KnownParseObjectDecoder(fetchedObjects);
                return getObjectController().saveAsync(getState(), operations, sessionToken, decoder);
            }
        }).continueWithTask(new Continuation<ParseObject.State, Task<Void>>() {
            @Override
            public Task<Void> then(final Task<ParseObject.State> saveTask) {
                ParseObject.State result = saveTask.getResult();  <--- THIS IS TAKING LONG TIME
                return handleSaveResultAsync(result, operations).continueWithTask(new Continuation<Void, Task<Void>>() {
                    @Override
                    public Task<Void> then(Task<Void> task) {
                        if (task.isFaulted() || task.isCancelled()) {
                            return task;
                        }

                        // We still want to propagate saveTask errors
                        return saveTask.makeVoid();
                    }
                });
            }
        });
    }
1 Answers

From the docs:

Most save functions execute immediately, and inform your app when the save is complete. If you don’t need to know when the save has finished, you can use saveEventually instead.

It can take a long time because with saveEventually you are basically saying "save it soon". If you want to "save it as soon a possible" then use saveInBackground as described in the docs.

Further it says:

All calls to saveEventually (and deleteEventually) are executed in the order they are called, so it is safe to call saveEventually on an object multiple times. If you have the local datastore enabled, then any object you saveEventually will be pinned as long as that save is in progress. That makes it easy to retrieve your local changes while waiting for the network to be available.

Which means that you can save and modify the object locally multiple times and the latest version will be stored in the database as soon as the network connection is reestablished.

Related