Tengo una clase en javascript, con la siguiente estructura:
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); } } La idea detrás de esta clase es que recibirá comandos a través de un socket. Por ejemplo, puede recibir el comando joinTable , en cuyo caso, primero debe verificar si la tabla que se está uniendo ya existe en la memoria: si lo hace, agregará al usuario a esa tabla, de lo contrario, creará el tabla, guárdelo en la memoria y agregue el usuario a la tabla.
Estoy un poco preocupado de que esto podría resultar en una condición de carrera, si se realizan dos llamadas a joinTable() en un corto período de tiempo, en cuyo caso las tablas se crearán dos veces y se almacenarán en la memoria como dos instancias de tablas separadas. ¿Tengo razón en tener miedo de esto? En caso afirmativo, ¿verificar si la tabla existe antes de agregarla a la matriz en la función createTable resolvería esta condición de carrera?
Tu preocupación es correcta. la idea son las transacciones y asegurarse de que solo haya una transacción ejecutándose en un momento dado. En Nodejs, puede usar Mutex para implementar eso. Lea más: https://www.nodejsdesignpatterns.com/blog/node-js-race-conditions/ .
Estoy un poco preocupado de que esto podría resultar en una condición de carrera, si se realizan dos llamadas a joinTable() en un corto período de tiempo, en cuyo caso las tablas se crearán dos veces y se almacenarán en la memoria como dos instancias de tablas separadas. ¿Tengo razón en tener miedo de esto?
Esto no debería ser un problema siempre que await cada llamada (o la encadene correctamente). Es decir, no será un problema siempre que las operaciones sean secuenciales. Si permite que las promesas se resuelvan al mismo tiempo (como con Promise.all ), entonces sí, tal como está ahora, habría una condición de carrera.
En caso afirmativo, ¿verificar si la tabla existe antes de agregarla a la matriz en la función createTable resolvería esta condición de carrera?
Según tengo entendido, no, todavía crearía una condición de carrera. La primera llamada de función haría la verificación, vería que la tabla no existe y procedería a enviar la consulta a su servidor para crear la nueva entrada. La segunda llamada de función también haría la verificación, pero dado que no está esperando la solicitud anterior, es posible que la verificación ocurra antes de que finalice la primera solicitud (esta es su condición de carrera). Eso significa que se puede enviar otra solicitud para crear otra tabla.
Lo que puede hacer es almacenar su entrada como una promesa. Yo usaría un Map para esto:
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; } De esta manera, joinTable puede hacer getTable , que también crea la Table si no existe. Si la Table se está creando, recogerá la promesa y nunca se realizarán duplicados de esta manera.
En última instancia, la creación o no de cualquier entidad en un servidor, debe gestionarse allí... en el servidor. De lo contrario, corre el riesgo de que varios clientes (o incluso un reinicio del cliente) creen estos duplicados.