{"record":{"id":"968e76e5cb8e86bc","repo":"actix/actix-web","slug":"failed-to-build-hickory-dns-resolver","errorCode":null,"errorMessage":"failed to build Hickory DNS resolver","messagePattern":"failed to build Hickory DNS resolver","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"awc/src/client/connector.rs","lineNumber":1108,"sourceCode":"        }\n\n        // get from thread local or construct a new hickory dns resolver.\n        HICKORY_DNS_RESOLVER.with(|local| {\n            local\n                .get_or_init(|| {\n                    let (cfg, opts) = match read_system_conf() {\n                        Ok((cfg, opts)) => (cfg, opts),\n                        Err(err) => {\n                            log::error!(\"Hickory DNS can not load system config: {err}\");\n                            (ResolverConfig::default(), ResolverOpts::default())\n                        }\n                    };\n\n                    let resolver =\n                        TokioResolver::builder_with_config(cfg, TokioRuntimeProvider::default())\n                            .with_options(opts)\n                            .build()\n                            .expect(\"failed to build Hickory DNS resolver\");\n\n                    Resolver::custom(HickoryDnsResolver(resolver))\n                })\n                .clone()\n        })\n    }\n}\n\n#[cfg(feature = \"dangerous-h2c\")]\n#[cfg(test)]\nmod tests {\n    use std::convert::Infallible;\n\n    use actix_http::{HttpService, Request, Response, Version};\n    use actix_http_test::test_server;\n    use actix_service::ServiceFactoryExt as _;\n\n    use super::*;","sourceCodeStart":1090,"sourceCodeEnd":1126,"githubUrl":"https://github.com/actix/actix-web/blob/4d435abc281842f3cbee165b6cde739e001d3a25/awc/src/client/connector.rs#L1090-L1126","documentation":"This is a panic (via .expect) inside awc's resolver module when the `hickory-dns` Cargo feature is enabled. The resolver is constructed lazily in a thread-local OnceCell (connector.rs:1088-1093); on first use per thread it calls `TokioResolver::builder_with_config(cfg, opts).build().expect(\"failed to build Hickory DNS resolver\")` (connector.rs:1104-1108). Note the code already handles a failed read_system_conf() by falling back to `ResolverConfig::default()` (connector.rs:1096-1102), so the panic is specifically from TokioResolver.build() failing — i.e. an error building the async resolver from a (possibly defaulted) config, not from parsing resolv.conf. The panic therefore indicates a lower-level runtime/provider/IO problem at resolver construction.","triggerScenarios":"Enabling awc's `hickory-dns` (or the deprecated `trust-dns`) feature and performing the first DNS resolution on a given worker thread, which triggers the thread-local OnceCell initialization and the TokioResolver::build() call. The panic surfaces as soon as a client tries to resolve a hostname and the cached resolver does not yet exist on that thread. read_system_conf failure is tolerated, so the trigger is build() itself returning Err.","commonSituations":"Containers where the tokio runtime provider for hickory cannot function correctly (missing network namespaces, restricted syscalls); version incompatibilities between hickory-resolver and the tokio runtime (e.g. awc's hickory-resolver 0.26.1 pinned against an incompatible tokio); misconfigured or absent /etc/resolv.conf combined with a build() that still errors; async runtimes not backed by tokio (hickory's TokioRuntimeProvider requires a tokio context); extremely locked-down environments where resolver background tasks cannot spawn.","solutions":["Disable the `hickory-dns` (and legacy `trust-dns`) feature and use awc's default resolver (getaddrinfo-based Resolver::default()), which has no build() step and no extra runtime dependency. This removes the panic entirely.","Ensure the process runs inside a tokio multi-thread runtime (the hickory TokioRuntimeProvider requires it); if you use a single-threaded/current-thread runtime, switch to the default resolver or a proper tokio runtime.","Pin compatible versions: keep hickory-resolver at the version awc expects (0.26.1) and ensure tokio is the version hickory 0.26 was built against; resolve Cargo.lock conflicts via `cargo update -p hickory-resolver`.","If you must keep hickory-dns, provide a valid /etc/resolv.conf and verify the resolver can be built outside awc first (see validationCode) to isolate whether the failure is environmental.","As a last resort, supply a custom Resolver (Resolver::custom(...)) to awc::Connector so the hickory build path is never taken."],"exampleFix":"# before: awc depends on hickory for DNS\nawc = { version = \"4\", features = [\"hickory-dns\"] }\n\n# after: use the default getaddrinfo resolver (no hickory, no panic path)\nawc = { version = \"4\" }\n\n# or, in code, avoid the default hickory resolver by passing a custom one:\n# let connector = awc::Connector::new().resolver(my_resolver);","handlingStrategy":"fallback","validationCode":"// Validate that a hickory TokioResolver can be built BEFORE awc tries to,\n// by attempting the same construction read_system_conf + build() sequence.\n// Run this at startup when the `hickory-dns` feature is enabled.\nuse hickory_resolver::{\n    config::{ResolverConfig, ResolverOpts},\n    system_conf::read_system_conf,\n    TokioResolver, async_resolver::TokioRuntimeProvider,\n};\n\nfn hickory_builds_ok() -> bool {\n    let (cfg, opts) = read_system_conf()\n        .unwrap_or((ResolverConfig::default(), ResolverOpts::default()));\n    TokioResolver::builder_with_config(cfg, TokioRuntimeProvider::default())\n        .with_options(opts)\n        .build()\n        .is_ok()\n}\n\nif !hickory_builds_ok() {\n    panic!(\"hickory DNS resolver cannot be built; disable the hickory-dns feature or fix the runtime\");\n}","typeGuard":"// No type guard: feature-gated and panics at runtime inside a thread-local.\n// Detect via the standalone build() probe above instead.\n// null","tryCatchPattern":"// awc's resolver() panics inside a thread-local OnceCell; you cannot catch it\n// directly because it runs lazily on first DNS use. Prevent it by constructing\n// the resolver yourself and passing a custom Resolver, so the panic path is\n// never taken:\nuse actix_tls::connect::Resolver;\nuse std::panic;\n\nlet client = match panic::catch_unwind(|| {\n    // verify hickory builds; if it panics, fall back to default GAI resolver\n    awc::Client::new()\n}) {\n    Ok(c) => c,\n    Err(_) => {\n        // explicit default resolver avoids the hickory code path entirely\n        awc::Client::builder()\n            .connector(awc::Connector::new()) // Resolver::default(), no hickory\n            .finish()\n    }\n};\n// Best practice: do NOT enable the hickory-dns feature unless you need it.","preventionTips":["Do not enable `hickory-dns`/`trust-dns` unless you specifically need it; the default getaddrinfo resolver has no build() panic path.","Ensure the process runs on a tokio multi-thread runtime; hickory's TokioRuntimeProvider requires it.","Keep hickory-resolver at the version awc expects (0.26.1) and resolve Cargo.lock version conflicts.","At startup, run the standalone build() probe (see validationCode) to fail fast with a clear message.","If you ship to locked-down/container environments, prefer the default resolver or pass an explicit Resolver::custom to awc::Connector to bypass the hickory thread-local entirely."],"tags":["awc","dns","hickory","resolver","tokio","panic","startup"],"backgroundTag":null,"analyzedSha":"4d435abc281842f3cbee165b6cde739e001d3a25","analyzedAt":"2026-08-09T01:01:40.926Z","contentChangedAt":"2026-08-09T01:01:40.926Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}