quickwit-oss/quickwit · error

read-only

Error message

read-only

What it means

The read_only_directory! macro in quickwit-directories provides a default `atomic_write` implementation for read-only tantivy Directory wrappers (e.g. StorageDirectory, UnionDirectory). Calling atomic_write on such a directory always aborts with unimplemented!("read-only") — writes are structurally impossible by design on these directories.

Source

Thrown at quickwit/quickwit-directories/src/lib.rs:44

mod bundle_directory;
mod caching_directory;
mod debug_proxy_directory;
mod hot_directory;
mod storage_directory;
mod union_directory;

pub use self::bundle_directory::{BundleDirectory, get_hotcache_from_split, read_split_footer};
pub use self::caching_directory::CachingDirectory;
pub use self::debug_proxy_directory::{DebugProxyDirectory, ReadOperation};
pub use self::hot_directory::{HotDirectory, write_hotcache};
pub use self::storage_directory::StorageDirectory;
pub use self::union_directory::UnionDirectory;

macro_rules! read_only_directory {
    () => {
        fn atomic_write(&self, _path: &Path, _data: &[u8]) -> io::Result<()> {
            unimplemented!("read-only")
        }

        fn delete(&self, _path: &Path) -> Result<(), tantivy::directory::error::DeleteError> {
            unimplemented!("read-only")
        }

        fn open_write(
            &self,
            _path: &Path,
        ) -> Result<tantivy::directory::WritePtr, tantivy::directory::error::OpenWriteError> {
            unimplemented!("read-only")
        }

        fn sync_directory(&self) -> io::Result<()> {
            unimplemented!("read-only")
        }

        fn watch(

View on GitHub (pinned to a39730c5cd)

Solutions

  1. Use a writable directory (e.g. ManagedDirectory with its storage, or a temp-dir directory) for any code path that writes.
  2. Restructure the code so read-only directories are only used for open/read operations.
  3. If you only need an in-memory write target, wrap with caching/BoundingBoxDirectory semantics or RAMDirectory.
Defensive patterns

Strategy: type-guard

Validate before calling

// ensure the directory supports writes before calling:
fn supports_write<D: Directory>(dir: &D) -> bool {
    std::any::type_name_of_val(dir).contains("StorageDirectory") == false // read-only macro users
}

Type guard

trait WritableDirectory: Directory {}
fn require_writable(dir: &dyn Directory) -> Option<&dyn WritableDirectory> {
    // narrow via downcast to the managed/writable impl
    dir.as_any().downcast_ref::<ManagedDirectory>().map(|d| d as _)
}

Try / catch

// unimplemented! panics; cannot be caught as an io::Error. Guard by design:
if writable_dir_for(path).is_none() { bail!("directory is read-only"); }

Prevention

When it happens

Trigger: Any code path that attempts `directory.atomic_write(path, data)` on a directory created via read_only_directory! (e.g. a StorageDirectory over S3 used for search).

Common situations: Tantivy trying to write hotspot metadata or a deleted-bitset during a search on a read-only split; accidentally pointing a component that needs writes at a read-only directory implementation.

Related errors


AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08). Data as JSON: /api/errors/7a3717f65fe640a3. Report an issue: GitHub.