Skip to content

Comments

fix: cactus soundness — enforce Sync invariant, guard callback aliasing, lock in Drop#4103

Merged
yujonglee merged 2 commits intomainfrom
devin/1771524443-cactus-soundness-fixes
Feb 20, 2026
Merged

fix: cactus soundness — enforce Sync invariant, guard callback aliasing, lock in Drop#4103
yujonglee merged 2 commits intomainfrom
devin/1771524443-cactus-soundness-fixes

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Feb 19, 2026

fix: cactus soundness — enforce Sync invariant, guard callback aliasing, lock in Drop

Summary

Addresses three soundness issues identified in crates/cactus:

  1. Compile-time Sync invariant enforcement (model.rs): Introduced InferenceGuard — the model's raw FFI handle is now only accessible through the guard returned by lock_inference(). Previously, raw_handle() was a standalone pub(crate) method callable without holding the lock, relying purely on convention. Now it's a compile error to touch the handle without locking. stop() remains the sole exception (atomic-only, documented).

  2. Token callback aliasing (llm/complete.rs): token_trampoline previously cast user_data to &mut CallbackState, which would be instant UB if C++ ever re-entered the callback. Replaced with a shared &CallbackState reference using Cell/UnsafeCell for interior mutability, plus an in_callback re-entrancy guard that bails out before creating a second &mut to the closure.

  3. Lock in Model::drop (model.rs): cactus_destroy now waits for any in-flight FFI operation to complete by acquiring inference_lock before destroying the handle.

std::env::set_var unsoundness in CloudConfig::prepare_env is not addressed here (out of scope per discussion).

Review & Testing Checklist for Human

  • Audit the UnsafeCell + in_callback pattern in token_trampoline: Confirm the C++ side calls the token callback strictly sequentially from a single thread (never re-entrantly). Cell is !Sync, so multi-threaded callback invocation would be unsound — the in_callback guard only protects against single-threaded re-entrancy.
  • Check Model::drop deadlock risk: The new lock acquisition is safe under normal Rust ownership (exclusive &mut self at drop), but verify no code path could hold inference_lock and then trigger a drop on the same thread (e.g. via Arc cycle or panic unwinding).
  • Confirm Transcriber::process/call_stop/drop remain correct — they lock inference for serialization but access the Transcriber's own stream handle, not the model handle. No functional change intended.

Recommended test: run existing integration/unit tests for LLM completion (streaming + non-streaming), STT (batch + streaming), and VAD to confirm no regressions.

Notes

  • CI checks passing: cactus, ci, fmt, desktop_ci (linux-x86_64, linux-aarch64) all green. macOS desktop CI was queued at time of submission.
  • Changes were not compile-tested locally (VM couldn't build native C++ in cactus-sys due to architecture mismatch). CI was the first real compilation check.
  • Link to Devin run
  • Requested by @yujonglee

… lock in Drop

- Introduce InferenceGuard: the model's raw FFI handle is now only
  accessible through the guard returned by lock_inference(), making it
  a compile error to touch the handle without holding the lock.
  stop() remains the sole documented exception (atomic-only).

- Rewrite token_trampoline to use &CallbackState (shared ref) with
  Cell/UnsafeCell interior mutability and an in_callback re-entrancy
  guard, eliminating the previous &mut aliasing risk.

- Acquire inference_lock in Model::drop so cactus_destroy waits for
  any in-flight FFI operation to complete.

- Add SAFETY comment for the stack-pinned CallbackState pointer
  passed to cactus_complete.

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
@netlify
Copy link

netlify bot commented Feb 19, 2026

Deploy Preview for hyprnote-storybook canceled.

Name Link
🔨 Latest commit 119ce0c
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/699754e93b3b9b0008c4f3f7

@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@netlify
Copy link

netlify bot commented Feb 19, 2026

Deploy Preview for hyprnote canceled.

Name Link
🔨 Latest commit 119ce0c
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/699754e98e8a0c00084ee91b

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
@yujonglee yujonglee merged commit 22d6f9b into main Feb 20, 2026
15 of 16 checks passed
@yujonglee yujonglee deleted the devin/1771524443-cactus-soundness-fixes branch February 20, 2026 02:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant