I have a class in javascript, with the following structure:
class TableManager {
/** an array containing Table objects **/
protected Tables = [];
protected getTable(tableId) {
// iterates over this.Tables, and searches for a table with a specific id: if found, it returns the table object, otherwise it returns null
}
protected async createTable(tableId) {
const Table = await fetchTable(tableId); /** performs an asynchronous operation, that creates a Table object by performing a select operation on the database **/
this.Tables.push(Table);
return Table;
}
protected async joinTable(user, tableId) {
const Table = this.getTable(tableId) ?? await this.createTable(tableId);
Table.addUser(user);
}
}
The idea behind this class, is that it will receive commands via a socket. For example, it may receive the joinTable
command, in which case, it should first check if the table that is being joined already exists in the memory: if it does, it will add the user to that table, otherwise, it will create the table, store it in the memory, and add the user to the table.
I am a bit concerned, that this could result in a race condition, if two joinTable()
calls are made in a short amount of time, in which case the tables will be created twice, and stored in memory as two separate table instances. Am I right to be affraid about this? If yes, would checking if the table exists before adding it to the array in the createTable
function, solve this race condition?
CodePudding user response:
Your concern is right. the idea is transactions and make sure that there is only one transaction running at a given time. In Nodejs, you can use Mutex to implement that. Read more: https://www.nodejsdesignpatterns.com/blog/node-js-race-conditions/.
CodePudding user response:
I am a bit concerned, that this could result in a race condition, if two joinTable() calls are made in a short amount of time, in which case the tables will be created twice, and stored in memory as two separate table instances. Am I right to be affraid about this?
This shouldn't be a problem as long as you await
each call (or chain it properly). That is to say, it won't be a problem as long as the operations are sequential. If you allow the promises to resolve at the same time (like with Promise.all
) then yes, as it is right now, there would be a race condition.
If yes, would checking if the table exists before adding it to the array in the createTable function, solve this race condition?
As I understand it, no, it would still create a race condition. The first function call would do the check, see the table does not exist and proceed to send the query to your server in order to create the new entry. The second function call would also do the check but since it's not waiting for the previous request, it's possible the check happens before the first request finishes (this is your race condition). That means another request can be sent to create another table.
What you can do is store your entry as a promise. I would use a Map
for this:
protected Tables = new Map();
protected getTable(tableId) {
let Table = this.Tables.get(tableId);
if(!Table){
Table = fetchTable(tableId);
this.Tables.set(tableId, Table);
}
return Table;
}
This way, joinTable
can instead do getTable
which also creates the Table
if it doesn't exist. If the Table
is being created it will pick up the promise and no duplicates are ever made this way.
Ultimately, the creation or not of any entity on a server, needs to be managed there... on the server. Otherwise, you risk multiple clients (or even a client restart) creating these duplicates.