Hey all! I have a suspend function and am trying ...
# coroutines
n
Hey all! I have a suspend function and am trying to implement thread safeness with Mutex
Copy code
private suspend fun refreshAccounts() {
    if (!mutex.isLocked) {
        mutex.withLock {
            try {
                action()
            } catch (e: IllegalStateException) {
                Timber.e(e)
            }
        }
    }
}
How can I unit test this behavior? I did this
Copy code
launch { repository.subscribeToAccounts().test {expectMostRecentItem() } }
launch { repository.subscribeToAccounts().test {expectMostRecentItem() } }
launch { repository.subscribeToAccounts().test {expectMostRecentItem() } }

verify(accountsApi, times(1)).getAccounts()
but
accountsApi
is called 3 times not 1
p
What if you change from
Copy code
if (!mutex.isLocked) {mutex.withLock{ ...
To
Copy code
if (mutex.tryLock()) {
  try{...} finally{mutex.unlock()}
}
In your case several coroutines can check lock state and jump to withLock. Because both statements 'if' and 'withLock' is not atomic operation.
Anyway i dont understand what you are going to acheave in your test :)
n
Thanks changed it but my tests are still failing. What is the best way to unit test concurrency?
I am trying to ensure that
action()
is called once not couple of times when accessed from different coroutines at a time
p
I think mutex is well tested by itself. But if you want you can make global variable and increment it in withMutex block, then delay for some time and after check that it is the same value as before delay. And run it for several coroutines. If i'm correctly understand you.
n
what is
withMutex
block? Is it in
coroutines-test
artifact?
p
Or may be you want other coroutines to wait for action complete for first one?
n
yeah 🙂
p
If yes then try like this:
Copy code
mutex.withLock {
            if (!someVariableWithActionExecutedFlag) { // declare this variable in the same scope as mutex
                  action()
                  someVariableWithActionExecutedFlag = true 
                }
        }
n
but other coroutines shouldn’t that action in any case as it is a remote api call but only the first one should call
p
This example is doing like this
👀 1
n
thanks! what is the difference between this and your previous suggestion?
p
In previous suggestion other coroutines don't wait for first one complete action.
If they can't lock mutex they continue immediatelly skipping action wait
n
I see. So
mutex.unlock
should be deleted then right?
p
Yes. Just use 'withLock' as in example. It doing all the work capturing and after releasing mutex
n
thanks for bearing with me 🙏 I will apply your suggestions
👍 1
last question sorry 😄 do I need
try catch
inside
withLock
because mutex may throw exception if it is locked and tried to be accessed with another coroutines?
p
To be clear how withLock is working, here it's source code (without contracts for simplicity) :
Copy code
public suspend inline fun <T> Mutex.withLock(owner: Any? = null, action: () -> T): T {
    lock(owner)
    return try {
        action()
    } finally {
        unlock(owner)
    }
}
unlock is in finally block, so it will be unlocked in any scenario. But you have to think how you will handle it for your action. Should you mark action as executed in this case or throw exception for every coroutines, or retry action ...
👍 1