javascript - seccion - race condition vulnerability
Evitando una condiciĆ³n de carrera de javascript (7)
Creo que el tiempo de espera está causando su problema ... si eso va a ser un código simple (no hay llamadas asíncronas AJAX, tiempos de espera, etc.), entonces no creo que se ejecute SaveForm antes de que UserInputChanged finalice.
Aquí está el escenario:
A mis usuarios se les presenta una grilla, básicamente, una versión simplificada de una hoja de cálculo. Hay cuadros de texto en cada fila de la grilla. Cuando cambian un valor en un cuadro de texto, estoy validando su entrada, actualizando la colección que está manejando la cuadrícula y redibujando los subtotales en la página. Todo esto se maneja mediante el evento OnChange de cada cuadro de texto.
Cuando hacen clic en el botón "Guardar", estoy usando el evento OnClick del botón para realizar una validación final de los montos y luego enviar su entrada completa a un servicio web, guardándola.
Al menos, eso es lo que ocurre si pasan el formulario por el formulario hasta el botón Enviar.
El problema es que si ingresan un valor, inmediatamente haga clic en el botón Guardar, SaveForm () comienza a ejecutarse antes de que UserInputChanged () complete - una condición de carrera. Mi código no usa setTimeout, pero lo estoy usando para simular el código de validación lento UserInputChanged:
<!-- snip -->
<script>
var amount = null;
var currentControl = null;
function UserInputChanged(control) {
currentControl = control;
// use setTimeout to simulate slow validation code (production code does not use setTimeout)
setTimeout("ValidateAmount()", 100);
}
function SaveForm() {
// call web service to save value
document.getElementById("SavedAmount").innerHTML = amount;
}
function ValidateAmount() {
// various validationey functions here
amount = currentControl.value; // save value to collection
document.getElementById("Subtotal").innerHTML = amount; // update subtotals
}
</script>
<!-- snip -->
Amount: <input type="text" id="UserInputValue" onchange="UserInputChanged(this);" /> <br />
Subtotal: <span id="Subtotal"></span> <br />
<input type="button" onclick="SaveForm();" value="Save" /> <br /><br />
Saved amount: <span id="SavedAmount"></span>
<!-- snip -->
No creo que pueda acelerar el código de validación, es bastante liviano, pero aparentemente lo suficientemente lento como para que el código intente llamar al servicio web antes de que se complete la validación.
En mi máquina, ~ 95ms es el número mágico entre si el código de validación se ejecuta antes de que comience el código de guardado. Esto puede ser mayor o menor dependiendo de la velocidad de la computadora del usuario.
¿Alguien tiene alguna idea de cómo manejar esta condición? Un compañero de trabajo sugirió usar un semáforo mientras el código de validación se está ejecutando y un bucle ocupado en el código de guardado para esperar hasta que el semáforo se desbloquee, pero me gustaría evitar el uso de cualquier tipo de bucle ocupado en mi código.
Deshabilite el botón guardar durante la validación. Establézcalo deshabilitado como lo primero que hace la validación, y vuelva a habilitarlo cuando termine.
p.ej
function UserInputChanged(control) {
// --> disable button here --<
currentControl = control;
// use setTimeout to simulate slow validation code (production code does not use setTimeout)
setTimeout("ValidateAmount()", 100);
}
y
function ValidateAmount() {
// various validationey functions here
amount = currentControl.value; // save value to collection
document.getElementById("Subtotal").innerHTML = amount; // update subtotals
// --> enable button here if validation passes --<
}
Tendrás que ajustar cuando elimines setTimeout y hagas que la función de validación sea una, pero a menos que tus usuarios tengan reflejos sobrehumanos, deberías estar listo.
No tienes una condición de carrera, las condiciones de carrera no pueden suceder en JavaScript dado que javascript tiene un solo hilo, por lo que 2 hilos no pueden interferir entre sí.
El ejemplo que das no es un buen ejemplo. La llamada a setTimeout colocará la función llamada en una cola en el motor de JavaScript y la ejecutará más tarde. Si en ese momento hace clic en el botón Guardar, la función setTimeout no se invocará hasta DESPUÉS de que el guardado haya finalizado por completo.
Lo que probablemente está sucediendo en su javascript es que el motor javascript llama al evento onClick antes de llamar al evento onChange.
Como sugerencia, tenga en cuenta que javascript tiene un único subproceso, a menos que utilice un depurador de javascript (firebug, microsoft screipt depurador). Esos programas interceptan el hilo y lo detienen. A partir de ese punto, se pueden ejecutar otros subprocesos (ya sea a través de eventos, llamadas setTimeout o manejadores XMLHttp), lo que hace parecer que javascript puede ejecutar varios subprocesos al mismo tiempo.
Puede configurar una función recurrente que supervise el estado de toda la grilla y genere un evento que indique si toda la grilla es válida o no.
Su botón ''enviar formulario'' se activará o deshabilitará en función de ese estado.
Oh, ahora veo una respuesta similar, eso también funciona, por supuesto.
Usa el semáforo (llamémoslo StillNeedsValidating). si la función SaveForm ve que el semáforo StillNeedsValidating está activo, haz que active un segundo semáforo propio (que llamaré aquí FormNeedsSaving) y devuelve. Cuando la función de validación finaliza, si el semáforo de FormNeedsSaving está activo, llama a la función SaveForm por sí misma.
En jankcode;
function UserInputChanged(control) {
StillNeedsValidating = true;
// do validation
StillNeedsValidating = false;
if (FormNeedsSaving) saveForm();
}
function SaveForm() {
if (StillNeedsValidating) { FormNeedsSaving=true; return; }
// call web service to save value
FormNeedsSaving = false;
}
Al trabajar con orígenes de datos asincrónicos, puede tener condiciones de carrera porque el subproceso de proceso de JavaScript continúa ejecutando directivas que pueden depender de los datos que aún no han regresado de la fuente de datos remota. Es por eso que tenemos funciones de devolución de llamada.
En su ejemplo, la llamada al código de validación debe tener una función de devolución de llamada que pueda hacer algo cuando la validación regrese.
Sin embargo, al hacer algo con una lógica complicada o al tratar de resolver problemas o mejorar una serie existente de devoluciones de llamadas, puede volverse loco.
Esa es la razón por la que creé la biblioteca proto-q: http://code.google.com/p/proto-q/
Compruébalo si haces mucho de este tipo de trabajo.
Un semáforo o mutex es probablemente el mejor camino a seguir, pero en lugar de un bucle ocupado, simplemente use un setTimeout()
para simular un hilo de reposo. Me gusta esto:
busy = false;
function UserInputChanged(control) {
busy = true;
currentControl = control;
// use setTimeout to simulate slow validation code (production code does not use setTimeout)
setTimeout("ValidateAmount()", 100);
}
function SaveForm() {
if(busy)
{
setTimeout("SaveForm()", 10);
return;
}
// call web service to save value
document.getElementById("SavedAmount").innerHTML = amount;
}
function ValidateAmount() {
// various validationey functions here
amount = currentControl.value; // save value to collection
document.getElementById("Subtotal").innerHTML = amount; // update subtotals
busy = false;
}