commit
7de15fa260
7 changed files with 167 additions and 42 deletions
|
|
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- `primop::RecoverableError` for primop errors that should not be memoized in the thunk, allowing retry on next force. Required by Nix >= 2.34 ([release note](https://nix.dev/manual/nix/2.34/release-notes/rl-2.34.html#c-api-changes)) for recoverable errors to remain recoverable, as Nix 2.34 memoizes errors by default.
|
||||
|
||||
## [0.2.0] - 2026-01-13
|
||||
|
||||
### Added
|
||||
|
|
|
|||
30
dev/flake.lock
generated
30
dev/flake.lock
generated
|
|
@ -24,11 +24,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1768135262,
|
||||
"narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
|
||||
"lastModified": 1769996383,
|
||||
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
|
||||
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -63,11 +63,11 @@
|
|||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1768476106,
|
||||
"narHash": "sha256-V0YOJRum50gtKgwavsAfwXc9+XAsJCC7386YZx1sWGQ=",
|
||||
"lastModified": 1771131391,
|
||||
"narHash": "sha256-HPBNYf7HiKtBVy7/69vKpLYHX6wTcUxndxmybzDlXP8=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "hercules-ci-effects",
|
||||
"rev": "c19e263e6e22ec7379d972f19e6a322f943c73fb",
|
||||
"rev": "0b152e0f7c5cc265a529cd63374b80e2771b207b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -78,11 +78,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1768305791,
|
||||
"narHash": "sha256-AIdl6WAn9aymeaH/NvBj0H9qM+XuAuYbGMZaP0zcXAQ=",
|
||||
"lastModified": 1771008912,
|
||||
"narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1412caf7bf9e660f2f962917c14b1ea1c3bc695e",
|
||||
"rev": "a82ccc39b39b621151d6732718e3e250109076fa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -99,11 +99,11 @@
|
|||
"nixpkgs": []
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769069492,
|
||||
"narHash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U=",
|
||||
"lastModified": 1772024342,
|
||||
"narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
|
||||
"rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -124,11 +124,11 @@
|
|||
"nixpkgs": []
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769691507,
|
||||
"narHash": "sha256-8aAYwyVzSSwIhP2glDhw/G0i5+wOrren3v6WmxkVonM=",
|
||||
"lastModified": 1770228511,
|
||||
"narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "28b19c5844cc6e2257801d43f2772a4b4c050a1b",
|
||||
"rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
48
flake.lock
generated
48
flake.lock
generated
|
|
@ -77,11 +77,11 @@
|
|||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1768135262,
|
||||
"narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
|
||||
"lastModified": 1769996383,
|
||||
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
|
||||
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -170,11 +170,11 @@
|
|||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769798267,
|
||||
"narHash": "sha256-vpI7XEfX5zeCVRANUzhMNsZfrMWuN0rwNenQ3z0rJNo=",
|
||||
"lastModified": 1772224943,
|
||||
"narHash": "sha256-jJIlRLPPVYu860MVFx4gsRx3sskmLDSRWXXue5tYncw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nix",
|
||||
"rev": "77b6b01b727f0cd1324e431a32a8854768b957ef",
|
||||
"rev": "0acd0566e85e4597269482824711bcde7b518600",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -196,11 +196,11 @@
|
|||
"treefmt": "treefmt"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769840916,
|
||||
"narHash": "sha256-bjtDp0NHjLjDOjklQVHCDCVM5q39zDzuwenNri0p4Ys=",
|
||||
"lastModified": 1772260057,
|
||||
"narHash": "sha256-NaUqM0i6XIGdgRNxxQ9sfgCAVeE2Ko9rz7e19RsNUKw=",
|
||||
"owner": "90-008",
|
||||
"repo": "nix-cargo-integration",
|
||||
"rev": "6d583e2098fa3df490c2597df06386e3efcc39b6",
|
||||
"rev": "c783c5dff02c06f2af6226d4dd4d494542d0a4d2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -211,11 +211,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1769461804,
|
||||
"narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=",
|
||||
"lastModified": 1772198003,
|
||||
"narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d",
|
||||
"rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -243,11 +243,11 @@
|
|||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1765674936,
|
||||
"narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=",
|
||||
"lastModified": 1769909678,
|
||||
"narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85",
|
||||
"rev": "72716169fe93074c333e8d0173151350670b824c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -280,11 +280,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1768135262,
|
||||
"narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
|
||||
"lastModified": 1769996383,
|
||||
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
|
||||
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -355,11 +355,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769828398,
|
||||
"narHash": "sha256-zmnvRUm15QrlKH0V1BZoiT3U+Q+tr+P5Osi8qgtL9fY=",
|
||||
"lastModified": 1772247314,
|
||||
"narHash": "sha256-x6IFQ9bL7YYfW2m2z8D3Em2YtAA3HE8kiCFwai2fwrw=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "a1d32c90c8a4ea43e9586b7e5894c179d5747425",
|
||||
"rev": "a1ab5e89ab12e1a37c0b264af6386a7472d68a15",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -399,11 +399,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769691507,
|
||||
"narHash": "sha256-8aAYwyVzSSwIhP2glDhw/G0i5+wOrren3v6WmxkVonM=",
|
||||
"lastModified": 1770228511,
|
||||
"narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "28b19c5844cc6e2257801d43f2772a4b4c050a1b",
|
||||
"rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ use nix_bindings_util::nix_version::emit_version_cfg;
|
|||
|
||||
fn main() {
|
||||
let nix_version = pkg_config::probe_library("nix-expr-c").unwrap().version;
|
||||
emit_version_cfg(&nix_version, &["2.26"]);
|
||||
emit_version_cfg(&nix_version, &["2.26", "2.34.0pre"]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2845,4 +2845,81 @@ mod tests {
|
|||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(nix_at_least = "2.34.0pre")]
|
||||
fn eval_state_primop_recoverable_error() {
|
||||
gc_registering_current_thread(|| {
|
||||
let store = Store::open(None, []).unwrap();
|
||||
let mut es = EvalState::new(store, []).unwrap();
|
||||
|
||||
let call_count = std::cell::Cell::new(0u32);
|
||||
let v = es
|
||||
.new_value_thunk(
|
||||
"recoverable_test",
|
||||
Box::new(move |es: &mut EvalState| {
|
||||
let count = call_count.get();
|
||||
call_count.set(count + 1);
|
||||
if count == 0 {
|
||||
Err(primop::RecoverableError::new("transient failure").into())
|
||||
} else {
|
||||
es.new_value_int(42)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// First force should fail with the recoverable error
|
||||
let r = es.force(&v);
|
||||
assert!(r.is_err());
|
||||
assert!(
|
||||
r.unwrap_err().to_string().contains("transient failure"),
|
||||
"Error message should contain 'transient failure'"
|
||||
);
|
||||
|
||||
// Second force should succeed because the error was recoverable
|
||||
es.force(&v).unwrap();
|
||||
let i = es.require_int(&v).unwrap();
|
||||
assert_eq!(i, 42);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(nix_at_least = "2.34.0pre")]
|
||||
fn eval_state_primop_recoverable_error_in_chain() {
|
||||
gc_registering_current_thread(|| {
|
||||
let store = Store::open(None, []).unwrap();
|
||||
let mut es = EvalState::new(store, []).unwrap();
|
||||
|
||||
let call_count = std::cell::Cell::new(0u32);
|
||||
let v = es
|
||||
.new_value_thunk(
|
||||
"recoverable_chain_test",
|
||||
Box::new(move |es: &mut EvalState| {
|
||||
let count = call_count.get();
|
||||
call_count.set(count + 1);
|
||||
if count == 0 {
|
||||
// Wrap RecoverableError in .context(), pushing it down the chain
|
||||
use anyhow::Context;
|
||||
Err(primop::RecoverableError::new("transient failure"))
|
||||
.context("wrapper context")?
|
||||
} else {
|
||||
es.new_value_int(42)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// First force should fail
|
||||
let r = es.force(&v);
|
||||
assert!(r.is_err());
|
||||
|
||||
// Second force should succeed if RecoverableError is found in the chain
|
||||
es.force(&v).unwrap();
|
||||
let i = es.require_int(&v).unwrap();
|
||||
assert_eq!(i, 42);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,35 @@ use std::ffi::{c_int, c_void, CStr, CString};
|
|||
use std::mem::ManuallyDrop;
|
||||
use std::ptr::{null, null_mut};
|
||||
|
||||
/// A primop error that is not memoized in the thunk that triggered it,
|
||||
/// allowing the thunk to be forced again.
|
||||
///
|
||||
/// Since [Nix 2.34](https://nix.dev/manual/nix/2.34/release-notes/rl-2.34.html#c-api-changes),
|
||||
/// primop errors are memoized by default: once a thunk fails, forcing it
|
||||
/// again returns the same error. Use `RecoverableError` for errors that
|
||||
/// are transient, so the caller can retry.
|
||||
///
|
||||
/// On Nix < 2.34, all errors are already recoverable, so this type has
|
||||
/// no additional effect.
|
||||
///
|
||||
/// Available since nix-bindings-expr 0.2.1.
|
||||
#[derive(Debug)]
|
||||
pub struct RecoverableError(String);
|
||||
|
||||
impl RecoverableError {
|
||||
pub fn new(msg: impl Into<String>) -> Self {
|
||||
RecoverableError(msg.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RecoverableError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RecoverableError {}
|
||||
|
||||
/// Metadata for a primop, used with `PrimOp::new`.
|
||||
pub struct PrimOpMeta<'a, const N: usize> {
|
||||
/// Name of the primop. Note that primops do not have to be registered as
|
||||
|
|
@ -36,6 +65,11 @@ impl Drop for PrimOp {
|
|||
}
|
||||
}
|
||||
impl PrimOp {
|
||||
/// Create a new primop with the given metadata and implementation.
|
||||
///
|
||||
/// When `f` returns an `Err`, the error is propagated to the Nix evaluator.
|
||||
/// To return a [recoverable error](RecoverableError), include it in the
|
||||
/// error chain (e.g. `Err(RecoverableError::new("...").into())`).
|
||||
pub fn new<const N: usize>(
|
||||
eval_state: &mut EvalState,
|
||||
meta: PrimOpMeta<N>,
|
||||
|
|
@ -108,13 +142,22 @@ unsafe extern "C" fn function_adapter(
|
|||
raw::copy_value(context_out, ret, v.raw_ptr());
|
||||
},
|
||||
Err(e) => unsafe {
|
||||
let err_code = error_code(&e);
|
||||
let cstr = CString::new(e.to_string()).unwrap_or_else(|_e| {
|
||||
CString::new("<rust nix-expr application error message contained null byte>")
|
||||
.unwrap()
|
||||
});
|
||||
raw_util::set_err_msg(context_out, raw_util::err_NIX_ERR_UNKNOWN, cstr.as_ptr());
|
||||
raw_util::set_err_msg(context_out, err_code, cstr.as_ptr());
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn error_code(e: &anyhow::Error) -> raw_util::err {
|
||||
#[cfg(nix_at_least = "2.34.0pre")]
|
||||
if e.downcast_ref::<RecoverableError>().is_some() {
|
||||
return raw_util::err_NIX_ERR_RECOVERABLE;
|
||||
}
|
||||
raw_util::err_NIX_ERR_UNKNOWN
|
||||
}
|
||||
|
||||
static FUNCTION_ADAPTER: raw::PrimOpFun = Some(function_adapter);
|
||||
|
|
|
|||
|
|
@ -788,7 +788,8 @@ mod tests {
|
|||
Err(e) => e.to_string(),
|
||||
};
|
||||
assert!(
|
||||
err.contains("required system or feature not available"),
|
||||
err.contains("required system or feature not available")
|
||||
|| err.contains("platform mismatch"),
|
||||
"Error should mention system not available, got: {}",
|
||||
err
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue