How To Lock On Object Which Shared By Multiple Async Method In Nodejs?
Solution 1:
I have done Locking using async-lock node module. Now I can achieve the goal which is mention in question.
Example:
varAsyncLock = require('async-lock');
var lock = newAsyncLock();
functionoperation1() {
console.log("Execute operation1");
lock.acquire("key1", function(done) {
console.log("lock1 enter")
setTimeout(function() {
console.log("lock1 Done")
done();
}, 3000)
}, function(err, ret) {
console.log("lock1 release")
}, {});
}
functionoperation2() {
console.log("Execute operation2");
lock.acquire("key1", function(done) {
console.log("lock2 enter")
setTimeout(function() {
console.log("lock2 Done")
done();
}, 1000)
}, function(err, ret) {
console.log("lock2 release")
}, {});
}
functionoperation3() {
console.log("Execute operation3");
lock.acquire("key1", function(done) {
console.log("lock3 enter")
setTimeout(function() {
console.log("lock3 Done")
done();
}, 1)
}, function(err, ret) {
console.log("lock3 release")
}, {});
}operation1(); operation2(); operation3();
Output:
Execute operation1
lock1 enter
Execute operation2
Execute operation3
lock1 Done
lock1 release
lock2 enter
lock2 Done
lock2 release
lock3 enter
lock3 Done
lock3 release
Solution 2:
I wrote this code to solve similar problem.
I think it is kind of classic DB transaction model. It implements only lock mechanism, not rollbacks. It works in following way:
- you adding bunch of sync methods in order in which they should be called
- these methods will be called in N milliseconds
- there will be only one get request to get DB data (or any other data that you need to modify) and one request to save DB data
- every method will receive reference to "up to date data", i.e. handler № 2 will receive data object that was changed by handler № 1. Order depends on order of № 1
It works in browsers. It should work in Node.js, but I didn't test it.
Note that because every method will receive reference to object, you should modify actual reference, and if you want to read it and return some value, then copy it, don't return that reference, because values of that reference may be changed in future by future handlers
TRANSACTION
will wait for N ms (defaults to 250) before execution. That defines which methods will be grouped into single transaction. You can also make instant calls.
Here is the code:
letTIMER_ID = 0;
letLOCK = Promise.resolve();
letRESOLVE_LOCK = undefined;
letFUNC_BUFFER = [];
letSUCCESS_BUFFER = [];
letFAIL_BUFFER = [];
/**
* Gets DB data.
*/asyncfunctionget() {
return {
key1: "value1",
key2: {
key3: "value2"
}
};
}
/**
* Sets new data in DB.
*/asyncfunctionset(value) {
return;
}
/**
* Classic database transaction implementation.
*
* You adding bunch of methods, every method will be
* executed with valid instance of data at call time,
* after all functions end the transaction will end.
* If any method failed, then entire transaction will fail
* and no changes will be written. If current transaction is
* active, new one will be not started until end of previous one.
*
* In short, this transaction have ACID properties.
*
* Operations will be auto grouped into separate transactions
* based on start timeout, which is recreated every time on
* operation call.
*
* @example
* ```
* // Without transaction:
* create("1", 123)
* update("1", 321)
* read("1") => 123 // because `update` is async
*
* // With transaction:
* create("1", 123)
* update("1", 321)
* read("1") => 321
*
* // Without transaction:
* create("v", true)
* update("v", false) // throws internal error,
* // "v" will be created
* read("v") => true // because `update` is async
* update("v", false) // will update because `update` is async
*
* // With transaction:
* create("v", true)
* update("v", false) // throws internal error,
* // entire transaction will throw error,
* // "v" will be not created
* read("v") => true // entire transaction will throw error
* update("v", false) // entire transaction will throw error
* ```
*
* @example
* ```
* // Transaction start
* create()
* update()
* update()
* remove()
* // Transaction end
*
* // Transaction start
* create()
* update()
* sleep(1000)
* // Transaction end
*
* // Transaction start
* update()
* remove()
* // Transaction end
* ```
*/constTRANSACTION = {
/**
* Adds function in transaction.
*
* NOTE:
* you shouldn't await this function, because
* it only represents transcation lock, not
* entire transaction end.
*
* @paramf
* Every function should only modify passed state, don't
* reassign passed state and not save changes manually!
* @paramonSuccess
* Will be called on entire transaction success.
* @paramonFail
* Will be called on entire transaction fail.
* @paramstartTimeout
* Passed `f` will be added in current transaction,
* and current transaction will be called after
* `startTimeout` ms if there will be no more `f` passed.
* Default value is recommended.
*/add: asyncfunction(
f,
onSuccess,
onFail,
startTimeout
) {
awaitLOCK;
window.clearTimeout(TIMER_ID);
FUNC_BUFFER.push(f);
if (onSuccess) {
SUCCESS_BUFFER.push(onSuccess);
}
if (onFail) {
FAIL_BUFFER.push(onFail);
}
if (startTimeout == null) {
startTimeout = 250;
}
TIMER_ID = window.setTimeout(() => {
TRANSACTION.start();
}, startTimeout);
console.debug("Added in transaction");
},
start: asyncfunction() {
LOCK = newPromise((resolve) => {
RESOLVE_LOCK = resolve;
});
console.debug("Transaction locked");
let success = true;
try {
awaitTRANSACTION.run();
} catch (error) {
success = false;
console.error(error);
console.warn("Transaction failed");
}
if (success) {
for (const onSuccess ofSUCCESS_BUFFER) {
try {
onSuccess();
} catch (error) {
console.error(error);
}
}
} else {
for (const onFail ofFAIL_BUFFER) {
try {
onFail();
} catch (error) {
console.error(error);
}
}
}
FUNC_BUFFER = [];
SUCCESS_BUFFER = [];
FAIL_BUFFER = [];
RESOLVE_LOCK();
console.debug("Transaction unlocked");
},
run: asyncfunction() {
const data = awaitget();
const state = {
value1: data.key1,
value2: data.key2
};
for (const f ofFUNC_BUFFER) {
console.debug("Transaction function started");
f(state);
console.debug("Transaction function ended");
}
awaitset({
key1: state.value1,
key2: state.value2
});
}
}
Example № 1:
/**
* Gets DB data.
*/asyncfunctionget() {
return {
key1: "value1",
key2: {
key3: "value2"
}
};
}
/**
* Sets new data in DB.
*/asyncfunctionset(value) {
console.debug("Will be set:", value);
return;
}
newPromise(
(resolve) => {
TRANSACTION.add(
(data) => {
data.value2.key3 = "test1";
},
() =>console.debug("success № 1")
);
TRANSACTION.add(
(data) => {
const copy = {
...data.value2
};
resolve(copy);
},
() =>console.debug("success № 2")
);
TRANSACTION.add(
(data) => {
data.value1 = "test10";
data.value2.key3 = "test2";
},
() =>console.debug("success № 3")
);
}
)
.then((value) => {
console.debug("result:", value);
});
/* Output:
Added in transaction
Added in transaction
Added in transaction
Transaction locked
Transaction function started
Transaction function ended
Transaction function started
Transaction function ended
Transaction function started
Transaction function ended
Will be set: {key1: 'test10', key2: {key3: 'test2'}}
result: {key3: 'test1'}
success № 1
success № 2
success № 3
Transaction unlocked
*/
Example № 2:
TRANSACTION.add(
() => {
console.log(1);
}
);
TRANSACTION.add(
() => {
console.log(2);
},
undefined,
undefined,
0// instant call
);
/* Output:
16:15:34.715 Added in transaction
16:15:34.715 Added in transaction
16:15:34.717 Transaction locked
16:15:34.717 Transaction function started
16:15:34.718 1
16:15:34.718 Transaction function ended
16:15:34.718 Transaction function started
16:15:34.718 2
16:15:34.718 Transaction function ended
16:15:34.719 Transaction unlocked
*/
Example № 3:
TRANSACTION.add(
() => {
console.log(1);
}
);
TRANSACTION.add(
() => {
console.log(2);
},
undefined,
undefined,
0// instant call
);
awaitnewPromise((resolve) => {
window.setTimeout(() => {
resolve();
}, 1000);
});
TRANSACTION.add(
() => {
console.log(3);
}
);
/* Output:
16:19:56.840 Added in transaction
16:19:56.840 Added in transaction
16:19:56.841 Transaction locked
16:19:56.841 Transaction function started
16:19:56.842 1
16:19:56.842 Transaction function ended
16:19:56.842 Transaction function started
16:19:56.842 2
16:19:56.842 Transaction function ended
16:19:56.842 Transaction unlocked
16:19:57.840 Added in transaction
16:19:58.090 Transaction locked
16:19:58.090 Transaction function started
16:19:58.090 3
16:19:58.091 Transaction function ended
16:19:58.091 Transaction unlocked
*/
Post a Comment for "How To Lock On Object Which Shared By Multiple Async Method In Nodejs?"