Skip to content

feat: unsync feature flag for ?Send contexts#16

Merged
27Bslash6 merged 2 commits intomainfrom
feat/unsync-feature-flag
Apr 26, 2026
Merged

feat: unsync feature flag for ?Send contexts#16
27Bslash6 merged 2 commits intomainfrom
feat/unsync-feature-flag

Conversation

@27Bslash6
Copy link
Copy Markdown
Contributor

@27Bslash6 27Bslash6 commented Apr 26, 2026

Summary

  • Adds an unsync Cargo feature flag that gates the existing ?Send / Rc code paths (previously only active on wasm32) for use on native targets
  • Enables tokio::task::LocalSet, single-threaded runtimes, and embedded async executors to use cachekit-rs without Send bounds
  • Zero code duplication: uses cfg_attr to switch between #[async_trait] and #[async_trait(?Send)] on the same impl blocks

Affected code

File Change
Cargo.toml unsync = [] feature
backend/mod.rs Backend, TtlInspectable, LockableBackend trait defs
client.rs SharedBackend (Arc vs Rc), SharedEncryption, from_env()
error.rs BackendError::source drops Send+Sync bounds
backend/{cachekitio,redis,cachekitio_lock,cachekitio_ttl}.rs cfg_attr on impl blocks
tests/ shared() / new_with_handle() pattern for Arc/Rc-agnostic mocking

Usage

[dependencies]
cachekit-rs = { version = "0.2", features = ["unsync", "cachekitio"] }

Test plan

  • cargo test (default features) — 108 tests pass
  • cargo test --features unsync — 108 tests pass
  • cargo check --features unsync,redis,encryption,l1 — compiles clean
  • Pre-commit hooks (fmt, clippy, secrets) all pass

Closes #15

Summary by CodeRabbit

  • New Features

    • Added an unsync Cargo feature to toggle relaxed async behavior.
  • Refactor

    • Async trait implementations now optionally relax Send requirements when the feature is enabled.
    • Shared backend/encryption types now switch between thread-safe and single-threaded wrappers based on the feature.
  • Tests

    • Test helpers and mocks updated to use the conditional shared backend wrapper.

Gate the existing ?Send / Rc code paths (previously wasm32-only) behind
a feature flag so native single-threaded runtimes (tokio::task::LocalSet,
embedded async executors) get first-class support.

Changes:
- Cargo.toml: add `unsync = []` feature
- backend/mod.rs: trait definitions use cfg(any(wasm32, unsync))
- client.rs: SharedBackend/SharedEncryption aliases use Rc when unsync
- backend impls: use cfg_attr for async_trait vs async_trait(?Send)
- error.rs: BackendError::source drops Send+Sync bounds when unsync
- test mocks: shared()/new_with_handle() pattern for Arc/Rc-agnostic testing

Closes #15
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc5780b1-5e64-43fb-8928-b04a91b3d6f5

📥 Commits

Reviewing files that changed from the base of the PR and between f71cd42 and 0e38a6f.

📒 Files selected for processing (12)
  • crates/cachekit/Cargo.toml
  • crates/cachekit/src/backend/cachekitio.rs
  • crates/cachekit/src/backend/cachekitio_lock.rs
  • crates/cachekit/src/backend/cachekitio_ttl.rs
  • crates/cachekit/src/backend/mod.rs
  • crates/cachekit/src/backend/redis.rs
  • crates/cachekit/src/client.rs
  • crates/cachekit/src/error.rs
  • crates/cachekit/tests/client_tests.rs
  • crates/cachekit/tests/common/mod.rs
  • crates/cachekit/tests/encryption_tests.rs
  • crates/cachekit/tests/macro_tests.rs

📝 Walkthrough

Walkthrough

A new unsync Cargo feature enables the existing WASM ?Send/Rc code paths on native targets. Traits, impls, type aliases, error types, and tests are conditionally compiled to use async_trait(?Send) and Rc when feature = "unsync" (or target_arch = "wasm32"), otherwise Send + Sync and Arc remain.

Changes

Cohort / File(s) Summary
Feature Declaration
crates/cachekit/Cargo.toml
Add new unsync feature flag.
Trait Definitions
crates/cachekit/src/backend/mod.rs
Gate Backend, TtlInspectable, and LockableBackend trait variants on any(target_arch = "wasm32", feature = "unsync") to select #[async_trait(?Send)] vs #[async_trait] / Send + Sync.
Backend Impl Attributes
crates/cachekit/src/backend/cachekitio.rs, crates/cachekit/src/backend/cachekitio_lock.rs, crates/cachekit/src/backend/cachekitio_ttl.rs, crates/cachekit/src/backend/redis.rs
Change #[async_trait] to #[cfg_attr(..., async_trait(?Send))] / #[cfg_attr(..., async_trait)] so impls compile with or without ?Send based on feature/target.
Client Types & Init
crates/cachekit/src/client.rs
SharedBackend and SharedEncryption now alias to Arc or Rc depending on unsync/wasm32; CacheKit::from_env now wraps backends with the matching smart pointer.
Error Struct
crates/cachekit/src/error.rs
BackendError::source chooses between Box<dyn Error + Send + Sync> and Box<dyn Error> based on unsync/wasm32 gating.
Tests: Mock Backend API
crates/cachekit/tests/common/mod.rs
MockBackend API changes: new() removed, shared() and new_with_handle() added; shared handle creation returns SharedBackend (Arc vs Rc) and impl async_trait is cfg-dependent.
Tests: Call Sites & Helpers
crates/cachekit/tests/client_tests.rs, crates/cachekit/tests/encryption_tests.rs, crates/cachekit/tests/macro_tests.rs
Update tests to use MockBackend::shared() or new_with_handle(); refactor CountingBackend to separate shared state and provide cloneable handle; adjust imports and return types for SharedBackend.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰
A tiny flag, a nimble hop,
Swaps Arc for Rc — now runtimes stop,
?Send takes a gentle bow,
Local tasks can run, oh wow! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: unsync feature flag for ?Send contexts' clearly and concisely summarizes the main change: adding a new unsync feature flag to enable ?Send trait bounds on native targets.
Linked Issues check ✅ Passed The pull request fully implements the three core objectives from issue #15: adds the unsync feature flag, uses Rc and #[async_trait(?Send)] when enabled, and reuses existing wasm32 code paths to avoid duplication.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the unsync feature implementation scope; no extraneous modifications or unrelated refactoring is present in the pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/unsync-feature-flag

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/cachekit/src/backend/mod.rs (1)

30-124: ⚠️ Potential issue | 🟡 Minor

Add per-method documentation to the ?Send trait variants for consistency.

Each of Backend, TtlInspectable, and LockableBackend is defined twice with #[cfg] gating — once for Send + Sync contexts and once for ?Send (wasm32/unsync). While trait duplication is the standard pattern for conditional Send bounds in async-trait, the ?Send variants (lines 56-67, 89-97, 117-124) omit the per-method /// doc comments present on the default variants.

This causes documentation drift: running cargo doc --features unsync will render with less per-method documentation than the default build, leading to inconsistent developer experience across feature flags.

Porting the /// comments from the Send + Sync variants to their ?Send counterparts will keep the documentation consistent without requiring structural changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cachekit/src/backend/mod.rs` around lines 30 - 124, The ?Send trait
variants are missing the per-method doc comments present on the Send+Sync
versions; update the ?Send variants of Backend, TtlInspectable, and
LockableBackend by copying the existing /// comments for each method
(Backend::get, set, delete, exists, health; TtlInspectable::ttl, refresh_ttl;
LockableBackend::acquire_lock, release_lock) from the Send+Sync definitions into
their corresponding #[async_trait(?Send)] definitions so documentation is
consistent across feature flags.
🧹 Nitpick comments (2)
crates/cachekit/tests/macro_tests.rs (2)

26-35: Minor: Arc<CountingInner> is used unconditionally even under unsync.

The shared-state wrapper inside CountingBackend is hard-coded to std::sync::Arc regardless of whether unsync/wasm32 is active. It's harmless (the atomics + tokio mutex are Send + Sync so the ?Send trait is satisfied either way), but it diverges from the PR's principle of flipping Arc↔Rc by cfg. Consider mirroring the same selection here for consistency with the production code paths the test suite exercises.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cachekit/tests/macro_tests.rs` around lines 26 - 35, The test uses
std::sync::Arc for CountingBackend.inner unconditionally; mirror the crate's cfg
switch so tests use Rc under unsync/wasm32: introduce a cfg-gated alias (e.g.,
type SharedCounting = std::sync::Arc<CountingInner> when not cfg(unsync) and
type SharedCounting = std::rc::Rc<CountingInner> under cfg(unsync) or wasm32)
and change CountingBackend.inner to that alias (and adjust derives/impls if
needed for Clone/Default) so the test flips Arc↔Rc the same way production code
does.

38-48: Optional: factor the Arc/Rc selection into a helper to mirror MockBackend::into_shared.

This block re-implements the same cfg-based pointer selection that tests/common/mod.rs already centralizes for MockBackend. Extracting a small helper (or moving CountingBackend next to MockBackend in common) would remove the duplication and keep the per-target pointer choice in one place.

Also, since CountingBackend derives Default, the explicit construction can be simplified.

♻️ Suggested simplification
 fn new_with_handle() -> (SharedBackend, Self) {
-    let backend = Self {
-        inner: std::sync::Arc::new(CountingInner::default()),
-    };
+    let backend = Self::default();
     let handle = backend.clone();
     #[cfg(not(any(target_arch = "wasm32", feature = "unsync")))]
     let shared: SharedBackend = std::sync::Arc::new(backend);
     #[cfg(any(target_arch = "wasm32", feature = "unsync"))]
     let shared: SharedBackend = std::rc::Rc::new(backend);
     (shared, handle)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cachekit/tests/macro_tests.rs` around lines 38 - 48, The
new_with_handle function duplicates cfg-based Arc/Rc selection already
centralized by MockBackend::into_shared; refactor by calling that helper (or
create a small into_shared for CountingBackend alongside MockBackend) to return
the SharedBackend instead of repeating cfg blocks, and simplify construction by
using CountingInner::default() (or Default::default()) when building the
backend/handle; update references to SharedBackend, new_with_handle,
CountingInner, and MockBackend::into_shared to locate and reuse the centralized
pointer-selection logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@crates/cachekit/src/backend/mod.rs`:
- Around line 30-124: The ?Send trait variants are missing the per-method doc
comments present on the Send+Sync versions; update the ?Send variants of
Backend, TtlInspectable, and LockableBackend by copying the existing ///
comments for each method (Backend::get, set, delete, exists, health;
TtlInspectable::ttl, refresh_ttl; LockableBackend::acquire_lock, release_lock)
from the Send+Sync definitions into their corresponding #[async_trait(?Send)]
definitions so documentation is consistent across feature flags.

---

Nitpick comments:
In `@crates/cachekit/tests/macro_tests.rs`:
- Around line 26-35: The test uses std::sync::Arc for CountingBackend.inner
unconditionally; mirror the crate's cfg switch so tests use Rc under
unsync/wasm32: introduce a cfg-gated alias (e.g., type SharedCounting =
std::sync::Arc<CountingInner> when not cfg(unsync) and type SharedCounting =
std::rc::Rc<CountingInner> under cfg(unsync) or wasm32) and change
CountingBackend.inner to that alias (and adjust derives/impls if needed for
Clone/Default) so the test flips Arc↔Rc the same way production code does.
- Around line 38-48: The new_with_handle function duplicates cfg-based Arc/Rc
selection already centralized by MockBackend::into_shared; refactor by calling
that helper (or create a small into_shared for CountingBackend alongside
MockBackend) to return the SharedBackend instead of repeating cfg blocks, and
simplify construction by using CountingInner::default() (or Default::default())
when building the backend/handle; update references to SharedBackend,
new_with_handle, CountingInner, and MockBackend::into_shared to locate and reuse
the centralized pointer-selection logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b6dd110-3f64-41c9-8e78-70c8eef6fd57

📥 Commits

Reviewing files that changed from the base of the PR and between f71cd42 and 8b9dcdc.

📒 Files selected for processing (12)
  • crates/cachekit/Cargo.toml
  • crates/cachekit/src/backend/cachekitio.rs
  • crates/cachekit/src/backend/cachekitio_lock.rs
  • crates/cachekit/src/backend/cachekitio_ttl.rs
  • crates/cachekit/src/backend/mod.rs
  • crates/cachekit/src/backend/redis.rs
  • crates/cachekit/src/client.rs
  • crates/cachekit/src/error.rs
  • crates/cachekit/tests/client_tests.rs
  • crates/cachekit/tests/common/mod.rs
  • crates/cachekit/tests/encryption_tests.rs
  • crates/cachekit/tests/macro_tests.rs

The unsync/wasm32 variants of Backend, TtlInspectable, and
LockableBackend were missing per-method doc comments, producing
missing_docs warnings. Copy the docs from the Send+Sync definitions.
@27Bslash6
Copy link
Copy Markdown
Contributor Author

@coderabbitai re-review pls

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

@27Bslash6 Sure! I'll re-review the PR now.

✅ Actions performed

Full review triggered.

@27Bslash6 27Bslash6 merged commit c52c3f2 into main Apr 26, 2026
5 checks passed
@27Bslash6 27Bslash6 deleted the feat/unsync-feature-flag branch April 26, 2026 08:36
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.

feat: unsync feature flag for ?Send contexts on native targets

1 participant