How are Race Conditions in single threaded JavaScript possible?
- 1. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
toString().padStart(width) : cell.padEnd(width); }).join(''))).join
}; const proportion = (max, val) => Math.round(val * 100 / max); co
calcProportion = table => { table.sort((row1, row2) => row2[DENSITY
row1[DENSITY_COL]); const maxDensity = table[0][DENSITY_COL]; table
forEach(row => { row.push(proportion(maxDensity, row[DENSITY_COL]))
return table; }; const getDataset = file => { const lines = fs.read
FileSync(file, 'utf8').toString().split('n'); lines.shift(); lines
return lines.map(line => line.split(',')); }; const main = compose
(getDataset, calcProportion, renderTable); const fs = require('fs'
compose = (...funcs) => x => funcs.reduce((x, fn) => fn(x), x); con
DENSITY_COL = 3; const renderTable = table => { const cellWidth = [
8, 8, 18, 6]; return table.map(row => (row.map((cell, i) => { const
= cellWidth[i]; return i ? cell.toString().padStart(width) : cell.p
(width); }).join(''))).join('n'); }; const proportion = (max, val)
How are Race Conditions in
single threaded JS possible?
Timur Shemsedinov
github.com/tshemsedinov
Chief Software Architect at Metarhia
Lecturer at Kiev Polytechnic Institute
- 2. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Concurrency Problems
● Race condition
● Deadlock
● Livelock
● Resource starvation
● Resource leaks
- 3. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Solutions
javascruptissinglethreaded
- 4. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Solutions
nodejsissinglethreaded
- 5. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Solutions
Promises
async/await
- 6. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Concurrent Computing
- 7. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Race Condition
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
async move(dx, dy) {
this.x = await add(this.x, dx);
this.y = await add(this.y, dy);
}
}
- 8. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Race Condition
const random = (min, max) => Math
.floor(Math.random() * (max - min + 1)) + min;
const add = (x, dx) => new Promise(resolve => {
setTimeout(() => {
resolve(x + dx);
}, random(20, 100));
});
- 9. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Race Condition
const p1 = new Point(10, 10);
console.log(p1);
p1.move(5, 5);
p1.move(6, 6);
p1.move(7, 7);
p1.move(8, 8);
setTimeout(() => {
console.log(p1);
}, 1000);
- 10. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Race Condition
Initial
Point { x: 10, y: 10 }
Expected
Point { x: 36, y: 36 }
Actual
Point { x: 18, y: 25 }
- 11. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Race Condition
- 12. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Possible Solutions
● Synchronization
● Resource locking
● Special control flow organization
● Queuing theory
● Actor model
● Use DBMS transactions
- 13. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Synchronization Primitives
Semaphore
Binary semaphore
Counting semaphore
Condition variable
Spinlock
Mutex
Timed mutex
Shared mutex
Recursive mutex
Monitor
Barrier
- 14. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Semaphore
class Semaphore {
constructor()
enter(callback)
leave()
}
semaphore.enter(() => {
// do something
semaphore.leave();
});
github.com/HowProgrammingWorks/Semaphore
class Semaphore {
constructor()
async enter()
leave()
}
await semaphore.enter();
// do something
semaphore.leave();
- 15. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Mutex
class Mutex {
constructor()
async enter()
leave()
}
await mutex.enter();
// do something with shared resources
mutex.leave();
https://github.com/HowProgrammingWorks/Mutex
- 16. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Resource Locking
class Lock {
constructor() {
this.active = false;
this.queue = [];
}
leave() {
if (!this.active) return;
this.active = false;
const next = this.queue.pop();
if (next) next();
}
}
enter() {
return new Promise(resolve => {
const start = () => {
this.active = true;
resolve();
};
if (!this.active) {
start();
return;
}
this.queue.push(start);
});
}
- 17. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Resource Locking
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
this.lock = new Lock();
}
async move(dx, dy) {
await this.lock.enter();
this.x = await add(this.x, dx);
this.y = await add(this.y, dy);
this.lock.leave();
}
}
- 18. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Resource Locking
const p1 = new Point(10, 10);
console.log(p1);
p1.move(5, 5);
p1.move(6, 6);
p1.move(7, 7);
p1.move(8, 8);
setTimeout(() => {
console.log(p1);
}, 1000);
- 19. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Resource Locking
Initial
Point { x: 10, y: 10 }
Expected
Point { x: 36, y: 36 }
Actual
Point { x: 36, y: 36 }
- 20. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Resource Locking
- 21. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Asynchronous Res Locks
- 22. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Flow Commutation
const fx = metasync(
[f1, f2, f3, [[f4, f5, [f6, f7], f8]], f9]
);
like in electronics
- 23. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Real-life Example
Warehouse API
● Check balances
● Ship goods
● Lock balances
github.com/HowProgrammingWorks/RaceCondition
- 24. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Web Locks API
locks.request('resource', opt, async lock => {
if (lock) {
// critical section for `resource`
// will be released after return
}
});
https://wicg.github.io/web-locks/
- 25. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Web Locks: await
(async () => {
await something();
await locks.request('resource', async lock => {
// critical section for `resource`
});
await somethingElse();
})();
- 26. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Web Locks: Promise
locks.request('resource', lock => new Promise(
(resolve, reject) => {
// you can store or pass
// resolve and reject here
}
));
- 27. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Web Locks: Thenable
locks.request('resource', lock => ({
then((resolve, reject) => {
// critical section for `resource`
// you can call resolve and reject here
})
}));
- 28. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Web Locks: Abort
const controller = new AbortController();
setTimeout(() => controller.abort(), 200);
const { signal } = controller;
locks.request('resource', { signal }, async lock => {
// lock is held
}).catch(err => {
// err is AbortError
});
- 29. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Web Locks for Node.js
github.com/nodejs/node/issues/22702
Open
github.com/nodejs/node/pull/22719
Closed
- 30. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Safe data structures
● Low-level structures
e.g. Register, Counter, Buffer, Array, Lists, etc.
● Abstract structures
e.g. Queue, Graph, Polyline, etc.
● Subject-domain classes
e.g. Sensors, Payment, Biometric data, etc.
● Resources and handles
e.g. Sockets, Connections, Streams, etc.
- 31. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Deadlock
(async () => {
await locks.request('A', async lock => {
await locks.request('B', async lock => {
});
});
})(); (async () => {
await locks.request('B', async lock => {
await locks.request('A', async lock => {
});
});
})();
- 32. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Livelock
State1 State2
State3 State4
- 33. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Alternative Solutions
● Thread safe data structures
● Lock-free data structures
● Wait-free algorithms
● Conflict-free data structures
- 34. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Links
habr.com/ru/post/452974/
github.com/HowProgrammingWorks/RaceCondition
github.com/HowProgrammingWorks/Semaphore
github.com/HowProgrammingWorks/Mutex
github.com/metarhia/metasync
wicg.github.io/web-locks
- 35. const fs = require('fs'); const compose = (...funcs) => x => funcs.
reduce((x, fn) => fn(x), x); const DENSITY_COL = 3; const renderTab
table => { const cellWidth = [18, 10, 8, 8, 18, 6]; return table.ma
=> (row.map((cell, i) => { const width = cellWidth[i]; return i ? c
Thanks
github.com/tshemsedinov
youtube.com/TimurShemsedinov
github.com/HowProgrammingWorks
timur.shemsedinov@gmail.com