scatter-proxy 0.8.0

Async request scheduler for unreliable SOCKS5 proxies — multi-path race for maximum throughput
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.8.0] - 2026-05-09

### Changed (breaking)

- **`ScatterProxyRouter::new()`** now accepts per-host `(host, ScatterProxyConfig)` tuples
  instead of the previous `(hosts, shared_config, classifier)` signature.  Each host gets
  its own independent configuration, enabling per-host tuning of sources, concurrency,
  timeouts, and log prefix.
  ```rust
  // Before (0.7.x)
  let router = ScatterProxyRouter::new(
      ["szse.cn", "sse.com.cn"],
      ScatterProxyConfig::default(),
      DefaultClassifier,
  ).await?;

  // After (0.8.0)
  let router = ScatterProxyRouter::new(
      [
          ("szse.cn",    ScatterProxyConfig { max_inflight: 20, ..Default::default() }),
          ("sse.com.cn", ScatterProxyConfig { max_inflight: 10, ..Default::default() }),
      ],
      DefaultClassifier,
  ).await?;
  ```
- **`ProxyManager`**: Removed remaining `DIRECT` (`direct://localhost`) code — the const,
  the `set_state` guard that prevented eviction, and the `get_client` direct-connection
  branch.  All connections unconditionally route through SOCKS5 proxies.

### Added

- **Task-completed log**`debug!` emitted on successful task completion with `host`,
  `proxy`, `attempt`, and `latency_ms` fields; symmetric to the existing `"task requeued"` log.
- **Host field in proxy-dead log**`"proxy dead | global success_rate=0%"` now includes
  `host = %host` so the triggering host is identifiable in multi-host deployments.

## [0.7.0] - 2026-05-09

### Added

- **`ScatterProxyRouter`** — routes proxied requests to per-host [`ScatterProxy`] instances.
  Each registered host gets its own independent proxy pool, health tracker, scheduler, and
  metrics view.  A struggling host (all proxies in cooldown, zero success) can no longer
  starve tasks for other hosts, and proxy eviction decisions are scoped per-host.
- **`ScatterProxyConfig::name: Option<String>`** — optional label prepended to every
  periodic metrics log line as `[name]`.  `ScatterProxyRouter` automatically sets this to
  the host string for each pool, so multi-host log output is immediately identifiable.
- **`ScatterProxyError::UnknownHost(String)`** — returned by `ScatterProxyRouter::submit`
  and `try_submit` when the request targets a host not registered in the router.
- **`ScatterProxyConfig: Clone`** and **`RateLimitConfig: Clone`** — required to share a
  single config across router-managed per-host pools.

## [0.6.0] - 2026-05-07

### Changed

- Repository hosting migrated from GitHub to Codeberg.
- CI/CD migrated from GitHub Actions to Woodpecker CI.
- Project website/docs deployment migrated from GitHub Pages to Codeberg Pages.
- Default first-party proxy source switched to the Codeberg Pages URL.

### Infrastructure

- Added `.woodpecker.yml` with CI checks, docs publish pipeline, scheduled proxy list refresh, and tag-based crates.io publish.
- Removed legacy `.github/workflows/*` GitHub Actions pipelines.

## [0.5.0] - 2026-04-23

### Changed (breaking)

- Removed the implicit DIRECT proxy candidate (`direct://localhost`). Proxy mode now uses only fetched/configured proxies; direct traffic is no longer auto-inserted into the race.
- Removed the circuit breaker subsystem entirely. Temporary lack of runnable proxies is now handled by the delayed retry queue instead of host-level breaker state.
- Reworked scheduling to use multiple scheduler workers plus a ready queue and delayed heap-backed retry queue.
- Expanded metrics to report delayed tasks, requeues, zero-available events, dispatch count, and skip reasons.

### Fixed

- Reduced hot-loop requeue behavior by delaying tasks until their next retry window.
- Improved fairness by keeping ready tasks in FIFO order while delayed tasks are promoted by `ready_at`.

## [0.3.0] - 2026-04-22

### Changed (breaking)

- **`ScatterProxy::submit`** is now `async` and returns `TaskHandle` directly instead of `Result<TaskHandle, ScatterProxyError>`.  The scheduler retries internally forever; no error is ever returned from the submit path or the handle's `.await`.
- **`ScatterProxy::submit_batch`** is now `async` and returns `Vec<TaskHandle>` directly.
- **`ScatterProxyConfig`**: removed `task_timeout` and `max_attempts` fields; the scheduler no longer imposes per-task attempt limits or overall timeouts.

### Added

- **`ScatterProxy::try_submit`** — non-blocking submit; returns `Err(PoolFull)` immediately when the pool is at capacity.
- **`ScatterProxy::try_submit_batch`** — atomic non-blocking batch submit; rejects the entire batch if capacity is insufficient.
- **`ScatterProxy::submit_timeout`** — blocks up to a deadline waiting for a pool slot; returns `Err(Timeout)` if the deadline elapses before capacity is available.
- **`TaskHandle::with_timeout(Duration)`** — wraps the handle with a caller-side deadline; returns `Err(Timeout)` if the task hasn't resolved in time.  The underlying task remains in the scheduler and continues to retry.  `tokio::time::timeout(dur, handle).await` also works since `TaskHandle: Future`.
- **Cold-start fast-fail** — when any available proxy has not yet been health-tested, the fan-out *K* is boosted to `max(5, max_concurrent_per_request)` to collect health data quickly.

### Fixed

- Scheduler no longer sends terminal errors to task handles for IO failures, proxy timeouts, or max-attempt exhaustion.  All such tasks are silently requeued and retried.

## [0.1.0] - 2025-07-13

### Added
- `ScatterProxy` — main entry point with `new()`, `submit()`, `submit_batch()`, `metrics()`, `shutdown()`
- Multi-path race scheduling: fan-out K proxies per task, first success wins
- Adaptive fan-out: K auto-adjusts based on success rate and available proxy count
- Per-(proxy, host) rate limiting with configurable intervals and host overrides
- Health tracking with sliding-window success rates and latency stats
- Exponential-backoff cooldown for consecutively-failing (proxy, host) pairs
- Per-host circuit breaker with Open → HalfOpen → Closed state machine
- Proxy eviction: mark dead if global success rate = 0% after sufficient samples
- `BodyClassifier` trait + `DefaultClassifier` for response classification
- `ScatterResponse` with status, headers, and body bytes
- JSON state persistence with atomic writes and hot-restart restore
- Automatic proxy source refresh from URLs
- Structured logging via `tracing` with periodic metrics summaries
- `socks5h://` by default for remote DNS resolution
- `PoolMetrics` for real-time observability
- 240+ unit tests across all modules
- Integration tests with real free SOCKS5 proxy sources