6d2eaf93cb
The mutex will prevent duplicate functions from being called and throwing funky errors.
111 lines
3.6 KiB
TypeScript
111 lines
3.6 KiB
TypeScript
import { strict as assert } from 'assert'
|
|
import { Mutex } from '../WAConnection/Mutex'
|
|
|
|
const DEFAULT_WAIT = 1000
|
|
|
|
class MyClass {
|
|
didDoWork = false
|
|
values: { [k: string]: number } = {}
|
|
counter = 0
|
|
|
|
@Mutex ()
|
|
async myFunction () {
|
|
if (this.didDoWork) return
|
|
|
|
await new Promise (resolve => setTimeout(resolve, DEFAULT_WAIT))
|
|
if (this.didDoWork) {
|
|
throw new Error ('work already done')
|
|
}
|
|
this.didDoWork = true
|
|
}
|
|
@Mutex (key => key)
|
|
async myKeyedFunction (key: string) {
|
|
if (!this.values[key]) {
|
|
await new Promise (resolve => setTimeout(resolve, DEFAULT_WAIT))
|
|
if (this.values[key]) throw new Error ('value already set for ' + key)
|
|
this.values[key] = Math.floor(Math.random ()*100)
|
|
}
|
|
return this.values[key]
|
|
}
|
|
@Mutex (key => key)
|
|
async myQueingFunction (key: string) {
|
|
await new Promise (resolve => setTimeout(resolve, DEFAULT_WAIT))
|
|
}
|
|
@Mutex ()
|
|
async myErrorFunction () {
|
|
await new Promise (resolve => setTimeout(resolve, 100))
|
|
this.counter += 1
|
|
if (this.counter % 2 === 0) {
|
|
throw new Error ('failed')
|
|
}
|
|
}
|
|
}
|
|
|
|
describe ('garbage', () => {
|
|
it ('should only execute once', async () => {
|
|
const stuff = new MyClass ()
|
|
const start = new Date ()
|
|
await Promise.all ([...Array(1000)].map(() => stuff.myFunction ()))
|
|
const diff = new Date ().getTime()-start.getTime()
|
|
assert.ok (diff < DEFAULT_WAIT*1.25)
|
|
})
|
|
it ('should only execute once based on the key', async () => {
|
|
const stuff = new MyClass ()
|
|
const start = new Date ()
|
|
/*
|
|
In this test, the mutex will lock the function based on the key.
|
|
|
|
So, if the function with argument `key1` is underway
|
|
and another function with key `key1` is called,
|
|
the call is blocked till the first function completes.
|
|
However, if argument `key2` is passed, the function is allowed to pass.
|
|
*/
|
|
const keys = ['key1', 'key2', 'key3']
|
|
const duplicates = 1000
|
|
const results = await Promise.all (
|
|
keys.flatMap (key => (
|
|
[...Array(duplicates)].map(() => stuff.myKeyedFunction (key))
|
|
))
|
|
)
|
|
assert.deepEqual (
|
|
results.slice(0, duplicates).filter (r => r !== results[0]),
|
|
[]
|
|
)
|
|
|
|
const diff = new Date ().getTime()-start.getTime()
|
|
assert.ok (diff < DEFAULT_WAIT*1.25)
|
|
})
|
|
it ('should execute operations in a queue', async () => {
|
|
const stuff = new MyClass ()
|
|
const start = new Date ()
|
|
|
|
const keys = ['key1', 'key2', 'key3']
|
|
|
|
await Promise.all (
|
|
keys.flatMap (key => (
|
|
[...Array(2)].map(() => stuff.myQueingFunction (key))
|
|
))
|
|
)
|
|
|
|
const diff = new Date ().getTime()-start.getTime()
|
|
assert.ok (diff < DEFAULT_WAIT*2.2 && diff > DEFAULT_WAIT*1.5)
|
|
})
|
|
it ('should throw an error on selected items', async () => {
|
|
const stuff = new MyClass ()
|
|
const start = new Date ()
|
|
|
|
const WAIT = 100
|
|
const FUNCS = 40
|
|
const results = await Promise.all (
|
|
[...Array(FUNCS)].map(() => stuff.myErrorFunction ().catch(err => err.message))
|
|
)
|
|
|
|
const diff = new Date ().getTime()-start.getTime()
|
|
assert.ok (diff < WAIT*FUNCS*1.1)
|
|
|
|
assert.equal (
|
|
results.filter (r => r === 'failed').length,
|
|
FUNCS/2 // half should fail
|
|
)
|
|
})
|
|
}) |