-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Release the asset infos lock before acquiring the file transaction lock. #22017
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it ok to not make the two “operations" (the modifying of asset_infos and the actual removal/writing over of each asset) in handle_removed_asset and handle_renamed_asset atomic with respect to the locking? Now hypothetically both operations could wait after modifying asset_infos. I assume it’s fine but I just wanted to make sure.
Unfortunately for testing, my computer never came up with the deadlock on main, so I can neither confirm nor deny that the code fixes it in reality, but from my reading, it would fix it in theory.
I also have a clarifying question, as someone more unfamiliar with the asset code. You described that there is a situation where a thread (I presume when going through the function process_asset_internal?):
- locks the asset_infos
- locks the asset specific lock
- unlocks the asset_infos (these first three steps seem to correspond what’s happening in
process_asset_internal)
Is the 4th step that the same thread eventually wants to lock asset_infos again, causing an inversion of the locking order with other threads (hence deadlock)? If so, where/how exactly does that happen?
Beyond that, I don’t see any mismatch between what you wrote in code and the way you described the solution in the description, so in that respect it looks good!
In theory we could have specifically the remove and rename still lock asset_infos and then also lock the files. However this means blocking all other access to the files while we wait for access to the old and new assets. I'd prefer not blocking *all access just for that.
If I just run on main, the deadlock is very rare. Once I enabled logging, the deadlock was much more common. As an aside, we should probably just enable logging for bevy_asset tests in general - it makes it impossible to debug these issues otherwise.
Yes exactly! "Inversion of the locking order" is a much better way to describe it too haha. This happens through nested immediate asset loading. The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am embarrassed to say how much time I spent trying to understand the specific details and mechanism surrounding the deadlocking, but I think I understand it enough from a high level enough to approve this (Knowing that there is a double processing of the same asset is key!). At the very least, the locking strategy here should not result in any hypothetical deadlocks since afaict the two locks in question, the asset_infos lock and the per asset lock, are not going to be held together anymore.
I also turned on debugging and still couldn’t get a deadlock after a couple minutes! Maybe I just have a special mac laptop. Turning on debugging sounds good for this crate with all this multi-threaded business going on, but I’m unsure what that means for verbosity / if people would be bothered by it / if that’s tweakable.
Probably out of scope comment: I noticed in the function wait_until_processed in this file, asset_infos is being locked for a write Line 1353, but it’s only being read from. Is that intentional? Seems like an oversight
|
I spent 5 days staring at this code and putting traces everywhere I could think of, so I deeply understand being confused here haha. Thank you for taking the time to understand it!
Yeah I noticed this too, but decided not to mess with it for now. I also suspect it's a mistake. I'll leave that for another PR though. |
This comment was marked as resolved.
This comment was marked as resolved.
|
At the end of the day, the "source of truth" is the asset source, not the transaction lock. So it's totally valid for us to lock the transaction lock, and for the This did make me realize we should probably also delete the processed asset path if we fail so we don't leave a corrupted file around. But that's a future PR. |
Objective
asset_infos, then lock one of the assets withinasset_infosand then dropped theasset_infoslock. This means however that if a process needs to accessasset_infos(which is necessary for loading nested assets during processing), we'll have one thread trying to lock 1) asset_infos, 2) per-asset lock, and we'll have another thread trying to lock 1) per-asset lock (we've already dropped the asset_infos lock), 2) asset_infos. A classic deadlock!Solution
Arc<RwLock>out of theasset_infos, drop theasset_infos, and only then lock the per-asset lock. This ensures that we never hang on theasset_infoslock when just trying to fetch a single asset.asset_infos"short" - they get what they need out ofasset_infosand then drop the lock as soon as possible.?to cleanup some methods inProcessorAssetInfos, where previously it was "remove the asset info, then have one bigif letafter it". Now we just return early if the value is none.Testing
only_reprocesses_wrong_hash_on_startuptest quite quickly (after a minute or so). With this fix, I now only get the assertion failure problem. If I also skip that assertion, the test hasn't flaked for a while! Yay, no more deadlock!