Fix flaky test with transactions (#7187)

* Fix flaky test with transactions

* Add CHANGELOG entry

* Fix the other transactions related tests that became flaky because now Parse Server tries to submit the transaction multilpe times in the case of TransientError

* Remove fit from tests
This commit is contained in:
Antonio Davi Macedo Coelho de Castro
2021-02-18 10:18:54 -08:00
committed by GitHub
parent 9a9fc5fa5f
commit a430d6f7b7
6 changed files with 158 additions and 100 deletions

View File

@@ -1046,9 +1046,20 @@ export class MongoStorageAdapter implements StorageAdapter {
}
commitTransactionalSession(transactionalSection: any): Promise<void> {
return transactionalSection.commitTransaction().then(() => {
transactionalSection.endSession();
});
const commit = retries => {
return transactionalSection
.commitTransaction()
.catch(error => {
if (error && error.hasErrorLabel('TransientTransactionError') && retries > 0) {
return commit(retries - 1);
}
throw error;
})
.then(() => {
transactionalSection.endSession();
});
};
return commit(5);
}
abortTransactionalSession(transactionalSection: any): Promise<void> {

View File

@@ -48,44 +48,60 @@ function ParseServerRESTController(applicationId, router) {
}
if (path === '/batch') {
let initialPromise = Promise.resolve();
if (data.transaction === true) {
initialPromise = config.database.createTransactionalSession();
}
return initialPromise.then(() => {
const promises = data.requests.map(request => {
return handleRequest(request.method, request.path, request.body, options, config).then(
response => {
if (options.returnStatus) {
const status = response._status;
delete response._status;
return { success: response, _status: status };
const batch = transactionRetries => {
let initialPromise = Promise.resolve();
if (data.transaction === true) {
initialPromise = config.database.createTransactionalSession();
}
return initialPromise.then(() => {
const promises = data.requests.map(request => {
return handleRequest(request.method, request.path, request.body, options, config).then(
response => {
if (options.returnStatus) {
const status = response._status;
delete response._status;
return { success: response, _status: status };
}
return { success: response };
},
error => {
return {
error: { code: error.code, error: error.message },
};
}
return { success: response };
},
error => {
return {
error: { code: error.code, error: error.message },
};
}
);
});
return Promise.all(promises).then(result => {
if (data.transaction === true) {
if (result.find(resultItem => typeof resultItem.error === 'object')) {
return config.database.abortTransactionalSession().then(() => {
return Promise.reject(result);
});
} else {
return config.database.commitTransactionalSession().then(() => {
);
});
return Promise.all(promises)
.then(result => {
if (data.transaction === true) {
if (result.find(resultItem => typeof resultItem.error === 'object')) {
return config.database.abortTransactionalSession().then(() => {
return Promise.reject(result);
});
} else {
return config.database.commitTransactionalSession().then(() => {
return result;
});
}
} else {
return result;
});
}
} else {
return result;
}
}
})
.catch(error => {
if (
error &&
error.find(
errorItem => typeof errorItem.error === 'object' && errorItem.error.code === 251
) &&
transactionRetries > 0
) {
return batch(transactionRetries - 1);
}
throw error;
});
});
});
};
return batch(5);
}
let query;

View File

@@ -83,49 +83,66 @@ function handleBatch(router, req) {
req.config.publicServerURL
);
let initialPromise = Promise.resolve();
if (req.body.transaction === true) {
initialPromise = req.config.database.createTransactionalSession();
}
const batch = transactionRetries => {
let initialPromise = Promise.resolve();
if (req.body.transaction === true) {
initialPromise = req.config.database.createTransactionalSession();
}
return initialPromise.then(() => {
const promises = req.body.requests.map(restRequest => {
const routablePath = makeRoutablePath(restRequest.path);
return initialPromise.then(() => {
const promises = req.body.requests.map(restRequest => {
const routablePath = makeRoutablePath(restRequest.path);
// Construct a request that we can send to a handler
const request = {
body: restRequest.body,
config: req.config,
auth: req.auth,
info: req.info,
};
// Construct a request that we can send to a handler
const request = {
body: restRequest.body,
config: req.config,
auth: req.auth,
info: req.info,
};
return router.tryRouteRequest(restRequest.method, routablePath, request).then(
response => {
return { success: response.response };
},
error => {
return { error: { code: error.code, error: error.message } };
}
);
});
return router.tryRouteRequest(restRequest.method, routablePath, request).then(
response => {
return { success: response.response };
},
error => {
return { error: { code: error.code, error: error.message } };
}
);
});
return Promise.all(promises).then(results => {
if (req.body.transaction === true) {
if (results.find(result => typeof result.error === 'object')) {
return req.config.database.abortTransactionalSession().then(() => {
return Promise.reject({ response: results });
});
} else {
return req.config.database.commitTransactionalSession().then(() => {
return Promise.all(promises)
.then(results => {
if (req.body.transaction === true) {
if (results.find(result => typeof result.error === 'object')) {
return req.config.database.abortTransactionalSession().then(() => {
return Promise.reject({ response: results });
});
} else {
return req.config.database.commitTransactionalSession().then(() => {
return { response: results };
});
}
} else {
return { response: results };
});
}
} else {
return { response: results };
}
}
})
.catch(error => {
if (
error &&
error.response &&
error.response.find(
errorItem => typeof errorItem.error === 'object' && errorItem.error.code === 251
) &&
transactionRetries > 0
) {
return batch(transactionRetries - 1);
}
throw error;
});
});
});
};
return batch(5);
}
module.exports = {