Repository: google/filament
File: filament/backend/src/vulkan/VulkanAsyncHandles.cpp (C++)

Code before:
        if (mStatus == VK_SUCCESS) {
            return FenceStatus::CONDITION_SATISFIED;
        }

        // Or it could have been canceled, return immediately
        if (mStatus == VK_ERROR_UNKNOWN) {
            return FenceStatus::ERROR;
        }
    }

    // If we're here, we know that vkQueueSubmit has been called (because it sets the status
    // to VK_NOT_READY).
    // Now really wait for the fence while holding the shared_lock, this allows several
    // threads to call vkWaitForFences(), but will prevent vkResetFence from taking
    // place simultaneously. vkResetFence is only called once it knows the fence has signaled,
    // which guaranties that vkResetFence won't have to wait too long, just enough for
    // all the vkWaitForFences() to return.
    VkResult const status = vkWaitForFences(device, 1, &mFence, VK_TRUE, timeout);
    if (status == VK_TIMEOUT) {
        return FenceStatus::TIMEOUT_EXPIRED;
    }

    if (status == VK_SUCCESS) {
        std::lock_guard const l(mLock);
        mStatus = status;
        return FenceStatus::CONDITION_SATISFIED;
    }

    return FenceStatus::ERROR; // not supported
}

void VulkanCmdFence::resetFence(VkDevice device) {
    // This lock prevents vkResetFences() from being called simultaneously with vkWaitForFences(),
    // but by construction, when we're here, we know that the fence has signaled and
    // vkWaitForFences() will return shortly.
    std::lock_guard const l(mLock);
    assert_invariant(mStatus == VK_SUCCESS);
    vkResetFences(device, 1, &mFence);
}

} // namespace filament::backend


Review comment (bug):
There might be a race condition here you may want to avoid.

e.g., if cancel() is called while the other thread is blocked in vkWaitForFences(), and it returns VK_SUCCESS, the cancel() call, which was waiting for this lock to be freed, may squeeze in between `rl.unlock();` and `std::lock_guard const wl(mLock);`. This wil ignore the cancellation request.

If the cancel() should be honored. I think this block should check if a cancellation was issued after the lock_guard, something like

```
        if (mCanceled) {
            return FenceStatus::ERROR;
        }
        mStatus = status;
        return FenceStatus::CONDITION_SATISFIED;
```

Code after:
        return FenceStatus::CONDITION_SATISFIED;
    }

    // Or it could have been canceled, return immediately
    if (mCanceled) {
        return FenceStatus::ERROR;
    }

    // If we're here, we know that vkQueueSubmit has been called (because it sets the status
    // to VK_NOT_READY).
    // Now really wait for the fence while holding the shared_lock, this allows several
    // threads to call vkWaitForFences(), but will prevent vkResetFence from taking
    // place simultaneously. vkResetFence is only called once it knows the fence has signaled,
    // which guaranties that vkResetFence won't have to wait too long, just enough for
    // all the vkWaitForFences() to return.
    VkResult const status = vkWaitForFences(device, 1, &mFence, VK_TRUE, timeout);
    if (status == VK_TIMEOUT) {
        return FenceStatus::TIMEOUT_EXPIRED;
    }

    if (status == VK_SUCCESS) {
        rl.unlock();
        std::lock_guard const wl(mLock);
        mStatus = status;
        return FenceStatus::CONDITION_SATISFIED;
    }

    return FenceStatus::ERROR; // not supported
}

void VulkanCmdFence::resetFence(VkDevice device) {
    // This lock prevents vkResetFences() from being called simultaneously with vkWaitForFences(),
    // but by construction, when we're here, we know that the fence has signaled and
    // vkWaitForFences() will return shortly.
    std::lock_guard const l(mLock);
    assert_invariant(mStatus == VK_SUCCESS);
    vkResetFences(device, 1, &mFence);
}

} // namespace filament::backend