Fix AssetServer::get_asset_loader deadlock (#2395)

# Objective

Fixes a possible deadlock between `AssetServer::get_asset_loader` / `AssetServer::add_loader`

A thread could take the `extension_to_loader_index` read lock,
and then have the `server.loader` write lock taken in add_loader
before it can. Then add_loader can't take the extension_to_loader_index
lock, and the program deadlocks.

To be more precise:

## Step 1: Thread 1 grabs the `extension_to_loader_index` lock on lines 138..139:

3a1867a92e/crates/bevy_asset/src/asset_server.rs (L133-L145)

## Step 2: Thread 2 grabs the `server.loader` write lock on line 107:

3a1867a92e/crates/bevy_asset/src/asset_server.rs (L103-L116)

## Step 3: Deadlock, since Thread 1 wants to grab `server.loader` on line 141...:

3a1867a92e/crates/bevy_asset/src/asset_server.rs (L133-L145)

... and Thread 2 wants to grab 'extension_to_loader_index` on lines 111..112:

3a1867a92e/crates/bevy_asset/src/asset_server.rs (L103-L116)


## Solution

Fixed by descoping the extension_to_loader_index lock, since
`get_asset_loader` doesn't need to hold the read lock on the extensions map for the duration,
just to get a copyable usize. The block might not be needed,
I think I could have gotten away with just inserting a `copied()`
call into the chain, but I wanted to make the reasoning clear for
future maintainers.
This commit is contained in:
Theia Vogel 2021-07-06 17:35:16 +00:00
parent 337e6d5893
commit 85a10eccc5

View file

@ -134,11 +134,13 @@ impl AssetServer {
&self,
extension: &str,
) -> Result<Arc<Box<dyn AssetLoader>>, AssetServerError> {
self.server
.extension_to_loader_index
.read()
.get(extension)
.map(|index| self.server.loaders.read()[*index].clone())
let index = {
// scope map to drop lock as soon as possible
let map = self.server.extension_to_loader_index.read();
map.get(extension).copied()
};
index
.map(|index| self.server.loaders.read()[index].clone())
.ok_or_else(|| AssetServerError::MissingAssetLoader {
extensions: vec![extension.to_string()],
})