Skip to content

refactor(lib): CountCfg/FindCfg leak Arc<Mutex>/Arc<[String]> representation #537

@dekobon

Description

@dekobon

Summary

The Count / Find callback config structs leak internal concurrency
primitives into the public API, the same class of representation-leak the #495
FilesData / ConcurrentRunner reshape addresses — but #495 does not name these
structs.

Evidence

  • CountCfg.stats: Arc<Mutex<Count>> (src/count.rs:73)
  • CountCfg.filters: Arc<[String]> (src/count.rs:71),
    FindCfg.filters: Arc<[String]> (src/count.rs:78)

Why 2.0-worthy

Arc<Mutex<Count>> forces every caller to materialize the exact shared-mutability
shape the library happens to use internally; we cannot switch to a lock-free
accumulator or a channel without breaking the signature. Hiding the sync
primitive behind an opaque collector is a breaking change → 2.0.

Proposed change

Fold these callback configs into the #495 terminal-processor reshape: hide the
Arc/Mutex behind an opaque collector type, and reconsider whether
filters: Arc<[String]> should be a borrowed slice or an opaque matcher.

Acceptance

  • CountCfg / FindCfg expose no Arc<Mutex<…>> / Arc<[String]> in their
    public signatures.

Part of the pre-2.0 review (#505); companion to #495.

Resolution

Implemented in commit 3f3f573 (branch fix/issue-537, integration
fix/batch-2026-06-06).

Design (chosen with the user): two opaque newtypes in the library.

  • NodeTypeFilters(Arc<[String]>) — replaces CountCfg.filters and
    FindCfg.filters. new(&[String]), From<Vec<String>>,
    From<&[String]>, and a borrowed as_slice(&self) -> &[String]
    accessor for the internal count/find calls.
  • CountCollector(Arc<Mutex<Count>>) — replaces CountCfg.stats.
    new() / with_count(Count), Clone (cheap Arc bump), and
    into_count(self) -> Count to recover the tally after the walk.

Why a borrowed &[String] field was rejected: CountCfg/FindCfg
are handed to ConcurrentRunner::run, whose Config: 'static bound
wraps the config in an Arc shared across worker threads. A borrow
tied to the caller's stack cannot satisfy 'static. The filter set
must stay owned; the newtype keeps it owned-but-opaque.

Poison recovery (#445) preserved. into_count handles
Arc::try_unwrap + Mutex::into_inner and degrades on poison via
PoisonError::into_inner rather than panicking; the inner-field
Callback::call path keeps the existing clear_poison recovery. The
inner fields are private, so neither newtype leaks Arc/Mutex in
its public signature.

Call sites updated: dispatch.rs (CountCfg/FindCfg
construction), the CLI Config.count_lock field type, and
run_command_count (now extracts via into_count() instead of a bare
Arc::try_unwrap().into_inner().expect() chain). Both newtypes are
re-exported from lib.rs.

Tests: the #445 poison-recovery test updated to the new
constructor (still exercises the poison path); into_count returns the
accumulated tally after concurrent updates; into_count degrades on a
poisoned mutex; NodeTypeFilters round-trips its patterns. Full
workspace cargo test --all-features, clippy, fmt, and rustdoc all
clean; CLI count/find verified end-to-end.

This is a breaking public field-type change, deferred to 2.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions