Repository: meilisearch/meilisearch
File: index-scheduler/src/index_mapper.rs (Rust)

Code before:
                let index_status = std::mem::replace(occupied.get_mut(), BeingDeleted);
                match index_status {
                    Available(index) => {
                        // signal that will be sent when the resize operation completes
                        let resize_operation = Arc::new(SignalEvent::manual(false));
                        // temporarily block further attempts to get the index
                        *(occupied.get_mut()) = BeingResized(resize_operation);
                        index
                    }
                    // the index is already being deleted or resized, so let's ignore this new resize request.
                    entry @ (BeingDeleted | BeingResized(_)) => {
                        // reset the index_status to what it was
                        *(occupied.get_mut()) = entry;
                        return Ok(());
                    }
                }
            }
            // the index could not be found, so let's ignore this new resize request.
            Entry::Vacant(_) => return Ok(()),
        };

        drop(lock);

        let current_size = index.map_size()?;
        let closing_event = index.prepare_for_closing();
        log::info!("Resizing index {} from {} to {} bytes", name, current_size, current_size * 2);

        closing_event.wait();

        log::info!("Resized index {} from {} to {} bytes", name, current_size, current_size * 2);

        let index_path = self.base_path.join(uuid.to_string());
        let index = self.create_or_open_index(&index_path, None, 2 * current_size)?;

        // Add back the resized index
        let mut lock = self.index_map.write().unwrap();
        match lock.insert(uuid, Available(index)) {
            Some(BeingResized(resize_operation)) => {
                // drop the lock before signaling completion so that other threads don't immediately await on the lock after waking up.
                drop(lock);
                resize_operation.signal();
                Ok(())
            }
            _ => {
                unreachable!("Index state was modified while it was being resized")
            }
        }
    }

    /// Return an index, may open it if it wasn't already opened.
    pub fn index(&self, rtxn: &RoTxn, name: &str) -> Result<Index> {

Review comment (bug):
Can we log that we are waiting for the index to be closed here instead? It would be easier to debug.

Code after:
        let index = match lock.insert(uuid, BeingResized(resize_operation)) {
            Some(Available(index)) => index,
            Some(previous_status) => {
                lock.insert(uuid, previous_status);
                panic!(
                    "Attempting to resize index {name} that is already being resized or deleted."
                )
            }
            None => {
                panic!("Could not find the status of index {name} in the in-memory index mapper.")
            }
        };

        drop(lock);

        let resize_succeeded = (move || {
            let current_size = index.map_size()?;
            let new_size = current_size * 2;
            let closing_event = index.prepare_for_closing();

            log::debug!("Waiting for index {name} to close");

            if !closing_event.wait_timeout(std::time::Duration::from_secs(600)) {
                // fail after 10 minutes waiting
                panic!("Could not resize index {name} (unable to close it)");
            }

            log::info!("Resized index {name} from {current_size} to {new_size} bytes");
            let index_path = self.base_path.join(uuid.to_string());
            let index = self.create_or_open_index(&index_path, None, new_size)?;
            Ok(index)
        })();

        // Put the map back to a consistent state.
        // Even if there was an error we don't want to leave the map in an inconsistent state as it would cause
        // deadlocks.
        let mut lock = self.index_map.write().unwrap();
        let (resize_operation, resize_succeeded) = match resize_succeeded {
            Ok(index) => {
                // insert the resized index
                let Some(BeingResized(resize_operation)) = lock.insert(uuid, Available(index)) else {
                    panic!("Index state for index {name} was modified while it was being resized")
                };

                (resize_operation, Ok(()))
            }
            Err(error) => {
                // there was an error, not much we can do... delete the index from the in-memory map to prevent future errors
                let Some(BeingResized(resize_operation)) = lock.remove(&uuid) else {
                                    panic!("Index state for index {name} was modified while it was being resized")
                                };