Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,12 @@ jobs:
# Get all commits in this PR (exclude merge commits)
COMMITS=$(git log --format="%s" --no-merges origin/${{ github.base_ref }}..HEAD)

# Regex for conventional commits
PATTERN="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .+"
# Regex for conventional commits. The optional `!` marks a breaking
# change (type[(scope)][!]: description) - it must be accepted here
# because this check reads only the subject line, so a BREAKING CHANGE
# footer would be invisible to it (and to release.yml's notes, which
# also format with %s).
PATTERN="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+"

FAILED=0
while IFS= read -r commit; do
Expand All @@ -83,8 +87,9 @@ jobs:

if [[ ! "$commit" =~ $PATTERN ]]; then
echo "::error::Non-conventional commit: '$commit'"
echo "Expected format: type(scope): description"
echo "Expected format: type(scope)!: description (scope and ! are optional)"
echo "Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert"
echo "Use ! to mark a breaking change, e.g. refactor(api)!: rename the client trait"
FAILED=1
fi
done <<< "$COMMITS"
Expand Down
61 changes: 60 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.4.0] - 2026-08-01

Session-03 tech-debt sweep: TD-2026-07-09 resolved. Plan review ran three
rounds before any code was written (25 agents; artifacts under
`docs/code-reviews/session-03-plan-round{1,2,3}.md`), which is why
TD-2026-07-08 moved to session 04 — round 2 established the two records were
mis-sequenced rather than merely mis-specified.

### Added

- Ownership of half-open circuit-breaker probe tokens. `admit()` returns a
`ProbePermit` that returns its token on drop and is consumed when the
outcome is recorded, closing the three accounting leaks TD-2026-07-09
named: a request admitted while closed can no longer release a token it
never took, a token from an expired probe window is discarded instead of
credited to the live one, and a request future dropped mid-probe — a client
disconnecting during an outage — returns its token instead of stranding it
until the re-grant window
- `iggy_circuit_breaker_probe_dispositions_total{disposition}` — how probe
tokens end, as `consumed` / `released` / `stale` / `abandoned` /
`inconsistent`. The labels partition every admitted token, which is what
makes `consumed` usable as a denominator; an abandoned-only counter could not
distinguish a healthy system from a dead release path. `inconsistent` is
separate on purpose — it means the token accounting is wrong, and it must not
hide inside the routine `abandoned` volume

### Changed

- Circuit-breaker state is an enum whose variants own their own data, so a
field belonging to another state is unrepresentable. Deletes two `Option`s,
a window guard, a six-field hygiene reset and three defensive resets that
were previously maintained by convention at each mutation site
- The breaker's state is guarded by `std::sync::Mutex` and its methods are
synchronous. Not a preference: `Drop` cannot await, so a blocking guard is
what makes the probe permit's release possible at all. Also removes the
read-lock fast path and the read-to-write upgrade race it required
- Tracing and monotonic counters moved out of the breaker's critical section.
The Prometheus state gauge deliberately stays inside it: it is
last-writer-wins, and emitting it after releasing the guard would let racing
transitions leave it permanently disagreeing with the breaker
- **Breaking**: `CircuitBreaker`, `CircuitBreakerConfig` and `CircuitState`
are crate-internal, and `IggyClientWrapper`'s `circuit_breaker_state`,
`circuit_breaker_metrics` and `force_close_circuit` accessors are removed.
All had zero callers; narrowing the surface is what keeps the rest of this
release non-breaking
- CI accepts the Conventional Commits `!` breaking-change marker, which its
regex previously rejected outright

### Fixed

- `Config::validate` rejects a zero `CIRCUIT_BREAKER_OPEN_DURATION_SECS`, which
disabled the breaker entirely — Open never rejected, and every admission past
the budget re-granted — and a zero `OPERATION_TIMEOUT_SECS`, which opened the
circuit on a healthy service and never closed it
- The 503 body for a rejected request now names the state that actually
rejected. It previously re-read the breaker after the fact and could report a
state a concurrent transition had already moved past

### Security

- Bumped transitive `crossbeam-epoch` 0.9.18 -> 0.9.20 (lockfile-only) to
Expand Down Expand Up @@ -197,7 +255,8 @@ triggers (`docs/tech-debt/`):
- Trusted proxy configuration for X-Forwarded-For validation
- Input validation to prevent injection attacks

[Unreleased]: https://github.com/mlevkov/iggy_sample/compare/v0.3.0...HEAD
[Unreleased]: https://github.com/mlevkov/iggy_sample/compare/v0.4.0...HEAD
[0.4.0]: https://github.com/mlevkov/iggy_sample/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/mlevkov/iggy_sample/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/mlevkov/iggy_sample/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/mlevkov/iggy_sample/releases/tag/v0.1.0
11 changes: 10 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,39 @@ By participating in this project, you agree to maintain a respectful and inclusi
This project uses [Conventional Commits](https://www.conventionalcommits.org/):

```
<type>(<scope>): <description>
<type>(<scope>)!: <description>

[optional body]

[optional footer(s)]
```

The scope and the `!` are both optional. Use `!` to mark a breaking change —
CI checks only the subject line, so a `BREAKING CHANGE:` footer alone will not
be recognized (and would not appear in the generated release notes either).

**Types:**
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation changes
- `style`: Formatting only, no code change
- `refactor`: Code refactoring (no functional change)
- `test`: Adding or updating tests
- `chore`: Maintenance tasks
- `perf`: Performance improvements
- `ci`: CI/CD changes
- `revert`: Reverting a previous commit

**Examples:**
```
feat(messages): add batch message validation
fix(auth): handle empty API key gracefully
docs(readme): update configuration examples
refactor(circuit-breaker)!: narrow the public surface
```

Keep the subject line at 72 characters or fewer (`.commitlintrc.json`).

## Development Setup

### Prerequisites
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "iggy_sample"
version = "0.3.0"
version = "0.4.0"
edition = "2024"
rust-version = "1.93.0"
description = "A comprehensive demonstration of Apache Iggy message streaming with Axum"
Expand Down Expand Up @@ -97,4 +97,9 @@ unsafe_code = "warn"
debug = true
codegen-units = 1
lto = true
# Load-bearing beyond binary size: CircuitBreaker::lock recovers a poisoned
# state mutex rather than propagating, and its justification is that a panic
# under the guard aborts here instead of unwinding into a poisoned lock. The
# recovery path is therefore unreachable in release and exercised only by
# `cargo test`. Changing this means revisiting that reasoning.
panic = "abort"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Apache Iggy is capable of processing millions of messages per second with ultra-

### Development & Testing
- Docker Compose setup for local development
- Comprehensive test suite (183 unit tests, 30 integration tests, 18 model tests, plus a metrics exporter smoke test)
- Comprehensive test suite (194 unit tests, 30 integration tests, 18 model tests, plus a metrics exporter smoke test)
- Integration tests with testcontainers (auto-spins Iggy server)
- Fuzz testing for input validation functions

Expand Down
Loading
Loading