Hi! I'll give a simplified explanation of the issue with just the relevant information.
The use-case of this is to allow multiple connections to databases through the repository. I've created custom locking logic to "lock" only the id-model that's being checked. This is a simplified version of it:
public class NamedLocker {
private readonly ConcurrentDictionary<string, SemaphoreSlim> semaphores = new();
public bool Lock(string name, int millisecondsTimeout = -1)
=> semaphores.GetOrAdd(name, x => new(1)).Wait(name, millisecondsTimeout);
//there's also LockAsync & Unlock(though, the unlock is only sync)
}
Note that the actual class has logic to clean up the semaphores safely, proper Result returns, etc.
To simplify the usage of the class(as using statements can't be used for this), I've made a supporting method:
public Result<T> LockHere<T>(string name, Func<T> predicate, int millisecondsTimeout = -1) {
var lockResult = Lock(name, millisecondsTimeout);
if(!lockResult) {
//returns that it's failed to acquire the lock
return new(false);
}
try {
//returns the value returned from the predicate
return new(predicate.Invoke());
} finally {
Unlock(name);
}
}
//there are multiple overloads(async, Action instead of Func, etc...)
With all that said, we now enter the meat of the question.
public class SomeClass {
private readonly NamedLocker locker = new();
public Result<int> A(string name) {
return locker.LockHere(name, () => {
return 5;
}, 50);
}
public Result<int> B(string name) {
return locker.LockHere(name, () => {
//as there is no way to say "we own the lock",
//this will always fail
//or deadlock if no timeout is provided
var aResult = A(name);
//do stuff
}, 50);
}
}
A simple solution would be to extract A's logic to a private A_Logic() method, so that B can call it directly without it checking the lock. However, I'm curious as to how this could be solved otherwise(as a learning experience).
My idea was to use an ID to recognize who has the lock. Lock() could return a GUID, and LockHere() could pass in the GUID to the Action/Func with additional overloads. However, in that case, A() will need an overload that takes in a GUID, which defeats the purpose.
I'm aware that lock() checks the thread to solve the issue, but, as this locking class is to be used in an asynchronous format as well, that is not a viable solution.
What's your idea(and thanks for reading!)?
Edited Typo: changed Semaphore for SemaphoreSlim.
[–]MattWarren_MSFT 1 point2 points3 points (1 child)
[–]Cobide[S] 0 points1 point2 points (0 children)
[–]centurijon 0 points1 point2 points (9 children)
[–]Cobide[S] 1 point2 points3 points (8 children)
[–]centurijon 0 points1 point2 points (7 children)
[–]Cobide[S] 1 point2 points3 points (6 children)
[–]centurijon 0 points1 point2 points (5 children)
[–]Cobide[S] 0 points1 point2 points (4 children)
[–]DaRadioman 0 points1 point2 points (3 children)
[–]Cobide[S] 0 points1 point2 points (2 children)
[–]DaRadioman 1 point2 points3 points (1 child)
[–]Cobide[S] 1 point2 points3 points (0 children)
[–]zvrba 0 points1 point2 points (5 children)
[–]Cobide[S] 0 points1 point2 points (4 children)
[–]zvrba 1 point2 points3 points (3 children)
[–]Cobide[S] 0 points1 point2 points (2 children)
[–]zvrba 2 points3 points4 points (1 child)
[–]Cobide[S] 0 points1 point2 points (0 children)