batch-aint-one 0.15.0

I got 99 problems, but a batch ain't one
Documentation
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## 0.15.0

### Added

- Per-item `queue_durations` field on `BatchStats`, measuring time from worker receipt to processing start.
- `MetricsRecorderFactory` trait: the batcher creates its own recorder using its name, replacing the old `.metrics_recorder(Arc<dyn MetricsRecorder>)` builder method.

### Changed

- Builder method renamed from `.metrics_recorder()` to `.metrics()`, now accepts `Box<dyn MetricsRecorderFactory>`.

## 0.14.0

### Added

- Optional `MetricsRecorder` trait for observability, passed via `Batcher::builder().metrics_recorder(...)`. Implement this to bridge batcher metrics (item rates, batch sizes, latencies, rejections, queue depth, concurrency) to your metrics system.
- `BatchStats` struct with public constructor, passed to `MetricsRecorder::batch_completed`.

## 0.13.1

### Internal

- The worker event loop is now the single writer of batch state. Resource-acquisition tasks
  report their result by message instead of mutating shared state, removing the
  `Arc<Mutex<…>>` previously shared with those tasks and making the race fixed in 0.13.0
  structurally impossible. No public API or behaviour change.
- Resource-acquisition failures are now handled directly on the worker rather than via a spawned
  task and a round-trip message, keeping the in-flight concurrency counters symmetric.

## 0.13.0

### Changed

- `Limits` now panics on construction if any limit is zero, instead of panicking obscurely or
  stalling later
- `Limits::default()` now matches the builder defaults: `max_batch_queue_size` defaults to
  `max_key_concurrency * 2` (was `max_key_concurrency`)

### Fixed

- Fixed a race condition where a batch whose resource acquisition failed could be processed
  anyway, leaking concurrency capacity and eventually stalling the key
- Batch queues for idle keys are now removed, fixing unbounded memory growth when batching over
  many distinct keys
- The `batch.first_item_wait_time_secs` span attribute now has sub-second precision (was
  truncated to whole seconds, so almost always `0`)

### Documentation

- The batching policy comparison is now on `BatchingPolicy` itself, so it is visible on docs.rs
- Documented `shut_down()` semantics: new items are still accepted while shutting down, and
  shutdown may never complete under sustained traffic
- Documented `Limits` builder defaults and clarified when items are rejected
- Documented the `Processor::process` output invariant: exactly one output per input, in order
- Various smaller documentation fixes

## 0.12.0

### Changed

- [Breaking] Added `BatchError::ProcessorInvariantViolation` variant to indicate processor invariant violations

## 0.11.2

### Fixed

- Fixed double error messaging

## 0.11.1

### Fixed

- Add error sources to `BatchError` variants to implement `std::error::Error` correctly

## 0.11.0

### Added

- `BatchingPolicy` now implements `Display` for human-readable output
- `Limits` now implements `Display` for human-readable output
- `BatchingPolicy` now implements `Clone`

### Changed

- Improved logging: changed some debug logs to info level
- Improved documentation for `Limits`
- Internal: replaced atomic-based concurrency tracking with simpler counter variables, reducing potential for race conditions
- Internal refactoring: policy implementations moved to separate modules for better code organization

### Fixed

- Fixed bugs in policy logic
- Improved accuracy of limit checking

## 0.10.0

### Changed

- [Breaking] Updated to use `bon` for `Limits` builder
- [Breaking] `RejectionReason` enum variants refactored for clarity

### Added

- New `BatchingPolicy::Balanced` policy for efficient resource usage
- New `max_batch_queue_size` limit to control queued batches per key

## 0.9.0

### Changed

- [Breaking] Replaced `Batcher::new` with `Batcher::builder`
- [Breaking] Prefixed `Limits` builder functions with `with_`
- [Breaking] Require a name for a `Batcher` for tracing/logs
- [Breaking] Replaced generics with associated types on `Processor` (and pretty much everything else)
- [Breaking] Added `acquire_resources()` to the `Processor` trait

### Added

- Ability to wait for shut down using `worker.wait_for_shutdown().await`
- Ability to shut down gracefully using `worker.shut_down().await`

### Fixed

- Improved `Processor` panic handling