Sistemas y Tecnologías Web: Servidor

Master de II. ULL. 1er cuatrimestre


Organization ULL-MII-SYTWS-2122   Classroom ULL-MII-SYTWS-2122   Campus Virtual SYTWS   Chat Chat   Profesor Casiano

Table of Contents

Race Conditions

Loading an image with some delay

Consider this file index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE HTML>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Asynchronous Image Loading</title>
</head>
<body>
  <div id="holder-div"></div>

  <script type="text/javascript">
    let image = new Image(100),
        //url = "https://elementscpa.com/wp-content/uploads/2017/08/elementscpa-business-infinity-loop-tal-e1504182065499.png", 
        url = "infinity-loop.png", 
        container = document.getElementById("holder-div");

    image.src = url; // I suppose the "load" process starts here

    let waitFor = 0;
    //let waitFor = 2000;
    setTimeout(function(){
      // The onload event occurs when an object has been loaded
      // We only append it to the container when the load has finished
      // If 1000 the handler is inserted in the event queue too late
      // If an EventListener is added to an EventTarget while it is 
      // processing an event, that event does not trigger the listener.
      image.addEventListener("load", function() {
        console.trace();
        container.appendChild(image)
      });
    }, waitFor);

  </script>
  <a href="http://www.infoq.com/presentations/javascript-concurrency-parallelism">Concurrency and Parallel Computing in JavaScript (Recorded at: StrangeLoop) by Stephan Herhut on Mar 05, 2014 </a>
</body>
</html>

Experiment

Copy and serve this file index.html with:

1
http-server -p 9000 -o

with this line uncommented

1
let waitFor = 1000;

Can you see the infinite loop image?

Now comment the line where waitFor is initialized and uncomment the other:

1
let waitFor = 0;

and run:

1
http-server -p 8000 -o

(Change the port to avoid cache problems)

What do you think it will happen? Can you explain it?

Where is it:

1
2
➜  race-condition git:(curso2021) ✗ ls ~/campus-virtual/2021/sytws2021/apuntes/tema2-async/event-loop/exercises/race-condition
index.html          infinity-loop.png   instructions.md     not-race-example.js race-example.js
<i>There’s an endless loop, when JavaScript engine waits for tasks, executes them and then sleeps waiting for more tasks</i>
There’s an endless loop, when JavaScript engine waits for tasks, executes them and then sleeps waiting for more tasks

Comentarios

References

Manually Interleaving Promises

This other example (using promises instead of callbacks) is taken from this blog:

Promises are a (relatively) new way to handle asynchronous programming in JS. For an introduction to Promises see this section:

File race-example.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// https://medium.com/@ubershmekel/yes-there-are-race-conditions-in-javascript-ba044571a914
// An example race condition in JavaScript
// When you run this script using Node or in a browser, it
// does not print "Ended with 0", but a random number.
// Even though the functions running
// simply loop 100 iterations of adding and subtracting.
// The reason the end result is random is because the
// sleeps are of random duration and the time between the read
// of the variable causes the eventual write to be incorrect
// when `adder` and `subber` interleave.
// This problem is similar to:
// https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use

let number = 0;
const times = 100;

function sleep() { 
  return new Promise(
    (resolve) => setTimeout(resolve, Math.random() * 5));
}

async function adder() {
  for (let i = 0; i < times; i++) {
    await sleep();

      let read = number; 
      read = read + 1;

    await sleep(); // This task is interrupted here giving opportunity for 'subber' to take the processor

    number = read; // But there is a chance 'read' is obsolete. 
                   // It is a sort of "manual" co-routine race cndition: not atomic anymore
  }
}

async function subber() {
  for (let i = 0; i < times; i++) {
    await sleep();
      let read = number;
      read = read - 1;
    await sleep();

    number = read;
  }
}

async function main() {
  console.log("Started with", number);

  await Promise.all([
    adder(),
    subber(),
  ]);
  /*
  await adder().then(subber)
  */

  console.log("Ended with", number);
}

main()
  .then(() => console.log("All done"))
  .catch((err) => console.error(err));

An example of race condition in JavaScript. When you run this script using Node or in a browser, it does not print “Ended with 0”, but a random number.

1
2
3
4
5
6
7
➜  race-condition git:(curso2021) ✗ node race-example.js 
Started with 0
Ended with 3
All done
➜  race-condition git:(curso2021) ✗ node race-example.js
Started with 0
Ended with 20

Even though the functions running simply loop 100 iterations of adding and subtracting.

The reason the end result is random is because the sleeps are of random duration and the time between the read of the variable causes the eventual write to be incorrect when adder and subber interleave.

This problem is similar to:

Time-of-check to time-of-use

Comment with GitHub Utterances