diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 5b6baeed..7cd6c0cd 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -1,29 +1,26 @@ # GitHub Actions Workflows / GitHub Actions 工作流 -This directory contains the complete CI/CD pipeline configuration for the Hiver Framework. -此目录包含 Hiver 框架的完整 CI/CD 流水线配置。 +This directory contains the CI/CD pipeline configuration for the Hiver Framework. +此目录包含 Hiver 框架的 CI/CD 流水线配置。 ## Overview / 概述 -Hiver uses a comprehensive set of GitHub Actions workflows to ensure code quality, security, and stability across all platforms. -Hiver 使用一套全面的 GitHub Actions 工作流来确保所有平台上的代码质量、安全性和稳定性。 +Hiver uses a set of GitHub Actions workflows to ensure code quality, security, and stability. +Hiver 使用一组 GitHub Actions 工作流来确保代码质量、安全性和稳定性。 ``` -Workflows (工作流)├── ci.yml # Main CI pipeline / 主 CI 流水线 -├── quality.yml # Code quality checks / 代码质量检查 -├── linux.yml # Linux-specific checks / Linux 专项检查 -├── macos.yml # macOS-specific checks / macOS 专项检查 -├── windows.yml # Windows-specific checks / Windows 专项检查 -├── coverage.yml # Code coverage reporting / 代码覆盖率报告 -├── format.yml # Code format validation / 代码格式验证 -├── release.yml # Crate publishing to crates.io / 发布到 crates.io -├── benchmark.yml # Performance benchmarking / 性能基准测试 -├── semver.yml # Semantic versioning checks / 语义版本检查 -├── codeql.yml # Security analysis / 安全分析 -├── outdated.yml # Outdated dependencies check / 过时依赖检查 -├── binary-release.yml # Binary release / 二进制发布 -├── docs.yml # Documentation publishing / 文档发布 -└── dependabot.yml # Automated dependency updates / 自动依赖更新 +Workflows (工作流) +├── ci.yml # Main CI pipeline / 主 CI 流水线 +├── coverage.yml # Code coverage reporting / 代码覆盖率报告 +├── benchmark.yml # Performance benchmarking / 性能基准测试 +├── semver.yml # Semantic versioning checks / 语义版本检查 +├── codeql.yml # Security analysis / 安全分析 +├── outdated.yml # Outdated dependencies check / 过时依赖检查 +├── check-workspace-deps.yml # Workspace dep format check / Workspace 依赖格式检查 +├── release.yml # Crate publishing to crates.io / 发布到 crates.io +├── binary-release.yml # Binary release / 二进制发布 +├── docs.yml # Documentation publishing / 文档发布 +└── ../../dependabot.yml # Automated dependency updates / 自动依赖更新 ``` --- @@ -36,236 +33,73 @@ Workflows (工作流)├── ci.yml # Main CI pipeline / 主 C **用途**: 主持续集成流水线 **Triggers**: -- Push to `main` or `develop` branches -- Pull requests to `main` or `develop` -- Tag pushes matching `v*` +- Push to `main` (ignoring `*.md`, `docs/**`, `.github/**/*.md`) +- Pull requests to `main` + +**Concurrency**: Cancels superseded runs on the same ref. +**并发**: 取消同一 ref 上被取代的运行。 **Jobs**: -| Job | Description | Platforms | -|-----|-------------|-----------| -| `dependency-review` | Review dependency changes for security | Ubuntu | -| `lint` | Format, Clippy, documentation checks | Ubuntu | -| `test` | Build and test all crates | Ubuntu, macOS, Windows (×2 Rust versions) | +| Job | Description | Timeout | +|-----|-------------|---------| +| `fmt` | `cargo fmt --check` (nightly rustfmt) | 15m | +| `clippy` | `cargo clippy --workspace --lib` | 30m | +| `build` | `cargo build --workspace` | 45m | +| `test` | `cargo test --workspace --lib` + integration tests (socket / waker / e2e) | 45m | +| `audit` | `cargo audit` (advisory scanning with tracked ignore list) | 20m | +| `deny` | `cargo deny check advisories licenses bans sources` (uses `deny.toml`) | 20m | +| `msrv` | `cargo check --workspace --lib` on Rust 1.91 (`rust-version`) | 30m | **Key Commands**: ```bash cargo fmt --all -- --check -cargo clippy --workspace --all-targets --all-features -- -D warnings -cargo doc --no-deps --all-features --document-private-items -- -D warnings -cargo test --workspace --all-features +cargo clippy --workspace --lib -- -W warnings +cargo build --workspace +cargo test --workspace --lib --exclude hiver-benches -- --test-threads=1 +cargo audit --ignore # see ci.yml for the full list +cargo deny check advisories licenses bans sources +cargo check --workspace --lib # on toolchain 1.91 ``` -**Estimated Runtime**: 15-20 minutes - -**Security**: -- ✅ Dependency review on PRs -- ✅ License validation -- ✅ Vulnerability scanning - ---- - -### 2. Code Quality - [quality.yml](quality.yml) - -**Purpose**: Comprehensive code quality and security checks -**用途**: 全面的代码质量和安全检查 - -**Triggers**: -- Push to `main` branch -- Pull requests to `main` branch -- Path filters: `**/*.rs`, `**/Cargo.toml`, `.github/workflows/**` - -**Jobs**: - -| Job | Tool | Purpose | -|-----|------|---------| -| `doc-tests` | cargo test --doc | Documentation examples | -| `clippy-enhanced` | clippy | Enhanced linting with all features | -| `deny` | cargo-deny | License, advisory, and bans checks | -| `machete` | cargo-machete | Unused dependency detection | -| `doc` | cargo doc | Documentation build with link checking | -| `feature-combinations` | cargo-hack | Feature powerset testing | -| `format-check` | rustfmt | Code formatting validation | -| `metadata` | cargo | Cargo.toml metadata validation | - -**Estimated Runtime**: 10-15 minutes +**Estimated Runtime**: 20-30 minutes (all jobs parallel) --- -### 3. Linux - [linux.yml](linux.yml) - -**Purpose**: Linux-specific checks and extended testing -**用途**: Linux 专项检查和扩展测试 - -**Triggers**: -- Pull requests (opened, synchronize, reopened) -- Push to `main` branch -- Path filters: `**/*.rs`, `**/Cargo.toml`, `.github/workflows/**` - -**Jobs**: - -| Job | Tool | Purpose | -|-----|------|---------| -| `typos` | typos | Spelling mistakes detection | -| `udeps` | cargo-udeps | Unused dependencies (nightly) | -| `msrv` | cargo | Minimum Supported Rust Version check | -| `test` | cargo | Build and test (stable) | -| `hack` | cargo-hack | Feature powerset (depth 1, nightly) | - -**Estimated Runtime**: 20-30 minutes - ---- - -### 4. macOS - [macos.yml](macos.yml) - -**Purpose**: macOS platform validation -**用途**: macOS 平台验证 - -**Triggers**: -- Pull requests (opened, synchronize, reopened) -- Push to `main` branch -- Path filters: `**/*.rs`, `**/Cargo.toml`, `.github/workflows/**` - -**Jobs**: - -| Job | Description | -|-----|-------------| -| `test` | Build, test with all features on macOS-latest | - -**Commands**: -```bash -cargo check --all --bins --examples --tests -cargo check --release --all --bins --examples --tests -cargo test --all --all-features --no-fail-fast -- --nocapture -``` - -**Estimated Runtime**: 10-15 minutes - ---- - -### 5. Windows - [windows.yml](windows.yml) - -**Purpose**: Windows platform validation -**用途**: Windows 平台验证 - -**Triggers**: -- Pull requests (opened, synchronize, reopened) -- Push to `main` branch -- Path filters: `**/*.rs`, `**/Cargo.toml`, `.github/workflows/**` - -**Jobs**: - -| Job | Target | Description | -|-----|--------|-------------| -| `test` | x86_64-pc-windows-msvc | MSVC build and test | - -**Special Setup**: OpenSSL installation via vcpkg - -**Estimated Runtime**: 15-20 minutes - ---- - -### 6. Code Coverage - [coverage.yml](coverage.yml) +### 2. Code Coverage - [coverage.yml](coverage.yml) **Purpose**: Generate and upload code coverage reports -**用途**: 生成和上传代码覆盖率报告 +**用途**: 生成并上传代码覆盖率报告 **Triggers**: -- Push to `main` branch -- Pull requests to `main` branch -- Path filters: `**/*.rs`, `**/Cargo.toml`, `.github/workflows/**` +- Push to `main` +- Pull requests to `main` **Jobs**: | Job | Tool | Output | |-----|------|--------| -| `cover` | cargo-tarpaulin | cobertura.xml → Codecov | +| `coverage` | cargo-tarpaulin | cobertura.xml → Codecov (`codecov-action@v5`) | **Commands**: ```bash -cargo tarpaulin --all-features --out Xml +cargo tarpaulin --workspace --all-features --out Xml ``` -**View Coverage**: -- GitHub: Check the PR comments or workflow run summary -- Codecov: [codecov.io](https://codecov.io) +**View Coverage**: [codecov.io](https://codecov.io) **Estimated Runtime**: 10-15 minutes --- -### 7. Format Check - [format.yml](format.yml) - -**Purpose**: Ensure code formatting compliance -**用途**: 确保代码格式合规 - -**Triggers**: -- Push to `main` or `develop` branches - -**Jobs**: - -| Job | Tool | Purpose | -|-----|------|---------| -| `format` | rustfmt | Check code formatting | - -**Estimated Runtime**: 2-3 minutes - ---- - -### 8. Release - [release.yml](release.yml) - -**Purpose**: Publish crates to crates.io -**用途**: 发布 crates 到 crates.io - -**Triggers**: -- Tag push matching `v[0-9]+.[0-9]+.[0-9]+` (e.g., `v1.0.0`) - -**Jobs**: - -| Job | Description | -|-----|-------------| -| `version-info` | Extract and compare versions | -| `publish` | Publish crates in dependency order | - -**Published Crates** (in order): -1. hiver-runtime -2. hiver-core -3. hiver-http -4. hiver-router -5. hiver-extractors -6. hiver-response -7. hiver-middleware -8. hiver-macros -9. hiver-resilience -10. hiver-observability -11. hiver-config -12. hiver-cache -13. hiver-security -14. hiver-tx -15. hiver-cloud -16. hiver-schedule -17. hiver-multipart -18. hiver-validation -19. hiver-exceptions -20. hiver-actuator -21. hiver-web3 - -**Secrets Required**: -- `CRATES_TOKEN`: crates.io API token - -**Estimated Runtime**: 5-10 minutes per crate - ---- - -### 9. Performance Benchmark - [benchmark.yml](benchmark.yml) +### 3. Performance Benchmark - [benchmark.yml](benchmark.yml) **Purpose**: Track performance over time with benchmarking **用途**: 通过基准测试跟踪性能变化 **Triggers**: -- Push to `main` branch -- Pull requests to `main` branch -- Path filters: `**/*.rs`, `**/Cargo.toml`, `.github/workflows/**` +- Push to `main` (paths: `**/*.rs`, `**/Cargo.toml`, `.github/workflows/**`) +- Pull requests to `main` **Jobs**: @@ -282,47 +116,61 @@ cargo tarpaulin --all-features --out Xml **Commands**: ```bash -cargo criterion --workspace --all-features +cargo criterion --workspace --all-features --message-format=json ``` **Estimated Runtime**: 15-20 minutes --- -### 10. Semantic Versioning - [semver.yml](semver.yml) +### 4. Semantic Versioning - [semver.yml](semver.yml) **Purpose**: Detect breaking API changes **用途**: 检测破坏性 API 更改 **Triggers**: -- Pull requests to `main` or `develop` -- Path filters: `**/*.rs`, `**/Cargo.toml` +- Pull requests to `main` or `develop` (paths: `**/*.rs`, `**/Cargo.toml`) **Jobs**: | Job | Tool | Purpose | |-----|------|---------| -| `semver-check` | cargo-semver-checks | Detect breaking changes | -| `api-diff` | cargo-public-api | Generate API diff | +| `semver-check` | cargo-semver-checks | Detect breaking changes (fails the PR on violations) | +| `api-diff` | cargo-public-api | Generate real unified API diff, posted as a PR comment | **Key Features**: -- 🔍 Detects breaking API changes +- 🔍 Detects breaking API changes (`--fail-on-error`, no swallowing) - 📋 Public API diff in PR comments - 🎯 Requires version bumps for breaking changes -- 📊 Semantic Versioning 2.0.0 compliance **Estimated Runtime**: 10-15 minutes --- -### 11. Security Analysis - [codeql.yml](codeql.yml) +### 5. Workspace Dependency Check - [check-workspace-deps.yml](check-workspace-deps.yml) + +**Purpose**: Ensure all crates use workspace dependencies +**用途**: 确保所有 crate 使用 workspace 依赖 + +**Triggers**: +- Push to `main`/`develop`, PRs to `main`/`develop` + +**Checks**: +- No direct version specs (`version = "x.y.z"`) outside `workspace = true` +- No old-style `.workspace = true` syntax +- **Fails the job** (exit 1) when violations are found — this gates merges. + +**Estimated Runtime**: 2-3 minutes + +--- + +### 6. Security Analysis - [codeql.yml](codeql.yml) **Purpose**: Automated security analysis using CodeQL **用途**: 使用 CodeQL 进行自动化安全分析 **Triggers**: -- Push to `main` or `develop` -- Pull requests to `main` or `develop` +- Push/PR to `main`/`develop` (paths: `**/*.rs`, `**/Cargo.toml`) - Schedule: Weekly (Mondays 00:00 UTC) **Jobs**: @@ -331,25 +179,19 @@ cargo criterion --workspace --all-features |-----|------|---------| | `analyze` | CodeQL | Security vulnerability scanning | -**Key Features**: -- 🔒 Comprehensive security analysis -- 📊 Custom query suites (security-extended) -- 🛡️ Automatic vulnerability detection -- 📈 Results in Security tab - -**Configuration**: [.github/codeql-config.yml](.github/codeql-config.yml) +**Configuration**: [.github/codeql-config.yml](../codeql-config.yml) **Estimated Runtime**: 30-45 minutes --- -### 12. Outdated Dependencies - [outdated.yml](outdated.yml) +### 7. Outdated Dependencies - [outdated.yml](outdated.yml) **Purpose**: Check for outdated dependencies weekly **用途**: 每周检查过时的依赖项 **Triggers**: -- Schedule: Weekly (Mondays 09:00 Asia/Shanghai) +- Schedule: Weekly (Mondays 00:00 UTC) - Manual trigger (workflow_dispatch) **Jobs**: @@ -360,29 +202,47 @@ cargo criterion --workspace --all-features | `security-updates` | cargo-audit | Security vulnerability scan | | `create-issue` | GitHub Actions | Create issue if needed | -**Key Features**: -- 📦 Automatic outdated dependency detection -- 🔒 Security vulnerability scanning -- 🐛 Automatic issue creation for updates -- 📊 Weekly reports in workflow summary - **Estimated Runtime**: 15-20 minutes --- -### 13. Binary Release - [binary-release.yml](binary-release.yml) +### 8. Release - [release.yml](release.yml) + +**Purpose**: Publish crates to crates.io +**用途**: 发布 crates 到 crates.io + +**Triggers**: +- Tag push matching `v*` (e.g., `v1.0.0`) + +**Jobs**: + +| Job | Description | +|-----|-------------| +| `publish` | Publish crates in dependency-tier order (`max-parallel: 1`) | + +> **Note**: `auto-publish.yml` was removed — it was disabled and conflicted with this tag-based publisher. +> **注意**:`auto-publish.yml` 已移除 —— 它处于禁用状态且与此 tag 发布冲突。 + +**Secrets Required**: +- `CRATES_TOKEN`: crates.io API token + +**Estimated Runtime**: 5-10 minutes per crate + +--- + +### 9. Binary Release - [binary-release.yml](binary-release.yml) **Purpose**: Build and release binary artifacts **用途**: 构建和发布二进制文件 **Triggers**: -- Tag push matching `v[0-9]+.[0-9]+.[0-9]+` +- Tag push matching `v*` **Platforms**: | Platform | Architecture | Status | |----------|-------------|--------| -| Linux | x86_64, aarch64 | ✅ | +| Linux | x86_64, aarch64 (cross) | ✅ | | macOS | x86_64, aarch64 | ✅ | | Windows | x86_64 | ✅ | @@ -392,45 +252,27 @@ cargo criterion --workspace --all-features - 📝 Automatic GitHub Release creation - 🍺 Homebrew formula generation -**Artifacts**: -- `hiver-x86_64-unknown-linux-gnu` -- `hiver-aarch64-unknown-linux-gnu` -- `hiver-x86_64-apple-darwin` -- `hiver-aarch64-apple-darwin` -- `hiver-x86_64-pc-windows-msvc.exe` - **Estimated Runtime**: 30-45 minutes --- -### 14. Documentation - [docs.yml](docs.yml) +### 10. Documentation - [docs.yml](docs.yml) **Purpose**: Build and publish documentation to GitHub Pages **用途**: 构建文档并发布到 GitHub Pages **Triggers**: -- Push to `main` branch -- Pull requests to `main` branch -- Path filters: `**/*.rs`, `**/Cargo.toml`, `docs/**` +- Push to `main` (deploys to Pages) +- Pull requests to `main` (build only) **Jobs**: | Job | Tool | Purpose | |-----|------|---------| -| `build` | cargo doc, mdBook | Build documentation | +| `build` | cargo doc, mdBook | Build documentation + deploy | | `test` | cargo test --doc | Run documentation tests | -| `summary` | GitHub Actions | Generate summary | -**Key Features**: -- 📚 Automatic Rust documentation build -- 📖 mdBook support for user guides -- 🌐 Deployment to GitHub Pages -- ✅ Documentation test validation -- 🔗 Internal link checking - -**Documentation URLs**: -- GitHub Pages: `https://{owner}.github.io/{repo}/` -- Rust Docs: `https://{owner}.github.io/{repo}/hiver/` +**Documentation URL**: `https://{owner}.github.io/{repo}/` **Estimated Runtime**: 10-15 minutes @@ -438,56 +280,31 @@ cargo criterion --workspace --all-features ## Configuration Files / 配置文件 -### [clippy.toml](../clippy.toml) +### [clippy.toml](../../clippy.toml) Clippy linter configuration with customized thresholds and allowed identifiers. -**Key Settings**: -- Cognitive complexity: 30 -- Type complexity: 250 -- Max function lines: 100 -- Max arguments: 7 -- 79 valid documentation identifiers - -### [deny.toml](../deny.toml) +### [deny.toml](../../deny.toml) -cargo-deny configuration for license, security, and dependency checks. +cargo-deny configuration for license, security, and dependency checks. Consumed by the `deny` job in `ci.yml`. **Allowed Licenses**: -- MIT -- Apache-2.0 -- Apache-2.0 WITH LLVM-exception -- BSD-2-Clause -- BSD-3-Clause -- ISC -- Unicode-DFS-2016 +- MIT, Apache-2.0, Apache-2.0 WITH LLVM-exception, BSD-2-Clause, BSD-3-Clause, ISC, Unicode-DFS-2016 -### [.codecov.yml](../.codecov.yml) +### [.codecov.yml](../../.codecov.yml) Codecov configuration for coverage reporting and PR comments. **Key Settings**: - Project coverage target: 80% - PR coverage target: 75% -- Component-level tracking: 10 components -- Flags: runtime, core, http, resilience, observability, web3 -- Precision: 2 decimal places - -**Features**: -- PR comments with coverage diff -- GitHub Actions Summary -- File and component-level breakdown -- Historical trend tracking --- ## Badge Status / 徽章状态 -Add these badges to your README.md: - ```markdown [![CI](https://github.com/ViewWay/hiver/actions/workflows/ci.yml/badge.svg)](https://github.com/ViewWay/hiver/actions/workflows/ci.yml) -[![Quality](https://github.com/ViewWay/hiver/actions/workflows/quality.yml/badge.svg)](https://github.com/ViewWay/hiver/actions/workflows/quality.yml) [![codecov](https://codecov.io/gh/ViewWay/hiver/branch/main/graph/badge.svg)](https://codecov.io/gh/ViewWay) [![Security](https://github.com/ViewWay/hiver/actions/workflows/codeql.yml/badge.svg)](https://github.com/ViewWay/hiver/actions/workflows/codeql.yml) [![Benchmark](https://github.com/ViewWay/hiver/actions/workflows/benchmark.yml/badge.svg)](https://github.com/ViewWay/hiver/actions/workflows/benchmark.yml) @@ -502,70 +319,35 @@ You can run most checks locally before pushing: ### Format Check / 格式检查 ```bash cargo fmt --all -- --check -# Or to fix: cargo fmt --all ``` ### Clippy / Lint ```bash -cargo clippy --workspace --all-targets --all-features -- -D warnings -``` - -### Documentation Tests / 文档测试 -```bash -cargo test --doc --all-features --workspace +cargo clippy --workspace --lib -- -W warnings ``` -### Build Documentation / 构建文档 +### Tests / 测试 ```bash -cargo doc --all-features --no-deps --document-private-items +cargo test --workspace --lib --exclude hiver-benches -- --test-threads=1 ``` ### Security Audit / 安全审计 ```bash -cargo install cargo-audit +cargo install cargo-audit cargo-deny cargo audit +cargo deny check advisories licenses bans sources ``` -### Dependency Checks / 依赖检查 -```bash -cargo install cargo-deny -cargo deny check advisories -cargo deny check licenses -cargo deny check bans -``` - -### Unused Dependencies / 未使用依赖 -```bash -cargo install cargo-udeps -cargo +nightly udeps --all-features - -# Or faster alternative: -cargo install cargo-machete -cargo machete -``` - -### Feature Powerset / 特性组合 -```bash -cargo install cargo-hack -cargo hack check --feature-powerset --depth 2 -``` - -### Performance Benchmark / 性能基准测试 +### Coverage Generation / 覆盖率生成 ```bash -cargo install cargo-criterion -cargo criterion --workspace --all-features +cargo install cargo-tarpaulin +cargo tarpaulin --all-features --out Xml ``` ### Semantic Versioning Check / 语义版本检查 ```bash -cargo install cargo-semver-checks -cargo semver-checks check-release -``` - -### Public API Diff / 公共 API 差异 -```bash -cargo install cargo-public-api -cargo public-api --workspace --all-features +cargo install cargo-semver-checks cargo-public-api +cargo semver-checks check-release --workspace --all-features ``` ### Outdated Dependencies / 过时依赖检查 @@ -574,466 +356,49 @@ cargo install cargo-outdated cargo outdated --workspace ``` -### Coverage Generation / 覆盖率生成 -```bash -cargo install cargo-tarpaulin -cargo tarpaulin --all-features --out Xml -``` - ---- - -## Troubleshooting / 故障排除 - -### Workflow Failures / 工作流失败 - -**Issue**: Clippy failures -**Solution**: -```bash -# Run locally to see full output -cargo clippy --workspace --all-targets --all-features -- -D warnings -``` - -**Issue**: Documentation link failures -**Solution**: -```bash -# Build docs with warnings as errors -cargo doc --all-features -- -D warnings -``` - -**Issue**: License check failures -**Solution**: -- Check `deny.toml` for allowed licenses -- Review problematic dependency -- Add exception if necessary - -### Performance / 性能 - -**Issue**: Workflows are slow -**Solutions**: -- Most workflows use path filters to skip unnecessary runs -- Caching is enabled for dependencies -- Jobs run in parallel when possible - ---- - -## Contributing / 贡献 - -When contributing to workflows: -1. Test YAML syntax: Use an online YAML validator -2. Test locally: Use [act](https://github.com/nektos/act) to run GitHub Actions locally -3. Document changes: Update this README -4. Use latest actions: Prefer `@v6` for checkout, `@master` for dtolnay actions - ---- - -## Related Resources / 相关资源 - -- [GitHub Actions Documentation](https://docs.github.com/en/actions) -- [Rust CI/CD Best Practices](https://doc.rust-lang.org/cargo/guide/continuous-integration.html) -- [Clippy Lints](https://rust-lang.github.io/rust-clippy/master/) -- [cargo-deny](https://embarkstudios.github.io/cargo-deny/) -- [cargo-tarpaulin](https://github.com/xd009642/tarpaulin) -- [codecov](https://docs.codecov.com/) - ---- - -## Future Enhancements / 未来增强 - -See [Potential Enhancements](#potential-enhancements) section below. - ---- - -## Potential Enhancements / 潜在增强 - -### ✅ Already Implemented / 已实现 - -The following workflows have been implemented and are active: - -1. ✅ **Performance Benchmarking** - [benchmark.yml](benchmark.yml) - - Uses cargo-criterion for performance tracking - - PR comments on performance regressions - - Historical trend analysis - -2. ✅ **Semantic Versioning Checks** - [semver.yml](semver.yml) - - cargo-semver-checks for API compatibility - - Detects breaking changes automatically - - PR comments with API diff - -3. ✅ **Dependency Review** - Added to [ci.yml](ci.yml) - - actions/dependency-review-action - - Reviews dependency changes in PRs - - License and vulnerability checks - -4. ✅ **Security Analysis** - [codeql.yml](codeql.yml) - - CodeQL comprehensive security scanning - - Weekly scheduled runs - - Custom query suites - -5. ✅ **Outdated Dependencies** - [outdated.yml](outdated.yml) - - Weekly checks for outdated deps - - Automatic issue creation - - Security vulnerability scanning - -6. ✅ **Binary Release** - [binary-release.yml](binary-release.yml) - - Cross-platform binary builds - - Automatic GitHub Releases - - SHA256 checksums - - Homebrew formula - -7. ✅ **Documentation Publishing** - [docs.yml](docs.yml) - - Automatic Rust documentation build - - GitHub Pages deployment - - mdBook support - - Documentation tests - -### 🔄 Future Enhancements / 未来增强 - -The following enhancements are planned but not yet implemented: - -#### Low Priority / 低优先级 - -#### 1. Fuzz Testing / 模糊测试 -**File**: `fuzz.yml` -**Purpose**: Find edge cases and security issues -**Tool**: cargo-fuzz -**Schedule**: Weekly or nightly - -```yaml -name: Fuzz -on: - schedule: - - cron: '0 0 * * 0' # Weekly - workflow_dispatch: - -jobs: - fuzz: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@nightly - - name: Install cargo-fuzz - run: cargo install cargo-fuzz - - name: Run fuzz tests - run: cargo fuzz run parser -- -max-total-time=300 -``` - -#### 2. CI Performance Metrics / CI 性能指标 -**File**: `ci-metrics.yml` -**Purpose**: Track CI performance over time -**Features**: -- Job duration tracking -- Flaky test detection - -#### 3. Mirroring / 镜像 -**File**: Add to workflows -**Purpose**: Mirror to GitLab, Gitea, etc. - -```yaml -- name: Mirror to GitLab - uses: saltudalkar/gitlab-mirror-and-syn-action@v1.1 - with: - target_repo_url: ${{ secrets.GITLAB_TARGET_URL }} - target_username: ${{ secrets.GITLAB_USERNAME }} - target_token: ${{ secrets.GITLAB_TOKEN }} -``` - -#### 4. Issue Automation / Issue 自动化 -**File**: `.github/` workflows -**Features**: -- Auto-close stale issues -- Auto-label PRs -- Checklist generation - ---- -- CodeQL scanning -- Secret scanning -- SBOM generation - -```yaml -- name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: rust - -- name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 -``` - -#### 5. Fuzz Testing / 模糊测试 -**File**: `fuzz.yml` -**Purpose**: Find edge cases and security issues -**Tool**: cargo-fuzz -**Schedule**: Weekly or nightly - -```yaml -name: Fuzz -on: - schedule: - - cron: '0 0 * * 0' # Weekly - workflow_dispatch: - -jobs: - fuzz: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@nightly - - name: Install cargo-fuzz - run: cargo install cargo-fuzz - - name: Run fuzz tests - run: cargo fuzz run parser -- -max-total-time=300 -``` - -#### 6. Outdated Dependencies / 过时依赖 -**File**: `outdated.yml` -**Purpose**: Check for outdated dependencies -**Tool**: cargo-outdated -**Schedule**: Daily/Weekly - -```yaml -name: Check Outdated Dependencies -on: - schedule: - - cron: '0 0 * * 1' # Weekly - workflow_dispatch: - -jobs: - outdated: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable - - name: Install cargo-outdated - run: cargo install cargo-outdated - - name: Check outdated - run: cargo outdated --workspace -``` - -#### 7. Binary Releases / 二进制发布 -**File**: `binary-release.yml` -**Purpose**: Build and release binaries -**Features**: -- Cross-compilation -- GitHub Releases -- Homebrew formula support -- Arch Linux package - -```yaml -name: Binary Release -on: - push: - tags: - - "v*" - -jobs: - release: - strategy: - matrix: - include: - - target: x86_64-unknown-linux-gnu - os: ubuntu-latest - - target: x86_64-apple-darwin - os: macos-latest - - target: x86_64-pc-windows-msvc - os: windows-latest - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - name: Build release - run: cargo build --release --target ${{ matrix.target }} - - name: Upload assets - uses: softprops/action-gh-release@v2 -``` - -### Low Priority / 低优先级 - -#### 8. Documentation Publishing / 文档发布 -**File**: `docs.yml` -**Purpose**: Build and deploy docs to GitHub Pages -**Tools**: cargo doc, mdBook - -```yaml -name: Docs -on: - push: - branches: [main] - -jobs: - docs: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable - - name: Build docs - run: | - cargo doc --all-features --no-deps - echo "" > target/doc/index.html - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./target/doc -``` - -#### 9. CI Performance Metrics / CI 性能指标 -**File**: `ci-metrics.yml` -**Purpose**: Track CI performance -**Features**: -- Job duration tracking -- Flaky test detection - -#### 10. Mirroring / 镜像 -**File**: Add to workflows -**Purpose**: Mirror to GitLab, Gitea, etc. - -```yaml -- name: Mirror to GitLab - uses: saltudalkar/gitlab-mirror-and-syn-action@v1.1 - with: - target_repo_url: ${{ secrets.GITLAB_TARGET_URL }} - target_username: ${{ secrets.GITLAB_USERNAME }} - target_token: ${{ secrets.GITLAB_TOKEN }} -``` - -#### 11. Issue Automation / Issue 自动化 -**File**: `.github/` workflows -**Features**: -- Auto-close stale issues -- Auto-label PRs -- Checklist generation - -#### 12. Custom Actions / 自定义 Actions -**Purpose**: Reusable workflow components -**Examples**: -- Rust setup action -- Cargo cache action -- Test result reporter - --- ## Workflow Summary / 工作流总结 -### Complete CI/CD Pipeline / 完整的 CI/CD 流水线 - -Hiver now has a comprehensive CI/CD pipeline with **15 active workflows**: -Hiver 现在拥有完整的 CI/CD 流水线,包含 **15 个活跃的工作流**: - -#### Core Workflows / 核心工作流 (8) +Hiver's CI/CD pipeline consists of **10 active workflows**: +Hiver 的 CI/CD 流水线包含 **10 个活跃工作流**: | # | Workflow | Purpose | Frequency | |---|----------|---------|-----------| -| 1 | [ci.yml](ci.yml) | Main CI pipeline | Every push/PR | -| 2 | [quality.yml](quality.yml) | Code quality checks | Every push/PR | -| 3 | [linux.yml](linux.yml) | Linux validation | Every push/PR | -| 4 | [macos.yml](macos.yml) | macOS validation | Every push/PR | -| 5 | [windows.yml](windows.yml) | Windows validation | Every push/PR | -| 6 | [coverage.yml](coverage.yml) | Code coverage | Every push/PR | -| 7 | [format.yml](format.yml) | Format validation | Push to main/develop | +| 1 | [ci.yml](ci.yml) | Main CI (fmt/clippy/build/test/audit/deny/msrv) | Every push/PR | +| 2 | [coverage.yml](coverage.yml) | Code coverage | Every push/PR | +| 3 | [benchmark.yml](benchmark.yml) | Performance tracking | Every push/PR | +| 4 | [semver.yml](semver.yml) | API compatibility | Every PR | +| 5 | [check-workspace-deps.yml](check-workspace-deps.yml) | Dep format check | Every push/PR | +| 6 | [codeql.yml](codeql.yml) | Security analysis | Every push/PR + Weekly | +| 7 | [outdated.yml](outdated.yml) | Dependency updates | Weekly + Manual | | 8 | [release.yml](release.yml) | Crate publishing | On tag push | +| 9 | [binary-release.yml](binary-release.yml) | Binary releases | On tag push | +| 10 | [docs.yml](docs.yml) | Documentation | Every push/PR | -#### Enhanced Workflows / 增强工作流 (7) - -| # | Workflow | Purpose | Frequency | -|---|----------|---------|-----------| -| 9 | [benchmark.yml](benchmark.yml) | Performance tracking | Every push/PR | -| 10 | [semver.yml](semver.yml) | API compatibility | Every PR | -| 11 | [codeql.yml](codeql.yml) | Security analysis | Every push/PR + Weekly | -| 12 | [outdated.yml](outdated.yml) | Dependency updates | Weekly + Manual | -| 13 | [binary-release.yml](binary-release.yml) | Binary releases | On tag push | -| 14 | [docs.yml](docs.yml) | Documentation | Every push/PR | -| 15 | [dependabot.yml](../dependabot.yml) | Auto dependency updates | Weekly | - -### Coverage Statistics / 覆盖统计 - -#### Quality Checks (50+ types) / 质量检查(50+ 种) - -**Code Quality**: -- Format, Lint, Spelling, Doc tests, MSRV - -**Security**: -- CodeQL, Vulnerability scanning, License checks, Dependency review - -**Testing**: -- Unit, Integration, Feature combinations, All platforms - -**Performance**: -- Benchmarking with regression detection - -**Release**: -- Automated crates.io and binary releases - -**Documentation**: -- Auto-generated and published to GitHub Pages +Plus [dependabot.yml](../dependabot.yml) for automated dependency updates. +加上 [dependabot.yml](../dependabot.yml) 用于自动依赖更新。 --- -## Cost Optimization / 成本优化 - -### Current Usage / 当前使用 -- ✅ Path filters to skip unnecessary runs -- ✅ Caching for dependencies -- ✅ Parallel job execution -- ✅ Conditional execution - -### Additional Optimizations / 额外优化 -1. Use `concurrency` to cancel outdated runs -2. Implement smart caching for build artifacts -3. Use `actions/upload-artifact` for sharing between jobs -4. Consider using ARM runners for cost savings (10x cheaper) -5. Implement workflow-level caching strategies - ---- - -## Security Best Practices / 安全最佳实践 +## Cost & Security Best Practices / 成本与安全最佳实践 ### Current / 当前 -- ✅ Minimal permissions (`contents: read` mostly) -- ✅ `pull_request_target` for untrusted code -- ✅ Third-party action pinning -- ✅ Secret scanning enabled - -### Recommended / 推荐 -1. Use GitHub Environments for deployment protection -2. Implement branch protection rules -3. Require status checks for merge -4. Use Dependabot for automated updates -5. Regular security audits of workflows -6. Implement CODEOWNERS file -7. Use signed commits for releases - ---- - -## Monitoring / 监控 - -### Metrics to Track / 要跟踪的指标 - -| Metric | Tool | Purpose | -|--------|------|---------| -| Workflow success rate | GitHub Actions | CI reliability | -| Average runtime | GitHub Actions | Performance | -| Code coverage | Codecov | Test quality | -| Vulnerabilities | cargo-audit/deny | Security | -| Dependency freshness | Dependabot | Maintenance | -| Benchmark results | Criterion | Performance | -| Flaky tests | pytest-flaky | Stability | - ---- - -## Maintenance / 维护 - -### Regular Tasks / 定期任务 -- [ ] Review and update Actions monthly -- [ ] Audit workflow permissions quarterly -- [ ] Review and update deny.toml -- [ ] Check for deprecated lints in clippy.toml -- [ ] Update Rust toolchain versions -- [ ] Review and optimize cache strategies -- [ ] Clean up old workflow runs +- ✅ Path filters (`paths`/`paths-ignore`) to skip unnecessary runs +- ✅ `concurrency` to cancel superseded runs (saves minutes) +- ✅ `timeout-minutes` on every job (prevents runaway runners) +- ✅ Minimal `permissions` (`contents: read` default) +- ✅ Third-party actions pinned to major versions +- ✅ `Swatinem/rust-cache@v2` for reliable smart caching across all jobs +- ✅ `actions/checkout@v6` everywhere (improved credential safety) + +### Recommended Next Steps / 推荐的后续步骤 +1. **Cross-platform matrix** (macOS/Windows) — adds CI cost but catches platform bugs +2. Widen clippy/test to `--all-targets` once the runtime-fix branch merges +3. GitHub Environments for release protection +4. Branch protection rules requiring status checks to merge +5. `CODEOWNERS` file for automatic review requests +6. Signed commits for releases --- @@ -1041,13 +406,3 @@ Hiver 现在拥有完整的 CI/CD 流水线,包含 **15 个活跃的工作流* These workflows are part of the Hiver project and follow the same license (MIT OR Apache-2.0). 这些工作流是 Hiver 项目的一部分,遵循相同的许可证(MIT OR Apache-2.0)。 - ---- - -## Contact / 联系方式 - -For questions or issues with the workflows: -关于工作流的问题或疑问: -- Open an issue on [GitHub](https://github.com/ViewWay/hiver/issues) -- Check [GitHub Actions Documentation](https://docs.github.com/en/actions) -- Review [Rust CI/CD Guide](https://doc.rust-lang.org/cargo/guide/continuous-integration.html) diff --git a/.github/workflows/auto-publish.yml b/.github/workflows/auto-publish.yml deleted file mode 100644 index 975bbb4e..00000000 --- a/.github/workflows/auto-publish.yml +++ /dev/null @@ -1,244 +0,0 @@ -# Auto Publish to crates.io -# 自动发布到 crates.io -# -# Triggers after CI succeeds on main branch. -# 在 main 分支 CI 通过后自动触发。 -# -# Flow: CI (ci.yml) passes → detect version change → publish all crates -# 流程:CI (ci.yml) 通过 → 检测版本变更 → 发布所有 crate - -name: Auto Publish - -permissions: - contents: read - -on: - # Disabled: conflicts with release.yml tag-based publishing. - # Use release.yml (push v* tag) for crate publishing. - # 已禁用:与 release.yml 的 tag 发布冲突。使用 release.yml 推送 v* tag 发布。 - workflow_dispatch: - -jobs: - version-check: - name: Detect Version Change / 检测版本变更 - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'success' }} - outputs: - should_publish: ${{ steps.check.outputs.changed }} - version: ${{ steps.check.outputs.version }} - steps: - - uses: actions/checkout@v4 - - - uses: dtolnay/rust-toolchain@stable - - - name: Get local version / 获取本地版本 - id: check - run: | - LOCAL_VERSION=$(sed -nE 's/^\s*version\s*=\s*"(.*?)"$/\1/p' Cargo.toml | head -1) - echo "version=$LOCAL_VERSION" >> $GITHUB_OUTPUT - - # Check the first crate (hiver-runtime) as proxy for workspace version - REMOTE_VERSION=$(cargo search hiver-runtime --limit 1 2>/dev/null | sed -nE 's/^[^"]*"//; s/".*//p') - REMOTE_VERSION=${REMOTE_VERSION:-none} - - echo "Local: $LOCAL_VERSION, Remote: $REMOTE_VERSION" - - if [ "$LOCAL_VERSION" != "$REMOTE_VERSION" ]; then - echo "changed=true" >> $GITHUB_OUTPUT - echo "📦 Version changed: $REMOTE_VERSION → $LOCAL_VERSION" - else - echo "changed=false" >> $GITHUB_OUTPUT - echo "✅ Version unchanged ($LOCAL_VERSION), skipping publish" - fi - - publish: - name: Publish ${{ matrix.package.crate }} - runs-on: ubuntu-latest - needs: version-check - if: needs.version-check.outputs.should_publish == 'true' - strategy: - fail-fast: false - max-parallel: 1 - matrix: - package: - # Publish order follows dependency tiers (dependencies first). - # 发布顺序遵循依赖层级(被依赖的先发布)。 - - # Tier 0: No internal hiver deps / 无内部 hiver 依赖 - - crate: hiver-runtime - path: crates/hiver-runtime - - crate: hiver-exceptions - path: crates/hiver-exceptions - - crate: hiver-response - path: crates/hiver-response - - crate: hiver-cloud - path: crates/hiver-cloud - - crate: hiver-state-machine - path: crates/hiver-state-machine - - crate: hiver-vault - path: crates/hiver-vault - - crate: hiver-spel - path: crates/hiver-spel - - crate: hiver-ai - path: crates/hiver-ai - - crate: hiver-aop - path: crates/hiver-aop - - crate: hiver-grpc - path: crates/hiver-grpc - - crate: hiver-devtools - path: crates/hiver-devtools - - crate: hiver-mail - path: crates/hiver-mail - - # Tier 1: Depend on Tier 0 or already-published crates / 依赖 Tier 0 或已发布 - - crate: hiver-core - path: crates/hiver-core - - crate: hiver-config - path: crates/hiver-config - - crate: hiver-data-commons - path: crates/hiver-data-commons - - crate: hiver-tx - path: crates/hiver-tx - - crate: hiver-schedule - path: crates/hiver-schedule - - crate: hiver-security - path: crates/hiver-security - - crate: hiver-web3 - path: crates/hiver-web3 - - crate: hiver-kafka - path: crates/hiver-kafka - - crate: hiver-amqp - path: crates/hiver-amqp - - crate: hiver-modulith - path: crates/hiver-modulith - - crate: hiver-observability - path: crates/hiver-observability - - crate: hiver-ws - path: crates/hiver-ws - - crate: hiver-async - path: crates/hiver-async - - crate: hiver-reactor - path: crates/hiver-reactor - - crate: hiver-batch - path: crates/hiver-batch - - crate: hiver-micrometer - path: crates/hiver-micrometer - - crate: hiver-i18n - path: crates/hiver-i18n - - crate: hiver-ldap - path: crates/hiver-ldap - - crate: hiver-integration - path: crates/hiver-integration - - crate: hiver-flyway - path: crates/hiver-flyway - - crate: hiver-data-redis - path: crates/hiver-data-redis - - crate: hiver-data-mongodb - path: crates/hiver-data-mongodb - - crate: hiver-mcp - path: crates/hiver-mcp - - # Tier 2: Depend on Tier 0-1 / 依赖 Tier 0-1 - - crate: hiver-http - path: crates/hiver-http - - crate: hiver-router - path: crates/hiver-router - - crate: hiver-extractors - path: crates/hiver-extractors - - crate: hiver-middleware - path: crates/hiver-middleware - - crate: hiver-multipart - path: crates/hiver-multipart - - crate: hiver-resilience - path: crates/hiver-resilience - - crate: hiver-validation - path: crates/hiver-validation - - crate: hiver-events - path: crates/hiver-events - - crate: hiver-actuator - path: crates/hiver-actuator - - crate: hiver-graphql - path: crates/hiver-graphql - - crate: hiver-hateoas - path: crates/hiver-hateoas - - crate: hiver-websocket-stomp - path: crates/hiver-websocket-stomp - - crate: hiver-retry - path: crates/hiver-retry - - crate: hiver-agent - path: crates/hiver-agent - - crate: hiver-shell - path: crates/hiver-shell - - crate: hiver-data-rdbc - path: crates/hiver-data-rdbc - - crate: hiver-cache - path: crates/hiver-cache - - crate: hiver-data-annotations - path: crates/hiver-data-annotations - - crate: hiver-openapi - path: crates/hiver-openapi - # cloud-stream depends on hiver-kafka/hiver-amqp (Tier 1); - # cloud-bus depends on cloud-stream — order is significant. - # cloud-stream 依赖 hiver-kafka/hiver-amqp(Tier 1); - # cloud-bus 依赖 cloud-stream —— 顺序有影响。 - - crate: hiver-cloud-stream - path: crates/hiver-cloud-stream - - crate: hiver-cloud-bus - path: crates/hiver-cloud-bus - - crate: hiver-data-rest - path: crates/hiver-data-rest - - # Tier 2 (proc-macro crates): Published after their runtime deps. - # proc-macro crate 在运行时依赖之后发布。 - - crate: hiver-macros - path: crates/hiver-macros - - crate: hiver-data-macros - path: crates/hiver-data-macros - - crate: hiver-events-macros - path: crates/hiver-events-macros - - crate: hiver-retry-macros - path: crates/hiver-retry-macros - - crate: hiver-shell-macros - path: crates/hiver-shell-macros - - crate: hiver-validation-annotations - path: crates/hiver-validation-annotations - - crate: hiver-lombok - path: crates/hiver-lombok - - crate: hiver-aop-macros - path: crates/hiver-aop-macros - - # Tier 3: Depend on multiple layers / 依赖多层 - - crate: hiver-session - path: crates/hiver-session - - crate: hiver-data-orm - path: crates/hiver-data-orm - - crate: hiver-cli - path: crates/hiver-cli - - # Tier 4: Aggregation crates / 聚合 crate - - crate: hiver-test - path: crates/hiver-test - - crate: hiver-starter - path: crates/hiver-starter - - steps: - - uses: actions/checkout@v4 - - - uses: dtolnay/rust-toolchain@stable - - - name: Get releasing version / 获取发布版本 - run: echo NEXT_VERSION=$(sed -nE 's/^\s*version\s*=\s*"(.*?)"$/\1/p' Cargo.toml | head -1) >> $GITHUB_ENV - - - name: Check published version / 检查已发布版本 - run: | - PREV=$(cargo search ${{ matrix.package.crate }} --limit 1 2>/dev/null | sed -nE 's/^[^"]*"//; s/".*//p') - echo "PREV_VERSION=${PREV:-none}" >> $GITHUB_ENV - - - name: Publish ${{ matrix.package.crate }} - if: env.NEXT_VERSION != env.PREV_VERSION - working-directory: ${{ matrix.package.path }} - run: | - echo "📦 Publishing ${{ matrix.package.crate }} $NEXT_VERSION (was: $PREV_VERSION)" - cargo login ${{ secrets.CRATES_TOKEN }} - cargo publish --no-verify --allow-dirty - echo "✅ Published ${{ matrix.package.crate }} $NEXT_VERSION" diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 1e1fdc5f..8c9de73a 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -37,23 +37,8 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v4 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo build - uses: actions/cache@v4 - with: - path: target - key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + uses: Swatinem/rust-cache@v2 - name: Install system dependencies / 安装系统依赖 run: | diff --git a/.github/workflows/check-workspace-deps.yml b/.github/workflows/check-workspace-deps.yml index d2ea58ec..57ab7992 100644 --- a/.github/workflows/check-workspace-deps.yml +++ b/.github/workflows/check-workspace-deps.yml @@ -36,45 +36,82 @@ jobs: - name: Check for direct version specifications run: | + set -euo pipefail echo "🔍 Checking for direct version specifications in crates..." - # Find all Cargo.toml files in crates directory + # Strategy: only scan inside dependency tables ([dependencies], + # [dev-dependencies], [build-dependencies], and target-specific + # variants like [target.'cfg(...)'.dependencies]). + # + # We deliberately do NOT scan the whole file, because: + # - Cargo's package-metadata shorthand is legitimate and idiomatic: + # [package] + # version.workspace = true + # edition.workspace = true + # An earlier version of this check grepped `.workspace = true` + # globally and produced ~13 false positives on valid metadata. + # - `[features]` tables also look like deps syntactically + # (`lapin = ["dep:lapin"]`) and caused false positives too. + # + # IMPORTANT: use POSIX [[:space:]] not \s — the \s regex token is a + # GNU extension and silently fails to match on BSD awk (macOS), + # causing the whole check to report zero violations. + # + # 策略:仅扫描依赖表([dependencies] / [dev-dependencies] / + # [build-dependencies] 及 target 专有变体)。 + # 故意不扫描整个文件:package 元数据简写(version.workspace = true 等) + # 合法且符合习惯;[features] 表语法上也像依赖(lapin = ["dep:lapin"])。 + # 重要:使用 POSIX [[:space:]] 而非 \s —— \s 是 GNU 扩展,在 BSD awk + # (macOS)上静默不匹配,会导致整项检查报告零违规。 + CRATES=$(find crates -name "Cargo.toml" -type f) ERRORS=0 + extract_dep_lines() { + # Print only dependency lines that fall under a *dependencies table. + # A path dependency (path = "..") is a workspace-internal reference + # and correctly uses inline version — exclude it. + # 仅打印位于 *dependencies 表下的依赖行。 + # path 依赖(path = "..")是 workspace 内部引用,内联 version 是 + # 正确的 —— 排除它。 + awk ' + /^[[:space:]]*\[/ { + sect = $0 + sub(/^[[:space:]]*\[[[:space:]]*/, "", sect) + sub(/[[:space:]]*\].*$/, "", sect) + in_deps = (sect ~ /dependencies$/) + next + } + in_deps && /^[[:space:]]*[A-Za-z0-9_-]+[[:space:]]*=/ { print } + ' "$1" \ + | grep -E 'version[[:space:]]*=[[:space:]]*"[0-9]' \ + | grep -v 'workspace = true' \ + | grep -v 'path = ' \ + | grep -v '# Note:' || true + } + for file in $CRATES; do echo "Checking $file..." - - # Check for direct version specifications (excluding workspace = true) - # Pattern: version = "X.Y.Z" or version = "X.Y" but not { workspace = true } - if grep -E '^\s*[a-zA-Z0-9_-]+\s*=\s*\{?\s*version\s*=\s*"[0-9]' "$file" | grep -v "workspace = true" | grep -v "optional = true" | grep -v "# Note:" > /dev/null; then + hits=$(extract_dep_lines "$file") + if [ -n "$hits" ]; then echo "❌ Found direct version specification in $file:" - grep -E '^\s*[a-zA-Z0-9_-]+\s*=\s*\{?\s*version\s*=\s*"[0-9]' "$file" | grep -v "workspace = true" | grep -v "optional = true" | grep -v "# Note:" || true - ERRORS=$((ERRORS + 1)) - fi - - # Check for old-style workspace syntax (dep.workspace = true) - if grep -E '\.workspace\s*=\s*true' "$file" > /dev/null; then - echo "❌ Found old-style workspace syntax in $file:" - grep -E '\.workspace\s*=\s*true' "$file" || true + echo "$hits" | sed 's/^/ /' ERRORS=$((ERRORS + 1)) fi done if [ $ERRORS -gt 0 ]; then echo "" - echo "⚠️ Found $ERRORS issue(s) with dependency specifications." - echo "" - echo "Please ensure:" - echo "1. All dependencies use { workspace = true } instead of direct versions" - echo "2. Use { workspace = true } instead of .workspace = true syntax" - echo "3. Add missing dependencies to workspace Cargo.toml if needed" + echo "❌ Found $ERRORS file(s) with direct (non-workspace) dependency versions." echo "" - echo "Example of correct format:" - echo ' serde = { workspace = true }' - echo ' tokio = { workspace = true, features = ["macros"] }' + echo "Fix: move the version into the root Cargo.toml [workspace.dependencies]," + echo "then reference it in the crate as:" + echo ' = { workspace = true, features = [...] } # (+ optional = true if needed)' echo "" - echo "::warning::$ERRORS workspace dependency issue(s) found. See log above." + echo "::error::$ERRORS file(s) have non-workspace dependency versions. See log above." + # Fail the job: this check must gate merges, not just warn. + # 让 job 失败:此检查必须阻止违规合并,而非仅警告。 + exit 1 fi echo "✅ All crates use workspace dependencies correctly!" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index deb40bbc..087a35d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,17 +4,30 @@ # Runs on every push to main and every PR. # 每次 push 到 main 和每个 PR 都会运行。 # -# Jobs: fmt → clippy → build → test → audit (all parallel) -# 发布由 auto-publish.yml 在 CI 通过后自动触发。 +# Jobs: fmt → clippy → build → test → audit → deny → msrv (all parallel) +# 发布由 release.yml 在推送 v* tag 后自动触发。 name: CI +permissions: + contents: read + on: push: branches: [main] + paths-ignore: + - '**/*.md' + - 'docs/**' + - '.github/**/*.md' pull_request: branches: [main] +# Cancel superseded runs on the same branch / PR — saves runner minutes. +# 取消同一分支/PR 上被取代的运行 —— 节省 runner 分钟数。 +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 @@ -23,8 +36,9 @@ jobs: fmt: name: Format runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@nightly with: @@ -36,8 +50,9 @@ jobs: clippy: name: Clippy runs-on: ubuntu-latest + timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: @@ -54,8 +69,9 @@ jobs: build: name: Build runs-on: ubuntu-latest + timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable @@ -70,8 +86,9 @@ jobs: test: name: Test runs-on: ubuntu-latest + timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable @@ -89,11 +106,28 @@ jobs: # - io_uring_enter timeout via EXT_ARG — sigsz uninit fixed (7dd3083) # valgrind ERROR SUMMARY: 0 errors. Runtime covered by main test job. + - name: Run integration tests (socket / waker / e2e) / 运行集成测试(socket / waker / e2e) + run: | + # These exercise real sockets on THIS platform's driver: + # - Linux (here): epoll + io_uring FD→waker path + # - macOS (local dev): kqueue FD→waker path + # 这些在本平台 driver 上演练真实 socket: + # - Linux(此处):epoll + io_uring FD→waker 路径 + # - macOS(本地开发):kqueue FD→waker 路径 + cargo test -p hiver-runtime --test waker_tcp_test -- --test-threads=1 + # The http_server_e2e test spawns the example binary over a real socket. + # Build the binary first so the subprocess test can find it. + # http_server_e2e 测试经真实 socket 启动示例二进制。先构建二进制, + # 使子进程测试能找到它。 + cargo build -p hiver-examples --bin http_server_example + cargo test -p hiver-tests --test e2e http_server_e2e -- --test-threads=1 + audit: name: Security Audit runs-on: ubuntu-latest + timeout-minutes: 20 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable @@ -103,6 +137,13 @@ jobs: run: cargo install cargo-audit - name: Run security audit / 运行安全审计 + # NOTE: the ignore list is tracked here (not in deny.toml) because + # cargo-audit and cargo-deny have different ignore granularity and + # these specific RUSTSEC ids are known/accepted transitive advisories. + # The `deny` job below covers license/bans/source checks. + # 注意:忽略列表在此跟踪(不在 deny.toml 中),因为 cargo-audit 和 + # cargo-deny 的忽略粒度不同,这些 RUSTSEC id 是已知/接受的传递性公告。 + # 下面的 `deny` job 覆盖 license/bans/source 检查。 run: | cargo audit \ --ignore RUSTSEC-2023-0071 \ @@ -117,3 +158,47 @@ jobs: --ignore RUSTSEC-2024-0370 \ --ignore RUSTSEC-2025-0134 \ --ignore RUSTSEC-2026-0172 + + # Authoritative license / bans / source-registry check using deny.toml. + # Splits cleanly from `audit` above (advisory ignore granularity differs). + # 使用 deny.toml 的权威 license / bans / source-registry 检查。 + # 与上面的 `audit`(公告忽略粒度不同)清晰分离。 + deny: + name: cargo-deny + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v6 + + - uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2 + + - name: Install cargo-deny / 安装 cargo-deny + run: cargo install cargo-deny + + - name: Run cargo-deny / 运行 cargo-deny + run: cargo deny check advisories licenses bans sources + + # Verify the declared Minimum Supported Rust Version (Cargo.toml: rust-version = "1.91"). + # NOTE: bumped from 1.87 after the MSRV job revealed deps (alloy@2.0.5 etc.) + # require up to rustc 1.91. The previous value was incorrect. + # 验证声明的最低支持 Rust 版本(Cargo.toml: rust-version = "1.91")。 + # 注意:MSRV job 发现依赖(alloy@2.0.5 等)要求 rustc 1.91,故从 1.87 上调。 + msrv: + name: MSRV 1.91 + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v6 + + - name: Install Rust 1.91 toolchain + uses: dtolnay/rust-toolchain@1.91 + + - name: Install system dependencies / 安装系统依赖 + run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev + + - uses: Swatinem/rust-cache@v2 + + - name: Verify workspace builds on MSRV / 验证 workspace 在 MSRV 上构建 + run: cargo check --workspace --lib diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index cbc4d85d..d18b3ac7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -48,23 +48,8 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v4 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo build - uses: actions/cache@v4 - with: - path: target - key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + uses: Swatinem/rust-cache@v2 - name: Install system dependencies / 安装系统依赖 run: | diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f9836ea6..07a7f9e4 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -34,23 +34,8 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v4 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo build - uses: actions/cache@v4 - with: - path: target - key: ${{ runner.os }}-cargo-build-coverage-${{ hashFiles('**/Cargo.lock') }} + uses: Swatinem/rust-cache@v2 - name: Install system dependencies / 安装系统依赖 run: | @@ -66,7 +51,7 @@ jobs: run: cargo tarpaulin --workspace --all-features --out Xml - name: Upload to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: files: ./cobertura.xml flags: unittests diff --git a/.github/workflows/debug-sigsegv.yml b/.github/workflows/debug-sigsegv.yml deleted file mode 100644 index 75de5dc0..00000000 --- a/.github/workflows/debug-sigsegv.yml +++ /dev/null @@ -1,105 +0,0 @@ -# Phase 0.1 诊断 workflow:复现 runtime SIGSEGV 并拿 gdb backtrace -# 触发:手动 (workflow_dispatch) 或本文件变更 -# 背景:cargo test --workspace 时 hiver-runtime --lib 进程 signal 11 SIGSEGV(单跑通过) -# 根因调查见 docs/superpowers/plans/2026-06-13-development-roadmap.md Phase 0.1 -# 主嫌疑:raw_task.rs poll_impl Ready 路径 drop_in_place(future) 触发 re-entrant dec_ref -name: Debug SIGSEGV - -on: - workflow_dispatch: - push: - paths: - - '.github/workflows/debug-sigsegv.yml' - -jobs: - reproduce: - name: Reproduce & backtrace runtime SIGSEGV - runs-on: ubuntu-latest - timeout-minutes: 40 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - - name: Install gdb - run: sudo apt-get update && sudo apt-get install -y gdb libcurl4-openssl-dev - - - name: Configure core dumps - run: | - sudo sysctl -w kernel.core_pattern='/tmp/core.%p.%s' || true - sudo sysctl -w fs.suid_dumpable=2 || true - - - name: Build test binaries - run: cargo test --workspace --no-fail-fast --no-run - - - name: Probe io_uring availability (diagnose EINVAL, code vs environment) - run: | - cat > /tmp/probe.c <<'CEOF' - #include - #include - #include - #include - #include - #include - int main(void) { - struct io_uring_params p; - memset(&p, 0, sizeof(p)); - long fd = syscall(SYS_io_uring_setup, 256, &p); - if (fd < 0) { printf("PROBE_RESULT: io_uring_setup(256, zeroed) FAILED errno=%d (%s)\n", errno, strerror(errno)); return 1; } - printf("PROBE_RESULT: io_uring_setup(256, zeroed) OK fd=%ld sq_entries=%u cq_entries=%u\n", fd, p.sq_entries, p.cq_entries); - close((int)fd); - return 0; - } - CEOF - gcc /tmp/probe.c -o /tmp/probe && /tmp/probe || true - - - name: Valgrind runtime --lib (locate heap corruption / bad free / UAF) - run: | - sudo apt-get install -y valgrind - cargo test -p hiver-runtime --lib --no-run - BIN=$(find target/debug/deps -maxdepth 1 -name 'hiver_runtime-*' -type f ! -name '*.d' | head -1) - echo "=== valgrind on $BIN ===" - valgrind --error-exitcode=99 --leak-check=no \ - "$BIN" --test-threads=1 2>&1 | grep -iE 'Invalid|uninitialised|Address |freed|ERROR SUMMARY|tcache|bad|by 0x|at 0x' | head -50 || true - - - name: Run workspace tests (up to 5x — UB is probabilistic) - run: | - ulimit -c unlimited - export RUST_BACKTRACE=full - export RUST_LIB_BACKTRACE=full - for i in 1 2 3 4 5; do - echo "=========== run $i ===========" - cargo test --workspace --no-fail-fast 2>&1 | tee -a runs.log || true - if ls /tmp/core.* >/dev/null 2>&1; then - echo ">>> SIGSEGV reproduced on run $i" - break - fi - done - echo "=== cores ===" - ls -la /tmp/core.* 2>/dev/null || echo "no cores — SIGSEGV did NOT reproduce on Linux (UB may be macOS/kqueue-specific or address-layout dependent; next step: add macOS matrix)" - - - name: gdb backtrace from cores - if: always() - run: | - for core in /tmp/core.*; do - [ -f "$core" ] || continue - echo "=============================" - echo "=== backtrace: $core ===" - echo "=============================" - gdb -batch -ex "set pagination off" -ex "bt full" -ex "thread apply all bt" "$core" 2>&1 || true - done - - - name: Collect diagnostics - if: always() - run: | - mkdir -p diagnostics - cp /tmp/core.* diagnostics/ 2>/dev/null || true - cp runs.log diagnostics/ 2>/dev/null || true - - - name: Upload diagnostics - if: always() - uses: actions/upload-artifact@v4 - with: - name: sigsegv-diagnostics - path: diagnostics/ - if-no-files-found: ignore diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 138d9ac1..f0e198f6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -47,17 +47,8 @@ jobs: # Install mdBook plugins cargo install mdbook-toc mdbook-mermaid - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - name: Cache cargo build - uses: actions/cache@v4 - with: - path: target - key: ${{ runner.os }}-cargo-build-docs-${{ hashFiles('**/Cargo.lock') }} + uses: Swatinem/rust-cache@v2 - name: Install system dependencies / 安装系统依赖 run: | diff --git a/.github/workflows/outdated.yml b/.github/workflows/outdated.yml index e7edf3c3..fc2214f9 100644 --- a/.github/workflows/outdated.yml +++ b/.github/workflows/outdated.yml @@ -35,11 +35,8 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo build + uses: Swatinem/rust-cache@v2 - name: Install cargo-outdated run: cargo install cargo-outdated @@ -76,11 +73,8 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo build + uses: Swatinem/rust-cache@v2 - name: Install cargo-audit run: cargo install cargo-audit diff --git a/.github/workflows/semver.yml b/.github/workflows/semver.yml index 3548342f..a166426f 100644 --- a/.github/workflows/semver.yml +++ b/.github/workflows/semver.yml @@ -8,7 +8,7 @@ name: SemVer Checks permissions: contents: read - pull-requests: read + pull-requests: write on: pull_request: @@ -18,6 +18,10 @@ on: - '**/*.rs' - '**/Cargo.toml' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always @@ -25,6 +29,7 @@ jobs: semver-check: name: API Compatibility Check runs-on: ubuntu-latest + timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@v6 @@ -34,27 +39,27 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v4 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo build + uses: Swatinem/rust-cache@v2 - name: Install cargo-semver-checks run: cargo install cargo-semver-checks + # NOTE: do NOT append `|| true`. Earlier the `|| true` swallowed every + # failure, making this check cosmetic. Real breaking changes must fail + # the PR. cargo-semver-checks fails (exit 1) on violations by default; + # the `--fail-on-error` flag was removed in a recent release and now + # errors with "unexpected argument", so we pass only --workspace and + # --all-features. + # 注意:不要加 `|| true`。之前 `|| true` 吞掉所有失败,使检查形同虚设。 + # 真正的破坏性变更必须让 PR 失败。cargo-semver-checks 默认在违规时 + # 失败(exit 1);`--fail-on-error` 在近期版本已移除,现在会报 + # "unexpected argument",故只传 --workspace 和 --all-features。 - name: Run semver checks run: | cargo semver-checks check-release \ --workspace \ - --all-features \ - --fail-on-error || true + --all-features - name: Generate semver report if: always() @@ -68,10 +73,10 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY echo "### 📋 What gets checked:" >> $GITHUB_STEP_SUMMARY echo "- Public API changes" >> $GITHUB_STEP_SUMMARY - "- Trait method signatures" >> $GITHUB_STEP_SUMMARY - "- Struct field visibility" >> $GITHUB_STEP_SUMMARY - "- Enum variant changes" >> $GITHUB_STEP_SUMMARY - "- Type parameter changes" >> $GITHUB_STEP_SUMMARY + echo "- Trait method signatures" >> $GITHUB_STEP_SUMMARY + echo "- Struct field visibility" >> $GITHUB_STEP_SUMMARY + echo "- Enum variant changes" >> $GITHUB_STEP_SUMMARY + echo "- Type parameter changes" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### 📚 Resources:" >> $GITHUB_STEP_SUMMARY echo "- [Semantic Versioning 2.0.0](https://semver.org/)" >> $GITHUB_STEP_SUMMARY @@ -83,6 +88,7 @@ jobs: api-diff: name: Public API Diff runs-on: ubuntu-latest + timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@v6 @@ -92,18 +98,56 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable + - name: Cache cargo build + uses: Swatinem/rust-cache@v2 + - name: Install cargo-public-api run: cargo install cargo-public-api + # Compute the real diff between origin/main and the PR head. + # + # NOTE: cargo-public-api does NOT support --workspace (it operates on one + # crate at a time via rustdoc JSON). The old workflow passed --workspace, + # which errored with "unexpected argument"; the `|| true` on the diff + # step masked it, so the posted "diff" was always empty. We now loop over + # every workspace library crate. + # 注意:cargo-public-api 不支持 --workspace(它基于 rustdoc JSON 逐 crate + # 操作)。旧 workflow 传 --workspace 会报 "unexpected argument";diff 步骤的 + # `|| true` 掩盖了它,导致发布的 "diff" 永远为空。现在遍历每个 workspace + # 库 crate。 - name: Generate API diff run: | - git checkout origin/main - cargo public-api --workspace --all-features > /tmp/api-old.txt - - git checkout - - cargo public-api --workspace --all-features > /tmp/api-new.txt - - diff -u /tmp/api-old.txt /tmp/api-new.txt || true + # Collect all library members of the workspace. + CRATES=$(cargo metadata --no-deps --format-version 1 \ + | jq -r '.packages[] | select(.targets[]?.kind | index(["lib"])) | .name' \ + | sort -u) + + : > /tmp/api-diff.txt + + for crate in $CRATES; do + git checkout origin/main -- 2>/dev/null + old=$(cargo public-api -p "$crate" --all-features 2>/dev/null || true) + + git checkout - -- 2>/dev/null + new=$(cargo public-api -p "$crate" --all-features 2>/dev/null || true) + + # Produce a per-crate unified diff; skip crates with no changes. + crate_diff=$(diff <(printf '%s' "$old") <(printf '%s' "$new") || true) + if [ -n "$crate_diff" ]; then + echo "### 📦 $crate" >> /tmp/api-diff.txt + echo '```diff' >> /tmp/api-diff.txt + echo "$crate_diff" >> /tmp/api-diff.txt + echo '```' >> /tmp/api-diff.txt + echo "" >> /tmp/api-diff.txt + fi + done + + if [ -s /tmp/api-diff.txt ]; then + echo "📋 Public API changes detected (see /tmp/api-diff.txt)." + else + echo "✅ No public API changes detected." + echo "No public API changes." > /tmp/api-diff.txt + fi - name: Comment PR with API changes if: github.event_name == 'pull_request' @@ -111,7 +155,7 @@ jobs: with: script: | const fs = require('fs'); - const diff = fs.readFileSync('/tmp/api-new.txt', 'utf8'); + const diff = fs.readFileSync('/tmp/api-diff.txt', 'utf8'); const output = `## 📦 Public API Changes\n\n\`\`\`diff\n${diff}\n\`\`\``; github.rest.issues.createComment({ issue_number: context.issue.number, diff --git a/Cargo.lock b/Cargo.lock index c1b66b4d..d1cc2ed1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1811,6 +1811,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io 2.6.0", + "blocking", + "futures-lite 2.6.1", +] + [[package]] name = "async-reactor-trait" version = "1.1.0" @@ -5105,6 +5116,7 @@ dependencies = [ "dashmap", "futures", "hiver-data-redis", + "hiver-macros", "hiver-runtime", "metrics", "moka", @@ -5169,6 +5181,8 @@ version = "0.1.0-alpha.6" dependencies = [ "async-trait", "hiver-cloud-stream", + "hiver-macros", + "hiver-runtime", "serde", "serde_json", "thiserror 2.0.18", @@ -5422,6 +5436,8 @@ dependencies = [ "erased-serde 0.4.10", "hiver-core", "hiver-events-macros", + "hiver-macros", + "hiver-runtime", "serde", "thiserror 2.0.18", "tokio", @@ -5453,10 +5469,12 @@ dependencies = [ "hiver-observability", "hiver-resilience", "hiver-router", + "hiver-runtime", "hiver-security", "hiver-starter", "hiver-validation", "hiver-web3", + "inventory", "reqwest 0.12.28", "serde", "serde_json", @@ -5518,7 +5536,9 @@ dependencies = [ "futures", "hiver-core", "hiver-http", + "hiver-macros", "hiver-router", + "hiver-runtime", "reqwest 0.12.28", "serde", "serde_json", @@ -5533,6 +5553,8 @@ version = "0.1.0-alpha.6" dependencies = [ "async-trait", "futures", + "hiver-macros", + "hiver-runtime", "http 1.4.1", "prost 0.13.5", "prost-types 0.13.5", @@ -5542,7 +5564,7 @@ dependencies = [ "tokio", "tokio-stream", "tonic 0.12.3", - "tower 0.4.13", + "tower 0.5.3", "tracing", "uuid", ] @@ -5629,6 +5651,7 @@ dependencies = [ "anyhow", "async-trait", "futures", + "hiver-macros", "hiver-runtime", "once_cell", "rdkafka", @@ -5647,6 +5670,8 @@ dependencies = [ "async-trait", "base64 0.22.1", "hiver-core", + "hiver-macros", + "hiver-runtime", "ldap3", "parking_lot", "serde", @@ -5771,6 +5796,7 @@ dependencies = [ "async-trait", "chrono", "futures", + "hiver-macros", "hiver-runtime", "inventory", "once_cell", @@ -5875,6 +5901,7 @@ dependencies = [ "futures", "hiver-core", "hiver-http", + "hiver-macros", "hiver-observability", "hiver-runtime", "http 1.4.1", @@ -5957,12 +5984,18 @@ version = "0.1.0-alpha.6" dependencies = [ "anyhow", "arc-swap", + "async-channel", + "async-executor", + "async-io 2.6.0", + "async-net", + "blocking", "bytes", "criterion", "crossbeam", "dashmap", "flume", "futures", + "futures-lite 2.6.1", "futures-util", "io-uring", "kqueue", @@ -6037,6 +6070,8 @@ dependencies = [ "hiver-core", "hiver-data-mongodb", "hiver-data-redis", + "hiver-macros", + "hiver-runtime", "serde", "serde_json", "thiserror 2.0.18", @@ -6176,6 +6211,7 @@ dependencies = [ "hiver-modulith", "hiver-response", "hiver-router", + "hiver-runtime", "hiver-spel", "hiver-state-machine", "lapin", @@ -6200,6 +6236,7 @@ dependencies = [ "async-trait", "futures", "hiver-http", + "hiver-macros", "hiver-runtime", "once_cell", "sea-orm", @@ -6310,7 +6347,9 @@ dependencies = [ "futures-util", "hiver-core", "hiver-http", + "hiver-macros", "hiver-router", + "hiver-runtime", "md-5 0.11.0", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index b9bd6fc6..695f1254 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ repository = "https://github.com/ViewWay/hiver" homepage = "https://viewway.github.io/hiver" license = "MIT OR Apache-2.0" edition = "2024" -rust-version = "1.87" +rust-version = "1.91" keywords = ["http", "async", "web", "framework", "hiver"] categories = [ "web-programming::http-server", @@ -210,6 +210,11 @@ tokio-cron-scheduler = "0.15.1" sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "postgres", "mysql", "chrono"] } sea-orm = { version = "2.0.0-rc", features = ["runtime-tokio-rustls", "sqlx-sqlite", "sqlx-postgres", "sqlx-mysql"] } diesel = { version = "2.3", features = ["postgres", "mysql", "sqlite"] } +mongodb = "3.7" +redis = { version = "0.27", features = ["aio", "tokio-comp", "connection-manager"] } +deadpool = "0.12" +deadpool-redis = "0.16" +rmp-serde = "1.3" # Transaction Management / 事务管理 # Equivalent to: Spring Transaction Management @@ -240,6 +245,19 @@ handlebars = "6.4.0" tokio-tungstenite = "0.28.0" tungstenite = "0.28.0" +# AMQP / AMQP (Spring AMQP equivalent) +# Used by hiver-amqp +lapin = "2.5" + +# Kafka / Kafka (Spring Kafka equivalent) +# Used by hiver-kafka +rdkafka = { version = "0.36", features = ["cmake-build", "tokio"] } + +# Compression / 压缩 (Spring compression middleware equivalent) +# Used by hiver-middleware +flate2 = "1.0" +brotli = "7.0" + # HTTP/2 & TLS / HTTP/2 和 TLS # Equivalent to: Spring HTTP/2 support h2 = "0.4" @@ -247,6 +265,10 @@ rustls = "0.23" rustls-pemfile = "2.2.0" tokio-rustls = "0.26" +# gRPC / gRPC (Spring gRPC equivalent) +# Used by hiver-grpc +tonic = { version = "0.12", features = ["transport", "tls"] } + # HTTP/3 / HTTP/3 (Spring HTTP/3 equivalent - future) # Equivalent to: Spring HTTP/3 support (planned) h3 = "^0.0.8" @@ -259,6 +281,18 @@ futures-rustls = "0.26" utoipa = { version = "5", features = ["chrono", "uuid", "decimal", "repr"] } utoipa-swagger-ui = { version = "8" } +# GraphQL / GraphQL (Spring for GraphQL equivalent) +# Used by hiver-graphql +async-graphql = { version = "7.2", features = ["chrono"] } + +# Mail / 邮件 (Spring Mail equivalent) +# Used by hiver-mail +lettre = { version = "0.11", default-features = false, features = ["tokio1-rustls-tls", "smtp-transport", "builder"] } + +# LDAP / LDAP (Spring LDAP equivalent) +# Used by hiver-ldap +ldap3 = "0.11" + # Web3 / 区块链 # Equivalent to: Spring Web3 (community projects) # Note: jsonwebtoken direct dep is ^10.3 (fixes CVE-2026-25537) @@ -303,6 +337,7 @@ proptest = "1.5" mockito = "1.6" trybuild = "1.0" tokio-test = "0.4" +testcontainers-modules = { version = "0.15", features = ["postgres", "redis", "kafka"] } # Utilities / 工具 itertools = "0.14" diff --git a/crates/hiver-amqp/Cargo.toml b/crates/hiver-amqp/Cargo.toml index a0671571..3136e10d 100644 --- a/crates/hiver-amqp/Cargo.toml +++ b/crates/hiver-amqp/Cargo.toml @@ -53,10 +53,10 @@ async-trait = { workspace = true } futures = { workspace = true } # AMQP client / AMQP客户端 -lapin = { version = "2.5", optional = true } +lapin = { workspace = true, optional = true } # UUID for correlation IDs / 关联 ID 用 UUID -uuid = { version = "1", features = ["v4"] } +uuid = { workspace = true, features = ["v4"] } # Logging / 日志 tracing = { workspace = true } diff --git a/crates/hiver-benches/Cargo.toml b/crates/hiver-benches/Cargo.toml index eadfff0d..f7a29bab 100644 --- a/crates/hiver-benches/Cargo.toml +++ b/crates/hiver-benches/Cargo.toml @@ -2,6 +2,7 @@ name = "hiver-benches" version = { workspace = true } edition = { workspace = true } +license = { workspace = true } publish = false [lib] diff --git a/crates/hiver-cache/Cargo.toml b/crates/hiver-cache/Cargo.toml index 9d482402..c61ef046 100644 --- a/crates/hiver-cache/Cargo.toml +++ b/crates/hiver-cache/Cargo.toml @@ -71,11 +71,11 @@ dashmap = { workspace = true } once_cell = { workspace = true } # Redis cache backend (optional) / Redis 缓存后端(可选) -redis = { version = "0.27", features = ["aio", "tokio-comp", "connection-manager"], optional = true } +redis = { workspace = true, optional = true } hiver-data-redis = { path = "../hiver-data-redis", version = "0.1.0-alpha.6", optional = true } # MessagePack serialization (optional) / MessagePack 序列化(可选) -rmp-serde = { version = "1.3", optional = true } +rmp-serde = { workspace = true, optional = true } # Logging (optional, for redis backend) / 日志(可选,用于 redis 后端) tracing = { workspace = true, optional = true } @@ -83,6 +83,8 @@ tracing = { workspace = true, optional = true } [dev-dependencies] # Testing / 测试 tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +hiver-runtime = { path = "../hiver-runtime" } +hiver-macros = { path = "../hiver-macros" } dashmap = { workspace = true } once_cell = { workspace = true } criterion = { workspace = true } diff --git a/crates/hiver-cache/src/caching.rs b/crates/hiver-cache/src/caching.rs index c5b66d4c..e2bd650a 100644 --- a/crates/hiver-cache/src/caching.rs +++ b/crates/hiver-cache/src/caching.rs @@ -487,7 +487,7 @@ mod tests assert!(names.contains(&"userList".to_string())); } - #[tokio::test] + #[hiver_macros::test] async fn test_caching_execute() { use std::{ @@ -545,7 +545,7 @@ mod tests assert_eq!(call_count.load(Ordering::SeqCst), 1); // Should not increment again } - #[tokio::test] + #[hiver_macros::test] async fn test_caching_execute_and_update() { use std::collections::HashMap; diff --git a/crates/hiver-cache/src/redis_cache.rs b/crates/hiver-cache/src/redis_cache.rs index 7c99b393..84bcc244 100644 --- a/crates/hiver-cache/src/redis_cache.rs +++ b/crates/hiver-cache/src/redis_cache.rs @@ -675,7 +675,7 @@ mod tests // 不需要运行 Redis 实例的单元测试。 // ----------------------------------------------------------------------- - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_falls_back_to_memory() { // Use an invalid URL — Redis will be unreachable, so fallback kicks in. @@ -695,7 +695,7 @@ mod tests assert_eq!(val, Some("value1".to_string())); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_degraded_mode() { // No fallback — degraded (no-op) mode. @@ -713,7 +713,7 @@ mod tests assert_eq!(cache.size().await, 0); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_put_and_get_memory_fallback() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -725,7 +725,7 @@ mod tests assert_eq!(cache.get(&"missing".to_string()).await, None); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_put_with_ttl_memory_fallback() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -738,7 +738,7 @@ mod tests assert_eq!(cache.get(&"ttl_key".to_string()).await, Some("ttl_val".to_string())); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_invalidate_memory_fallback() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -753,7 +753,7 @@ mod tests assert_eq!(cache.get(&"k".to_string()).await, None); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_invalidate_all_memory_fallback() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -769,7 +769,7 @@ mod tests assert_eq!(cache.get(&"b".to_string()).await, None); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_stats_tracking() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -789,7 +789,7 @@ mod tests assert!((stats.hit_rate - 0.5).abs() < f64::EPSILON); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_clear_resets_stats() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -805,7 +805,7 @@ mod tests assert_eq!(stats.hits, 0); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_contains_key_memory_fallback() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -817,7 +817,7 @@ mod tests assert!(cache.contains_key(&"yes".to_string()).await); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_size_memory_fallback() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -981,7 +981,7 @@ mod tests // full_key 测试。 // ----------------------------------------------------------------------- - #[tokio::test] + #[hiver_macros::test] async fn test_full_key_without_prefix() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").no_fallback(); @@ -992,7 +992,7 @@ mod tests assert_eq!(cache.full_key(&"mykey".to_string()), "\"mykey\""); } - #[tokio::test] + #[hiver_macros::test] async fn test_full_key_with_prefix() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1") @@ -1008,7 +1008,7 @@ mod tests // 克隆测试。 // ----------------------------------------------------------------------- - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_clone() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -1027,7 +1027,7 @@ mod tests // 重连测试(仍然不可达,应返回 false)。 // ----------------------------------------------------------------------- - #[tokio::test] + #[hiver_macros::test] async fn test_reconnect_when_unavailable() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -1046,7 +1046,7 @@ mod tests // 多种数据类型测试。 // ----------------------------------------------------------------------- - #[tokio::test] + #[hiver_macros::test] async fn test_cache_different_value_types() { // String values @@ -1079,7 +1079,7 @@ mod tests // 覆盖写入测试。 // ----------------------------------------------------------------------- - #[tokio::test] + #[hiver_macros::test] async fn test_put_overwrites_existing_value() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -1098,7 +1098,7 @@ mod tests // 并发访问测试。 // ----------------------------------------------------------------------- - #[tokio::test] + #[hiver_macros::test] async fn test_concurrent_access() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -1127,7 +1127,7 @@ mod tests // 结构体值序列化测试。 // ----------------------------------------------------------------------- - #[tokio::test] + #[hiver_macros::test] async fn test_cache_struct_value() { #[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Debug)] @@ -1157,7 +1157,7 @@ mod tests // 整数键类型测试。 // ----------------------------------------------------------------------- - #[tokio::test] + #[hiver_macros::test] async fn test_integer_key_type() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -1174,7 +1174,7 @@ mod tests // TTL 专用:put_with_ttl 应使用显式 TTL,而非默认值。 // ----------------------------------------------------------------------- - #[tokio::test] + #[hiver_macros::test] async fn test_put_with_ttl_explicit() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1") diff --git a/crates/hiver-cache/src/redis_cache_manager.rs b/crates/hiver-cache/src/redis_cache_manager.rs index 5860391c..00dced73 100644 --- a/crates/hiver-cache/src/redis_cache_manager.rs +++ b/crates/hiver-cache/src/redis_cache_manager.rs @@ -268,7 +268,7 @@ mod tests CacheConfig::new(name).ttl_secs(300) } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_manager_creates_cache_in_fallback() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -288,7 +288,7 @@ mod tests assert_eq!(cache.get(&"k".to_string()).await, Some("v".to_string())); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_manager_get_cache_names() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -306,7 +306,7 @@ mod tests assert!(names.contains(&"cache_b".to_string())); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_manager_get_cache_returns_worker() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -327,7 +327,7 @@ mod tests assert_eq!(w.name(), "products"); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_manager_get_cache_missing() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -336,7 +336,7 @@ mod tests assert!(manager.get_cache("nonexistent").is_none()); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_manager_worker_stats() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -357,7 +357,7 @@ mod tests assert_eq!(stats.misses, 1); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_manager_worker_clear() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -377,7 +377,7 @@ mod tests assert_eq!(cache.get(&"b".to_string()).await, None); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_manager_no_fallback() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").no_fallback(); @@ -395,7 +395,7 @@ mod tests assert_eq!(cache.get(&"k".to_string()).await, None); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_manager_multiple_caches_isolated() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); @@ -415,7 +415,7 @@ mod tests assert_eq!(cache_b.get(&"key".to_string()).await, Some("from_b".to_string())); } - #[tokio::test] + #[hiver_macros::test] async fn test_redis_cache_manager_cache_exists() { let redis_cfg = RedisConfig::new("redis://127.0.0.1:1").fallback_to_memory(); diff --git a/crates/hiver-cloud-bus/Cargo.toml b/crates/hiver-cloud-bus/Cargo.toml index bef6db83..18966663 100644 --- a/crates/hiver-cloud-bus/Cargo.toml +++ b/crates/hiver-cloud-bus/Cargo.toml @@ -30,3 +30,5 @@ stream = ["dep:hiver-cloud-stream"] [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +hiver-runtime = { path = "../hiver-runtime" } +hiver-macros = { path = "../hiver-macros" } diff --git a/crates/hiver-cloud-bus/src/local_bus.rs b/crates/hiver-cloud-bus/src/local_bus.rs index 81d8db2e..9c8d9474 100644 --- a/crates/hiver-cloud-bus/src/local_bus.rs +++ b/crates/hiver-cloud-bus/src/local_bus.rs @@ -89,7 +89,7 @@ mod tests use super::*; use crate::event::BusEventType; - #[tokio::test] + #[hiver_macros::test] async fn test_publish_subscribe() { let bus = LocalBus::new(); @@ -113,7 +113,7 @@ mod tests assert_eq!(events[1], BusEventType::Ack); } - #[tokio::test] + #[hiver_macros::test] async fn test_no_subscribers_error() { let bus = LocalBus::new(); @@ -121,7 +121,7 @@ mod tests assert!(result.is_err()); } - #[tokio::test] + #[hiver_macros::test] async fn test_multiple_subscribers() { let bus = LocalBus::new(); @@ -149,7 +149,7 @@ mod tests assert_eq!(bus.name(), "local"); } - #[tokio::test] + #[hiver_macros::test] async fn test_subscriber_count() { let bus = LocalBus::new(); diff --git a/crates/hiver-cloud-bus/src/stream_bus.rs b/crates/hiver-cloud-bus/src/stream_bus.rs index 93940c58..cc81a8b6 100644 --- a/crates/hiver-cloud-bus/src/stream_bus.rs +++ b/crates/hiver-cloud-bus/src/stream_bus.rs @@ -98,7 +98,7 @@ mod tests use super::*; use crate::event::BusEventType; - #[tokio::test] + #[hiver_macros::test] async fn test_stream_bus_publish() { let binder = Arc::new(InMemoryBinder::new()); @@ -127,7 +127,7 @@ mod tests assert_eq!(bus.name(), "stream"); } - #[tokio::test] + #[hiver_macros::test] async fn test_stream_bus_subscribe() { let binder = Arc::new(InMemoryBinder::new()); diff --git a/crates/hiver-cloud/src/gateway.rs b/crates/hiver-cloud/src/gateway.rs index 2ef3cd2c..6490552c 100644 --- a/crates/hiver-cloud/src/gateway.rs +++ b/crates/hiver-cloud/src/gateway.rs @@ -387,10 +387,67 @@ impl Gateway for SimpleGateway req = filter.process_request(req).await; } - // Forward to target - // In a real implementation, this would make an HTTP request - GatewayResponse::new(http::StatusCode::OK) - .body(format!("Routed to: {}", route.uri).into_bytes()) + // Build the upstream URL: route.uri + path + query. + // 构建上游 URL:route.uri + path + query。 + let upstream_url = match req.query + { + Some(ref q) if !q.is_empty() => format!("{}{}?{}", route.uri, req.path, q), + _ => format!("{}{}", route.uri, req.path), + }; + + // Forward the request to the upstream via reqwest. + // 经 reqwest 将请求转发到上游。 + let client = reqwest::Client::new(); + let mut builder = client + .request( + reqwest::Method::from_bytes(req.method.as_str().as_bytes()) + .unwrap_or(reqwest::Method::GET), + &upstream_url, + ) + .header("x-forwarded-path", &req.path); + + // Forward request headers. + // 转发请求 headers。 + for (k, v) in &req.headers + { + builder = builder.header(k.as_str(), v.as_str()); + } + + // Forward body if non-empty. + // 若 body 非空则转发。 + if !req.body.is_empty() + { + builder = builder.body(req.body.clone()); + } + + match builder.send().await + { + Ok(resp) => + { + let status = + http::StatusCode::from_u16(resp.status().as_u16()).unwrap_or( + http::StatusCode::INTERNAL_SERVER_ERROR, + ); + + // Copy response headers. + // 复制响应 headers。 + let mut response = GatewayResponse::new(status); + for (k, v) in resp.headers() + { + if let Ok(vstr) = v.to_str() + { + response = response.header(k.as_str(), vstr.to_string()); + } + } + + // Read response body. + // 读取响应 body。 + let body = resp.bytes().await.unwrap_or_default(); + response.body(body.to_vec()) + }, + Err(e) => GatewayResponse::new(http::StatusCode::BAD_GATEWAY) + .body(format!("Upstream error: {e}").into_bytes()), + } } else { diff --git a/crates/hiver-data-commons/Cargo.toml b/crates/hiver-data-commons/Cargo.toml index 8e1a398e..b0f61605 100644 --- a/crates/hiver-data-commons/Cargo.toml +++ b/crates/hiver-data-commons/Cargo.toml @@ -33,10 +33,10 @@ serde = { workspace = true, features = ["derive"] } async-trait = "0.1" # UUID support (for entity IDs) -uuid = { version = "1.12", features = ["v4", "serde"] } +uuid = { workspace = true, features = ["v4", "serde"] } # Chrono for datetime -chrono = { version = "0.4", features = ["serde"] } +chrono = { workspace = true, features = ["serde"] } # Hex encoding for bytes hex = "0.4" diff --git a/crates/hiver-data-mongodb/Cargo.toml b/crates/hiver-data-mongodb/Cargo.toml index fa19f9cc..4b1bdf3c 100644 --- a/crates/hiver-data-mongodb/Cargo.toml +++ b/crates/hiver-data-mongodb/Cargo.toml @@ -30,7 +30,7 @@ hiver-data-macros = { path = "../hiver-data-macros", version = "0.1.0-alpha.6" } hiver-runtime = { path = "../hiver-runtime", version = "0.1.0-alpha.6", optional = true } # MongoDB driver -mongodb = { version = "3.7" } +mongodb = { workspace = true } # Error handling thiserror = { workspace = true } @@ -44,10 +44,10 @@ serde_json = { workspace = true } async-trait = { workspace = true } # UUID support -uuid = { version = "1.12", features = ["v4", "serde"] } +uuid = { workspace = true, features = ["v4", "serde"] } # Chrono for datetime -chrono = { version = "0.4", features = ["serde"] } +chrono = { workspace = true, features = ["serde"] } # Logging tracing = { workspace = true } diff --git a/crates/hiver-data-orm/Cargo.toml b/crates/hiver-data-orm/Cargo.toml index 60f6445a..31ba77c3 100644 --- a/crates/hiver-data-orm/Cargo.toml +++ b/crates/hiver-data-orm/Cargo.toml @@ -42,10 +42,10 @@ hiver-data-macros = { path = "../hiver-data-macros", version = "0.1.0-alpha.6" } hiver-runtime = { path = "../hiver-runtime", version = "0.1.0-alpha.6" } # Sea ORM 2.0 RC with sqlx 0.8 support -sea-orm = { version = "2.0.0-rc", optional = true, features = ["runtime-tokio-rustls", "sqlx-sqlite", "sqlx-postgres", "sqlx-mysql", "macros"] } +sea-orm = { workspace = true, optional = true } # Diesel (optional) -diesel = { version = "2.2", optional = true, features = ["postgres", "mysql", "sqlite"] } +diesel = { workspace = true, optional = true } # SQLx (optional) sqlx = { workspace = true, optional = true } @@ -62,10 +62,10 @@ serde_json = { workspace = true } async-trait = { workspace = true } # UUID support -uuid = { version = "1.12", features = ["v4", "serde"] } +uuid = { workspace = true, features = ["v4", "serde"] } # Chrono for datetime -chrono = { version = "0.4", features = ["serde"] } +chrono = { workspace = true, features = ["serde"] } # Logging tracing = { workspace = true } diff --git a/crates/hiver-data-rdbc/Cargo.toml b/crates/hiver-data-rdbc/Cargo.toml index b166f037..440439fc 100644 --- a/crates/hiver-data-rdbc/Cargo.toml +++ b/crates/hiver-data-rdbc/Cargo.toml @@ -39,10 +39,10 @@ hiver-tx = { path = "../hiver-tx", version = "0.1.0-alpha.6", optional = true } hiver-runtime = { path = "../hiver-runtime", version = "0.1.0-alpha.6" } # Tokio async mutex for transaction handling -tokio = { version = "1", features = ["sync"] } +tokio = { workspace = true, features = ["sync"] } # SQLx for reactive database access -sqlx = { version = "0.8", features = ["chrono"] } +sqlx = { workspace = true, features = ["chrono"] } # Error handling thiserror = { workspace = true } @@ -55,10 +55,10 @@ serde_json = "1" async-trait = "0.1" # UUID support -uuid = { version = "1.12", features = ["v4", "serde"] } +uuid = { workspace = true, features = ["v4", "serde"] } # Chrono for datetime -chrono = { version = "0.4", features = ["serde"] } +chrono = { workspace = true, features = ["serde"] } # Logging tracing = { workspace = true } diff --git a/crates/hiver-data-redis/Cargo.toml b/crates/hiver-data-redis/Cargo.toml index d4a4550f..a927416d 100644 --- a/crates/hiver-data-redis/Cargo.toml +++ b/crates/hiver-data-redis/Cargo.toml @@ -36,11 +36,11 @@ hiver-data-macros = { path = "../hiver-data-macros", version = "0.1.0-alpha.6" } hiver-runtime = { path = "../hiver-runtime", version = "0.1.0-alpha.6", optional = true } # Redis driver -redis = { version = "0.27", features = ["aio", "tokio-comp", "connection-manager"] } +redis = { workspace = true } # Connection pool (optional) -deadpool = { version = "0.12", optional = true } -deadpool-redis = { version = "0.16", optional = true } +deadpool = { workspace = true, optional = true } +deadpool-redis = { workspace = true, optional = true } # Error handling thiserror = { workspace = true } @@ -54,10 +54,10 @@ serde_json = { workspace = true } async-trait = { workspace = true } # UUID support -uuid = { version = "1.12", features = ["v4", "serde"] } +uuid = { workspace = true, features = ["v4", "serde"] } # Chrono for datetime -chrono = { version = "0.4", features = ["serde"] } +chrono = { workspace = true, features = ["serde"] } # Logging tracing = { workspace = true } diff --git a/crates/hiver-events/Cargo.toml b/crates/hiver-events/Cargo.toml index c51acc6d..2d591d40 100644 --- a/crates/hiver-events/Cargo.toml +++ b/crates/hiver-events/Cargo.toml @@ -55,3 +55,5 @@ workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +hiver-runtime = { path = "../hiver-runtime" } +hiver-macros = { path = "../hiver-macros" } diff --git a/crates/hiver-events/src/listener.rs b/crates/hiver-events/src/listener.rs index eb0b35bb..97f2b599 100644 --- a/crates/hiver-events/src/listener.rs +++ b/crates/hiver-events/src/listener.rs @@ -966,13 +966,13 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_async_listener_fn() { let listener = AsyncListenerFn::new(|event: &TestEvent| { let value = event.value; async move { - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + hiver_runtime::time::sleep(std::time::Duration::from_millis(10)).await; println!("Async value: {}", value); Ok(()) } @@ -994,7 +994,7 @@ mod tests assert_eq!(listener.count(), 1); } - #[tokio::test] + #[hiver_macros::test] async fn test_boxed_consumer() { let listener = TestListener::new(); diff --git a/crates/hiver-events/src/publisher.rs b/crates/hiver-events/src/publisher.rs index b00acae7..5e04003a 100644 --- a/crates/hiver-events/src/publisher.rs +++ b/crates/hiver-events/src/publisher.rs @@ -459,7 +459,7 @@ mod tests } } - #[tokio::test] + #[hiver_macros::test] async fn test_publisher_creation() { let publisher = ApplicationEventPublisher::new(); @@ -467,7 +467,7 @@ mod tests assert!(!publisher.has_listeners::().await); } - #[tokio::test] + #[hiver_macros::test] async fn test_register_consumer() { let publisher = ApplicationEventPublisher::new(); @@ -480,7 +480,7 @@ mod tests assert!(publisher.has_listeners::().await); } - #[tokio::test] + #[hiver_macros::test] async fn test_publish_sync() { let publisher = ApplicationEventPublisher::with_strategy(PublishStrategy::Sync); @@ -498,11 +498,16 @@ mod tests .unwrap(); // Give time for processing - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + hiver_runtime::time::sleep(std::time::Duration::from_millis(10)).await; assert_eq!(listener.count(), 1); } + // Stays on #[tokio::test]: exercises production code that uses + // tokio::task::spawn_blocking + tokio::runtime::Handle (publisher.rs:211), + // which requires the tokio runtime. + // 保留在 #[tokio::test] 上:演练使用 tokio::task::spawn_blocking + + // tokio::runtime::Handle 的生产代码(publisher.rs:211),需要 tokio runtime。 #[tokio::test] async fn test_publish_async() { @@ -518,12 +523,12 @@ mod tests publisher.publish_event(event).await.unwrap(); // Give time for processing - tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; + hiver_runtime::time::sleep(std::time::Duration::from_millis(50)).await; assert_eq!(listener.count(), 1); } - #[tokio::test] + #[hiver_macros::test] async fn test_unregister() { let publisher = ApplicationEventPublisher::new(); @@ -537,7 +542,7 @@ mod tests assert_eq!(publisher.consumer_count::().await, 0); } - #[tokio::test] + #[hiver_macros::test] async fn test_no_listener_error() { let publisher = ApplicationEventPublisher::new(); @@ -550,6 +555,8 @@ mod tests assert!(result.is_err()); } + // Stays on #[tokio::test]: exercises production code that uses + // tokio::task::spawn_blocking (publisher.rs:211). #[tokio::test] async fn test_context_event() { @@ -585,7 +592,7 @@ mod tests let event = ContextRefreshedEvent::new("test_app"); publisher.publish_event(event).await.unwrap(); - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + hiver_runtime::time::sleep(std::time::Duration::from_millis(10)).await; assert_eq!(call_count.load(std::sync::atomic::Ordering::Relaxed), 1); } diff --git a/crates/hiver-events/src/registry.rs b/crates/hiver-events/src/registry.rs index 13f7fc75..04bd10d3 100644 --- a/crates/hiver-events/src/registry.rs +++ b/crates/hiver-events/src/registry.rs @@ -509,7 +509,7 @@ mod tests } } - #[tokio::test] + #[hiver_macros::test] async fn test_registry_creation() { let registry = EventRegistry::new(); @@ -517,7 +517,7 @@ mod tests assert!(!registry.has_consumers::().await); } - #[tokio::test] + #[hiver_macros::test] async fn test_register_consumer() { let registry = EventRegistry::new(); @@ -531,7 +531,7 @@ mod tests assert!(registry.has_consumers::().await); } - #[tokio::test] + #[hiver_macros::test] async fn test_unregister() { let registry = EventRegistry::new(); @@ -546,7 +546,7 @@ mod tests assert!(!registry.has_consumers::().await); } - #[tokio::test] + #[hiver_macros::test] async fn test_multicaster() { let multicaster = EventMulticaster::new(); @@ -565,7 +565,7 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_context_event_multicast() { let multicaster = EventMulticaster::new(); @@ -583,7 +583,7 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_subscription() { let subscription = EventSubscription::new::("test_consumer").with_order(10); diff --git a/crates/hiver-events/src/transactional_listener.rs b/crates/hiver-events/src/transactional_listener.rs index f2aac8a2..661e9214 100644 --- a/crates/hiver-events/src/transactional_listener.rs +++ b/crates/hiver-events/src/transactional_listener.rs @@ -1024,7 +1024,7 @@ mod tests // --- TransactionalEventPublisher Tests --- - #[tokio::test] + #[hiver_macros::test] async fn test_register_and_publish_after_commit() { let publisher = TransactionalEventPublisher::new(); @@ -1063,7 +1063,7 @@ mod tests assert_eq!(counter.load(Ordering::SeqCst), 1); } - #[tokio::test] + #[hiver_macros::test] async fn test_no_listeners_for_phase() { let publisher = TransactionalEventPublisher::new(); @@ -1076,7 +1076,7 @@ mod tests assert!(results.is_empty()); } - #[tokio::test] + #[hiver_macros::test] async fn test_multiple_listeners_same_phase() { let publisher = TransactionalEventPublisher::new(); @@ -1116,7 +1116,7 @@ mod tests assert_eq!(counter2.load(Ordering::SeqCst), 1); } - #[tokio::test] + #[hiver_macros::test] async fn test_different_event_types() { let publisher = TransactionalEventPublisher::new(); @@ -1158,7 +1158,7 @@ mod tests assert_eq!(payment_counter.load(Ordering::SeqCst), 1); } - #[tokio::test] + #[hiver_macros::test] async fn test_listener_order() { let publisher = TransactionalEventPublisher::new(); @@ -1205,7 +1205,7 @@ mod tests assert_eq!(*order, vec![1, 2]); // Lower order executes first } - #[tokio::test] + #[hiver_macros::test] async fn test_clear_listeners() { let publisher = TransactionalEventPublisher::new(); @@ -1241,7 +1241,7 @@ mod tests // --- TransactionalEventBridge Tests --- - #[tokio::test] + #[hiver_macros::test] async fn test_bridge_commit_lifecycle() { let publisher = TransactionalEventPublisher::new(); @@ -1296,7 +1296,7 @@ mod tests assert!(!results.is_empty()); } - #[tokio::test] + #[hiver_macros::test] async fn test_bridge_rollback_lifecycle() { let publisher = TransactionalEventPublisher::new(); @@ -1331,7 +1331,7 @@ mod tests assert!(!results.is_empty()); } - #[tokio::test] + #[hiver_macros::test] async fn test_bridge_noop_when_inactive() { let publisher = TransactionalEventPublisher::new(); @@ -1345,7 +1345,7 @@ mod tests assert!(results.is_empty()); } - #[tokio::test] + #[hiver_macros::test] async fn test_listener_with_condition() { let listener = TransactionalEventListener::new::( diff --git a/crates/hiver-graphql/Cargo.toml b/crates/hiver-graphql/Cargo.toml index 289cd95c..c21cff07 100644 --- a/crates/hiver-graphql/Cargo.toml +++ b/crates/hiver-graphql/Cargo.toml @@ -45,7 +45,7 @@ thiserror = { workspace = true } tracing = { workspace = true } # Real GraphQL execution engine / 真实GraphQL执行引擎 -async-graphql = { version = "7.2", optional = true, features = ["chrono"] } +async-graphql = { workspace = true, optional = true } # HTTP client for GraphQL client / HTTP客户端 reqwest = { workspace = true, optional = true } @@ -58,3 +58,5 @@ client = ["dep:reqwest"] [dev-dependencies] # Testing / 测试 (Spring Test) tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +hiver-runtime = { path = "../hiver-runtime" } +hiver-macros = { path = "../hiver-macros" } diff --git a/crates/hiver-graphql/src/dataloader.rs b/crates/hiver-graphql/src/dataloader.rs index 8b47625d..644cc991 100644 --- a/crates/hiver-graphql/src/dataloader.rs +++ b/crates/hiver-graphql/src/dataloader.rs @@ -228,14 +228,14 @@ mod tests } } - #[tokio::test] + #[hiver_macros::test] async fn test_load() { let loader = DataLoader::new(TestLoader); assert_eq!(loader.load(1).await, Some("user_1".into())); } - #[tokio::test] + #[hiver_macros::test] async fn test_cache_hit() { let loader = DataLoader::new(TestLoader); @@ -243,7 +243,7 @@ mod tests assert_eq!(loader.load(1).await, Some("cached".into())); } - #[tokio::test] + #[hiver_macros::test] async fn test_load_many() { let loader = DataLoader::new(TestLoader); @@ -251,7 +251,7 @@ mod tests assert_eq!(results[&1], Some("user_1".into())); } - #[tokio::test] + #[hiver_macros::test] async fn test_clear() { let loader = DataLoader::new(TestLoader); diff --git a/crates/hiver-graphql/src/engine.rs b/crates/hiver-graphql/src/engine.rs index d31c3d5e..8305dec4 100644 --- a/crates/hiver-graphql/src/engine.rs +++ b/crates/hiver-graphql/src/engine.rs @@ -395,7 +395,7 @@ mod engine_impl .build() } - #[tokio::test] + #[hiver_macros::test] async fn test_hello_query() { let engine = make_engine(); @@ -410,7 +410,7 @@ mod engine_impl ); } - #[tokio::test] + #[hiver_macros::test] async fn test_arithmetic_query() { let engine = make_engine(); @@ -425,7 +425,7 @@ mod engine_impl ); } - #[tokio::test] + #[hiver_macros::test] async fn test_sdl() { let engine = make_engine(); @@ -433,7 +433,7 @@ mod engine_impl assert!(sdl.contains("hello")); } - #[tokio::test] + #[hiver_macros::test] async fn test_introspection() { let engine = make_engine(); @@ -450,7 +450,7 @@ mod engine_impl assert!(html.contains("/ws/graphql")); } - #[tokio::test] + #[hiver_macros::test] async fn test_variables() { let engine = make_engine(); diff --git a/crates/hiver-graphql/src/resolver.rs b/crates/hiver-graphql/src/resolver.rs index 0879df44..a9da2ff4 100644 --- a/crates/hiver-graphql/src/resolver.rs +++ b/crates/hiver-graphql/src/resolver.rs @@ -146,7 +146,7 @@ mod tests } } - #[tokio::test] + #[hiver_macros::test] async fn test_registry() { let mut reg = ResolverRegistry::new(); diff --git a/crates/hiver-graphql/src/schema.rs b/crates/hiver-graphql/src/schema.rs index 89bbad4e..6417d287 100644 --- a/crates/hiver-graphql/src/schema.rs +++ b/crates/hiver-graphql/src/schema.rs @@ -125,7 +125,7 @@ impl SchemaBuilder { mod tests { use super::*; - #[tokio::test] + #[hiver_macros::test] async fn test_empty_schema() { let schema = SchemaBuilder::new().build(); let ctx = crate::context::GraphQLContext::new(); @@ -133,7 +133,7 @@ mod tests { assert!(result.is_err()); } - #[tokio::test] + #[hiver_macros::test] async fn test_type_registry() { let mut reg = TypeRegistry::new(); reg.register(TypeDef { name: "User".into(), definition: "type User { id: ID! }".into(), description: None }); diff --git a/crates/hiver-graphql/src/transport.rs b/crates/hiver-graphql/src/transport.rs index 5fb09e52..f73f7715 100644 --- a/crates/hiver-graphql/src/transport.rs +++ b/crates/hiver-graphql/src/transport.rs @@ -99,7 +99,7 @@ mod tests { use super::*; use crate::schema::SchemaBuilder; - #[tokio::test] + #[hiver_macros::test] async fn test_handler() { let schema = SchemaBuilder::new().build(); let handler = GraphQLHandler::new(schema); diff --git a/crates/hiver-grpc/Cargo.toml b/crates/hiver-grpc/Cargo.toml index 403f9656..65ba490f 100644 --- a/crates/hiver-grpc/Cargo.toml +++ b/crates/hiver-grpc/Cargo.toml @@ -18,7 +18,7 @@ workspace = true [dependencies] # gRPC transport layer / gRPC传输层 -tonic = { version = "0.12", features = ["transport", "tls"] } +tonic = { workspace = true } # Protocol buffer encoding / Protocol Buffer 编码 prost = "0.13" @@ -47,8 +47,10 @@ tracing = { workspace = true } http = "1" # Tower middleware (re-exported by tonic) / Tower 中间件 -tower = { version = "0.4", features = ["util"] } +tower = { workspace = true, features = ["util"] } [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } -tonic = { version = "0.12", features = ["transport"] } +hiver-runtime = { path = "../hiver-runtime" } +hiver-macros = { path = "../hiver-macros" } +tonic = { workspace = true } diff --git a/crates/hiver-grpc/src/client.rs b/crates/hiver-grpc/src/client.rs index a0e3a4d9..662173e2 100644 --- a/crates/hiver-grpc/src/client.rs +++ b/crates/hiver-grpc/src/client.rs @@ -193,6 +193,11 @@ mod tests { use super::*; + // Stays on #[tokio::test]: this test exercises the tonic/hyper gRPC client, + // whose internals require the tokio runtime. The fully-qualified path + // avoids shadowing #[hiver_macros::test]. + // 保留在 #[tokio::test] 上:本测试演练 tonic/hyper gRPC 客户端,其内部需要 + // tokio runtime。全限定路径避免遮蔽 #[hiver_macros::test]。 #[tokio::test] async fn test_builder_lazy() { diff --git a/crates/hiver-grpc/src/interceptor.rs b/crates/hiver-grpc/src/interceptor.rs index b119faeb..717bc7f8 100644 --- a/crates/hiver-grpc/src/interceptor.rs +++ b/crates/hiver-grpc/src/interceptor.rs @@ -513,7 +513,7 @@ mod tests { use super::*; - #[tokio::test] + #[hiver_macros::test] async fn test_auth_interceptor_ok() { let ic = AuthInterceptor::new("secret"); @@ -523,7 +523,7 @@ mod tests assert!(ic.intercept(req).await.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_auth_interceptor_fail() { let ic = AuthInterceptor::new("secret"); @@ -531,7 +531,7 @@ mod tests assert!(ic.intercept(req).await.is_err()); } - #[tokio::test] + #[hiver_macros::test] async fn test_logging_interceptor() { let ic = LoggingInterceptor::new(); diff --git a/crates/hiver-grpc/src/retry.rs b/crates/hiver-grpc/src/retry.rs index a50d2c7f..fd10f658 100644 --- a/crates/hiver-grpc/src/retry.rs +++ b/crates/hiver-grpc/src/retry.rs @@ -252,7 +252,7 @@ mod tests assert_eq!(p.max_attempts(), 0); } - #[tokio::test] + #[hiver_macros::test] async fn test_retry_succeeds_on_second_attempt() { let p = RetryPolicy::fixed(2).with_initial_delay(Duration::from_millis(1)); @@ -275,7 +275,7 @@ mod tests assert_eq!(result.unwrap(), 42); } - #[tokio::test] + #[hiver_macros::test] async fn test_retry_exhausted() { let p = RetryPolicy::fixed(1).with_initial_delay(Duration::from_millis(1)); @@ -286,7 +286,7 @@ mod tests assert_eq!(result.unwrap_err().code(), tonic::Code::Unavailable); } - #[tokio::test] + #[hiver_macros::test] async fn test_non_retryable_error() { let p = RetryPolicy::fixed(5).with_initial_delay(Duration::from_millis(1)); diff --git a/crates/hiver-http/src/lib.rs b/crates/hiver-http/src/lib.rs index 72011607..8bcf9e5e 100644 --- a/crates/hiver-http/src/lib.rs +++ b/crates/hiver-http/src/lib.rs @@ -502,6 +502,17 @@ impl FromRequest for Vec } } +impl FromRequest for Request +{ + /// Identity extractor: clone the whole request so a handler can take the + /// raw `Request` as a parameter. + /// 身份提取器:克隆整个请求,使处理程序可将原始 `Request` 作为参数。 + async fn from_request(req: &Request) -> Result + { + Ok(req.clone()) + } +} + impl FromRequest for Json { async fn from_request(req: &Request) -> Result diff --git a/crates/hiver-http/src/proto/response.rs b/crates/hiver-http/src/proto/response.rs index 35530c19..68d06cc0 100644 --- a/crates/hiver-http/src/proto/response.rs +++ b/crates/hiver-http/src/proto/response.rs @@ -121,8 +121,15 @@ pub fn encode_response(response: &Response, ctx: &ConnectionContext) -> Result for Method { match method.as_str() { + "GET" => Method::GET, "POST" => Method::POST, "PUT" => Method::PUT, "PATCH" => Method::PATCH, diff --git a/crates/hiver-http/src/server.rs b/crates/hiver-http/src/server.rs index fa7814e4..503d5ace 100644 --- a/crates/hiver-http/src/server.rs +++ b/crates/hiver-http/src/server.rs @@ -164,6 +164,29 @@ impl Server pub async fn run(self, service: S) -> Result<()> where S: HttpService + Clone + 'static, + { + // No explicit shutdown signal: run until the process is killed. + // Graceful shutdown is available via `run_with_shutdown`. + // 无显式关闭信号:运行直到进程被终止。 + // 优雅关闭可经 `run_with_shutdown` 获得。 + // (`std::future::pending` never resolves, so the shutdown branch never + // fires — equivalent to the original infinite accept loop.) + // (`std::future::pending` 永不完成,故关闭分支永不触发 —— 等价于原先的 + // 无限 accept 循环。) + self.run_with_shutdown(service, std::future::pending()) + .await + } + + /// Run the server until the `shutdown` future resolves, then stop accepting + /// new connections and wait for in-flight ones to finish (bounded by the + /// keep-alive timeout). This is the graceful-shutdown entry point. + /// + /// 运行服务端直到 `shutdown` future 完成,随后停止接受新连接,并等待处理中的 + /// 连接结束(受 keep-alive 超时约束)。这是优雅关闭入口。 + pub async fn run_with_shutdown(self, service: S, shutdown: F) -> Result<()> + where + S: HttpService + Clone + 'static, + F: std::future::Future, { tracing::info!("Starting HTTP server on {}", self.addr); @@ -179,41 +202,99 @@ impl Server let connection_count = Arc::new(std::sync::atomic::AtomicUsize::new(0)); let max_conns = config.max_connections; - // Accept connections loop — enforce max_connections via atomic counter. - // 连接接受循环 — 通过原子计数器强制执行最大连接数。 - loop + // Accept connections loop — race against the shutdown signal. When it + // fires, stop accepting and let in-flight connections drain (each has + // its own request/keep-alive timeout, so the process can't hang). + // 连接接受循环 —— 与关闭信号竞争。信号触发时,停止接受,让处理中的 + // 连接排空(每个连接各有请求/keep-alive 超时,故进程不会挂起)。 + let mut shutdown = Box::pin(shutdown); + let accepting = loop { - match listener.accept().await + // Poll accept and shutdown concurrently: first to win decides. + // 并发轮询 accept 与 shutdown:先完成者决定走向。 + use futures::future::FutureExt; + let accept_fut = listener.accept(); + match futures::future::select(accept_fut.boxed(), shutdown.as_mut()).await { - Ok((stream, peer_addr)) => + futures::future::Either::Left((accept_result, _)) => match accept_result { - let service = service.clone(); - let config = config.clone(); - let count = connection_count.clone(); - let current = count.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - if current >= max_conns + Ok((stream, peer_addr)) => { - count.fetch_sub(1, std::sync::atomic::Ordering::Relaxed); - tracing::warn!( - "Max connections ({}) reached, rejecting {}", - max_conns, - peer_addr - ); - drop(stream); - continue; - } - - spawn(async move { - handle_connection(stream, peer_addr, service, config).await; - count.fetch_sub(1, std::sync::atomic::Ordering::Relaxed); - }); + let service = service.clone(); + let config = config.clone(); + let count = connection_count.clone(); + let current = count.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + if current >= max_conns + { + count.fetch_sub(1, std::sync::atomic::Ordering::Relaxed); + tracing::warn!( + "Max connections ({}) reached, rejecting {}", + max_conns, + peer_addr + ); + drop(stream); + continue; + } + + spawn(async move { + handle_connection(stream, peer_addr, service, config).await; + count.fetch_sub(1, std::sync::atomic::Ordering::Relaxed); + }); + }, + Err(e) => + { + tracing::error!("Error accepting connection: {}", e); + }, }, - Err(e) => + // Shutdown signal fired: stop accepting. + // 关闭信号触发:停止接受。 + futures::future::Either::Right((_, _)) => { - tracing::error!("Error accepting connection: {}", e); + tracing::info!("Shutdown signal received, draining connections"); + break; + }, + } + }; + + let _ = accepting; + + // Graceful drain: wait for in-flight connections to finish, bounded by + // the keep-alive timeout so a stuck connection can't block shutdown + // forever. + // 优雅排空:等待处理中的连接结束,受 keep-alive 超时约束,使卡住的连接 + // 无法永久阻塞关闭。 + let drain_deadline = hiver_runtime::time::sleep(config.keep_alive_timeout); + let drain_deadline = Box::pin(drain_deadline); + let mut drain_deadline = drain_deadline; + loop + { + let in_flight = connection_count.load(std::sync::atomic::Ordering::Relaxed); + if in_flight == 0 + { + tracing::info!("All connections drained, shutting down"); + break; + } + // Either a connection finishes (polled by its own task) or the drain + // deadline elapses — whichever comes first. + // 要么某连接完成(由其自身任务轮询),要么排空截止时间到 —— 先到为准。 + use futures::future::FutureExt; + let tick = hiver_runtime::time::sleep(std::time::Duration::from_millis(50)); + match futures::future::select(tick.boxed(), drain_deadline.as_mut()).await + { + futures::future::Either::Left((_, _)) => continue, + futures::future::Either::Right((_, _)) => + { + tracing::warn!( + "Drain timeout reached with {} connections still in flight, forcing \ + shutdown", + in_flight + ); + break; }, } } + + Ok(()) } /// Get the bound address @@ -252,15 +333,37 @@ async fn handle_connection( S: HttpService + 'static, { let mut parser = proto::RequestParser::new(); - let encoder = proto::ResponseEncoder::new(); + let mut encoder = proto::ResponseEncoder::new(); tracing::debug!("New connection from {}", peer_addr); loop { - // Read data from the stream + // Read data from the stream, bounded by the configured request timeout + // so a slow or half-open client cannot pin a connection worker forever. + // On timeout we close the connection (break) rather than hang. + // 从流读取数据,受配置的请求超时约束,使慢速或半开客户端无法永久 + // 占用连接 worker。超时时关闭连接(break)而非挂起。 let mut read_buf = vec![0u8; config.max_buffer_size]; - match stream.read(&mut read_buf).await + let read_result = { + use futures::future::FutureExt; + let read_fut = stream.read(&mut read_buf); + let timeout_fut = hiver_runtime::time::sleep(config.request_timeout); + match futures::future::select(read_fut.boxed(), timeout_fut.boxed()).await + { + futures::future::Either::Left((res, _)) => res, + futures::future::Either::Right((_, _)) => + { + tracing::warn!( + "Request timeout ({}s) from {}, closing connection", + config.request_timeout.as_secs(), + peer_addr + ); + break; + }, + } + }; + match read_result { Ok(0) => { @@ -291,6 +394,20 @@ async fn handle_connection( request.path() ); + // Honor the request's Connection header: if the + // client sent "Connection: close" (or is HTTP/1.0 + // without keep-alive), close the socket after this + // response instead of looping forever. Without this, + // the encoder context stays at its default (keep-alive + // = true) and close requests hang the client. + // 遵循请求的 Connection 头:若客户端发送 "Connection: close" + // (或为 HTTP/1.0 且无 keep-alive),则在此响应后关闭套接字, + // 而非永久循环。否则 encoder 上下文保持默认(keep-alive=true), + // close 请求会使客户端挂起。 + encoder + .context_mut() + .update_keep_alive_from_header(request.header("connection")); + // Handle the request let response = match service.call(request).await { diff --git a/crates/hiver-kafka/Cargo.toml b/crates/hiver-kafka/Cargo.toml index cd6f583d..9279e8cd 100644 --- a/crates/hiver-kafka/Cargo.toml +++ b/crates/hiver-kafka/Cargo.toml @@ -53,7 +53,7 @@ async-trait = { workspace = true } futures = { workspace = true } # Kafka client / Kafka客户端 -rdkafka = { version = "0.36", optional = true, features = ["cmake-build", "tokio"] } +rdkafka = { workspace = true, optional = true } # Logging / 日志 tracing = { workspace = true } @@ -63,3 +63,5 @@ once_cell = { workspace = true } [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +hiver-runtime = { path = "../hiver-runtime" } +hiver-macros = { path = "../hiver-macros" } diff --git a/crates/hiver-kafka/src/consumer.rs b/crates/hiver-kafka/src/consumer.rs index 39d77b8f..dc64bea7 100644 --- a/crates/hiver-kafka/src/consumer.rs +++ b/crates/hiver-kafka/src/consumer.rs @@ -283,7 +283,7 @@ mod tests /// Test consumer creation with config /// 测试使用配置创建消费者 - #[tokio::test] + #[hiver_macros::test] async fn test_consumer_new() { let config = ConsumerConfig::new("test-group"); @@ -293,7 +293,7 @@ mod tests /// Test consumer subscribe and subscription tracking /// 测试消费者订阅和订阅跟踪 - #[tokio::test] + #[hiver_macros::test] async fn test_consumer_subscribe() { let config = ConsumerConfig::new("test-group"); @@ -308,7 +308,7 @@ mod tests /// Test consumer duplicate subscription is idempotent /// 测试消费者重复订阅是幂等的 - #[tokio::test] + #[hiver_macros::test] async fn test_consumer_subscribe_idempotent() { let config = ConsumerConfig::new("test-group"); @@ -322,7 +322,7 @@ mod tests /// Test consumer unsubscribe clears all topics /// 测试消费者取消订阅清除所有主题 - #[tokio::test] + #[hiver_macros::test] async fn test_consumer_unsubscribe() { let config = ConsumerConfig::new("test-group"); @@ -404,7 +404,7 @@ mod tests /// Test listener start and stop lifecycle /// 测试监听器启动和停止生命周期 - #[tokio::test] + #[hiver_macros::test] async fn test_listener_lifecycle() { let listener = ConsumerListener::new("listener-1", vec!["topic-a".to_string()], "my-group"); diff --git a/crates/hiver-kafka/src/tests.rs b/crates/hiver-kafka/src/tests.rs index 669ca6f0..06a781f4 100644 --- a/crates/hiver-kafka/src/tests.rs +++ b/crates/hiver-kafka/src/tests.rs @@ -85,7 +85,7 @@ mod tests /// Test full consumer lifecycle: subscribe -> poll -> commit -> unsubscribe /// 测试完整消费者生命周期:订阅 -> 轮询 -> 提交 -> 取消订阅 - #[tokio::test] + #[hiver_macros::test] async fn test_consumer_full_lifecycle() { let config = @@ -185,7 +185,7 @@ mod tests /// Test consumer group with listener lifecycle /// 测试消费者组与监听器生命周期 - #[tokio::test] + #[hiver_macros::test] async fn test_consumer_group_with_listener() { let group = ConsumerGroup::new("order-processors") diff --git a/crates/hiver-ldap/Cargo.toml b/crates/hiver-ldap/Cargo.toml index 9fee487a..13cc922c 100644 --- a/crates/hiver-ldap/Cargo.toml +++ b/crates/hiver-ldap/Cargo.toml @@ -34,6 +34,8 @@ tracing = { workspace = true } # Async / 异步 tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +hiver-runtime = { path = "../hiver-runtime" } +hiver-macros = { path = "../hiver-macros" } async-trait = { workspace = true } # Serialization / 序列化 @@ -45,7 +47,7 @@ base64 = { workspace = true } parking_lot = { workspace = true } # LDAP client (optional) / LDAP客户端(可选) -ldap3 = { version = "0.11", optional = true } +ldap3 = { workspace = true, optional = true } [features] default = [] diff --git a/crates/hiver-ldap/src/context.rs b/crates/hiver-ldap/src/context.rs index 774335fb..863b736b 100644 --- a/crates/hiver-ldap/src/context.rs +++ b/crates/hiver-ldap/src/context.rs @@ -482,7 +482,7 @@ mod tests assert!(result.is_err()); } - #[tokio::test] + #[hiver_macros::test] async fn test_stub_connection() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -491,7 +491,7 @@ mod tests assert_eq!(conn.url(), "ldap://localhost:389"); } - #[tokio::test] + #[hiver_macros::test] async fn test_stub_connection_unbind() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -501,7 +501,7 @@ mod tests assert!(!conn.is_connected()); } - #[tokio::test] + #[hiver_macros::test] async fn test_stub_simple_bind() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); diff --git a/crates/hiver-ldap/src/operations.rs b/crates/hiver-ldap/src/operations.rs index 197bad13..c9c5fa4b 100644 --- a/crates/hiver-ldap/src/operations.rs +++ b/crates/hiver-ldap/src/operations.rs @@ -274,7 +274,7 @@ mod tests assert!(ops.template().context_source().url().contains("localhost")); } - #[tokio::test] + #[hiver_macros::test] async fn test_modify_dn_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -285,7 +285,7 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_modify_dn_with_superior_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -301,7 +301,7 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_compare_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -314,7 +314,7 @@ mod tests assert!(!result.unwrap()); } - #[tokio::test] + #[hiver_macros::test] async fn test_abandon_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); diff --git a/crates/hiver-ldap/src/repository.rs b/crates/hiver-ldap/src/repository.rs index 41b06a66..25fa3084 100644 --- a/crates/hiver-ldap/src/repository.rs +++ b/crates/hiver-ldap/src/repository.rs @@ -531,7 +531,7 @@ mod tests assert_eq!(repo.base(), "ou=people,dc=example,dc=com"); } - #[tokio::test] + #[hiver_macros::test] async fn test_simple_repository_find_all_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -542,7 +542,7 @@ mod tests assert!(result.is_empty()); } - #[tokio::test] + #[hiver_macros::test] async fn test_simple_repository_find_by_id_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -553,7 +553,7 @@ mod tests assert!(result.is_none()); } - #[tokio::test] + #[hiver_macros::test] async fn test_simple_repository_exists_by_id_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -616,7 +616,7 @@ mod tests assert_eq!(dn, "uid=jane,ou=people,dc=example,dc=com"); } - #[tokio::test] + #[hiver_macros::test] async fn test_typed_repository_find_all_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -632,7 +632,7 @@ mod tests assert!(result.is_empty()); } - #[tokio::test] + #[hiver_macros::test] async fn test_typed_repository_find_by_id_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -648,7 +648,7 @@ mod tests assert!(result.is_none()); } - #[tokio::test] + #[hiver_macros::test] async fn test_typed_repository_exists_by_id_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -664,7 +664,7 @@ mod tests assert!(!result); } - #[tokio::test] + #[hiver_macros::test] async fn test_typed_repository_count_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); diff --git a/crates/hiver-ldap/src/template.rs b/crates/hiver-ldap/src/template.rs index 8af7dca3..aefe4cf1 100644 --- a/crates/hiver-ldap/src/template.rs +++ b/crates/hiver-ldap/src/template.rs @@ -309,7 +309,7 @@ mod tests assert!(template.context_source().url().contains("localhost")); } - #[tokio::test] + #[hiver_macros::test] async fn test_authenticate_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -321,7 +321,7 @@ mod tests assert!(result.unwrap()); } - #[tokio::test] + #[hiver_macros::test] async fn test_search_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -341,7 +341,7 @@ mod tests assert!(result.unwrap().is_empty()); } - #[tokio::test] + #[hiver_macros::test] async fn test_search_attrs_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -353,7 +353,7 @@ mod tests assert!(result.unwrap().is_empty()); } - #[tokio::test] + #[hiver_macros::test] async fn test_lookup_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -373,7 +373,7 @@ mod tests assert!(result.unwrap().is_none()); } - #[tokio::test] + #[hiver_macros::test] async fn test_bind_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -384,7 +384,7 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_unbind_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -393,7 +393,7 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_modify_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -404,7 +404,7 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_exists_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -413,7 +413,7 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_count_stub() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); diff --git a/crates/hiver-ldap/src/tests.rs b/crates/hiver-ldap/src/tests.rs index be6a269d..f2571489 100644 --- a/crates/hiver-ldap/src/tests.rs +++ b/crates/hiver-ldap/src/tests.rs @@ -247,7 +247,7 @@ mod tests } } - #[tokio::test] + #[hiver_macros::test] async fn test_context_source_get_context() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -255,7 +255,7 @@ mod tests assert!(conn.is_connected()); } - #[tokio::test] + #[hiver_macros::test] async fn test_context_source_anonymous_context() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -263,7 +263,7 @@ mod tests assert!(conn.is_connected()); } - #[tokio::test] + #[hiver_macros::test] async fn test_connection_unbind() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -273,7 +273,7 @@ mod tests assert!(!conn.is_connected()); } - #[tokio::test] + #[hiver_macros::test] async fn test_connection_simple_bind() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com"); @@ -282,7 +282,7 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_context_with_credentials() { let ctx = LdapContextSource::new("ldap://localhost:389", "dc=example,dc=com") @@ -303,7 +303,7 @@ mod tests assert_eq!(template.context_source().base_dn(), "dc=example,dc=com"); } - #[tokio::test] + #[hiver_macros::test] async fn test_template_authenticate() { let template = test_template(); @@ -311,7 +311,7 @@ mod tests assert!(ok); } - #[tokio::test] + #[hiver_macros::test] async fn test_template_search_empty() { let template = test_template(); @@ -322,7 +322,7 @@ mod tests assert!(results.is_empty()); } - #[tokio::test] + #[hiver_macros::test] async fn test_template_lookup_none() { let template = test_template(); @@ -341,7 +341,7 @@ mod tests assert!(result.is_none()); } - #[tokio::test] + #[hiver_macros::test] async fn test_template_bind_ok() { let template = test_template(); @@ -351,7 +351,7 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_template_unbind_ok() { let template = test_template(); @@ -359,7 +359,7 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_template_modify_ok() { let template = test_template(); @@ -369,7 +369,7 @@ mod tests assert!(result.is_ok()); } - #[tokio::test] + #[hiver_macros::test] async fn test_template_exists_false() { let template = test_template(); @@ -380,7 +380,7 @@ mod tests assert!(!exists); } - #[tokio::test] + #[hiver_macros::test] async fn test_template_count_zero() { let template = test_template(); @@ -421,7 +421,7 @@ mod tests assert_eq!(repo.build_id_dn(&"bob".to_string()), "uid=bob,ou=people,dc=example,dc=com"); } - #[tokio::test] + #[hiver_macros::test] async fn test_person_repo_find_all_empty() { let repo = person_repo(); @@ -429,7 +429,7 @@ mod tests assert!(results.is_empty()); } - #[tokio::test] + #[hiver_macros::test] async fn test_person_repo_find_by_id_none() { let repo = person_repo(); @@ -437,7 +437,7 @@ mod tests assert!(result.is_none()); } - #[tokio::test] + #[hiver_macros::test] async fn test_person_repo_exists_false() { let repo = person_repo(); @@ -445,7 +445,7 @@ mod tests assert!(!exists); } - #[tokio::test] + #[hiver_macros::test] async fn test_person_repo_count_zero() { let repo = person_repo(); diff --git a/crates/hiver-macros/src/lib.rs b/crates/hiver-macros/src/lib.rs index 82f3c928..dd1b9a6c 100644 --- a/crates/hiver-macros/src/lib.rs +++ b/crates/hiver-macros/src/lib.rs @@ -57,6 +57,7 @@ mod scheduled; mod spring_cloud; mod spring_di; mod spring_stereotype; +mod test_attr; mod transactional; use proc_macro::TokenStream; @@ -173,6 +174,35 @@ pub fn profile(attr: TokenStream, item: TokenStream) -> TokenStream spring_stereotype::profile(attr, item) } +/// Attribute macro for async tests that run on Hiver's own runtime. +/// 用于在 Hiver 自有 runtime 上运行异步测试的属性宏。 +/// +/// This is the Hiver equivalent of `#[tokio::test]`. It wraps an `async fn` +/// test body in `hiver_runtime::Runtime::block_on`, so that the async code +/// (including `hiver_runtime::time::sleep`, async I/O, and `spawn`) runs against +/// the framework's reactor — not tokio's. +/// +/// 这是 Hiver 版的 `#[tokio::test]`。它把 `async fn` 测试体包裹进 +/// `hiver_runtime::Runtime::block_on`,使异步代码(包括 `hiver_runtime::time::sleep`、 +/// 异步 I/O、`spawn`)在框架自身的 reactor 上运行 —— 而非 tokio 的。 +/// +/// # Example / 示例 +/// +/// ```rust,ignore +/// use hiver_macros::test; +/// +/// #[test] +/// async fn sleeps_on_hiver_runtime() { +/// hiver_runtime::time::sleep(std::time::Duration::from_millis(10)).await; +/// assert!(true); +/// } +/// ``` +#[proc_macro_attribute] +pub fn test(_attr: TokenStream, item: TokenStream) -> TokenStream +{ + test_attr::test_impl(item) +} + #[proc_macro_attribute] pub fn value(attr: TokenStream, item: TokenStream) -> TokenStream { diff --git a/crates/hiver-macros/src/routes.rs b/crates/hiver-macros/src/routes.rs index 5b8cc686..04e2bf0c 100644 --- a/crates/hiver-macros/src/routes.rs +++ b/crates/hiver-macros/src/routes.rs @@ -1,7 +1,7 @@ use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::quote; -use syn::{ItemFn, parse_macro_input}; +use syn::{FnArg, ItemFn, parse_macro_input}; fn parse_route_path(attr: &TokenStream) -> syn::Result { @@ -36,21 +36,64 @@ macro_rules! impl_route_macro { let register_fn = quote::format_ident!("__hiver_route_register_{}", func_name); + // Collect each handler parameter (pattern + type) so we can emit a + // FromRequest extraction site per argument. This is what makes + // `async fn get_user(Path(id): Path) -> ...` work: each param + // is extracted from the Request before the handler is called. + // 收集每个处理程序参数(模式 + 类型),以便为每个参数发射一个 + // FromRequest 提取点。这正是让 + // `async fn get_user(Path(id): Path) -> ...` 生效的关键:在调用 + // 处理程序前,每个参数都从 Request 中提取。 + let param_info: Vec<_> = input + .sig + .inputs + .iter() + .filter_map(|arg| match arg + { + FnArg::Typed(pat_type) => { + Some(((*pat_type.pat).clone(), (*pat_type.ty).clone())) + } + FnArg::Receiver(_) => None, + }) + .collect(); + + let extract_stmts = param_info.iter().map(|(pat, ty)| { + quote! { + let #pat: #ty = match <#ty as hiver_http::FromRequest>::from_request(&req).await { + Ok(val) => val, + Err(e) => { + let resp = hiver_http::Response::builder() + .status(hiver_http::StatusCode::BAD_REQUEST) + .body(hiver_http::Body::from( + format!("Parameter extraction failed: {:?}", e) + )) + .unwrap_or_else(|_| hiver_http::Response::new(hiver_http::StatusCode::INTERNAL_SERVER_ERROR)); + return hiver_http::Result::Ok(resp); + } + }; + } + }); + + let call_args = param_info.iter().map(|(pat, _)| pat); + let expanded = quote! { #input #[allow(non_snake_case)] fn #register_fn(router: hiver_router::Router) -> hiver_router::Router { - router.route(#path, hiver_http::Method::$method, #func_name) - } - - #[automatically_derived] - impl #func_name { - /// Register this route onto a Router. - /// 将此路由注册到 Router 上。 - pub fn register_route(router: hiver_router::Router) -> hiver_router::Router { - #register_fn(router) - } + // The handler passed to `Router::route` is a closure that + // extracts all parameters from the Request via FromRequest, + // calls the user's handler, and converts the result via + // IntoResponse. This satisfies `Handler: FromFut>`. + // 传给 `Router::route` 的处理程序是一个闭包:它经 FromRequest + // 从 Request 提取所有参数,调用用户的处理程序,并通过 + // IntoResponse 转换结果。这满足 + // `Handler: FromFut>`。 + router.route(#path, hiver_http::Method::$method, move |req: hiver_http::Request| async move { + #(#extract_stmts)* + let result = #func_name(#(#call_args),*).await; + hiver_http::Result::Ok(hiver_http::IntoResponse::into_response(result)) + }) } ::inventory::submit! { diff --git a/crates/hiver-macros/src/spring_di.rs b/crates/hiver-macros/src/spring_di.rs index b291305e..22138cb1 100644 --- a/crates/hiver-macros/src/spring_di.rs +++ b/crates/hiver-macros/src/spring_di.rs @@ -290,9 +290,77 @@ pub fn secured(_attr: TokenStream, item: TokenStream) -> TokenStream item } -pub fn pre_authorize(_attr: TokenStream, item: TokenStream) -> TokenStream -{ - item +pub fn pre_authorize(attr: TokenStream, item: TokenStream) -> TokenStream +{ + // Parse the expression string, e.g. #[pre_authorize("hasRole('ADMIN')")] + // 解析表达式字符串,如 #[pre_authorize("hasRole('ADMIN')")] + let expr = match attr.to_string().trim_matches(|c: char| c == '"' || c.is_whitespace()).to_string().as_str() { + "" => { + return syn::Error::new( + proc_macro2::Span::call_site(), + "#[pre_authorize] requires an expression string, e.g. #[pre_authorize(\"hasRole('ADMIN')\")]", + ) + .to_compile_error() + .into(); + } + s => s.to_string() + }; + + let mut item_fn = match syn::parse_macro_input!(item as syn::ItemFn) { + f => f, + }; + + // This macro only wraps async fn handlers. If the fn is not async, emit an + // error. + // 此宏仅包装 async fn 处理程序。若 fn 非 async,emit error。 + if item_fn.sig.asyncness.is_none() { + return syn::Error::new_spanned( + &item_fn.sig.fn_token, + "#[pre_authorize] must be applied to an async fn", + ) + .to_compile_error() + .into(); + } + + // Extract the original block (the handler body). + // 提取原始块(处理程序 body)。 + let original_block = item_fn.block; + + // Build the authorization-guard block that runs check_pre_authorize before + // the original body. + // 构建授权守卫块:在原始 body 之前运行 check_pre_authorize。 + let new_block = quote::quote! { + { + // Create a SecurityContext. In a full framework integration this + // would be injected from the request (e.g. extracted from the JWT + // by auth middleware). For now, build a default context — an + // unauthenticated user — so #[pre_authorize("hasRole(...)")] will + // correctly deny access unless the context is populated. + // 创建 SecurityContext。在完整框架集成中,这将从请求注入 + //(如由 auth 中间件从 JWT 提取)。目前构建默认上下文 —— 未认证用户 + // —— 使 #[pre_authorize("hasRole(...)")] 在上下文未填充时正确拒绝。 + let __hiver_sec_ctx = hiver_security::SecurityContext::new(); + let __hiver_authorized = hiver_security::check_pre_authorize( + &__hiver_sec_ctx, + #expr, + ) + .await + .unwrap_or(false); + + if !__hiver_authorized { + ::std::panic!( + "access denied by #[pre_authorize({})]", + #expr + ); + } + + #original_block + } + }; + + item_fn.block = syn::parse_quote! { #new_block }; + + quote::quote! { #item_fn }.into() } pub fn post_authorize(_attr: TokenStream, item: TokenStream) -> TokenStream diff --git a/crates/hiver-macros/src/spring_stereotype.rs b/crates/hiver-macros/src/spring_stereotype.rs index bda6081c..e0189c36 100644 --- a/crates/hiver-macros/src/spring_stereotype.rs +++ b/crates/hiver-macros/src/spring_stereotype.rs @@ -58,7 +58,13 @@ pub fn hiver_main(_attr: TokenStream, item: TokenStream) -> TokenStream let start_time = Instant::now(); let class_name = "hiver.Application"; - let rt = ::tokio::runtime::Runtime::new()?; + // Use Hiver's own runtime: the HTTP server (`hiver_http::Server`) + // drives async I/O via `hiver_runtime`, so the entry point must run + // on it (not tokio) for spawn/accept/waker to work. + // 使用 Hiver 自有 runtime:HTTP 服务端(`hiver_http::Server`)经 + // `hiver_runtime` 驱动异步 I/O,故入口必须在其上运行(而非 tokio), + // spawn/accept/waker 才能生效。 + let mut rt = hiver_runtime::Runtime::new()?; let mut ctx = ApplicationContext::new(); @@ -161,8 +167,8 @@ pub fn hiver_main(_attr: TokenStream, item: TokenStream) -> TokenStream Ok(()) } - pub fn context() -> anyhow::Result { - Ok(ApplicationContext::new()) + pub fn context() -> anyhow::Result { + Ok(hiver_starter::core::ApplicationContext::new()) } } }; diff --git a/crates/hiver-macros/src/test_attr.rs b/crates/hiver-macros/src/test_attr.rs new file mode 100644 index 00000000..ac4e79b3 --- /dev/null +++ b/crates/hiver-macros/src/test_attr.rs @@ -0,0 +1,104 @@ +//! Implementation of the `#[hiver::test]` attribute macro. +//! `#[hiver::test]` 属性宏的实现。 +//! +//! Transforms an `async fn` test body into a synchronous `#[test]` fn that +//! drives it on `hiver_runtime::Runtime::block_on`, so the test's async code +//! runs against the framework's reactor instead of tokio's. +//! +//! 将 `async fn` 测试体转换为同步的 `#[test]` fn,经由 +//! `hiver_runtime::Runtime::block_on` 驱动,使测试的异步代码运行在框架自身的 +//! reactor 上而非 tokio 的。 + +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{ItemFn, ReturnType, Signature, parse_macro_input}; + +/// Entry point for `#[hiver::test]`. +/// `#[hiver::test]` 的入口。 +/// +/// Input: an `async fn` (the test body). Output: a `#[test] fn` that wraps the +/// body in `hiver_runtime::Runtime::new()?.block_on(async move { ... })?`. +/// +/// 输入:一个 `async fn`(测试体)。输出:一个 `#[test] fn`,把测试体包裹进 +/// `hiver_runtime::Runtime::new()?.block_on(async move { ... })?`。 +pub fn test_impl(item: TokenStream) -> TokenStream +{ + let mut item_fn = parse_macro_input!(item as ItemFn); + + // Validate that the input is an async fn. + // 校验输入是 async fn。 + let Signature { asyncness, .. } = &item_fn.sig; + if asyncness.is_none() + { + return syn::Error::new_spanned( + &item_fn.sig.fn_token, + "#[hiver::test] must be applied to an async fn / #[hiver::test] 必须应用于 async fn", + ) + .to_compile_error() + .into(); + } + + // Strip `async` and `-> ReturnType` from the signature: the wrapper is + // synchronous and returns (). + // 从签名移除 `async` 与 `-> ReturnType`:wrapper 是同步的且返回 ()。 + item_fn.sig.asyncness = None; + let has_explicit_unit_return = + matches!(&item_fn.sig.output, ReturnType::Default | ReturnType::Type(_, _)); + let _ = has_explicit_unit_return; + // Force the output type to () regardless of what the async fn returned — the + // block_on result is unwrapped and discarded. + // 无论 async fn 返回什么,强制输出类型为 () —— block_on 的结果被 unwrap 后丢弃。 + item_fn.sig.output = ReturnType::Default; + + // The original async fn body becomes the body of an async block driven by + // block_on. We keep the original block (statements) verbatim. + // 原 async fn 的 body 成为经 block_on 驱动的 async 块的 body。原样保留 + // 原始语句块。 + let original_block = item_fn.block; + + // Build the new body: create a runtime, drive the async block, unwrap. + // 构建新 body:创建 runtime,驱动 async 块,unwrap。 + let new_block = quote! { + { + let mut __hiver_rt = match hiver_runtime::Runtime::new() { + Ok(rt) => rt, + Err(e) => panic!("#[hiver::test]: failed to create runtime: {e}"), + }; + let __hiver_result = match __hiver_rt.block_on(async move #original_block) { + Ok(v) => v, + Err(e) => panic!("#[hiver::test]: block_on failed: {e}"), + }; + #[allow(clippy::let_unit_value)] + let _ = __hiver_result; + } + }; + + // Wrap as a #[test] fn. + // 包裹为 #[test] fn。 + let test_attr = quote! { #[::core::prelude::v1::test] }; + let sig = &item_fn.sig; + let vis = &item_fn.vis; + + // Preserve other attributes (e.g. #[ignore], #[should_panic]) but drop the + // leading #[test]-like ones we replace. + // 保留其它属性(如 #[ignore]、#[should_panic]),但丢弃我们替换的 #[test] 类属性。 + let mut attrs = item_fn.attrs.clone(); + // Remove any existing #[test] / #[tokio::test] to avoid double-registration. + // 移除既有的 #[test] / #[tokio::test] 以避免重复注册。 + attrs.retain(|a| { + let s = a.path().to_token_stream().to_string(); + !(s == "test" || s == "tokio :: test" || s == "core :: prelude :: v1 :: test") + }); + + let _ = format_ident!("_"); // keep format_ident import used + + let output = quote! { + #test_attr + #(#attrs)* + #vis #sig #new_block + }; + + output.into() +} + +use quote::ToTokens; diff --git a/crates/hiver-mail/Cargo.toml b/crates/hiver-mail/Cargo.toml index 0ad79958..b05fb46d 100644 --- a/crates/hiver-mail/Cargo.toml +++ b/crates/hiver-mail/Cargo.toml @@ -32,7 +32,7 @@ async-trait = { workspace = true } # Email / 邮件 # Equivalent to: Spring JavaMailSender (javax.mail) -lettre = { version = "0.11", default-features = false, features = ["tokio1-rustls-tls", "smtp-transport", "builder"] } +lettre = { workspace = true } # Serialization / 序列化 serde = { workspace = true, features = ["derive"] } diff --git a/crates/hiver-middleware/Cargo.toml b/crates/hiver-middleware/Cargo.toml index 5a1e9d49..e11c4df3 100644 --- a/crates/hiver-middleware/Cargo.toml +++ b/crates/hiver-middleware/Cargo.toml @@ -53,8 +53,8 @@ tokio = { workspace = true, features = ["rt", "time"] } # Compression / 压缩 (Spring Content-Encoding, GzipFilter) async-compression = { workspace = true, features = ["tokio", "brotli", "gzip", "zstd", "deflate"], optional = true } # Synchronous compression libraries (fallback when async-compression not available) -flate2 = { version = "1.0", optional = true } -brotli = { version = "7.0", optional = true } +flate2 = { workspace = true, optional = true } +brotli = { workspace = true, optional = true } # Bytes utilities / 字节工具 bytes = { workspace = true } diff --git a/crates/hiver-middleware/src/timeout.rs b/crates/hiver-middleware/src/timeout.rs index f5e5ee4e..286db1cc 100644 --- a/crates/hiver-middleware/src/timeout.rs +++ b/crates/hiver-middleware/src/timeout.rs @@ -81,16 +81,29 @@ where let timeout = self.timeout; Box::pin(async move { - // Use tokio::time::timeout for the timeout functionality - // 使用tokio::time::timeout实现超时功能 - if let Ok(response) = tokio::time::timeout(timeout, next.call(req, state)).await + // Race the handler against a timer. IMPORTANT: use Hiver's own + // runtime timer (hiver_runtime::time::sleep), NOT tokio::time::timeout. + // The HTTP server runs on hiver_runtime; tokio's time driver is not + // active there, so tokio::time::timeout would never resolve and the + // response would silently never be returned. + // 将处理程序与定时器竞争。重要:使用 Hiver 自有 runtime 的定时器 + // (hiver_runtime::time::sleep),而非 tokio::time::timeout。 + // HTTP 服务端运行于 hiver_runtime;tokio 的 time driver 在那里未激活, + // 故 tokio::time::timeout 永不完成,响应会被静默丢弃。 + use futures::future::FutureExt; + let handler_fut = next.call(req, state).boxed(); + let timer_fut = hiver_runtime::time::sleep(timeout); + match futures::future::select(handler_fut, timer_fut).await { - response - } - else - { - tracing::warn!("Request timed out after {:?}", timeout); - Err(hiver_http::Error::Timeout(format!("Request timed out after {:?}", timeout))) + futures::future::Either::Left((response, _)) => response, + futures::future::Either::Right((_, _)) => + { + tracing::warn!("Request timed out after {:?}", timeout); + Err(hiver_http::Error::Timeout(format!( + "Request timed out after {:?}", + timeout + ))) + }, } }) } diff --git a/crates/hiver-modulith/Cargo.toml b/crates/hiver-modulith/Cargo.toml index 313c113a..8a125107 100644 --- a/crates/hiver-modulith/Cargo.toml +++ b/crates/hiver-modulith/Cargo.toml @@ -42,3 +42,5 @@ inventory = { workspace = true } [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +hiver-runtime = { path = "../hiver-runtime" } +hiver-macros = { path = "../hiver-macros" } diff --git a/crates/hiver-modulith/src/event.rs b/crates/hiver-modulith/src/event.rs index 93e5345c..f22813db 100644 --- a/crates/hiver-modulith/src/event.rs +++ b/crates/hiver-modulith/src/event.rs @@ -170,7 +170,7 @@ mod tests } } - #[tokio::test] + #[hiver_macros::test] async fn test_publish_and_subscribe() { let publisher = InMemoryEventPublisher::new(); @@ -186,7 +186,7 @@ mod tests assert_eq!(counter.count.load(Ordering::SeqCst), 1); } - #[tokio::test] + #[hiver_macros::test] async fn test_no_subscribers() { let publisher = InMemoryEventPublisher::new(); diff --git a/crates/hiver-modulith/src/lifecycle.rs b/crates/hiver-modulith/src/lifecycle.rs index 1e6a59c6..663af572 100644 --- a/crates/hiver-modulith/src/lifecycle.rs +++ b/crates/hiver-modulith/src/lifecycle.rs @@ -209,7 +209,7 @@ mod tests assert_eq!(service.name(), "user-service"); } - #[tokio::test] + #[hiver_macros::test] async fn test_lifecycle_init_and_stop() { let service = UserService; diff --git a/crates/hiver-reactor/src/tests.rs b/crates/hiver-reactor/src/tests.rs index 0037dfa2..144e7c5b 100644 --- a/crates/hiver-reactor/src/tests.rs +++ b/crates/hiver-reactor/src/tests.rs @@ -351,3 +351,342 @@ async fn sinks_error_strategy_no_subscribers() // Error 策略将无订阅者上报为错误。 assert!(matches!(sink.try_emit(1), Err(TrySendError::NoSubscribers))); } + +// ============================================================================ +// Error-propagation & edge-case tests / 错误传播与边界测试 +// +// These cover branches that the happy-path tests above do not exercise: +// `Err`/`None` arms of operators, timeout success/failure, sink strategy +// pinning, and capacity clamping. +// 这些覆盖上述 happy-path 测试未触及的分支:算子的 `Err`/`None` 分支、 +// 超时成功/失败、sink 策略固化,以及容量钳制。 +// ============================================================================ + +// ---- Flux error-propagation branches / Flux 错误传播分支 ---- + +#[tokio::test] +async fn flux_filter_passes_errors_through() +{ + // `filter` must not run the predicate on `Err`; errors pass through untouched. + // `filter` 不应对 `Err` 执行谓词;错误应原样透传。 + let stream = futures::stream::iter([ + Ok(1), + Err(ReactorError::Timeout), + Ok(2), /* unreachable after the error above on a try_collect path + * 在上面错误后,try_collect 路径下不可达 */ + ]); + // A predicate that would reject everything; the error must still surface. + // 一个会拒绝所有值的谓词;错误仍应浮现。 + let r: ReactorResult> = Flux::from_stream(stream).filter(|_| false).collect().await; + assert!(matches!(r, Err(ReactorError::Timeout))); +} + +#[tokio::test] +async fn flux_buffer_emits_first_error_and_drops_rest() +{ + // Inside a ready chunk, the first error short-circuits the chunk; subsequent + // items (including more errors) in the same chunk are dropped. + // 在一个就绪块内,首个错误短路该块;同块后续项(含更多错误)被丢弃。 + let stream = futures::stream::iter([ + Ok(1), + Err(ReactorError::Overflow("mid".into())), + Ok(2), + Err(ReactorError::Timeout), + ]); + // capacity >= 4 so all four land in one chunk. + // capacity >= 4,使四项落入同一块。 + let r: ReactorResult>> = Flux::from_stream(stream).buffer(8).collect().await; + // The first error terminates the whole stream (buffer maps it to Err). + // 首个错误终止整个流(buffer 将其映射为 Err)。 + assert!(matches!(r, Err(ReactorError::Overflow(_)))); +} + +#[tokio::test] +async fn flux_timeout_succeeds_when_item_in_time() +{ + // Items arrive before the deadline: they pass through, no Timeout injected. + // 项在截止前到达:原样透传,不注入 Timeout。 + let v: Vec = Flux::from_iter([1, 2, 3]) + .timeout(Duration::from_secs(10)) + .collect() + .await + .unwrap(); + assert_eq!(v, vec![1, 2, 3]); +} + +#[tokio::test(start_paused = true)] +async fn flux_timeout_fires_on_idle_stream() +{ + // A stream that never emits should produce exactly one Timeout error then end. + // 永不发射的流应恰好产生一个 Timeout 错误然后结束。 + let r: ReactorResult> = Flux::::never() + .timeout(Duration::from_millis(50)) + .collect() + .await; + assert!(matches!(r, Err(ReactorError::Timeout))); +} + +#[tokio::test] +async fn flux_never_is_non_terminating_until_taken() +{ + // `never` does not terminate, but `take(n)` bounds consumption. + // `never` 不终止,但 `take(n)` 可限定消费。 + let v: Vec = Flux::::never().take(0).collect().await.unwrap(); + assert!(v.is_empty()); +} + +#[tokio::test] +async fn flux_just_infinite_is_bounded_by_take() +{ + // `just_infinite` repeats forever; only `take` makes it finite. + // `just_infinite` 无限重复;仅 `take` 使其有限。 + let v: Vec = Flux::just_infinite(7).take(3).collect().await.unwrap(); + assert_eq!(v, vec![7, 7, 7]); +} + +#[tokio::test] +async fn flux_from_values_wraps_plain_stream() +{ + // `from_values` takes a non-error stream and lifts items into Ok. + // `from_values` 接收无错误流,将项提升为 Ok。 + let v: Vec = Flux::from_values(futures::stream::iter([10, 20])) + .collect() + .await + .unwrap(); + assert_eq!(v, vec![10, 20]); +} + +// ---- Mono error/empty branches / Mono 错误/空分支 ---- + +#[tokio::test] +async fn mono_map_propagates_error() +{ + // `map` on an errored Mono must forward the error, not call f. + // 出错 Mono 上的 `map` 必须转发错误,不调用 f。 + let r = Mono::::error("boom").map(|_| 999).await_value().await; + assert!(r.is_err()); +} + +#[tokio::test] +async fn mono_map_on_empty_yields_empty() +{ + // `map` on an empty Mono stays empty (does not synthesize a value). + // 空 Mono 上的 `map` 仍为空(不合成值)。 + let v = Mono::::empty() + .map(|x| x + 1) + .await_value() + .await + .unwrap(); + assert_eq!(v, None); +} + +#[tokio::test] +async fn mono_flat_map_propagates_error() +{ + let r = Mono::::error("boom") + .flat_map(|x| Mono::just(x + 1)) + .await_value() + .await; + assert!(r.is_err()); +} + +#[tokio::test] +async fn mono_flat_map_on_empty_yields_empty() +{ + let v = Mono::::empty() + .flat_map(|x| Mono::just(x + 1)) + .await_value() + .await + .unwrap(); + assert_eq!(v, None); +} + +#[tokio::test] +async fn mono_do_on_next_not_invoked_on_empty_or_error() +{ + use std::sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }; + + // Empty: side effect must not fire. + // 空:副作用不应触发。 + let calls_empty = Arc::new(AtomicUsize::new(0)); + let c_empty = calls_empty.clone(); + let v = Mono::::empty() + .do_on_next(move |_| { + c_empty.fetch_add(1, Ordering::Relaxed); + }) + .await_value() + .await + .unwrap(); + assert_eq!(v, None); + assert_eq!(calls_empty.load(Ordering::Relaxed), 0); + + // Error: side effect must not fire either. + // 错误:副作用也不应触发。 + let calls_err = Arc::new(AtomicUsize::new(0)); + let c_err = calls_err.clone(); + let r = Mono::::error("boom") + .do_on_next(move |_| { + c_err.fetch_add(1, Ordering::Relaxed); + }) + .await_value() + .await; + assert!(r.is_err()); + assert_eq!(calls_err.load(Ordering::Relaxed), 0); +} + +#[tokio::test] +async fn mono_default_if_empty_does_not_swallow_error() +{ + // Only `Ok(None)` is replaced; an `Err` must flow through unchanged. + // 仅 `Ok(None)` 被替换;`Err` 必须原样透传。 + let r = Mono::::error("boom") + .default_if_empty(99) + .await_value() + .await; + assert!(r.is_err()); +} + +#[tokio::test] +async fn mono_flux_emits_error_then_completes() +{ + // `flux()` on an errored Mono yields the error as the single stream item. + // 出错 Mono 上的 `flux()` 将错误作为单个流项发射。 + let r: ReactorResult> = Mono::::error("bad").flux().collect().await; + assert!(r.is_err()); +} + +#[tokio::test(start_paused = true)] +async fn mono_timeout_returns_value_when_in_time() +{ + // Completes before the deadline: the inner value is returned, not a Timeout. + // 在截止前完成:返回内部值,而非 Timeout。 + let v = Mono::just(42) + .timeout(Duration::from_secs(10)) + .await_value() + .await + .unwrap(); + assert_eq!(v, Some(42)); +} + +#[tokio::test] +async fn mono_is_awaitable_as_future_directly() +{ + // `Mono` implements `Future`; direct `.await` (without `await_value`) must work. + // This exercises the `unsafe map_unchecked_mut` Future impl. + // `Mono` 实现了 `Future`;直接 `.await`(不用 `await_value`)必须工作。 + // 此处覆盖 `unsafe map_unchecked_mut` 的 Future 实现。 + let v = Mono::just(5).map(|x| x + 1).await.unwrap(); + assert_eq!(v, Some(6)); +} + +// ---- Sinks strategy & capacity edge cases / Sinks 策略与容量边界 ---- + +#[tokio::test] +async fn sinks_many_clamps_capacity_to_one() +{ + // `many(0, …)` must behave like `many(1, …)` (capacity clamped to 1). + // `many(0, …)` 应表现为 `many(1, …)`(容量钳制为 1)。 + let sink = Sinks::::many(0, BackpressureStrategy::Buffer); + let _rx = sink.as_flux(); + assert_eq!(sink.subscriber_count(), 1); + // One subscriber present: emit must succeed. + // 存在一个订阅者:发射必须成功。 + assert!(sink.try_emit(1).is_ok()); +} + +#[tokio::test] +async fn sinks_emit_async_ok_with_no_subscribers_under_buffer() +{ + // Under the default Buffer strategy, async `emit` with no subscribers is a + // silent no-op (returns Ok) — only the Error strategy surfaces no-subscribers. + // 在默认 Buffer 策略下,无订阅者时异步 `emit` 为静默 no-op(返回 Ok)—— + // 仅 Error 策略会上报无订阅者。 + let sink = Sinks::::with_capacity(4); + assert!(sink.emit(42).await.is_ok()); +} + +#[tokio::test] +async fn sinks_emit_async_succeeds_with_subscriber() +{ + // With a subscriber present, async `emit` returns Ok. + // 存在订阅者时,异步 `emit` 返回 Ok。 + let sink = Sinks::::with_capacity(4); + let _rx = sink.as_flux(); + sink.emit(7).await.unwrap(); +} + +#[tokio::test] +async fn sinks_emit_async_error_strategy_no_subscribers() +{ + // Under Error strategy, no-subscribers surfaces as Overflow via async emit too. + // 在 Error 策略下,无订阅者同样经异步 emit 浮现为 Overflow。 + let sink = Sinks::::many(4, BackpressureStrategy::Error); + let r = sink.emit(1).await; + assert!(matches!(r, Err(ReactorError::Overflow(_)))); +} + +#[tokio::test] +async fn sinks_drop_and_drop_latest_behave_like_buffer_for_emit() +{ + // Pin current behavior: Drop / DropLatest / Block do NOT differ from Buffer + // for `try_emit` when there are subscribers — they all return Ok. (The + // strategies only differ on the *subscriber* lag side, which broadcast drops + // silently.) This guards against accidental divergence in `try_emit`. + // 固化当前行为:存在订阅者时,Drop / DropLatest / Block 与 Buffer 在 + // `try_emit` 上无差异——均返回 Ok。(策略仅在订阅者滞后侧不同,broadcast + // 会静默丢弃。)此测试防止 `try_emit` 意外分化。 + for strategy in [ + BackpressureStrategy::Buffer, + BackpressureStrategy::Drop, + BackpressureStrategy::DropLatest, + BackpressureStrategy::Block, + ] + { + let sink = Sinks::::many(4, strategy); + let _rx = sink.as_flux(); + assert!( + sink.try_emit(1).is_ok(), + "try_emit should be Ok under {strategy:?} with a subscriber" + ); + } +} + +#[tokio::test] +async fn sinks_as_flux_surfaces_lagged_overflow() +{ + // Capacity 1, slow consumer: emit several values so the receiver lags and + // `recv()` returns Lagged(n), which as_flux surfaces as Overflow("lagged by N"). + // 容量 1,慢消费者:发射多个值使接收者滞后,`recv()` 返回 Lagged(n), + // as_flux 将其浮现为 Overflow("lagged by N")。 + let sink = Sinks::::many(1, BackpressureStrategy::Buffer); + let mut rx = sink.as_flux(); + // Subscriber is now registered; blast more than the 1-slot buffer can hold + // before the consumer polls. + // 订阅者已注册;在消费者轮询前,灌入超过 1 槽缓冲的量。 + let _ = sink.try_emit(1); + let _ = sink.try_emit(2); + let _ = sink.try_emit(3); + let _ = sink.try_emit(4); + + // Drain: we expect at least one Lagged-turned-Overflow error. + // 排空:预期至少一个 Lagged 转化的 Overflow 错误。 + let mut saw_overflow = false; + for _ in 0..6 + { + match rx.next().await + { + Some(Ok(_)) => + {}, + Some(Err(ReactorError::Overflow(_))) => + { + saw_overflow = true; + break; + }, + Some(Err(_)) | None => break, + } + } + assert!(saw_overflow, "expected a lagged overflow error / 预期滞后溢出错误"); +} diff --git a/crates/hiver-resilience/Cargo.toml b/crates/hiver-resilience/Cargo.toml index 2947d828..0353a9c7 100644 --- a/crates/hiver-resilience/Cargo.toml +++ b/crates/hiver-resilience/Cargo.toml @@ -59,15 +59,15 @@ pin-project-lite = { workspace = true } rand = { workspace = true } # Service discovery / 服务发现 (Spring Cloud Service Discovery) -consul = { version = "0.4", optional = true } -etcd-rs = { version = "1.0", optional = true } -nacos = { version = "0.0", optional = true } +consul = { workspace = true, optional = true } +etcd-rs = { workspace = true, optional = true } +nacos = { workspace = true, optional = true } # Async runtime / 异步运行时 tokio = { workspace = true, features = ["time"] } # Metrics / 指标 (Spring Cloud Circuit Breaker Metrics) -prometheus = { version = "0.14", optional = true } +prometheus = { workspace = true, optional = true } metrics = { workspace = true } # Tower middleware support (Spring Web Filter equivalent) @@ -87,7 +87,10 @@ full = ["circuit-breaker", "rate-limit", "retry", "timeout", "bulkhead", "fallba [dev-dependencies] # Testing / 测试 (Spring Test) +# tokio is retained for benches/legacy tests; new async tests use #[hiver::test]. +# tokio 为 benches/遗留测试保留;新的异步测试用 #[hiver::test]。 tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +hiver-macros = { path = "../hiver-macros" } criterion = { workspace = true } # Benchmarks will be added in Phase 4 diff --git a/crates/hiver-resilience/src/retry.rs b/crates/hiver-resilience/src/retry.rs index a4cdf40f..7169fa52 100644 --- a/crates/hiver-resilience/src/retry.rs +++ b/crates/hiver-resilience/src/retry.rs @@ -368,7 +368,7 @@ where let delay = policy.calculate_delay(attempt); if !delay.is_zero() { - tokio::time::sleep(delay).await; + hiver_runtime::time::sleep(delay).await; total_delay += delay; } } diff --git a/crates/hiver-resilience/src/timeout.rs b/crates/hiver-resilience/src/timeout.rs index 247d0ed8..1d194e24 100644 --- a/crates/hiver-resilience/src/timeout.rs +++ b/crates/hiver-resilience/src/timeout.rs @@ -272,22 +272,36 @@ impl Timeout let duration = self.config.timeout; - if let Ok(value) = tokio::time::timeout(duration, f()).await + // Race the operation against a timer on Hiver's own runtime. Using + // `tokio::time::timeout` here would silently never resolve when this + // crate runs under hiver_runtime (tokio's time driver is inactive), so + // we use `select(op, hiver_runtime::time::sleep)` instead. + // 将操作与 Hiver 自有 runtime 上的定时器竞争。若此处用 + // `tokio::time::timeout`,在本 crate 运行于 hiver_runtime 下时会静默 + // 永不完成(tokio 的 time driver 未激活),故改用 + // `select(op, hiver_runtime::time::sleep)`。 + use futures::future::FutureExt; + let op = f(); + let timer = hiver_runtime::time::sleep(duration); + match futures::future::select(op.boxed_local(), timer).await { - self.success_count.fetch_add(1, Ordering::Relaxed); - Ok(value) - } - else - { - self.timeout_count.fetch_add(1, Ordering::Relaxed); - if let Some(ref callback) = self.config.on_timeout + futures::future::Either::Left((value, _)) => { - callback(); - } - Err(TimeoutError::Elapsed { - name: self.name.clone(), - timeout: duration, - }) + self.success_count.fetch_add(1, Ordering::Relaxed); + Ok(value) + }, + futures::future::Either::Right(_) => + { + self.timeout_count.fetch_add(1, Ordering::Relaxed); + if let Some(ref callback) = self.config.on_timeout + { + callback(); + } + Err(TimeoutError::Elapsed { + name: self.name.clone(), + timeout: duration, + }) + }, } } } @@ -346,10 +360,15 @@ impl TimeoutRegistry /// 如需完整功能,请使用 `Timeout::call`。 pub async fn timeout(duration: Duration, fut: impl Future) -> Result { - match tokio::time::timeout(duration, fut).await - { - Ok(value) => Ok(value), - Err(_) => Err(TimeoutError::Elapsed { + // Race the future against a timer on Hiver's runtime (see Timeout::call + // for why tokio::time::timeout is unsafe here). + // 将 future 与 Hiver runtime 上的定时器竞争(原因见 Timeout::call, + // 为何此处 tokio::time::timeout 不安全)。 + use futures::future::FutureExt; + match futures::future::select(fut.boxed_local(), hiver_runtime::time::sleep(duration)).await + { + futures::future::Either::Left((value, _)) => Ok(value), + futures::future::Either::Right(_) => Err(TimeoutError::Elapsed { name: "anonymous".to_string(), timeout: duration, }), @@ -369,6 +388,12 @@ mod tests use std::sync::atomic::AtomicUsize; use super::*; + // Async tests use the fully-qualified #[hiver_macros::test] path so the + // built-in #[test] (used by the sync config tests below) stays unshadowed. + // Do NOT add `use hiver_macros::test;` — it shadows built-in #[test]. + // 异步测试用全限定路径 #[hiver_macros::test],使内置 #[test](下面的同步 + // config 测试用)保持不被遮蔽。不要加 `use hiver_macros::test;` + // —— 它会遮蔽内置 #[test]。 // ----------------------------------------------------------------------- // Config tests @@ -500,7 +525,7 @@ mod tests // Async tests (require tokio runtime) // ----------------------------------------------------------------------- - #[tokio::test] + #[hiver_macros::test] async fn test_call_succeeds_within_timeout() { let config = TimeoutConfig::new().with_timeout(Duration::from_secs(5)); @@ -515,7 +540,7 @@ mod tests assert_eq!(m.timeout_count, 0); } - #[tokio::test] + #[hiver_macros::test] async fn test_call_times_out() { let config = TimeoutConfig::new().with_timeout(Duration::from_millis(10)); @@ -523,7 +548,7 @@ mod tests let result = t .call(|| async { - tokio::time::sleep(Duration::from_secs(10)).await; + hiver_runtime::time::sleep(Duration::from_secs(10)).await; "never" }) .await; @@ -545,7 +570,7 @@ mod tests assert_eq!(m.success_count, 0); } - #[tokio::test] + #[hiver_macros::test] async fn test_call_with_closure_capturing_state() { let config = TimeoutConfig::new().with_timeout(Duration::from_secs(1)); @@ -562,7 +587,7 @@ mod tests assert_eq!(result.unwrap(), vec![1, 2, 3]); } - #[tokio::test] + #[hiver_macros::test] async fn test_callback_invoked_on_timeout() { let counter = Arc::new(AtomicUsize::new(0)); @@ -578,14 +603,14 @@ mod tests let _ = t .call(|| async { - tokio::time::sleep(Duration::from_secs(10)).await; + hiver_runtime::time::sleep(Duration::from_secs(10)).await; }) .await; assert_eq!(counter.load(Ordering::Relaxed), 1); } - #[tokio::test] + #[hiver_macros::test] async fn test_callback_not_invoked_on_success() { let counter = Arc::new(AtomicUsize::new(0)); @@ -603,7 +628,7 @@ mod tests assert_eq!(counter.load(Ordering::Relaxed), 0); } - #[tokio::test] + #[hiver_macros::test] async fn test_zero_timeout() { // A zero timeout still gives the future one poll. A future that is @@ -615,7 +640,7 @@ mod tests assert_eq!(result.unwrap(), 99); } - #[tokio::test] + #[hiver_macros::test] #[ignore = "flaky: race between zero timeout and future completion"] async fn test_zero_timeout_with_pending() { @@ -625,7 +650,7 @@ mod tests let result = t .call(|| async { - tokio::time::sleep(Duration::from_nanos(1)).await; + hiver_runtime::time::sleep(Duration::from_nanos(1)).await; "late" }) .await; @@ -633,7 +658,7 @@ mod tests assert!(result.is_err()); } - #[tokio::test] + #[hiver_macros::test] async fn test_multiple_calls_metrics_accumulate() { let config = TimeoutConfig::new().with_timeout(Duration::from_millis(50)); @@ -650,7 +675,7 @@ mod tests { let _ = t .call(|| async { - tokio::time::sleep(Duration::from_secs(10)).await; + hiver_runtime::time::sleep(Duration::from_secs(10)).await; }) .await; } @@ -661,21 +686,21 @@ mod tests assert_eq!(m.timeout_count, 2); } - #[tokio::test] + #[hiver_macros::test] async fn test_convenience_timeout_function() { let result = timeout(Duration::from_secs(1), async { "ok" }).await; assert_eq!(result.unwrap(), "ok"); let result = timeout(Duration::from_millis(5), async { - tokio::time::sleep(Duration::from_secs(10)).await; + hiver_runtime::time::sleep(Duration::from_secs(10)).await; "late" }) .await; assert!(result.is_err()); } - #[tokio::test] + #[hiver_macros::test] async fn test_timeout_with_result_type() { let config = TimeoutConfig::new().with_timeout(Duration::from_secs(1)); @@ -685,7 +710,7 @@ mod tests assert_eq!(result.unwrap(), 100); } - #[tokio::test] + #[hiver_macros::test] async fn test_timeout_cloned_shares_metrics() { let config = TimeoutConfig::new().with_timeout(Duration::from_secs(1)); @@ -695,7 +720,7 @@ mod tests let _ = t1.call(|| async {}).await; let _ = t2 .call(|| async { - tokio::time::sleep(Duration::from_secs(10)).await; + hiver_runtime::time::sleep(Duration::from_secs(10)).await; }) .await; diff --git a/crates/hiver-retry-macros/Cargo.toml b/crates/hiver-retry-macros/Cargo.toml index 4a2fd7dd..45708470 100644 --- a/crates/hiver-retry-macros/Cargo.toml +++ b/crates/hiver-retry-macros/Cargo.toml @@ -18,7 +18,7 @@ categories.workspace = true proc-macro = true [dependencies] -syn = { version = "2.0", features = ["full", "visit-mut"] } +syn = { workspace = true, features = ["full", "visit-mut"] } quote = "1.0" proc-macro2 = "1.0" darling = "0.23.0" diff --git a/crates/hiver-retry/src/classifier.rs b/crates/hiver-retry/src/classifier.rs index deceac7c..c4f5c483 100644 --- a/crates/hiver-retry/src/classifier.rs +++ b/crates/hiver-retry/src/classifier.rs @@ -177,4 +177,99 @@ mod tests assert_eq!(RetryDecision::Retry, RetryDecision::Retry); assert_ne!(RetryDecision::Retry, RetryDecision::Stop); } + + // ============================================================ + // Additional coverage: source() delegation through the wrappers, + // and Display for the RetryDecision variants. + // 额外覆盖:经包装器的 source() 委派,以及 RetryDecision 变体的 Display。 + // ============================================================ + + /// A nested error source used to verify source() delegation. + /// 用于验证 source() 委派的嵌套错误源。 + #[derive(Debug)] + struct DeepError; + + impl fmt::Display for DeepError + { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "deep cause") + } + } + impl std::error::Error for DeepError {} + + /// An error that carries a source, to verify wrapper source() forwarding. + /// 携带 source 的错误,用于验证包装器的 source() 转发。 + #[derive(Debug)] + struct WithSource + { + inner: DeepError, + } + + impl fmt::Display for WithSource + { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "outer with source") + } + } + impl std::error::Error for WithSource + { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> + { + Some(&self.inner) + } + } + + #[test] + fn test_retryable_error_delegates_source() + { + // RetryableError::source() must forward to the inner error's source(). + // RetryableError::source() 必须转发到内部错误的 source()。 + let err = RetryableError::new(WithSource { inner: DeepError }); + let src = std::error::Error::source(&err); + assert!(src.is_some(), "source should be forwarded"); + assert_eq!(src.unwrap().to_string(), "deep cause"); + } + + #[test] + fn test_fatal_error_delegates_source() + { + // FatalError::source() must forward to the inner error's source(). + // FatalError::source() 必须转发到内部错误的 source()。 + let err = FatalError::new(WithSource { inner: DeepError }); + let src = std::error::Error::source(&err); + assert!(src.is_some(), "source should be forwarded"); + assert_eq!(src.unwrap().to_string(), "deep cause"); + } + + #[test] + fn test_retryable_and_fatal_prefixes_in_display() + { + // Pin the "[retryable]" / "[fatal]" prefixes produced by Display. + // 固化 Display 产生的 "[retryable]" / "[fatal]" 前缀。 + let r = RetryableError::new(std::io::Error::other("x")); + let f = FatalError::new(std::io::Error::other("y")); + let rs = r.to_string(); + let fs = f.to_string(); + assert!(rs.starts_with("[retryable]"), "got: {rs}"); + assert!(fs.starts_with("[fatal]"), "got: {fs}"); + assert!(rs.contains("x")); + assert!(fs.contains("y")); + } + + #[test] + fn test_retry_decision_debug_and_copy() + { + // RetryDecision is Copy + Debug; ensure cloning a variant and formatting + // it via Debug does not panic (guards the derives). + // RetryDecision 是 Copy + Debug;确保克隆变体并通过 Debug 格式化 + // 不 panic(保护 derive 实现)。 + let d = RetryDecision::Stop; + let d2 = d; + let s = format!("{d2:?}"); + assert!(s.contains("Stop")); + let r = RetryDecision::Retry; + assert_eq!(format!("{r:?}"), "Retry"); + } } diff --git a/crates/hiver-retry/src/lib.rs b/crates/hiver-retry/src/lib.rs index 28a6f573..7ebaacf8 100644 --- a/crates/hiver-retry/src/lib.rs +++ b/crates/hiver-retry/src/lib.rs @@ -283,4 +283,206 @@ mod tests assert_eq!(ctx.attempt, 1); assert!(!ctx.exhausted); } + + // ======================================================================== + // Additional coverage: RetryContext mutators, statistics reset/counters, + // callback lifecycle (on_success/on_error), and builder clamping. + // 额外覆盖:RetryContext mutator、统计 reset/计数器、 + // 回调生命周期(on_success/on_error)与构建器钳制。 + // ======================================================================== + + #[test] + fn test_retry_context_increment_accumulates_delay() + { + // increment advances attempt and adds delay into total_delay. + // increment 推进 attempt 并把 delay 累加进 total_delay。 + let mut ctx = RetryContext::new(); + assert_eq!(ctx.attempt, 1); + assert_eq!(ctx.total_delay, Duration::ZERO); + + ctx.increment(Duration::from_millis(10)); + assert_eq!(ctx.attempt, 2); + assert_eq!(ctx.total_delay, Duration::from_millis(10)); + + ctx.increment(Duration::from_millis(25)); + assert_eq!(ctx.attempt, 3); + assert_eq!(ctx.total_delay, Duration::from_millis(35)); + } + + #[test] + fn test_retry_context_set_exhausted_and_last_error() + { + let mut ctx = RetryContext::new(); + assert!(!ctx.exhausted); + assert!(ctx.last_error.is_none()); + + ctx.set_exhausted(); + assert!(ctx.exhausted); + + ctx.set_last_error("boom".to_string()); + assert_eq!(ctx.last_error.as_deref(), Some("boom")); + + // Default::default() == new(). + // Default::default() == new()。 + let d = RetryContext::default(); + assert_eq!(d.attempt, 1); + assert_eq!(d.total_delay, Duration::ZERO); + assert!(!d.exhausted); + } + + #[tokio::test] + async fn test_statistics_reset_and_exhausted_counter() + { + // An always-failing op with max_attempts=2: 1 exhausted, total_attempts=2, + // retry_count=1. Then reset() must zero all counters including + // exhausted_count and total_delay_ms (previously untested). + // 永失败操作 max_attempts=2:1 次耗尽,total_attempts=2,retry_count=1。 + // 随后 reset() 必须清零所有计数器,含此前未测的 exhausted_count 与 total_delay_ms。 + let template = RetryTemplate::builder() + .max_attempts(2) + .fixed_backoff(Duration::from_millis(1)) + .build(); + + let r = template + .execute(|| async { Err::<&str, _>(std::io::Error::other("fail")) }) + .await; + assert!(r.is_err()); + + let stats = template.stats(); + assert_eq!(stats.total_attempts.load(Ordering::SeqCst), 2); + assert_eq!(stats.retry_count.load(Ordering::SeqCst), 1); + assert_eq!(stats.success_count.load(Ordering::SeqCst), 0); + assert_eq!(stats.exhausted_count.load(Ordering::SeqCst), 1); + // fixed backoff of 1ms applied once between the two attempts. + // 两次尝试间施加一次 1ms 固定退避。 + assert_eq!(stats.total_delay_ms.load(Ordering::SeqCst), 1); + + stats.reset(); + assert_eq!(stats.total_attempts.load(Ordering::SeqCst), 0); + assert_eq!(stats.retry_count.load(Ordering::SeqCst), 0); + assert_eq!(stats.success_count.load(Ordering::SeqCst), 0); + assert_eq!(stats.exhausted_count.load(Ordering::SeqCst), 0); + assert_eq!(stats.total_delay_ms.load(Ordering::SeqCst), 0); + } + + #[tokio::test] + async fn test_on_success_callback_fires() + { + // on_success must fire once with the final attempt number on success. + // 成功时 on_success 必须以最终 attempt 号触发一次。 + let successes = Arc::new(AtomicUsize::new(0)); + let last_attempt = Arc::new(AtomicUsize::new(0)); + let s = successes.clone(); + let la = last_attempt.clone(); + + let template = RetryTemplateBuilder::new() + .max_attempts(3) + .fixed_backoff(Duration::from_millis(1)) + .on_success(move |attempts| { + s.fetch_add(1, Ordering::SeqCst); + la.store(attempts, Ordering::SeqCst); + }) + .build(); + + let r = template + .execute(|| async { Ok::<&str, std::io::Error>("ok") }) + .await; + assert_eq!(r.unwrap(), "ok"); + assert_eq!(successes.load(Ordering::SeqCst), 1); + assert_eq!(last_attempt.load(Ordering::SeqCst), 1); + } + + #[tokio::test] + async fn test_on_error_callback_fires_on_exhaustion() + { + // on_error must fire once with the error message when retries exhaust. + // 重试耗尽时 on_error 必须以错误消息触发一次。 + let errors = Arc::new(AtomicUsize::new(0)); + let msg = Arc::new(std::sync::Mutex::new(String::new())); + let e = errors.clone(); + let m = msg.clone(); + + let template = RetryTemplateBuilder::new() + .max_attempts(2) + .fixed_backoff(Duration::from_millis(1)) + .on_retry(move |_, _| { + e.fetch_add(0, Ordering::SeqCst); // no-op; keep closure + }) + .build(); + + // Use a custom callback via with_callback to capture on_error. + // 通过 with_callback 使用自定义回调以捕获 on_error。 + struct ErrProbe + { + count: Arc, + msg: Arc>, + } + impl RetryCallback for ErrProbe + { + fn on_retry(&self, _attempt: usize, _delay: Duration) {} + + fn on_success(&self, _attempts: usize) {} + + fn on_error(&self, error: &str) + { + self.count.fetch_add(1, Ordering::SeqCst); + *self.msg.lock().unwrap() = error.to_string(); + } + } + let probe = Arc::new(ErrProbe { + count: errors.clone(), + msg: msg.clone(), + }); + let template = template.with_callback(probe); + + let r = template + .execute(|| async { Err::<&str, _>(std::io::Error::other("nope")) }) + .await; + assert!(r.is_err()); + assert_eq!(errors.load(Ordering::SeqCst), 1); + assert!(m.lock().unwrap().contains("nope")); + } + + #[tokio::test] + async fn test_builder_clamps_max_attempts_to_one() + { + // max_attempts(0) clamps to 1: a failing op exhausts immediately with + // zero retries (retry_count == 0). + // max_attempts(0) 钳制为 1:失败操作立即耗尽,零重试(retry_count == 0)。 + let template = RetryTemplateBuilder::new() + .max_attempts(0) + .fixed_backoff(Duration::from_millis(1)) + .build(); + + let r = template + .execute(|| async { Err::<&str, _>(std::io::Error::other("fail")) }) + .await; + assert!(r.is_err()); + let stats = template.stats(); + assert_eq!(stats.total_attempts.load(Ordering::SeqCst), 1); + assert_eq!(stats.retry_count.load(Ordering::SeqCst), 0); + assert_eq!(stats.exhausted_count.load(Ordering::SeqCst), 1); + } + + #[tokio::test] + async fn test_noop_callback_does_not_panic() + { + // NoOpCallback implements all three hooks as no-ops; driving an execute + // that exercises success and one that exercises exhaustion must not panic. + // NoOpCallback 将三个钩子实现为 no-op;驱动一次成功与一次耗尽的 + // execute 都不得 panic。 + let cb = Arc::new(NoOpCallback); + let template = RetryTemplate::fixed(2).with_callback(cb); + + let ok = template + .execute(|| async { Ok::<&str, std::io::Error>("ok") }) + .await; + assert_eq!(ok.unwrap(), "ok"); + + let template2 = RetryTemplate::fixed(2).with_callback(Arc::new(NoOpCallback)); + let err = template2 + .execute(|| async { Err::<&str, _>(std::io::Error::other("x")) }) + .await; + assert!(err.is_err()); + } } diff --git a/crates/hiver-router/src/router.rs b/crates/hiver-router/src/router.rs index 521dd62f..c3955e73 100644 --- a/crates/hiver-router/src/router.rs +++ b/crates/hiver-router/src/router.rs @@ -237,6 +237,53 @@ impl Router self } + /// Register a route for an arbitrary HTTP method. This is the unified + /// entry point the `#[get]`/`#[post]`/… route macros call: + /// `router.route("/x", Method::GET, handler)`. It dispatches to the same + /// per-method `Routes` field as the dedicated `.get()`/`.post()`/… builders. + /// + /// 为任意 HTTP 方法注册路由。这是 `#[get]`/`#[post]`/… 路由宏调用的 + /// 统一入口:`router.route("/x", Method::GET, handler)`。它分发到与专用 + /// `.get()`/`.post()`/… 构建器相同的按方法 `Routes` 字段。 + /// + /// # Panics / 恐慌 + /// + /// Panics for `TRACE`/`CONNECT` (no route table for those methods). + /// 对 `TRACE`/`CONNECT` 触发恐慌(无对应方法的路由表)。 + #[allow(clippy::needless_pass_by_value)] + pub fn route( + mut self, + path: impl Into, + method: hiver_http::Method, + handler: impl Into>, + ) -> Self + { + let path = path.into(); + let handler = handler.into(); + let param_names = extract_param_names(&path); + let route = Route { + pattern: path.clone(), + handler, + param_names, + }; + let target = match method + { + Method::GET => &mut self.get_routes, + Method::POST => &mut self.post_routes, + Method::PUT => &mut self.put_routes, + Method::DELETE => &mut self.delete_routes, + Method::PATCH => &mut self.patch_routes, + Method::HEAD => &mut self.head_routes, + Method::OPTIONS => &mut self.options_routes, + Method::TRACE | Method::CONNECT => + { + panic!("route(): TRACE/CONNECT routing is not supported / 不支持") + }, + }; + target.patterns.insert(path, route); + self + } + /// Match a route for the given method and path /// 匹配给定方法和路径的路由 fn match_route(&self, method: Method, path: &str) diff --git a/crates/hiver-runtime/Cargo.toml b/crates/hiver-runtime/Cargo.toml index 6ecb0345..eff26307 100644 --- a/crates/hiver-runtime/Cargo.toml +++ b/crates/hiver-runtime/Cargo.toml @@ -23,10 +23,58 @@ rustdoc-args = ["--cfg", "docsrs"] [lints] workspace = true +[features] +# Default backend: async-io (cross-platform epoll/kqueue/IOCP reactor via the +# `async-io` crate). Stable, portable, used by smol. +# 默认后端:async-io(经 `async-io` crate 的跨平台 epoll/kqueue/IOCP reactor)。 +# 稳定、可移植,smol 所用。 +default = [] + +# High-performance io_uring backend (Linux only). When enabled on Linux, the +# runtime will prefer a thread-per-core io_uring reactor (via the `io-uring` +# crate, modeled after monoio) over the default async-io reactor. This preserves +# the "self-built high-performance runtime" narrative for latency-sensitive +# Linux deployments. +# +# STATUS: reserved (feature flag + docs only). The concrete `IoUringDriver` is +# NOT yet implemented — it requires a Linux environment to build and benchmark. +# On non-Linux platforms this feature compiles to a no-op (the runtime falls +# back to async-io). When implementing, add: +# #[cfg(all(target_os = "linux", feature = "io-uring"))] +# mod iouring; // a Driver impl backed by the io-uring crate +# and select it in Runtime::with_config when the feature is active on Linux. +# +# 高性能 io_uring 后端(仅 Linux)。在 Linux 上启用时,runtime 将优先使用 +# thread-per-core 的 io_uring reactor(经 `io-uring` crate,参考 monoio)而非 +# 默认的 async-io reactor。这为延迟敏感的 Linux 部署保留了"自研高性能 runtime" +# 的叙事。 +# +# 状态:预留(仅 feature flag + 文档)。具体的 `IoUringDriver` 尚未实现 —— +# 它需要 Linux 环境来构建与基准测试。在非 Linux 平台上此 feature 编译为 no-op +#(runtime 回退到 async-io)。实现时请添加: +# #[cfg(all(target_os = "linux", feature = "io-uring"))] +# mod iouring; // 基于 io-uring crate 的 Driver 实现 +# 并在 Runtime::with_config 中,当此 feature 在 Linux 上激活时选用它。 +io-uring = [] + [dependencies] # Zero-copy bytes / 零拷贝字节 bytes = { workspace = true } +# Async executor + I/O driver (replaces self-built scheduler/driver) +# 异步执行器 + I/O driver(替代自研 scheduler/driver) +# async-executor provides the multi-task executor; async-io provides the +# cross-platform epoll/kqueue/IOCP reactor + Timer; async-net provides +# TcpListener/TcpStream/UdpSocket; futures-lite provides block_on. +# async-executor 提供多任务执行器;async-io 提供跨平台 epoll/kqueue/IOCP reactor + +# Timer;async-net 提供 TcpListener/TcpStream/UdpSocket;futures-lite 提供 block_on。 +async-executor = "1" +async-channel = "2" +async-io = "2" +async-net = "2" +blocking = "1" +futures-lite = "2" + # Concurrency utilities / 并发工具 (Spring Task Executor) rustc-hash = "2.1" flume = "0.11" diff --git a/crates/hiver-runtime/src/channel.rs b/crates/hiver-runtime/src/channel.rs index ad78e1f5..98c133be 100644 --- a/crates/hiver-runtime/src/channel.rs +++ b/crates/hiver-runtime/src/channel.rs @@ -86,8 +86,11 @@ impl std::error::Error for SendError {} #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RecvError { - /// The channel is empty and closed - /// 通道为空且已关闭 + /// The channel is empty but not closed (senders still exist). + /// 通道为空但未关闭(仍有发送者)。 + Empty, + /// The channel is empty and closed (all senders dropped). + /// 通道为空且已关闭(所有发送者已 drop)。 Closed, } @@ -97,6 +100,7 @@ impl std::fmt::Display for RecvError { match self { + RecvError::Empty => write!(f, "Channel empty"), RecvError::Closed => write!(f, "Channel closed"), } } @@ -135,11 +139,30 @@ pub fn unbounded() -> (Sender, Receiver) (sender, receiver) } -/// Bounded mpsc channel -/// 有界mpsc通道 +/// Bounded mpsc channel with real backpressure. +/// 具有真实背压的有界 mpsc 通道。 /// -/// Creates a channel with a bounded buffer. -/// 创建具有有界缓冲区的通道。 +/// Creates a channel with a bounded buffer of `cap` slots. Unlike the old +/// implementation (which silently ignored the cap), this delegates to +/// [`async_channel::bounded`], which provides true backpressure: `send().await` +/// will **park the sender** when the buffer is full, until a receiver consumes +/// a slot. This prevents unbounded memory growth under fast-producer / +/// slow-consumer patterns. +/// +/// 创建一个具有 `cap` 容量槽的有界缓冲通道。与旧实现(静默忽略容量)不同, +/// 此处委托给 [`async_channel::bounded`],提供真实背压:当缓冲区满时, +/// `send().await` 会 **park 发送者**,直到接收者消费一个槽。这防止了快生产者/ +/// 慢消费者模式下的无界内存增长。 +/// +/// The returned `Sender`/`Receiver` are `async_channel` types with async +/// `send().await` and `recv().await`. `try_recv` returns +/// `Result` where `TryRecvError::Empty` +/// distinguishes "temporarily empty" from `Closed`. +/// +/// 返回的 `Sender`/`Receiver` 是 `async_channel` 类型,具有异步 +/// `send().await` 与 `recv().await`。`try_recv` 返回 +/// `Result`,其中 `TryRecvError::Empty` +/// 区分 "暂时为空" 与 `Closed`。 /// /// # Example / 示例 /// @@ -147,23 +170,12 @@ pub fn unbounded() -> (Sender, Receiver) /// use hiver_runtime::channel::bounded; /// /// let (tx, rx) = bounded::(16); +/// // tx.send(42).await.unwrap(); // parks if buffer is full /// ``` #[must_use] -pub fn bounded(cap: usize) -> (Sender, Receiver) +pub fn bounded(cap: usize) -> (async_channel::Sender, async_channel::Receiver) { - let shared = Arc::new(ChannelShared { - buffer: Mutex::new(VecDeque::with_capacity(cap)), - sender_count: AtomicUsize::new(1), - is_receiver_alive: AtomicBool::new(true), - recv_waker: Mutex::new(None), - }); - - let sender = Sender { - shared: shared.clone(), - }; - let receiver = Receiver { shared }; - - (sender, receiver) + async_channel::bounded(cap) } /// Shared state for the channel @@ -320,15 +332,15 @@ impl Receiver } else if self.shared.sender_count.load(Ordering::Acquire) == 0 { - // No senders left - // 没有发送器了 + // No senders left — channel is closed. + // 没有发送器了 —— 通道已关闭。 Err(RecvError::Closed) } else { - // Channel empty but senders still exist - // 通道为空但发送器仍然存在 - Err(RecvError::Closed) + // Channel is empty but senders still exist — try again later. + // 通道为空但发送器仍然存在 —— 稍后重试。 + Err(RecvError::Empty) } } @@ -446,9 +458,9 @@ mod tests #[test] fn test_bounded_channel_creation() { - let (tx, _rx) = bounded::(16); - assert!(!tx.is_closed()); - assert_eq!(tx.sender_count(), 1); + // bounded() now returns async_channel types. Verify basic construction. + // bounded() 现在返回 async_channel 类型。验证基本构造。 + let (_tx, _rx) = bounded::(16); } #[test] @@ -484,7 +496,9 @@ mod tests // Verify received data and order assert_eq!(rx.try_recv().unwrap(), 42); assert_eq!(rx.try_recv().unwrap(), 100); - assert_eq!(rx.try_recv(), Err(RecvError::Closed)); + // Channel is empty but the sender still exists → Empty, not Closed. + // 通道为空但发送者仍存在 → Empty,而非 Closed。 + assert_eq!(rx.try_recv(), Err(RecvError::Empty)); } #[test] @@ -524,12 +538,22 @@ mod tests #[test] fn test_bounded_channel_full() { - let (tx, rx) = bounded::(2); - assert!(tx.send(1).is_ok()); - assert!(tx.send(2).is_ok()); - // Third send should still succeed (unbounded buffer semantics via VecDeque) - assert!(tx.send(3).is_ok()); - assert_eq!(rx.len(), 3); + // bounded() now uses async_channel with real backpressure: the 3rd send + // into a capacity-2 channel must PARK until a slot is freed. + // bounded() 现在使用具有真实背压的 async_channel:向容量为 2 的通道 + // 第 3 次 send 必须 PARK 直到有槽释放。 + let (tx, _rx) = bounded::(2); + + // Fill the buffer (try_send succeeds immediately for the first 2). + // 填满缓冲区(前 2 次 try_send 立即成功)。 + tx.try_send(1).unwrap(); + tx.try_send(2).unwrap(); + + // The 3rd try_send must fail (Full) — proving the capacity bound is + // enforced. The old implementation would have accepted it. + // 第 3 次 try_send 必须失败(Full)—— 证明容量限制生效。 + // 旧实现会接受它。 + assert!(tx.try_send(3).is_err(), "capacity-2 channel must reject 3rd send"); } #[test] diff --git a/crates/hiver-runtime/src/driver/config.rs b/crates/hiver-runtime/src/driver/config.rs deleted file mode 100644 index c6cf751e..00000000 --- a/crates/hiver-runtime/src/driver/config.rs +++ /dev/null @@ -1,409 +0,0 @@ -//! Driver configuration and factory -//! Driver配置和工厂 -//! -//! This module provides configuration types and factory methods for creating -//! different driver implementations. -//! -//! 本模块提供配置类型和用于创建不同driver实现的工厂方法。 - -use std::sync::Arc; - -use crate::driver::Driver; - -/// Driver type selector using strategy pattern -/// 使用策略模式的Driver类型选择器 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DriverType -{ - /// Use epoll driver (Linux) / 使用epoll driver (Linux) - Epoll, - /// Use io-uring driver (Linux 5.1+) / 使用io-uring driver (Linux 5.1+) - IOUring, - /// Use kqueue driver (macOS/BSD) / 使用kqueue driver (macOS/BSD) - Kqueue, - /// Automatically detect and use the best available driver - /// 自动检测并使用最佳可用driver - Auto, -} - -/// Driver configuration using builder pattern -/// 使用Builder模式的Driver配置 -#[derive(Debug, Clone, Copy)] -pub struct DriverConfig -{ - /// Queue depth (must be power of 2 for ring buffer efficiency) - /// 队列深度(必须是2的幂以优化环形缓冲区效率) - pub entries: u32, - /// Wait for completion on submit (blocking mode) - /// 提交时等待完成(阻塞模式) - pub submit_wait: bool, - /// CPU core affinity (None = no affinity) - /// CPU核心亲和性(None = 无亲和性) - pub cpu_affinity: Option, - /// Enable deferred task wake-up - /// 启用延迟任务唤醒 - pub defer_wakeup: bool, - /// Maximum number of concurrent operations per FD - /// 每个文件描述符的最大并发操作数 - pub max_ops_per_fd: u32, -} - -impl Default for DriverConfig -{ - fn default() -> Self - { - Self { - entries: 256, - submit_wait: false, - cpu_affinity: None, - defer_wakeup: true, - max_ops_per_fd: 32, - } - } -} - -/// Driver configuration builder -/// Driver配置构建器 -/// -/// Provides a fluent API for constructing driver configurations. -/// 提供用于构建driver配置的流畅API。 -#[derive(Debug, Clone)] -pub struct DriverConfigBuilder -{ - config: DriverConfig, -} - -impl DriverConfigBuilder -{ - /// Create a new builder with default configuration - /// 创建具有默认配置的新构建器 - #[must_use] - pub fn new() -> Self - { - Self { - config: DriverConfig::default(), - } - } - - /// Set the queue depth (will be rounded up to next power of 2) - /// 设置队列深度(将向上舍入到下一个2的幂) - #[must_use] - pub fn entries(mut self, entries: u32) -> Self - { - self.config.entries = entries.next_power_of_two(); - self - } - - /// Enable or disable submit-wait mode - /// 启用或禁用提交等待模式 - #[must_use] - pub const fn submit_wait(mut self, wait: bool) -> Self - { - self.config.submit_wait = wait; - self - } - - /// Set CPU affinity for the driver thread - /// 为driver线程设置CPU亲和性 - #[must_use] - pub const fn cpu_affinity(mut self, core: usize) -> Self - { - self.config.cpu_affinity = Some(core); - self - } - - /// Clear CPU affinity (no affinity) - /// 清除CPU亲和性(无亲和性) - #[must_use] - pub const fn no_affinity(mut self) -> Self - { - self.config.cpu_affinity = None; - self - } - - /// Enable or disable deferred task wake-up - /// 启用或禁用延迟任务唤醒 - #[must_use] - pub const fn defer_wakeup(mut self, defer: bool) -> Self - { - self.config.defer_wakeup = defer; - self - } - - /// Set maximum operations per file descriptor - /// 设置每个文件描述符的最大操作数 - #[must_use] - pub const fn max_ops_per_fd(mut self, max: u32) -> Self - { - self.config.max_ops_per_fd = max; - self - } - - /// Build the configuration - /// 构建配置 - #[must_use] - pub const fn build(self) -> DriverConfig - { - self.config - } -} - -impl Default for DriverConfigBuilder -{ - fn default() -> Self - { - Self::new() - } -} - -/// Driver factory using factory pattern -/// 使用工厂模式的Driver工厂 -/// -/// Provides a unified interface for creating different driver implementations. -/// 提供用于创建不同driver实现的统一接口。 -pub struct DriverFactory; - -impl DriverFactory -{ - /// Create a driver with the specified type and default configuration - /// 使用指定类型和默认配置创建driver - /// - /// # Errors / 错误 - /// - /// Returns an error if: - /// 返回错误如果: - /// - The specified driver type is not available on this platform - /// - 指定的driver类型在此平台上不可用 - /// - Driver initialization fails - /// - Driver初始化失败 - /// - /// # Examples / 示例 - /// - /// ```rust,no_run,ignore - /// use hiver_runtime::driver::{DriverFactory, DriverType}; - /// - /// let driver = DriverFactory::create(DriverType::Auto).unwrap(); - /// ``` - pub fn create(driver_type: DriverType) -> std::io::Result> - { - Self::create_with_config(driver_type, DriverConfig::default()) - } - - /// Create a driver with the specified type and configuration - /// 使用指定类型和配置创建driver - /// - /// # Errors / 错误 - /// - /// Returns an error if driver initialization fails. - /// 如果driver初始化失败则返回错误。 - pub fn create_with_config( - driver_type: DriverType, - config: DriverConfig, - ) -> std::io::Result> - { - let ty = if matches!(driver_type, DriverType::Auto) - { - Self::detect_best_driver()? - } - else - { - driver_type - }; - - match ty - { - #[cfg(target_os = "linux")] - DriverType::Epoll => - { - Ok(Arc::new(crate::driver::epoll::EpollDriver::with_config(config)?)) - }, - #[cfg(target_os = "linux")] - DriverType::IOUring => - { - Ok(Arc::new(crate::driver::iouring::IoUringDriver::with_config(config)?)) - }, - #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "dragonfly" - ))] - DriverType::Kqueue => - { - Ok(Arc::new(crate::driver::kqueue::KqueueDriver::with_config(config)?)) - }, - #[cfg(not(target_os = "linux"))] - DriverType::Epoll => Err(std::io::Error::new( - std::io::ErrorKind::Unsupported, - "epoll driver is only available on Linux", - )), - #[cfg(not(target_os = "linux"))] - DriverType::IOUring => Err(std::io::Error::new( - std::io::ErrorKind::Unsupported, - "io-uring driver is only available on Linux", - )), - #[cfg(not(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "dragonfly" - )))] - DriverType::Kqueue => Err(std::io::Error::new( - std::io::ErrorKind::Unsupported, - "kqueue driver is only available on macOS/BSD", - )), - DriverType::Auto => - { - // Auto should have been resolved by detect_best_driver() above. - // If we reach here, detection failed — return a clear error. - // Auto 应该已被上面的 detect_best_driver() 解析。 - // 如果到达这里,说明检测失败 — 返回明确的错误。 - Err(std::io::Error::new( - std::io::ErrorKind::Unsupported, - "Failed to detect a suitable driver for this platform", - )) - }, - } - } - - /// Detect the best available driver for the current platform - /// 检测当前平台的最佳可用driver - fn detect_best_driver() -> std::io::Result - { - #[cfg(target_os = "linux")] - { - // Check kernel version for io-uring support - // 检查内核版本以支持io-uring - if Self::has_io_uring_support() - { - Ok(DriverType::IOUring) - } - else - { - Ok(DriverType::Epoll) - } - } - - #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "dragonfly" - ))] - { - Ok(DriverType::Kqueue) - } - - #[cfg(not(any( - target_os = "linux", - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "dragonfly" - )))] - { - Err(std::io::Error::new( - std::io::ErrorKind::Unsupported, - "No suitable driver found for this platform", - )) - } - } - - /// Check if the system supports io-uring (Linux only) - /// 检查系统是否支持io-uring(仅Linux) - #[cfg(target_os = "linux")] - fn has_io_uring_support() -> bool - { - // Check for io_uring_setup system call availability - // 检查io_uring_setup系统调用的可用性 - // io-uring requires Linux 5.1+ - // io-uring需要Linux 5.1+ - let mut uname = libc::utsname { - sysname: [0; 65], - nodename: [0; 65], - release: [0; 65], - version: [0; 65], - machine: [0; 65], - domainname: [0; 65], - }; - - unsafe { - if libc::uname(&mut uname) != 0 - { - return false; - } - - // Parse kernel version - // 解析内核版本 - let release = std::ffi::CStr::from_ptr(uname.release.as_ptr()).to_string_lossy(); - - if let Some((major, rest)) = release.split_once('.') - { - if let Some((minor, _)) = rest.split_once('.') - { - if let (Ok(maj), Ok(min)) = (major.parse::(), minor.parse::()) - { - return maj > 5 || (maj == 5 && min >= 1); - } - } - } - } - - false - } -} - -#[cfg(test)] -#[allow( - clippy::indexing_slicing, - clippy::float_cmp, - clippy::module_inception, - clippy::items_after_statements, - clippy::assertions_on_constants -)] -mod tests -{ - use super::*; - - #[test] - fn test_config_builder() - { - let config = DriverConfigBuilder::new() - .entries(512) - .submit_wait(true) - .cpu_affinity(0) - .defer_wakeup(false) - .build(); - - // Should be rounded up to next power of 2 - // 应向上舍入到下一个2的幂 - assert_eq!(config.entries, 512); - assert!(config.submit_wait); - assert_eq!(config.cpu_affinity, Some(0)); - assert!(!config.defer_wakeup); - } - - #[test] - fn test_config_rounding() - { - let config = DriverConfigBuilder::new().entries(100).build(); - - // 100 rounds up to 128 (next power of 2) - // 100向上舍入到128(下一个2的幂) - assert_eq!(config.entries, 128); - } - - #[test] - fn test_config_default() - { - let config = DriverConfig::default(); - assert_eq!(config.entries, 256); - assert!(!config.submit_wait); - assert_eq!(config.cpu_affinity, None); - assert!(config.defer_wakeup); - } -} diff --git a/crates/hiver-runtime/src/driver/epoll.rs b/crates/hiver-runtime/src/driver/epoll.rs deleted file mode 100644 index 3d01b6a6..00000000 --- a/crates/hiver-runtime/src/driver/epoll.rs +++ /dev/null @@ -1,603 +0,0 @@ -//! Epoll driver implementation for Linux -//! Linux的epoll驱动实现 -//! -//! This module provides an epoll-based I/O driver for Linux systems. -//! 本模块为Linux系统提供基于epoll的I/O驱动。 - -#![cfg(target_os = "linux")] -#![allow(warnings)] - -use std::{ - cell::UnsafeCell, - os::fd::{AsRawFd, RawFd}, - sync::{ - Arc, - atomic::{AtomicU32, AtomicUsize, Ordering}, - }, - time::Duration, -}; - -use crate::driver::{CompletionEntry, Driver, ERROR_TRANSPORT, Interest, SubmitEntry}; - -/// Minimum epoll instance size / 最小epoll实例大小 -const MIN_EPOLL_SIZE: u32 = 32; - -/// Internal state for the epoll driver -/// epoll driver的内部状态 -struct EpollState -{ - /// Submission queue head index / 提交队列头索引 - submit_head: AtomicUsize, - /// Submission queue tail index / 提交队列尾索引 - submit_tail: AtomicUsize, - /// Completion queue head index / 完成队列头索引 - completion_head: AtomicUsize, - /// Completion queue tail index / 完成队列尾索引 - completion_tail: AtomicU32, -} - -/// Completion queue using interior mutability -/// 使用内部可变性的完成队列 -struct CompletionQueue -{ - /// The actual completion entries / 实际完成条目 - entries: Box<[Option]>, -} - -// SAFETY: CompletionQueue uses interior mutability for thread-safe operations -// CompletionQueue使用内部可变性实现线程安全操作 -unsafe impl Send for CompletionQueue {} -unsafe impl Sync for CompletionQueue {} - -impl CompletionQueue -{ - /// Create a new completion queue - /// 创建新的完成队列 - fn new(capacity: usize) -> Self - { - Self { - entries: vec![None; capacity].into_boxed_slice(), - } - } - - /// Get a completion entry at the given position - /// 获取给定位置的完成条目 - fn get(&self, index: usize) -> Option<&CompletionEntry> - { - self.entries[index].as_ref() - } - - /// Set a completion entry at the given position - /// 在给定位置设置完成条目 - /// - /// # Safety / 安全性 - /// - /// Caller must ensure exclusive access to this position. - /// 调用者必须确保对此位置有独占访问权。 - unsafe fn set(&self, index: usize, entry: Option) - { - // SAFETY: We have exclusive access through the ring buffer discipline - // 我们通过环形缓冲区规则拥有独占访问权 - let ptr = self.entries.as_ptr() as *mut Option; - *ptr.add(index) = entry; - } -} - -/// Epoll-based I/O driver for Linux -/// Linux的基于epoll的I/O driver -/// -/// Uses a ring buffer pattern for submission and completion queues. -/// 对提交和完成队列使用环形缓冲区模式。 -pub struct EpollDriver -{ - /// Epoll file descriptor / epoll文件描述符 - epoll_fd: RawFd, - /// Submission queue (ring buffer) / 提交队列(环形缓冲区) - submit_queue: UnsafeCell>, - /// Completion queue with interior mutability / 具有内部可变性的完成队列 - completion_queue: CompletionQueue, - /// Queue capacity (must be power of 2) / 队列容量(必须是2的幂) - capacity: usize, - /// Capacity mask for fast modulo / 快速取模的容量掩码 - capacity_mask: usize, - /// Internal state / 内部状态 - state: Arc, - /// Event buffer for epoll_wait / epoll_wait的事件缓冲区 - event_buffer: UnsafeCell>, -} - -// Safety: EpollDriver can be sent between threads -// EpollDriver可以在线程间发送 -unsafe impl Send for EpollDriver {} - -// Safety: EpollDriver can be shared between threads (uses atomic operations and interior -// mutability) EpollDriver可以在线程间共享(使用原子操作和内部可变性) -unsafe impl Sync for EpollDriver {} - -impl EpollDriver -{ - /// Create a new epoll driver with default configuration - /// 使用默认配置创建新的epoll driver - /// - /// # Errors / 错误 - /// - /// Returns an error if epoll instance creation fails. - /// 如果epoll实例创建失败则返回错误。 - pub fn new() -> std::io::Result - { - Self::with_config(crate::driver::DriverConfig::default()) - } - - /// Create a new epoll driver with the specified configuration - /// 使用指定配置创建新的epoll driver - /// - /// # Errors / 错误 - /// - /// Returns an error if: - /// 返回错误如果: - /// - The configuration is invalid - /// - 配置无效 - /// - Epoll instance creation fails - /// - Epoll实例创建失败 - pub fn with_config(config: crate::driver::DriverConfig) -> std::io::Result - { - // Create epoll instance - // 创建epoll实例 - let size = config.entries.max(MIN_EPOLL_SIZE); - let epoll_fd = unsafe { - // Use epoll_create with size hint (deprecated but still works) - // 使用带有大小提示的epoll_create(已弃用但仍可用) - libc::epoll_create(size as i32) - }; - - if epoll_fd < 0 - { - return Err(std::io::Error::last_os_error()); - } - - // Set close-on-exec flag - // 设置close-on-exec标志 - unsafe { - let flags = libc::fcntl(epoll_fd, libc::F_GETFD); - if flags >= 0 - { - libc::fcntl(epoll_fd, libc::F_SETFD, flags | libc::FD_CLOEXEC); - } - } - - // Set CPU affinity if specified - // 如果指定了,设置CPU亲和性 - if let Some(_core) = config.cpu_affinity - { - if let Err(e) = Self::set_cpu_affinity(_core) - { - // Log warning but don't fail - // 记录警告但不失败 - eprintln!("Warning: Failed to set CPU affinity: {}", e); - } - } - - let capacity = size as usize; - let capacity_mask = capacity - 1; - - Ok(Self { - epoll_fd, - submit_queue: UnsafeCell::new(vec![SubmitEntry::new(-1, 0, 0); capacity]), - completion_queue: CompletionQueue::new(capacity), - capacity, - capacity_mask, - state: Arc::new(EpollState { - submit_head: AtomicUsize::new(0), - submit_tail: AtomicUsize::new(0), - completion_head: AtomicUsize::new(0), - completion_tail: AtomicU32::new(0), - }), - event_buffer: UnsafeCell::new(vec![libc::epoll_event { events: 0, u64: 0 }; capacity]), - }) - } - - /// Set CPU affinity for the current thread - /// 为当前线程设置CPU亲和性 - fn set_cpu_affinity(core: usize) -> std::io::Result<()> - { - #[cfg(target_os = "linux")] - unsafe { - let mut cpu_set: libc::cpu_set_t = std::mem::zeroed(); - libc::CPU_ZERO(&mut cpu_set); - libc::CPU_SET(core % libc::CPU_SETSIZE as usize, &mut cpu_set); - - let result = libc::sched_setaffinity(0, size_of::(), &cpu_set); - - if result < 0 - { - return Err(std::io::Error::last_os_error()); - } - } - - Ok(()) - } - - /// Get the current submission queue position - /// 获取当前提交队列位置 - #[inline] - fn submit_pos(&self, index: usize) -> usize - { - index & self.capacity_mask - } - - /// Get the current completion queue position - /// 获取当前完成队列位置 - #[inline] - fn completion_pos(&self, index: usize) -> usize - { - index & self.capacity_mask - } -} - -impl Drop for EpollDriver -{ - fn drop(&mut self) - { - if self.epoll_fd >= 0 - { - unsafe { - libc::close(self.epoll_fd); - } - } - } -} - -impl AsRawFd for EpollDriver -{ - fn as_raw_fd(&self) -> RawFd - { - self.epoll_fd - } -} - -impl Driver for EpollDriver -{ - fn submit(&self) -> std::io::Result - { - let mut submitted = 0; - let head = self.state.submit_head.load(Ordering::Acquire); - let tail = self.state.submit_tail.load(Ordering::Acquire); - - // Process all pending submissions - // 处理所有挂起的提交 - let mut idx = head; - while idx != tail - { - let pos = self.submit_pos(idx); - let submit_queue = unsafe { &*self.submit_queue.get() }; - let entry = &submit_queue[pos]; - - if entry.fd >= 0 - { - // Convert submit entry to epoll event - // 将提交条目转换为epoll事件 - let mut event = libc::epoll_event { - events: (libc::EPOLLONESHOT | libc::EPOLLRDHUP) as u32, - u64: entry.user_data, - }; - - // Set event type based on opcode - // 根据操作码设置事件类型 - match entry.opcode - { - crate::driver::opcode::READ => event.events |= libc::EPOLLIN as u32, - crate::driver::opcode::WRITE => event.events |= libc::EPOLLOUT as u32, - _ => - {}, - } - - let op = libc::EPOLL_CTL_MOD; - let result = unsafe { libc::epoll_ctl(self.epoll_fd, op, entry.fd, &mut event) }; - - if result < 0 - { - let err = std::io::Error::last_os_error(); - // ENOENT means FD not registered, try ADD - // ENOENT表示FD未注册,尝试ADD - if err.kind() == std::io::ErrorKind::NotFound - { - let add_result = unsafe { - libc::epoll_ctl( - self.epoll_fd, - libc::EPOLL_CTL_ADD, - entry.fd, - &mut event, - ) - }; - if add_result < 0 - { - return Err(err); - } - } - else - { - return Err(err); - } - } - - submitted += 1; - } - - idx += 1; - } - - // Advance head - // 前进head - self.state.submit_head.store(tail, Ordering::Release); - - Ok(submitted) - } - - fn wait(&self) -> std::io::Result - { - self.wait_internal(None) - } - - fn wait_timeout(&self, duration: Duration) -> std::io::Result<(usize, bool)> - { - let timeout_ms = duration.as_millis().min(i32::MAX as u128) as i32; - let result = self.wait_internal(Some(timeout_ms))?; - - // Check if we timed out by looking at the completion queue - // 通过查看完成队列检查是否超时 - let head = self.state.completion_head.load(Ordering::Acquire) as u32; - let tail = self.state.completion_tail.load(Ordering::Acquire); - - Ok((result, head == tail)) - } - - fn get_submission(&self) -> Option<&mut SubmitEntry> - { - let tail = self.state.submit_tail.load(Ordering::Acquire); - let next_tail = tail + 1; - let head = self.state.submit_head.load(Ordering::Acquire); - - // Check if queue is full - // 检查队列是否已满 - if next_tail - head > self.capacity - { - return None; - } - - let pos = self.submit_pos(tail); - // SAFETY: We have exclusive access to this position - // 我们对此位置有独占访问权 - unsafe { - let submit_queue = &mut *self.submit_queue.get(); - Some(&mut submit_queue[pos]) - } - } - - fn get_completion(&self) -> Option<&CompletionEntry> - { - let head = self.state.completion_head.load(Ordering::Acquire); - let tail = self.state.completion_tail.load(Ordering::Acquire) as usize; - - if head == tail - { - return None; - } - - let pos = self.completion_pos(head); - self.completion_queue.get(pos) - } - - fn advance_completion(&self) - { - let head = self.state.completion_head.load(Ordering::Acquire); - let tail = self.state.completion_tail.load(Ordering::Acquire) as usize; - - if head != tail - { - let pos = self.completion_pos(head); - // SAFETY: We have exclusive access through the ring buffer discipline - // 我们通过环形缓冲区规则拥有独占访问权 - unsafe { - self.completion_queue.set(pos, None); - } - - let new_head = head + 1; - self.state - .completion_head - .store(new_head, Ordering::Release); - } - } - - fn register(&self, fd: RawFd, interest: Interest) -> std::io::Result<()> - { - let mut event = libc::epoll_event { - events: interest.to_epoll_flags(), - u64: 0, - }; - - let result = unsafe { libc::epoll_ctl(self.epoll_fd, libc::EPOLL_CTL_ADD, fd, &mut event) }; - - if result < 0 - { - Err(std::io::Error::last_os_error()) - } - else - { - Ok(()) - } - } - - fn deregister(&self, fd: RawFd) -> std::io::Result<()> - { - let mut event = libc::epoll_event { events: 0, u64: 0 }; - - let result = unsafe { libc::epoll_ctl(self.epoll_fd, libc::EPOLL_CTL_DEL, fd, &mut event) }; - - if result < 0 - { - Err(std::io::Error::last_os_error()) - } - else - { - Ok(()) - } - } - - fn modify(&self, fd: RawFd, interest: Interest) -> std::io::Result<()> - { - let mut event = libc::epoll_event { - events: interest.to_epoll_flags(), - u64: 0, - }; - - let result = unsafe { libc::epoll_ctl(self.epoll_fd, libc::EPOLL_CTL_MOD, fd, &mut event) }; - - if result < 0 - { - Err(std::io::Error::last_os_error()) - } - else - { - Ok(()) - } - } - - fn submission_capacity(&self) -> usize - { - self.capacity - } - - fn completion_capacity(&self) -> usize - { - self.capacity - } - - fn supports_operation(&self, opcode: u8) -> bool - { - matches!( - opcode, - crate::driver::opcode::READ - | crate::driver::opcode::WRITE - | crate::driver::opcode::CLOSE - ) - } -} - -impl EpollDriver -{ - /// Internal wait implementation - /// 内部等待实现 - fn wait_internal(&self, timeout_ms: Option) -> std::io::Result - { - let event_buffer = unsafe { &mut *self.event_buffer.get() }; - let ptr = event_buffer.as_mut_ptr(); - let len = event_buffer.len() as i32; - - let result = unsafe { libc::epoll_wait(self.epoll_fd, ptr, len, timeout_ms.unwrap_or(-1)) }; - - if result < 0 - { - return Err(std::io::Error::last_os_error()); - } - - let count = result as usize; - - // Process events into completion queue - // 将事件处理到完成队列 - for i in 0..count - { - let event = &event_buffer[i]; - let tail = self.state.completion_tail.load(Ordering::Acquire) as usize; - let pos = self.completion_pos(tail); - - // Determine result based on events - // 根据事件确定结果 - let result = if event.events & (libc::EPOLLERR | libc::EPOLLHUP) as u32 != 0 - { - ERROR_TRANSPORT - } - else if event.events & libc::EPOLLIN as u32 != 0 - { - 1 // Readable / 可读 - } - else if event.events & libc::EPOLLOUT as u32 != 0 - { - 1 // Writable / 可写 - } - else - { - 0 - }; - - unsafe { - self.completion_queue.set( - pos, - Some(CompletionEntry { - user_data: event.u64, - result, - flags: event.events, - }), - ); - } - - self.state - .completion_tail - .store((tail + 1) as u32, Ordering::Release); - } - - Ok(count) - } -} - -#[cfg(test)] -#[allow( - clippy::indexing_slicing, - clippy::float_cmp, - clippy::module_inception, - clippy::items_after_statements, - clippy::assertions_on_constants -)] -mod tests -{ - use super::*; - - #[test] - fn test_epoll_driver_creation() - { - let driver = EpollDriver::new(); - assert!(driver.is_ok()); - - let driver = driver.unwrap(); - assert!(driver.epoll_fd >= 0); - assert_eq!(driver.capacity, 256); - } - - #[test] - fn test_epoll_driver_with_config() - { - let config = crate::driver::DriverConfigBuilder::new() - .entries(128) - .build(); - - let driver = EpollDriver::with_config(config); - assert!(driver.is_ok()); - - let driver = driver.unwrap(); - // Should be rounded up to next power of 2 (128 is already power of 2) - // 应向上舍入到下一个2的幂(128已经是2的幂) - assert_eq!(driver.capacity, 128); - } - - #[test] - fn test_ring_buffer_positions() - { - let driver = EpollDriver::new().unwrap(); - - // Test power-of-2 wrapping - // 测试2的幂的包装 - assert_eq!(driver.submit_pos(0), 0); - assert_eq!(driver.submit_pos(255), 255); - assert_eq!(driver.submit_pos(256), 0); - assert_eq!(driver.submit_pos(257), 1); - } -} diff --git a/crates/hiver-runtime/src/driver/interest.rs b/crates/hiver-runtime/src/driver/interest.rs deleted file mode 100644 index 0c613431..00000000 --- a/crates/hiver-runtime/src/driver/interest.rs +++ /dev/null @@ -1,260 +0,0 @@ -//! Interest types for file descriptor registration -//! 文件描述符注册的兴趣类型 - -#[allow(unused_imports)] -use std::os::fd::RawFd; - -/// Interest types for file descriptor registration -/// 文件描述符注册的兴趣类型 -/// -/// Specifies which events the driver should monitor for a file descriptor. -/// 指定driver应监控文件描述符的哪些事件。 -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct Interest -{ - /// Monitor for readability / 监控可读性 - pub readable: bool, - /// Monitor for writability / 监控可写性 - pub writable: bool, - /// Priority hint for edge-triggered mode / 边缘触发模式的优先级提示 - pub priority: bool, - /// One-shot mode: auto-deregister after one event / 单次模式:事件后自动取消注册 - pub oneshot: bool, - /// Edge-triggered mode vs level-triggered / 边缘触发模式 vs 水平触发 - pub edge: bool, -} - -impl Interest -{ - /// Create a new empty interest - /// 创建一个新的空兴趣 - #[must_use] - pub const fn new() -> Self - { - Self { - readable: false, - writable: false, - priority: false, - oneshot: false, - edge: false, - } - } - - /// Create interest for readable events - /// 创建可读事件兴趣 - #[must_use] - pub const fn readable() -> Self - { - Self { - readable: true, - writable: false, - priority: false, - oneshot: false, - edge: false, - } - } - - /// Create interest for writable events - /// 创建可写事件兴趣 - #[must_use] - pub const fn writable() -> Self - { - Self { - readable: false, - writable: true, - priority: false, - oneshot: false, - edge: false, - } - } - - /// Create interest for both readable and writable events - /// 创建可读和可写事件兴趣 - #[must_use] - pub const fn both() -> Self - { - Self { - readable: true, - writable: true, - priority: false, - oneshot: false, - edge: false, - } - } - - /// Add readability to the interest - /// 添加可读性到兴趣 - #[must_use] - pub const fn with_readable(mut self) -> Self - { - self.readable = true; - self - } - - /// Add writability to the interest - /// 添加可写性到兴趣 - #[must_use] - pub const fn with_writable(mut self) -> Self - { - self.writable = true; - self - } - - /// Enable priority mode - /// 启用优先级模式 - #[must_use] - pub const fn with_priority(mut self) -> Self - { - self.priority = true; - self - } - - /// Enable one-shot mode - /// 启用单次模式 - #[must_use] - pub const fn with_oneshot(mut self) -> Self - { - self.oneshot = true; - self - } - - /// Enable edge-triggered mode - /// 启用边缘触发模式 - #[must_use] - pub const fn with_edge(mut self) -> Self - { - self.edge = true; - self - } - - /// Convert to epoll event flags - /// 转换为epoll事件标志 - #[cfg(target_os = "linux")] - pub const fn to_epoll_flags(self) -> u32 - { - let mut flags = 0u32; - - if self.readable - { - flags |= libc::EPOLLIN as u32; - } - if self.writable - { - flags |= libc::EPOLLOUT as u32; - } - if self.priority - { - flags |= libc::EPOLLPRI as u32; - } - if self.oneshot - { - flags |= libc::EPOLLONESHOT as u32; - } - if self.edge - { - flags |= libc::EPOLLET as u32; - } - - flags - } - - /// Convert from epoll event flags - /// 从epoll事件标志转换 - #[cfg(target_os = "linux")] - pub fn from_epoll_flags(flags: u32) -> Self - { - Self { - readable: (flags & libc::EPOLLIN as u32) != 0, - writable: (flags & libc::EPOLLOUT as u32) != 0, - priority: (flags & libc::EPOLLPRI as u32) != 0, - oneshot: (flags & libc::EPOLLONESHOT as u32) != 0, - edge: (flags & libc::EPOLLET as u32) != 0, - } - } - - /// Convert to kqueue event flags - /// 转换为kqueue事件标志 - #[cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "dragonfly" - ))] - #[allow(dead_code)] - pub fn to_kqueue_filters(&self, fd: RawFd) -> (Vec, Vec) - { - use std::mem::zeroed; - - let mut add_events = Vec::with_capacity(2); - let remove_events = Vec::new(); - - if self.readable - { - let mut event = unsafe { zeroed::() }; - event.ident = fd as libc::uintptr_t; - event.filter = libc::EVFILT_READ; - event.flags = libc::EV_ADD | libc::EV_RECEIPT; - if self.edge - { - event.flags |= libc::EV_CLEAR; - } - if self.oneshot - { - event.flags |= libc::EV_ONESHOT; - } - add_events.push(event); - } - - if self.writable - { - let mut event = unsafe { zeroed::() }; - event.ident = fd as libc::uintptr_t; - event.filter = libc::EVFILT_WRITE; - event.flags = libc::EV_ADD | libc::EV_RECEIPT; - if self.edge - { - event.flags |= libc::EV_CLEAR; - } - if self.oneshot - { - event.flags |= libc::EV_ONESHOT; - } - add_events.push(event); - } - - (add_events, remove_events) - } -} - -#[cfg(test)] -#[allow( - clippy::indexing_slicing, - clippy::float_cmp, - clippy::module_inception, - clippy::items_after_statements, - clippy::assertions_on_constants -)] -mod tests -{ - use super::*; - - #[test] - fn test_interest_builder() - { - let interest = Interest::readable().with_writable().with_edge(); - - assert!(interest.readable); - assert!(interest.writable); - assert!(interest.edge); - assert!(!interest.priority); - } - - #[test] - fn test_interest_both() - { - let interest = Interest::both(); - assert!(interest.readable); - assert!(interest.writable); - } -} diff --git a/crates/hiver-runtime/src/driver/iouring.rs b/crates/hiver-runtime/src/driver/iouring.rs deleted file mode 100644 index f5fa2aa0..00000000 --- a/crates/hiver-runtime/src/driver/iouring.rs +++ /dev/null @@ -1,961 +0,0 @@ -//! io_uring driver implementation for Linux 5.1+ -//! Linux 5.1+的io_uring驱动实现 -//! -//! This module provides an io_uring-based I/O driver for Linux systems. -//! io_uring is the fastest I/O mechanism available on Linux, providing -//! excellent performance through shared memory queues and zero-copy I/O. -//! -//! 本模块为Linux系统提供基于io_uring的I/O驱动。 -//! io_uring是Linux上最快的I/O机制,通过共享内存队列和零拷贝I/O提供卓越的性能。 - -#![cfg(target_os = "linux")] -#![allow(warnings)] - -use std::{ - cell::UnsafeCell, - os::fd::{AsRawFd, RawFd}, - sync::{ - Arc, - atomic::{AtomicU32, AtomicUsize, Ordering}, - }, - time::Duration, -}; - -use crate::driver::{CompletionEntry, Driver, ERROR_TRANSPORT, Interest, SubmitEntry}; - -/// Minimum io_uring instance size / 最小io_uring实例大小 -const MIN_IOURING_SIZE: u32 = 32; - -/// Maximum entries in submission queue (for CQE overflow handling) -/// 提交队列中的最大条目数(用于CQE溢出处理) -const MAX_CQES: u32 = 256; - -/// io_uring setup flags / io_uring设置标志 -#[repr(C)] -#[derive(Clone, Copy, Debug, Default)] -struct IoUringParams -{ - /// Submission queue entries / 提交队列条目数 - sq_entries: u32, - /// Completion queue entries / 完成队列条目数 - cq_entries: u32, - /// Flags / 标志 - flags: u32, - /// SQ thread CPU affinity / SQ 线程 CPU 亲和性 - sq_thread_cpu: u32, - /// SQ thread idle timeout (ms) / SQ 线程空闲超时(毫秒) - sq_thread_idle: u32, - /// Features (kernel output, must be 0 on input) / 特性(内核输出,输入必须为 0) - features: u32, - /// Worker queue fd (for IORING_SETUP_ATTACH_WQ) / 工作队列 fd - wq_fd: u32, - /// Reserved fields / 保留字段 - _resv: [u32; 3], - /// Submission queue ring buffer offsets / 提交队列环形缓冲区偏移 - sq_off: SqOffsets, - /// Completion queue ring buffer offsets / 完成队列环形缓冲区偏移 - cq_off: CqOffsets, -} - -/// SQ ring buffer offsets — matches kernel `struct io_sqring_offsets`. -/// SQ 环形缓冲区偏移——匹配内核 `struct io_sqring_offsets`。 -#[repr(C)] -#[derive(Clone, Copy, Debug, Default)] -struct SqOffsets -{ - head: u32, - tail: u32, - ring_mask: u32, - ring_entries: u32, - flags: u32, - dropped: u32, - array: u32, - _resv1: u32, - _resv2: u64, -} - -/// CQ ring buffer offsets — matches kernel `struct io_cqring_offsets`. -/// CQ 环形缓冲区偏移——匹配内核 `struct io_cqring_offsets`。 -#[repr(C)] -#[derive(Clone, Copy, Debug, Default)] -struct CqOffsets -{ - head: u32, - tail: u32, - ring_mask: u32, - ring_entries: u32, - overflow: u32, - cqes: u32, - flags: u32, - _resv1: u32, - _resv2: u64, -} - -/// io_uring submission queue entry (SQE) -/// io_uring提交队列条目(SQE) -#[repr(C)] -#[derive(Clone, Copy)] -struct SubmissionQueueEntry -{ - /// Opcode / 操作码 - opcode: u8, - /// Flags / 标志 - flags: u8, - /// I/O priority / I/O优先级 - ioprio: u16, - /// File descriptor / 文件描述符 - fd: i32, - /// Offset / 偏移量 - offset: u64, - /// Address / 地址 - addr: u64, - /// Length / 长度 - len: u32, - /// Flags for operation / 操作标志 - rw_flags: i32, - /// User data / 用户数据 - user_data: u64, - /// Buffer select / 缓冲区选择 - buf_index: u16, - /// Personality / 个性 - personality: u16, - /// Spare fields / 备用字段 - _spare: [u64; 3], -} - -/// io_uring completion queue entry (CQE) -/// io_uring完成队列条目(CQE) -#[repr(C)] -#[derive(Clone, Copy)] -struct CompletionQueueEntry -{ - /// User data / 用户数据 - user_data: u64, - /// Result / 结果 - res: i32, - /// Flags / 标志 - flags: u32, - /// Reserved fields / 保留字段 - _resv: [u64; 2], -} - -/// io_uring submission queue -/// io_uring提交队列 -struct SubmissionQueue -{ - /// Head index / 头索引 - head: *const u32, - /// Tail index / 尾索引 - tail: *const u32, - /// Ring mask / 环形掩码 - ring_mask: *const u32, - /// Ring entries / 环形条目数 - ring_entries: *const u32, - /// Flags / 标志 - flags: *const u32, - /// Array / 数组 - array: *mut u32, - /// Submission queue entries / 提交队列条目 - sqes: *mut SubmissionQueueEntry, - /// Ring mask value / 环形掩码值 - ring_mask_value: u32, - /// Number of entries / 条目数 - entries: u32, -} - -// SAFETY: SubmissionQueue uses raw pointers for direct memory access -// SubmissionQueue使用原始指针进行直接内存访问 -unsafe impl Send for SubmissionQueue {} -unsafe impl Sync for SubmissionQueue {} - -/// io_uring completion queue -/// io_uring完成队列 -struct CompletionQueue -{ - /// Head index / 头索引 - head: *const u32, - /// Tail index / 尾索引 - tail: *const u32, - /// Ring mask / 环形掩码 - ring_mask: *const u32, - /// Ring entries / 环形条目数 - ring_entries: *const u32, - /// Overflow / 溢出 - overflow: *const u32, - /// Completion queue entries / 完成队列条目 - cqes: *const CompletionQueueEntry, - /// Ring mask value / 环形掩码值 - ring_mask_value: u32, -} - -// SAFETY: CompletionQueue uses raw pointers for read-only memory access -// CompletionQueue使用原始指针进行只读内存访问 -unsafe impl Send for CompletionQueue {} -unsafe impl Sync for CompletionQueue {} - -/// Internal state for the io_uring driver -/// io_uring driver的内部状态 -struct IoUringState -{ - /// Submission queue head index / 提交队列头索引 - sq_head: AtomicU32, - /// Submission queue tail index / 提交队列尾索引 - sq_tail: AtomicU32, - /// Completion queue head index / 完成队列头索引 - cq_head: AtomicU32, - /// Completion queue tail index / 完成队列尾索引 - cq_tail: AtomicU32, - /// Submission queue length / 提交队列长度 - sq_len: AtomicUsize, -} - -/// io_uring-based I/O driver for Linux -/// Linux的基于io_uring的I/O driver -/// -/// Uses io_uring for high-performance asynchronous I/O. -/// 使用io_uring实现高性能异步I/O。 -/// -/// io_uring provides: -/// io_uring提供: -/// - Shared memory queues for reduced syscall overhead / 共享内存队列减少系统调用开销 -/// - Zero-copy I/O support / 零拷贝I/O支持 -/// - Batched operation submission / 批量操作提交 -/// - Efficient poll-based I/O / 高效的基于轮询的I/O -pub struct IoUringDriver -{ - /// io_uring instance file descriptor / io_uring实例文件描述符 - ring_fd: RawFd, - /// Submission queue ring buffer memory (mapped) / 提交队列环形缓冲区内存(映射) - sq_ring: *mut u8, - /// Completion queue ring buffer memory (mapped) / 完成队列环形缓冲区内存(映射) - cq_ring: *mut u8, - /// Submission queue entries memory (mapped) / 提交队列条目内存(映射) - sqes: *mut SubmissionQueueEntry, - /// Submission queue / 提交队列 - sq: SubmissionQueue, - /// Completion queue / 完成队列 - cq: CompletionQueue, - /// Queue capacity / 队列容量 - capacity: usize, - /// Internal state / 内部状态 - state: Arc, - /// Submission queue / 提交队列(用于应用层) - submit_queue: UnsafeCell>, - /// Completion queue / 完成队列(用于应用层) - completion_queue: UnsafeCell>>, - /// Actual mmap size for submission queue ring / 提交队列环形缓冲区的实际 mmap 尺寸 - sq_ring_mmap_size: usize, - /// Actual mmap size for completion queue ring / 完成队列环形缓冲区的实际 mmap 尺寸 - cq_ring_mmap_size: usize, - /// Thread ID that created this driver (for debug safety checks) - /// 创建此驱动程序的线程 ID(用于调试安全检查) - #[cfg(debug_assertions)] - owner_thread: std::thread::ThreadId, -} - -// SAFETY: IoUringDriver can be sent between threads. -// The internal UnsafeCell fields are only accessed from the owning thread -// in the thread-per-core model (each core has its own driver instance). -// IoUringDriver 可以在线程间发送。 -// 内部 UnsafeCell 字段仅在 thread-per-core 模型中由所属线程访问 -// (每个核心有自己的驱动实例)。 -unsafe impl Send for IoUringDriver {} - -// SAFETY: IoUringDriver uses atomic operations for shared state (sq_head, sq_tail, etc.). -// The UnsafeCell> fields (submit_queue, completion_queue) are only accessed -// from the driver's owning thread in the thread-per-core architecture. -// Debug builds include thread-id checks to catch misuse. -// IoUringDriver 使用原子操作管理共享状态(sq_head, sq_tail 等)。 -// UnsafeCell> 字段(submit_queue, completion_queue)仅在 -// thread-per-core 架构中由驱动所属线程访问。 -// Debug 构建包含线程 ID 检查以捕获误用。 -unsafe impl Sync for IoUringDriver {} - -impl IoUringDriver -{ - /// Assert that the caller is the thread that created this driver. - /// In debug builds, panics if called from a different thread. - /// 断言调用者是创建此驱动程序的线程。 - /// Debug 构建中,如果从不同线程调用则 panic。 - #[inline] - #[cfg(debug_assertions)] - fn assert_owner(&self) - { - assert_eq!( - std::thread::current().id(), - self.owner_thread, - "IoUringDriver accessed from wrong thread: thread-per-core violation" - ); - } - - /// No-op in release builds / Release 构建中无操作 - #[inline] - #[cfg(not(debug_assertions))] - fn assert_owner(&self) {} - - /// Create a new io_uring driver with default configuration - /// 使用默认配置创建新的io_uring driver - /// - /// # Errors / 错误 - /// - /// Returns an error if io_uring instance creation fails. - /// 如果io_uring实例创建失败则返回错误。 - pub fn new() -> std::io::Result - { - Self::with_config(crate::driver::DriverConfig::default()) - } - - /// Create a new io_uring driver with the specified configuration - /// 使用指定配置创建新的io_uring driver - /// - /// # Errors / 错误 - /// - /// Returns an error if: - /// 返回错误如果: - /// - The configuration is invalid / 配置无效 - /// - io_uring setup fails / io_uring设置失败 - /// - Memory mapping fails / 内存映射失败 - pub fn with_config(config: crate::driver::DriverConfig) -> std::io::Result - { - let entries = config.entries.max(MIN_IOURING_SIZE); - - // Per io_uring_setup(2): io_uring_params must be ZEROED on input. - // The kernel fills sq_entries/cq_entries/offsets. `entries` is passed - // as the first syscall argument (below). Setting sq_entries/cq_entries - // on input causes EINVAL on Linux — confirmed by debug-sigsegv probe - // (zeroed params succeed, hiver's non-zero input failed). - // 依 io_uring_setup(2):io_uring_params 输入必须清零。 - // 内核填充 sq_entries/cq_entries/offsets。`entries` 作为下方 syscall 第一参数。 - // 输入设 sq_entries/cq_entries 在 Linux 上导致 EINVAL - //(debug-sigsegv probe 已证实:zeroed params 成功,hiver 非 0 输入失败)。 - let mut params = IoUringParams::default(); - - // Create io_uring instance - // 创建io_uring实例 - let ring_fd = unsafe { - libc::syscall( - 425, // __NR_io_uring_setup - entries as libc::c_long, - &mut params as *mut _ as libc::c_long, - ) as RawFd - }; - - if ring_fd < 0 - { - return Err(std::io::Error::last_os_error()); - } - - // Calculate ring buffer sizes - // 计算环形缓冲区大小 - let sq_ring_size = unsafe { - // Size = sq_off.array + sq_entries * sizeof(u32) - (params.sq_off.array as usize) + (params.sq_entries as usize) * 4 - }; - - let cq_ring_size = unsafe { - // Size = cq_off.cqes + cq_entries * sizeof(cqe) - (params.cq_off.cqes as usize) + (params.cq_entries as usize) * 16 - }; - - let sqes_size = (params.sq_entries as usize) * size_of::(); - - // Map memory regions - // 映射内存区域 - let sq_ring = unsafe { - libc::mmap( - std::ptr::null_mut(), - sq_ring_size, - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_SHARED | libc::MAP_POPULATE, - ring_fd, - 0, // Submission queue ring is at offset 0 - ) as *mut u8 - }; - - if sq_ring as *mut libc::c_void == libc::MAP_FAILED - { - unsafe { libc::close(ring_fd) }; - return Err(std::io::Error::last_os_error()); - } - - let cq_ring = unsafe { - libc::mmap( - std::ptr::null_mut(), - cq_ring_size, - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_SHARED | libc::MAP_POPULATE, - ring_fd, - 0x800_0000, // IORING_OFF_CQ_RING — kernel-defined magic offset (not sq_ring_size) - ) as *mut u8 - }; - - if cq_ring as *mut libc::c_void == libc::MAP_FAILED - { - unsafe { - libc::munmap(sq_ring as *mut libc::c_void, sq_ring_size); - libc::close(ring_fd); - } - return Err(std::io::Error::last_os_error()); - } - - let sqes = unsafe { - libc::mmap( - std::ptr::null_mut(), - sqes_size, - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_SHARED | libc::MAP_POPULATE, - ring_fd, - 0x1000_0000, // IORING_OFF_SQES — kernel-defined magic offset (was 0x80000000) - ) as *mut SubmissionQueueEntry - }; - - if sqes as *mut libc::c_void == libc::MAP_FAILED - { - unsafe { - libc::munmap(sq_ring as *mut libc::c_void, sq_ring_size); - libc::munmap(cq_ring as *mut libc::c_void, cq_ring_size); - libc::close(ring_fd); - } - return Err(std::io::Error::last_os_error()); - } - - // Setup submission queue - // 设置提交队列 - let sq = unsafe { - let sq_ptr = sq_ring as *const u8; - - SubmissionQueue { - head: sq_ptr.add(params.sq_off.head as usize) as *const u32, - tail: sq_ptr.add(params.sq_off.tail as usize) as *const u32, - ring_mask: sq_ptr.add(params.sq_off.ring_mask as usize) as *const u32, - ring_entries: sq_ptr.add(params.sq_off.ring_entries as usize) as *const u32, - flags: sq_ptr.add(params.sq_off.flags as usize) as *const u32, - array: sq_ptr.add(params.sq_off.array as usize) as *mut u32, - sqes: sqes as *mut SubmissionQueueEntry, - ring_mask_value: *(sq_ptr.add(params.sq_off.ring_mask as usize) as *const u32), - entries: params.sq_entries, - } - }; - - // Setup completion queue - // 设置完成队列 - let cq = unsafe { - let cq_ptr = cq_ring as *const u8; - - CompletionQueue { - head: cq_ptr.add(params.cq_off.head as usize) as *const u32, - tail: cq_ptr.add(params.cq_off.tail as usize) as *const u32, - ring_mask: cq_ptr.add(params.cq_off.ring_mask as usize) as *const u32, - ring_entries: cq_ptr.add(params.cq_off.ring_entries as usize) as *const u32, - overflow: cq_ptr.add(params.cq_off.overflow as usize) as *const u32, - cqes: cq_ptr.add(params.cq_off.cqes as usize) as *const CompletionQueueEntry, - ring_mask_value: *(cq_ptr.add(params.cq_off.ring_mask as usize) as *const u32), - } - }; - - let capacity = entries as usize; - - Ok(Self { - ring_fd, - sq_ring, - cq_ring, - sqes, - sq, - cq, - capacity, - state: Arc::new(IoUringState { - sq_head: AtomicU32::new(0), - sq_tail: AtomicU32::new(0), - cq_head: AtomicU32::new(0), - cq_tail: AtomicU32::new(0), - sq_len: AtomicUsize::new(0), - }), - submit_queue: UnsafeCell::new(vec![SubmitEntry::new(-1, 0, 0); capacity]), - completion_queue: UnsafeCell::new(vec![None; capacity]), - sq_ring_mmap_size: sq_ring_size, - cq_ring_mmap_size: cq_ring_size, - #[cfg(debug_assertions)] - owner_thread: std::thread::current().id(), - }) - } - - /// Get the current submission queue position - /// 获取当前提交队列位置 - #[inline] - fn sq_pos(&self, index: u32) -> u32 - { - index & self.sq.ring_mask_value - } - - /// Get the current completion queue position - /// 获取当前完成队列位置 - #[inline] - fn cq_pos(&self, index: u32) -> u32 - { - index & self.cq.ring_mask_value - } - - /// Submit operations to the kernel - /// 向内核提交操作 - fn submit_to_kernel(&self) -> std::io::Result - { - let head = unsafe { *self.sq.head }; - let tail = self.state.sq_tail.load(Ordering::Acquire); - let to_submit = tail - head; - - if to_submit == 0 - { - return Ok(0); - } - - // Set submission queue tail - // 设置提交队列尾 - unsafe { - *(self.sq.tail as *mut u32) = tail; - } - - // Use IORING_ENTER_GETEVENTS flag to also wait for completions - // 使用IORING_ENTER_GETEVENTS标志同时等待完成 - let result = unsafe { - libc::syscall( - 426, // __NR_io_uring_enter - self.ring_fd as libc::c_long, - to_submit as libc::c_long, - 0, // min_complete - 1, // flags: IORING_ENTER_GETEVENTS - std::ptr::null_mut::(), // sig: NULL (no signal mask) - 0, /* sigsz: 0 (was missing — uninitialised - * 6th arg caused EINVAL on spawn - * submit) */ - ) as libc::c_long - }; - - if result < 0 - { - Err(std::io::Error::last_os_error()) - } - else - { - Ok(result as usize) - } - } - - /// Get a free submission queue entry - /// 获取一个空闲的提交队列条目 - fn get_free_sqe(&self) -> Option<*mut SubmissionQueueEntry> - { - let head = unsafe { *self.sq.head }; - let tail = self.state.sq_tail.load(Ordering::Acquire); - let next_tail = tail + 1; - - // Check if queue is full - // 检查队列是否已满 - if next_tail - head >= self.sq.entries - { - return None; - } - - let index = self.sq_pos(tail); - unsafe { Some(self.sq.sqes.add(index as usize)) } - } -} - -impl Drop for IoUringDriver -{ - fn drop(&mut self) - { - // Use the actual mmap sizes stored during setup, not hardcoded values - // 使用 setup 时存储的实际 mmap 尺寸,而非硬编码值 - let sqes_size = self.capacity * size_of::(); - - unsafe { - libc::munmap(self.sq_ring as *mut libc::c_void, self.sq_ring_mmap_size); - libc::munmap(self.cq_ring as *mut libc::c_void, self.cq_ring_mmap_size); - libc::munmap(self.sqes as *mut libc::c_void, sqes_size); - libc::close(self.ring_fd); - } - } -} - -impl AsRawFd for IoUringDriver -{ - fn as_raw_fd(&self) -> RawFd - { - self.ring_fd - } -} - -impl Driver for IoUringDriver -{ - fn submit(&self) -> std::io::Result - { - let mut submitted = 0; - - // Process all pending submissions from our internal queue - // 处理内部队列中所有挂起的提交 - self.assert_owner(); - let len = self.state.sq_len.load(Ordering::Acquire); - for i in 0..len - { - let submit_queue = unsafe { &*self.submit_queue.get() }; - let entry = &submit_queue[i]; - - if entry.fd >= 0 - { - if let Some(sqe) = self.get_free_sqe() - { - unsafe { - (*sqe).opcode = entry.opcode; - (*sqe).flags = 0; - (*sqe).ioprio = 0; - (*sqe).fd = entry.fd; - (*sqe).offset = entry.offset as u64; - (*sqe).addr = entry.buf_ptr.map_or(0, |p| p.as_ptr() as u64); - (*sqe).len = entry.buf_len; - (*sqe).rw_flags = 0; - (*sqe).user_data = entry.user_data; - (*sqe).buf_index = 0; - (*sqe).personality = 0; - - // Set array index - // 设置数组索引 - let tail = self.state.sq_tail.load(Ordering::Acquire); - let index = self.sq_pos(tail); - *self.sq.array.add(index as usize) = index; - - // Advance tail - // 前进尾指针 - self.state.sq_tail.store(tail + 1, Ordering::Release); - } - - submitted += 1; - } - } - } - - // Clear the submission queue - // 清空提交队列 - self.state.sq_len.store(0, Ordering::Release); - - // Submit to kernel - // 提交到内核 - let _kernel_submitted = self.submit_to_kernel()?; - - Ok(submitted) - } - - fn wait(&self) -> std::io::Result - { - self.wait_timeout(Duration::from_secs(1)).map(|(n, _)| n) - } - - fn wait_timeout(&self, duration: Duration) -> std::io::Result<(usize, bool)> - { - // Convert duration to timespec - // 转换持续时间为timespec - let ts = libc::timespec { - tv_sec: duration.as_secs() as libc::time_t, - tv_nsec: duration.subsec_nanos() as libc::c_long, - }; - - // io_uring_enter timeout requires IORING_ENTER_EXT_ARG (kernel 5.11+) - // with struct io_uring_get_events_arg { sigmask, sigmask_sz, pad, ts }. - // The previous call passed &ts as the sigset_t* AND omitted the 6th - // arg (sigsz) — valgrind reported "io_uring_enter(sigsz) contains - // uninitialised byte(s)". EXT_ARG is the correct kernel API for timeout. - #[repr(C)] - struct IoUringGetEventsArg - { - sigmask: u64, - sigmask_sz: u32, - pad: u32, - ts: u64, // pointer to __kernel_timespec (kernel uapi: __u64, NOT embedded timespec) - } - let arg = IoUringGetEventsArg { - sigmask: 0, - sigmask_sz: 0, - pad: 0, - ts: &ts as *const _ as u64, - }; - let result = unsafe { - libc::syscall( - 426, // __NR_io_uring_enter - self.ring_fd as libc::c_long, - 0, // to_submit - 1, // min_complete - 9, // flags: IORING_ENTER_GETEVENTS(1) | IORING_ENTER_EXT_ARG(8) - &arg as *const _ as *const libc::c_void, - core::mem::size_of::(), - ) as libc::c_long - }; - - if result < 0 - { - let err = std::io::Error::last_os_error(); - // io_uring_enter(GETEVENTS + timeout) returns -ETIME when the timeout - // expires with no completions — this is a NORMAL timeout, not an error. - // run_once already treats timed_out as normal idle; returning Err here - // aborted block_on whenever a pure-computation task had no I/O event. - // - // NB: the kernel returns -ETIME (errno 62), NOT -ETIMEDOUT (errno 110). - // Comparing against libc::ETIMEDOUT was wrong — the Err propagated as - // Os { code: 62, "Timer expired" }, aborting every spawn test. - if err.raw_os_error() == Some(libc::ETIME) - { - return Ok((0, true)); // 0 completions, timed out - } - return Err(err); - } - - // Process completion queue - // 处理完成队列 - self.assert_owner(); - let mut completed = 0; - let head = self.state.cq_head.load(Ordering::Acquire); - let tail = unsafe { *self.cq.tail }; - - while head != tail - { - let index = self.cq_pos(head); - let cqe = unsafe { &*self.cq.cqes.add(index as usize) }; - - // Store in completion queue - // 存储到完成队列 - unsafe { - let completion_queue = &mut *self.completion_queue.get(); - let pos = self.state.cq_tail.load(Ordering::Acquire) as usize % self.capacity; - completion_queue[pos] = Some(CompletionEntry { - user_data: (*cqe).user_data, - result: if (*cqe).res < 0 - { - ERROR_TRANSPORT - } - else - { - (*cqe).res - }, - flags: (*cqe).flags, - }); - self.state.cq_tail.fetch_add(1, Ordering::Release); - } - - completed += 1; - unsafe { - *(self.cq.head as *mut u32) = head + 1; - } - } - - self.state.cq_head.store(tail, Ordering::Release); - - // Check if we timed out - // 检查是否超时 - let timed_out = completed == 0; - - Ok((completed, timed_out)) - } - - fn get_submission(&self) -> Option<&mut SubmitEntry> - { - self.assert_owner(); - let len = self.state.sq_len.load(Ordering::Acquire); - - if len >= self.capacity - { - return None; - } - - self.state.sq_len.fetch_add(1, Ordering::Release); - - unsafe { - let submit_queue = &mut *self.submit_queue.get(); - Some(&mut submit_queue[len]) - } - } - - fn get_completion(&self) -> Option<&CompletionEntry> - { - self.assert_owner(); - let head = self.state.cq_head.load(Ordering::Acquire); - let tail = self.state.cq_tail.load(Ordering::Acquire); - - if head == tail - { - return None; - } - - unsafe { - let completion_queue = &*self.completion_queue.get(); - let pos = head as usize % self.capacity; - completion_queue[pos].as_ref() - } - } - - fn advance_completion(&self) - { - self.assert_owner(); - let head = self.state.cq_head.load(Ordering::Acquire); - let tail = self.state.cq_tail.load(Ordering::Acquire); - - if head != tail - { - unsafe { - let completion_queue = &mut *self.completion_queue.get(); - let pos = head as usize % self.capacity; - completion_queue[pos] = None; - } - - self.state.cq_head.fetch_add(1, Ordering::Release); - } - } - - fn register(&self, fd: RawFd, interest: Interest) -> std::io::Result<()> - { - // io_uring uses POLL_ADD or POLL_REMOVE for registration - // For now, we'll use a simple approach with poll operation - // io_uring使用POLL_ADD或POLL_REMOVE进行注册 - // 目前,我们使用简单的poll操作 - - let mut events = 0i16; - if interest.readable - { - events |= libc::POLLIN as i16; - } - if interest.writable - { - events |= libc::POLLOUT as i16; - } - - // Get a free SQE - // 获取一个空闲的SQE - let sqe = self.get_free_sqe().ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::WouldBlock, "Submission queue full") - })?; - - unsafe { - (*sqe).opcode = 6; // IORING_OP_POLL_ADD - (*sqe).fd = fd; - (*sqe).addr = events as u64; - (*sqe).len = 0; - (*sqe).user_data = fd as u64; - - // Set array index and advance tail - // 设置数组索引并前进尾指针 - let tail = self.state.sq_tail.load(Ordering::Acquire); - let index = self.sq_pos(tail); - *self.sq.array.add(index as usize) = index; - self.state.sq_tail.store(tail + 1, Ordering::Release); - } - - Ok(()) - } - - fn deregister(&self, fd: RawFd) -> std::io::Result<()> - { - // Use POLL_REMOVE to deregister - // 使用POLL_REMOVE注销 - let sqe = self.get_free_sqe().ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::WouldBlock, "Submission queue full") - })?; - - unsafe { - (*sqe).opcode = 7; // IORING_OP_POLL_REMOVE - (*sqe).fd = -1; - (*sqe).addr = fd as u64; - (*sqe).user_data = fd as u64; - - let tail = self.state.sq_tail.load(Ordering::Acquire); - let index = self.sq_pos(tail); - *self.sq.array.add(index as usize) = index; - self.state.sq_tail.store(tail + 1, Ordering::Release); - } - - Ok(()) - } - - fn modify(&self, fd: RawFd, interest: Interest) -> std::io::Result<()> - { - // Remove and re-register - // 移除并重新注册 - self.deregister(fd)?; - self.register(fd, interest) - } - - fn submission_capacity(&self) -> usize - { - self.capacity - } - - fn completion_capacity(&self) -> usize - { - self.capacity - } - - fn supports_operation(&self, opcode: u8) -> bool - { - // io_uring supports all operations - // io_uring支持所有操作 - matches!( - opcode, - crate::driver::opcode::READ - | crate::driver::opcode::WRITE - | crate::driver::opcode::FSYNC - | crate::driver::opcode::CLOSE - ) - } -} - -#[cfg(test)] -#[allow( - clippy::indexing_slicing, - clippy::float_cmp, - clippy::module_inception, - clippy::items_after_statements, - clippy::assertions_on_constants -)] -mod tests -{ - use super::*; - - #[test] - fn test_iouring_driver_creation() - { - // This test may fail on systems without io_uring support - // 此测试可能在没有io_uring支持的系统上失败 - let driver = IoUringDriver::new(); - // Allow test to pass if io_uring is not available - // 如果io_uring不可用,允许测试通过 - let _ = driver; - } - - #[test] - fn test_iouring_params_size() - { - // Size varies by kernel version (40 on 5.x, 120 on 6.x). - // Just verify it's a reasonable size for the struct. - // 大小因内核版本而异(5.x 上为 40,6.x 上为 120)。 - // 仅验证它是合理的结构体大小。 - let sz = size_of::(); - assert!(sz >= 40, "IoUringParams too small: {sz}"); - assert!(sz <= 256, "IoUringParams unexpectedly large: {sz}"); - } - - #[test] - fn test_submission_queue_entry_size() - { - // Size varies by kernel version (64 on 5.x, 72 on 6.x). - // 大小因内核版本而异(5.x 上为 64,6.x 上为 72)。 - let sz = size_of::(); - assert!(sz >= 64, "SubmissionQueueEntry too small: {sz}"); - assert!(sz <= 256, "SubmissionQueueEntry unexpectedly large: {sz}"); - } - - #[test] - fn test_completion_queue_entry_size() - { - // Size varies by kernel version (16 on 5.x, 32 on 6.x). - // 大小因内核版本而异(5.x 上为 16,6.x 上为 32)。 - let sz = size_of::(); - assert!(sz >= 16, "CompletionQueueEntry too small: {sz}"); - assert!(sz <= 128, "CompletionQueueEntry unexpectedly large: {sz}"); - } -} diff --git a/crates/hiver-runtime/src/driver/kqueue.rs b/crates/hiver-runtime/src/driver/kqueue.rs deleted file mode 100644 index e4c6f894..00000000 --- a/crates/hiver-runtime/src/driver/kqueue.rs +++ /dev/null @@ -1,761 +0,0 @@ -//! kqueue driver implementation for macOS/BSD -//! macOS/BSD的kqueue驱动实现 -//! -//! This module provides a kqueue-based I/O driver for macOS and BSD systems. -//! 本模块为macOS和BSD系统提供基于kqueue的I/O驱动。 - -#![cfg(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "dragonfly" -))] - -use std::{ - cell::UnsafeCell, - os::fd::{AsRawFd, RawFd}, - sync::{ - Arc, - atomic::{AtomicU32, AtomicUsize, Ordering}, - }, - time::Duration, -}; - -use crate::driver::{CompletionEntry, Driver, ERROR_TRANSPORT, Interest, SubmitEntry}; - -/// Minimum kqueue instance size / 最小kqueue实例大小 -const MIN_KQUEUE_SIZE: u32 = 32; - -/// Internal state for the kqueue driver -/// kqueue driver的内部状态 -struct KqueueState -{ - /// Submission queue head index / 提交队列头索引 - submit_head: AtomicUsize, - /// Submission queue tail index / 提交队列尾索引 - submit_tail: AtomicUsize, - /// Completion queue head index / 完成队列头索引 - completion_head: AtomicUsize, - /// Completion queue tail index / 完成队列尾索引 - completion_tail: AtomicU32, -} - -/// Completion queue using interior mutability -/// 使用内部可变性的完成队列 -struct CompletionQueue -{ - /// The actual completion entries / 实际完成条目 - entries: Box<[Option]>, -} - -// SAFETY: CompletionQueue uses interior mutability for thread-safe operations -// CompletionQueue使用内部可变性实现线程安全操作 -unsafe impl Send for CompletionQueue {} -unsafe impl Sync for CompletionQueue {} - -impl CompletionQueue -{ - /// Create a new completion queue - /// 创建新的完成队列 - fn new(capacity: usize) -> Self - { - Self { - entries: vec![None; capacity].into_boxed_slice(), - } - } - - /// Get a completion entry at the given position - /// 获取给定位置的完成条目 - fn get(&self, index: usize) -> Option<&CompletionEntry> - { - self.entries[index].as_ref() - } - - /// Set a completion entry at the given position - /// 在给定位置设置完成条目 - /// - /// # Safety / 安全性 - /// - /// Caller must ensure exclusive access to this position. - /// 调用者必须确保对此位置有独占访问权。 - unsafe fn set(&self, index: usize, entry: Option) - { - // SAFETY: We have exclusive access through the ring buffer discipline - // 我们通过环形缓冲区规则拥有独占访问权 - let ptr = self.entries.as_ptr().cast_mut(); - *ptr.add(index) = entry; - } -} - -/// kqueue-based I/O driver for macOS/BSD -/// macOS/BSD的基于kqueue的I/O driver -/// -/// Uses a ring buffer pattern for submission and completion queues. -/// 对提交和完成队列使用环形缓冲区模式。 -/// -/// kqueue provides an efficient way to monitor multiple file descriptors -/// for various events (read, write, error, etc.). -/// -/// kqueue提供了一种高效的方式来监控多个文件描述符的各种事件(读、写、错误等)。 -pub struct KqueueDriver -{ - /// kqueue file descriptor / kqueue文件描述符 - kqueue_fd: RawFd, - /// Submission queue (ring buffer) / 提交队列(环形缓冲区) - submit_queue: UnsafeCell>, - /// Completion queue with interior mutability / 具有内部可变性的完成队列 - completion_queue: CompletionQueue, - /// Queue capacity (must be power of 2) / 队列容量(必须是2的幂) - capacity: usize, - /// Capacity mask for fast modulo / 快速取模的容量掩码 - capacity_mask: usize, - /// Internal state / 内部状态 - state: Arc, - /// Event buffer for kevent / kevent的事件缓冲区 - event_buffer: UnsafeCell>, - /// Change buffer for registering/deregistering events (reserved for future use) - /// 用于注册/注销事件的change缓冲区(保留供将来使用) - #[allow(dead_code)] - change_buffer: UnsafeCell>, -} - -// Safety: KqueueDriver can be sent between threads -// KqueueDriver可以在线程间发送 -unsafe impl Send for KqueueDriver {} - -// Safety: KqueueDriver can be shared between threads (uses atomic operations and interior -// mutability) KqueueDriver可以在线程间共享(使用原子操作和内部可变性) -unsafe impl Sync for KqueueDriver {} - -impl KqueueDriver -{ - /// Create a new kqueue driver with default configuration - /// 使用默认配置创建新的kqueue driver - /// - /// # Errors / 错误 - /// - /// Returns an error if kqueue instance creation fails. - /// 如果kqueue实例创建失败则返回错误。 - pub fn new() -> std::io::Result - { - Self::with_config(crate::driver::DriverConfig::default()) - } - - /// Create a new kqueue driver with the specified configuration - /// 使用指定配置创建新的kqueue driver - /// - /// # Errors / 错误 - /// - /// Returns an error if: - /// 返回错误如果: - /// - The configuration is invalid / 配置无效 - /// - kqueue instance creation fails / kqueue实例创建失败 - pub fn with_config(config: crate::driver::DriverConfig) -> std::io::Result - { - // Create kqueue instance - // 创建kqueue实例 - let kqueue_fd = unsafe { libc::kqueue() }; - - if kqueue_fd < 0 - { - return Err(std::io::Error::last_os_error()); - } - - // Set close-on-exec flag - // 设置close-on-exec标志 - unsafe { - let flags = libc::fcntl(kqueue_fd, libc::F_GETFD); - if flags >= 0 - { - libc::fcntl(kqueue_fd, libc::F_SETFD, flags | libc::FD_CLOEXEC); - } - } - - // Set CPU affinity if specified (platform-dependent) - // 如果指定了,设置CPU亲和性(取决于平台) - #[cfg(target_os = "freebsd")] - if let Some(_core) = config.cpu_affinity - { - if let Err(e) = Self::set_cpu_affinity(_core) - { - eprintln!("Warning: Failed to set CPU affinity: {}", e); - } - } - - let capacity = config.entries.max(MIN_KQUEUE_SIZE) as usize; - let capacity_mask = capacity - 1; - - Ok(Self { - kqueue_fd, - submit_queue: UnsafeCell::new(vec![SubmitEntry::new(-1, 0, 0); capacity]), - completion_queue: CompletionQueue::new(capacity), - capacity, - capacity_mask, - state: Arc::new(KqueueState { - submit_head: AtomicUsize::new(0), - submit_tail: AtomicUsize::new(0), - completion_head: AtomicUsize::new(0), - completion_tail: AtomicU32::new(0), - }), - event_buffer: UnsafeCell::new(vec![ - libc::kevent { - ident: 0, - filter: 0, - flags: 0, - fflags: 0, - data: 0, - udata: std::ptr::null_mut(), - }; - capacity - ]), - change_buffer: UnsafeCell::new(vec![ - libc::kevent { - ident: 0, - filter: 0, - flags: 0, - fflags: 0, - data: 0, - udata: std::ptr::null_mut(), - }; - 16 // Small buffer for registration changes - ]), - }) - } - - /// Set CPU affinity for the current thread (FreeBSD only) - /// 为当前线程设置CPU亲和性(仅FreeBSD) - #[cfg(target_os = "freebsd")] - fn set_cpu_affinity(core: usize) -> std::io::Result<()> - { - unsafe { - let mut cpuset: libc::cpuset_t = std::mem::zeroed(); - libc::CPU_ZERO(&mut cpuset); - libc::CPU_SET(core % libc::CPU_SETSIZE as usize, &mut cpuset); - - let result = libc::cpuset_setaffinity( - libc::CP_WHICH, - libc::CPU_LEVEL_WHICH, - libc::CPU_LEVEL_SIZE, - std::thread::current().id() as libc::pid_t, - std::mem::size_of::(), - &cpuset as *const _ as *const _, - ); - - if result < 0 - { - return Err(std::io::Error::last_os_error()); - } - } - - Ok(()) - } - - /// Get the current submission queue position - /// 获取当前提交队列位置 - #[inline] - fn submit_pos(&self, index: usize) -> usize - { - index & self.capacity_mask - } - - /// Get the current completion queue position - /// 获取当前完成队列位置 - #[inline] - fn completion_pos(&self, index: usize) -> usize - { - index & self.capacity_mask - } - - /// Convert Interest to kqueue filter and flags - /// 将Interest转换为kqueue过滤器和标志 - fn interest_to_kqueue(&self, interest: Interest) -> (i16, u16) - { - let mut filter = 0; - let mut flags = libc::EV_ADD | libc::EV_RECEIPT; - - if interest.readable - { - filter |= libc::EVFILT_READ; - } - if interest.writable - { - filter |= libc::EVFILT_WRITE; - } - - if interest.oneshot - { - flags |= libc::EV_ONESHOT; - } - if interest.edge - { - flags |= libc::EV_CLEAR; - } - if interest.priority - { - // Use EV_DISPATCH to give higher priority - flags |= libc::EV_DISPATCH; - } - - (filter, flags) - } - - /// Internal wait implementation - /// 内部等待实现 - fn wait_internal(&self, timeout_ms: Option) -> std::io::Result - { - let event_buffer = unsafe { &mut *self.event_buffer.get() }; - let ptr = event_buffer.as_mut_ptr(); - let len = event_buffer.len() as i32; - - // Calculate timeout for timespec - // 计算timespec的超时时间 - let timeout_ptr = if let Some(ms) = timeout_ms - { - let mut timeout = libc::timespec { - tv_sec: (ms / 1000) as libc::time_t, - tv_nsec: ((ms % 1000) * 1_000_000) as libc::c_long, - }; - &mut timeout as *mut _ - } - else - { - std::ptr::null_mut() - }; - - let result = - unsafe { libc::kevent(self.kqueue_fd, std::ptr::null(), 0, ptr, len, timeout_ptr) }; - - if result < 0 - { - return Err(std::io::Error::last_os_error()); - } - - let count = result as usize; - - // Process events into completion queue - // 将事件处理到完成队列 - for i in 0..count - { - let event = &event_buffer[i]; - let tail = self.state.completion_tail.load(Ordering::Acquire) as usize; - let pos = self.completion_pos(tail); - - // Determine result based on filter and flags - // 根据过滤器和标志确定结果 - let result = if event.flags & (libc::EV_ERROR | libc::EV_EOF) != 0 - { - ERROR_TRANSPORT - } - else - { - // Check data field for error indication - // 检查data字段是否有错误指示 - if event.data != 0 - { - event.data as i32 - } - else - { - 1 // Success / 成功 - } - }; - - unsafe { - self.completion_queue.set( - pos, - Some(CompletionEntry { - user_data: event.udata as u64, - result, - flags: event.flags as u32, - }), - ); - } - - self.state - .completion_tail - .store((tail + 1) as u32, Ordering::Release); - } - - Ok(count) - } -} - -impl Drop for KqueueDriver -{ - fn drop(&mut self) - { - if self.kqueue_fd >= 0 - { - unsafe { - libc::close(self.kqueue_fd); - } - } - } -} - -impl AsRawFd for KqueueDriver -{ - fn as_raw_fd(&self) -> RawFd - { - self.kqueue_fd - } -} - -impl Driver for KqueueDriver -{ - fn submit(&self) -> std::io::Result - { - let mut submitted = 0; - let head = self.state.submit_head.load(Ordering::Acquire); - let tail = self.state.submit_tail.load(Ordering::Acquire); - - // Process all pending submissions - // 处理所有挂起的提交 - let mut idx = head; - while idx != tail - { - let pos = self.submit_pos(idx); - let submit_queue = unsafe { &*self.submit_queue.get() }; - let entry = &submit_queue[pos]; - - if entry.fd >= 0 - { - // Convert submit entry to kevent change - // 将提交条目转换为kevent change - let (filter, flags) = match entry.opcode - { - crate::driver::opcode::READ => - { - (libc::EVFILT_READ, libc::EV_ADD | libc::EV_ONESHOT) - }, - crate::driver::opcode::WRITE => - { - (libc::EVFILT_WRITE, libc::EV_ADD | libc::EV_ONESHOT) - }, - _ => (0, 0), - }; - - let mut change = libc::kevent { - ident: entry.fd as libc::uintptr_t, - filter, - flags, - fflags: 0, - data: 0, - udata: entry.user_data as *mut _, - }; - - let result = unsafe { - libc::kevent( - self.kqueue_fd, - &change, - 1, - std::ptr::null_mut(), - 0, - std::ptr::null_mut(), - ) - }; - - if result < 0 - { - let err = std::io::Error::last_os_error(); - // ENOENT means FD not found, but kqueue handles this differently - // Try with EV_ADD instead - if err.kind() == std::io::ErrorKind::NotFound - { - change.flags = libc::EV_ADD | libc::EV_ONESHOT; - let add_result = unsafe { - libc::kevent( - self.kqueue_fd, - &change, - 1, - std::ptr::null_mut(), - 0, - std::ptr::null_mut(), - ) - }; - if add_result < 0 - { - return Err(err); - } - } - else - { - return Err(err); - } - } - - submitted += 1; - } - - idx += 1; - } - - // Advance head - // 前进head - self.state.submit_head.store(tail, Ordering::Release); - - Ok(submitted) - } - - fn wait(&self) -> std::io::Result - { - self.wait_internal(None) - } - - fn wait_timeout(&self, duration: Duration) -> std::io::Result<(usize, bool)> - { - let timeout_ms = duration.as_millis().min(i32::MAX as u128) as i32; - let result = self.wait_internal(Some(timeout_ms))?; - - // Check if we timed out by looking at the completion queue - // 通过查看完成队列检查是否超时 - let head = self.state.completion_head.load(Ordering::Acquire) as u32; - let tail = self.state.completion_tail.load(Ordering::Acquire); - - Ok((result, head == tail)) - } - - fn get_submission(&self) -> Option<&mut SubmitEntry> - { - let tail = self.state.submit_tail.load(Ordering::Acquire); - let next_tail = tail + 1; - let head = self.state.submit_head.load(Ordering::Acquire); - - // Check if queue is full - // 检查队列是否已满 - if next_tail - head > self.capacity - { - return None; - } - - // Atomically claim this slot to prevent concurrent get_submission calls - // from returning the same position - match self.state.submit_tail.compare_exchange( - tail, - next_tail, - Ordering::AcqRel, - Ordering::Acquire, - ) - { - Ok(_) => - { - let pos = self.submit_pos(tail); - // SAFETY: We have exclusive access to this position - // 我们对此位置有独占访问权 - unsafe { - let submit_queue = &mut *self.submit_queue.get(); - Some(&mut submit_queue[pos]) - } - }, - Err(_) => None, - } - } - - fn get_completion(&self) -> Option<&CompletionEntry> - { - let head = self.state.completion_head.load(Ordering::Acquire); - let tail = self.state.completion_tail.load(Ordering::Acquire) as usize; - - if head == tail - { - return None; - } - - let pos = self.completion_pos(head); - self.completion_queue.get(pos) - } - - fn advance_completion(&self) - { - let head = self.state.completion_head.load(Ordering::Acquire); - let tail = self.state.completion_tail.load(Ordering::Acquire) as usize; - - if head != tail - { - let pos = self.completion_pos(head); - // SAFETY: We have exclusive access through the ring buffer discipline - // 我们通过环形缓冲区规则拥有独占访问权 - unsafe { - self.completion_queue.set(pos, None); - } - - let new_head = head + 1; - self.state - .completion_head - .store(new_head, Ordering::Release); - } - } - - fn register(&self, fd: RawFd, interest: Interest) -> std::io::Result<()> - { - let (filter, flags) = self.interest_to_kqueue(interest); - - let change = libc::kevent { - ident: fd as libc::uintptr_t, - filter, - flags, - fflags: 0, - data: 0, - udata: std::ptr::null_mut(), - }; - - let result = unsafe { - libc::kevent(self.kqueue_fd, &change, 1, std::ptr::null_mut(), 0, std::ptr::null_mut()) - }; - - if result < 0 - { - Err(std::io::Error::last_os_error()) - } - else - { - Ok(()) - } - } - - fn deregister(&self, fd: RawFd) -> std::io::Result<()> - { - let change = libc::kevent { - ident: fd as libc::uintptr_t, - filter: 0, - flags: libc::EV_DELETE, - fflags: 0, - data: 0, - udata: std::ptr::null_mut(), - }; - - let result = unsafe { - libc::kevent(self.kqueue_fd, &change, 1, std::ptr::null_mut(), 0, std::ptr::null_mut()) - }; - - if result < 0 - { - Err(std::io::Error::last_os_error()) - } - else - { - Ok(()) - } - } - - fn modify(&self, fd: RawFd, interest: Interest) -> std::io::Result<()> - { - // kqueue uses EV_DELETE + EV_ADD for modification - // Or we can use the same filter with EV_ADD to update - // kqueue使用EV_DELETE + EV_ADD进行修改 - // 或者我们可以使用相同的filter和EV_ADD来更新 - self.deregister(fd)?; - self.register(fd, interest) - } - - fn submission_capacity(&self) -> usize - { - self.capacity - } - - fn completion_capacity(&self) -> usize - { - self.capacity - } - - fn supports_operation(&self, opcode: u8) -> bool - { - matches!( - opcode, - crate::driver::opcode::READ - | crate::driver::opcode::WRITE - | crate::driver::opcode::CLOSE - ) - } -} - -#[cfg(test)] -#[allow( - clippy::indexing_slicing, - clippy::float_cmp, - clippy::module_inception, - clippy::items_after_statements, - clippy::assertions_on_constants -)] -mod tests -{ - use super::*; - - #[test] - fn test_kqueue_driver_creation() - { - // Use dummy fd to avoid SIGBUS from real kqueue fd cleanup in tests - // 使用虚拟fd避免测试中真实kqueue fd清理导致的SIGBUS - let driver = KqueueDriver { - kqueue_fd: -1, - submit_queue: UnsafeCell::new(vec![SubmitEntry::new(-1, 0, 0); 256]), - completion_queue: CompletionQueue::new(256), - capacity: 256, - capacity_mask: 255, - state: Arc::new(KqueueState { - submit_head: AtomicUsize::new(0), - submit_tail: AtomicUsize::new(0), - completion_head: AtomicUsize::new(0), - completion_tail: AtomicU32::new(0), - }), - event_buffer: UnsafeCell::new(Vec::new()), - change_buffer: UnsafeCell::new(Vec::new()), - }; - assert_eq!(driver.capacity, 256); - assert_eq!(driver.capacity_mask, 255); - } - - #[test] - fn test_kqueue_driver_with_config() - { - // 128 is already power of 2, should remain 128 - // 128已经是2的幂,应保持128 - let cap = 128; - let driver = KqueueDriver { - kqueue_fd: -1, - submit_queue: UnsafeCell::new(vec![SubmitEntry::new(-1, 0, 0); cap]), - completion_queue: CompletionQueue::new(cap), - capacity: cap, - capacity_mask: cap - 1, - state: Arc::new(KqueueState { - submit_head: AtomicUsize::new(0), - submit_tail: AtomicUsize::new(0), - completion_head: AtomicUsize::new(0), - completion_tail: AtomicU32::new(0), - }), - event_buffer: UnsafeCell::new(Vec::new()), - change_buffer: UnsafeCell::new(Vec::new()), - }; - assert_eq!(driver.capacity, 128); - } - - #[test] - fn test_ring_buffer_positions() - { - // Create a driver without real kqueue fd (only needs ring buffer math) - // 创建不带真实kqueue fd的driver(只需要环形缓冲区计算) - let driver = KqueueDriver { - kqueue_fd: -1, // dummy fd / 虚拟fd - submit_queue: UnsafeCell::new(Vec::new()), - completion_queue: CompletionQueue::new(256), - capacity: 256, - capacity_mask: 255, - state: Arc::new(KqueueState { - submit_head: AtomicUsize::new(0), - submit_tail: AtomicUsize::new(0), - completion_head: AtomicUsize::new(0), - completion_tail: AtomicU32::new(0), - }), - event_buffer: UnsafeCell::new(Vec::new()), - change_buffer: UnsafeCell::new(Vec::new()), - }; - - // Test power-of-2 wrapping - // 测试2的幂的包装 - assert_eq!(driver.submit_pos(0), 0); - assert_eq!(driver.submit_pos(255), 255); - assert_eq!(driver.submit_pos(256), 0); - assert_eq!(driver.submit_pos(257), 1); - } -} diff --git a/crates/hiver-runtime/src/driver/mod.rs b/crates/hiver-runtime/src/driver/mod.rs deleted file mode 100644 index de590c6e..00000000 --- a/crates/hiver-runtime/src/driver/mod.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! Driver module for async I/O operations -//! 异步I/O操作的Driver模块 -//! -//! This module provides the driver abstraction layer for different I/O polling mechanisms: -//! - io-uring (Linux 5.1+) -//! - epoll (Linux) -//! - kqueue (macOS/BSD) -//! -//! 本模块提供不同I/O轮询机制的driver抽象层: -//! - io-uring (Linux 5.1+) -//! - epoll (Linux) -//! - kqueue (macOS/BSD) - -pub mod config; -pub mod epoll; -pub mod interest; -#[cfg(target_os = "linux")] -pub mod iouring; -pub mod kqueue; -pub mod queue; - -use std::os::fd::AsRawFd; - -pub use config::{DriverConfig, DriverConfigBuilder, DriverFactory, DriverType}; -pub use interest::Interest; -pub use queue::IoState; -// SubmitEntry/CompletionEntry are internal driver types — not re-exported -// publicly to avoid leaking unsafe raw I/O structs to downstream crates. -// SubmitEntry/CompletionEntry 是内部 driver 类型 —— 不公开重导出, -// 避免向下游 crate 泄漏不安全的原始 I/O 结构。 -pub(crate) use queue::{CompletionEntry, SubmitEntry}; - -/// Core driver trait for async I/O operations -/// 异步I/O操作的核心driver trait -/// -/// This trait abstracts different I/O polling mechanisms (io-uring, epoll, kqueue) -/// providing a unified interface for the runtime. -/// -/// 此trait抽象了不同的I/O轮询机制(io-uring、epoll、kqueue), -/// 为运行时提供统一接口。 -// Driver is the trait behind `Arc` returned by the public -// `DriverFactory::create()` API, so it must itself be public for downstream -// (and integration-test) code to even name the return type. The concrete -// implementations (iouring/epoll/kqueue) remain `pub(crate)`. -pub trait Driver: Send + Sync + AsRawFd -{ - /// Submit queued operations to the kernel - /// 将队列中的操作提交给内核 - /// - /// Returns the number of operations submitted. - /// 返回已提交的操作数量。 - fn submit(&self) -> std::io::Result; - - /// Wait for completion events indefinitely - /// 无限等待完成事件 - /// - /// Returns the number of completion events available. - /// 返回可用的完成事件数量。 - fn wait(&self) -> std::io::Result; - - /// Wait for completion events with a timeout - /// 带超时等待完成事件 - /// - /// Returns `(events_count, timed_out)` where: - /// - `events_count`: number of completion events / 完成事件数量 - /// - `timed_out`: true if timeout occurred, false if events arrived / 是否超时 - fn wait_timeout(&self, duration: std::time::Duration) -> std::io::Result<(usize, bool)>; - - /// Get a mutable reference to the next available submission entry - /// 获取下一个可用提交条目的可变引用 - /// - /// Returns `None` if the submission queue is full. - /// 如果提交队列已满则返回 `None`。 - fn get_submission(&self) -> Option<&mut SubmitEntry>; - - /// Get a reference to the next available completion entry - /// 获取下一个可用完成条目的引用 - /// - /// Returns `None` if the completion queue is empty. - /// 如果完成队列为空则返回 `None`。 - fn get_completion(&self) -> Option<&CompletionEntry>; - - /// Advance the completion queue cursor, consuming the current entry - /// 前进完成队列游标,消费当前条目 - fn advance_completion(&self); - - /// Register interest in a file descriptor - /// 注册对文件描述符的兴趣 - /// - /// This tells the driver to monitor the FD for specific events. - /// 这告诉driver监控文件描述符的特定事件。 - fn register(&self, fd: std::os::fd::RawFd, interest: Interest) -> std::io::Result<()>; - - /// Deregister interest in a file descriptor - /// 取消对文件描述符的兴趣注册 - fn deregister(&self, fd: std::os::fd::RawFd) -> std::io::Result<()>; - - /// Modify the interest for a registered file descriptor - /// 修改已注册文件描述符的兴趣 - fn modify(&self, fd: std::os::fd::RawFd, interest: Interest) -> std::io::Result<()>; - - /// Get the capacity of the submission queue - /// 获取提交队列的容量 - fn submission_capacity(&self) -> usize; - - /// Get the capacity of the completion queue - /// 获取完成队列的容量 - fn completion_capacity(&self) -> usize; - - /// Check if the driver supports the specified operation - /// 检查driver是否支持指定操作 - fn supports_operation(&self, opcode: u8) -> bool; -} - -/// Raw file descriptor type -/// 原始文件描述符类型 -pub type RawFd = std::os::fd::RawFd; - -/// Error code for transport errors -/// 传输错误的错误码 -pub const ERROR_TRANSPORT: i32 = -1; - -/// Operation opcodes -/// 操作操作码 -pub mod opcode -{ - /// Read operation / 读操作 - pub const READ: u8 = 0; - /// Write operation / 写操作 - pub const WRITE: u8 = 1; - /// Fsync operation / 同步操作 - pub const FSYNC: u8 = 2; - /// Close operation / 关闭操作 - pub const CLOSE: u8 = 4; -} diff --git a/crates/hiver-runtime/src/driver/queue.rs b/crates/hiver-runtime/src/driver/queue.rs deleted file mode 100644 index ad441734..00000000 --- a/crates/hiver-runtime/src/driver/queue.rs +++ /dev/null @@ -1,433 +0,0 @@ -//! Queue entries for I/O operations -//! I/O操作的队列条目 -//! -//! This module defines the submission and completion queue entries -//! used by the driver for asynchronous I/O operations. -//! -//! 本模块定义driver用于异步I/O操作的提交和完成队列条目。 - -use std::ptr::NonNull; - -/// I/O operation state machine -/// I/O操作状态机 -/// -/// Tracks the lifecycle of an I/O operation from submission to completion. -/// 跟踪I/O操作从提交到完成的生命周期。 -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u8)] -pub enum IoState -{ - /// Operation is idle, not yet submitted / 操作空闲,尚未提交 - Idle = 0, - /// Operation has been submitted to kernel / 操作已提交给内核 - Submitted = 1, - /// Operation is in progress / 操作进行中 - InProgress = 2, - /// Operation completed successfully / 操作成功完成 - Completed = 3, - /// Operation was cancelled / 操作被取消 - Cancelled = 4, - /// Operation failed / 操作失败 - Failed = 5, -} - -impl IoState -{ - /// Check if the operation is finished (completed, cancelled, or failed) - /// 检查操作是否已完成(成功、取消或失败) - #[must_use] - pub const fn is_finished(self) -> bool - { - matches!(self, Self::Completed | Self::Cancelled | Self::Failed) - } - - /// Check if the operation is in progress (submitted or in progress) - /// 检查操作是否进行中(已提交或进行中) - #[must_use] - pub const fn is_in_progress(self) -> bool - { - matches!(self, Self::Submitted | Self::InProgress) - } - - /// Check if the operation succeeded - /// 检查操作是否成功 - #[must_use] - pub fn is_success(self) -> bool - { - matches!(self, Self::Completed) - } -} - -/// Submission queue entry -/// 提交队列条目 -/// -/// Represents a single I/O operation to be submitted to the kernel. -/// 表示要提交给内核的单个I/O操作。 -/// -/// # Safety / 安全性 -/// -/// - `buf_ptr` must be valid for `buf_len` bytes if non-null -/// - The buffer must remain valid until the operation completes -/// - 如果非空,`buf_ptr` 必须对 `buf_len` 字节有效 -/// - 缓冲区必须在操作完成前保持有效 -#[derive(Debug, Clone)] -pub struct SubmitEntry -{ - /// File descriptor to operate on / 操作的文件描述符 - pub fd: i32, - /// Operation opcode (READ, WRITE, etc.) / 操作操作码 - pub opcode: u8, - /// Operation flags / 操作标志 - pub flags: u16, - /// User data for completion correlation (opaque pointer) - /// 用于完成关联的用户数据(不透明指针) - pub user_data: u64, - /// Buffer pointer / 缓冲区指针 - pub buf_ptr: Option>, - /// Buffer length in bytes / 缓冲区长度(字节) - pub buf_len: u32, - /// Offset for file operations / 文件操作的偏移量 - pub offset: u64, - /// Address for connect/accept operations / 连接/接受操作的地址 - pub addr: Option, -} - -/// Socket address storage for connection operations -/// 连接操作的套接字地址存储 -#[derive(Debug, Clone)] -pub struct SockAddr -{ - /// The raw socket address / 原始套接字地址 - pub storage: libc::sockaddr_storage, - /// Length of the address / 地址长度 - pub len: libc::socklen_t, -} - -impl SockAddr -{ - /// Create a new socket address from a raw sockaddr_storage - /// 从原始sockaddr_storage创建新的套接字地址 - /// - /// # Safety / 安全性 - /// - /// The storage must contain a valid socket address. - /// storage必须包含有效的套接字地址。 - pub(crate) unsafe fn from_raw(storage: libc::sockaddr_storage, len: libc::socklen_t) -> Self - { - Self { storage, len } - } -} - -impl SubmitEntry -{ - /// Create a new submission entry - /// 创建新的提交条目 - #[must_use] - pub const fn new(fd: i32, opcode: u8, user_data: u64) -> Self - { - Self { - fd, - opcode, - flags: 0, - user_data, - buf_ptr: None, - buf_len: 0, - offset: 0, - addr: None, - } - } - - /// Create a read operation entry - /// 创建读操作条目 - /// - /// # Safety / 安全性 - /// - /// `buf` must be valid for reads and remain valid until completion. - /// `buf` 必须对读取有效并在完成前保持有效。 - #[must_use] - pub(crate) unsafe fn read(fd: i32, buf: *mut u8, buf_len: u32, user_data: u64) -> Self - { - Self { - fd, - opcode: super::opcode::READ, - flags: 0, - user_data, - buf_ptr: NonNull::new(buf), - buf_len, - offset: 0, - addr: None, - } - } - - /// Create a write operation entry - /// 创建写操作条目 - /// - /// # Safety / 安全性 - /// - /// `buf` must be valid for reads and remain valid until completion. - /// `buf` 必须对读取有效并在完成前保持有效。 - #[must_use] - pub(crate) unsafe fn write(fd: i32, buf: *const u8, buf_len: u32, user_data: u64) -> Self - { - Self { - fd, - opcode: super::opcode::WRITE, - flags: 0, - user_data, - buf_ptr: NonNull::new(buf.cast_mut()), - buf_len, - offset: 0, - addr: None, - } - } - - /// Set the buffer for this operation - /// 为此操作设置缓冲区 - /// - /// # Safety / 安全性 - /// - /// `buf` must be valid for `buf_len` bytes. - /// `buf` 必须对 `buf_len` 字节有效。 - #[must_use] - pub(crate) unsafe fn with_buffer(mut self, buf: *mut u8, buf_len: u32) -> Self - { - self.buf_ptr = NonNull::new(buf); - self.buf_len = buf_len; - self - } - - /// Set the offset for file operations - /// 为文件操作设置偏移量 - #[must_use] - pub const fn with_offset(mut self, offset: u64) -> Self - { - self.offset = offset; - self - } - - /// Set operation flags - /// 设置操作标志 - #[must_use] - pub const fn with_flags(mut self, flags: u16) -> Self - { - self.flags = flags; - self - } - - /// Set socket address for connect/accept - /// 为connect/accept设置套接字地址 - #[must_use] - pub fn with_addr(mut self, addr: SockAddr) -> Self - { - self.addr = Some(addr); - self - } - - /// Get the buffer as a slice if available - /// 如果可用,获取缓冲区的切片 - /// - /// # Safety / 安全性 - /// - /// The returned slice is only valid if the buffer is still alive. - /// 返回的切片仅在缓冲区仍然存活时有效。 - #[must_use] - pub(crate) unsafe fn buffer(&self) -> Option<&[u8]> - { - self.buf_ptr - .map(|ptr| std::slice::from_raw_parts(ptr.as_ptr(), self.buf_len as usize)) - } - - /// Get the buffer as a mutable slice if available - /// 如果可用,获取缓冲区的可变切片 - /// - /// # Safety / 安全性 - /// - /// The returned slice is only valid if the buffer is still alive and mutable. - /// 返回的切片仅在缓冲区仍然存活且可变时有效。 - #[must_use] - pub(crate) unsafe fn buffer_mut(&self) -> Option<&mut [u8]> - { - self.buf_ptr - .map(|ptr| std::slice::from_raw_parts_mut(ptr.as_ptr(), self.buf_len as usize)) - } -} - -// Safety: SubmitEntry can be sent between threads if the underlying buffer is valid -// unsafe impl Send for SubmitEntry {} -// -// Note: We don't implement Send automatically because the buf_ptr may reference -// data that isn't Send-safe. Users must ensure thread safety. - -/// Completion queue entry -/// 完成队列条目 -/// -/// Represents a completed I/O operation returned from the kernel. -/// 表示从内核返回的已完成的I/O操作。 -#[derive(Debug, Clone, Copy)] -pub struct CompletionEntry -{ - /// User data from the corresponding submission / 来自相应提交的用户数据 - pub user_data: u64, - /// Result code: positive = bytes transferred, negative = error code - /// 结果码:正数=传输的字节数,负数=错误码 - pub result: i32, - /// Operation flags / 操作标志 - pub flags: u32, -} - -impl CompletionEntry -{ - /// Create a new completion entry - /// 创建新的完成条目 - #[must_use] - pub const fn new(user_data: u64, result: i32, flags: u32) -> Self - { - Self { - user_data, - result, - flags, - } - } - - /// Check if the operation succeeded - /// 检查操作是否成功 - #[must_use] - pub const fn is_success(self) -> bool - { - self.result >= 0 - } - - /// Check if the operation failed - /// 检查操作是否失败 - #[must_use] - pub const fn is_error(self) -> bool - { - self.result < 0 - } - - /// Get the number of bytes transferred - /// 获取传输的字节数 - /// - /// Returns `None` if the operation failed. - /// 如果操作失败则返回 `None`。 - #[must_use] - pub const fn bytes_transferred(self) -> Option - { - if self.result >= 0 - { - Some(self.result as u32) - } - else - { - None - } - } - - /// Get the error code if the operation failed - /// 如果操作失败,获取错误码 - #[must_use] - pub const fn error_code(self) -> Option - { - if self.result < 0 - { - Some(-self.result) - } - else - { - None - } - } - - /// Convert the result to a `std::io::Result` - /// 将结果转换为 `std::io::Result` - /// - /// Returns `Ok(bytes_transferred)` on success, `Err(error)` on failure. - /// 成功返回 `Ok(bytes_transferred)`,失败返回 `Err(error)`。 - #[must_use = "the result of the operation should be checked"] - pub fn into_result(self) -> std::io::Result - { - if self.result >= 0 - { - Ok(self.result as u32) - } - else - { - Err(std::io::Error::from_raw_os_error(-self.result)) - } - } -} - -#[cfg(test)] -#[allow( - clippy::indexing_slicing, - clippy::float_cmp, - clippy::module_inception, - clippy::items_after_statements, - clippy::assertions_on_constants -)] -mod tests -{ - use super::*; - - #[test] - fn test_io_state() - { - assert!(IoState::Completed.is_finished()); - assert!(IoState::Cancelled.is_finished()); - assert!(IoState::Failed.is_finished()); - assert!(!IoState::Idle.is_finished()); - assert!(!IoState::Submitted.is_finished()); - - assert!(IoState::Submitted.is_in_progress()); - assert!(IoState::InProgress.is_in_progress()); - assert!(!IoState::Idle.is_in_progress()); - } - - #[test] - fn test_completion_entry() - { - // Success case / 成功情况 - let entry = CompletionEntry::new(123, 1024, 0); - assert!(entry.is_success()); - assert!(!entry.is_error()); - assert_eq!(entry.bytes_transferred(), Some(1024)); - assert_eq!(entry.into_result().unwrap(), 1024); - - // Error case / 错误情况 - let entry = CompletionEntry::new(456, -2, 0); - assert!(!entry.is_success()); - assert!(entry.is_error()); - assert_eq!(entry.bytes_transferred(), None); - assert_eq!(entry.error_code(), Some(2)); - } - - #[test] - fn test_submit_entry_builder() - { - let buf = [0u8; 1024]; - let entry = unsafe { - SubmitEntry::read(0, buf.as_ptr().cast_mut(), 1024, 42) - .with_offset(100) - .with_flags(0x0001) - }; - - assert_eq!(entry.fd, 0); - assert_eq!(entry.opcode, super::super::opcode::READ); - assert_eq!(entry.user_data, 42); - assert_eq!(entry.buf_len, 1024); - assert_eq!(entry.offset, 100); - assert_eq!(entry.flags, 0x0001); - } - - #[test] - fn test_interest_builder() - { - let interest = crate::driver::Interest::readable() - .with_writable() - .with_edge(); - - assert!(interest.readable); - assert!(interest.writable); - assert!(interest.edge); - } -} diff --git a/crates/hiver-runtime/src/io.rs b/crates/hiver-runtime/src/io.rs index fcf15a32..297c1609 100644 --- a/crates/hiver-runtime/src/io.rs +++ b/crates/hiver-runtime/src/io.rs @@ -1,1565 +1,247 @@ //! I/O operations module -//! I/O操作模块 +//! I/O 操作模块 //! -//! # Overview / 概述 +//! Provides async I/O primitives for TCP and UDP networking, backed by +//! [`async-net`] (which runs on the [`async-io`] reactor driven by +//! `Runtime::block_on`). These types replace the former self-built FD/driver +//! futures; the public method names (`connect`, `bind`, `accept`, `read`, +//! `write_all`) are preserved so downstream crates (e.g. `hiver-http`) keep +//! compiling unchanged. //! -//! This module provides async I/O primitives for TCP and UDP networking. -//! 本模块提供用于TCP和UDP网络的异步I/O原语。 -//! -//! # Features / 功能 -//! -//! - Async TCP stream with connect/read/write / 带有connect/read/write的异步TCP流 -//! - Async TCP listener for accepting connections / 用于接受连接的异步TCP监听器 -//! - Zero-copy ready operations / 零拷贝就绪操作 -//! -//! # Example / 示例 -//! -//! ```rust,no_run,ignore -//! use hiver_runtime::io::TcpStream; -//! -//! async fn echo_client() -> std::io::Result<()> { -//! let mut stream = TcpStream::connect("127.0.0.1:8080").await?; -//! -//! stream.write_all(b"Hello, World!").await?; -//! -//! let mut buf = [0u8; 1024]; -//! let n = stream.read(&mut buf).await?; -//! -//! println!("Received: {}", String::from_utf8_lossy(&buf[..n])); -//! Ok(()) -//! } -//! ``` +//! 提供用于 TCP 与 UDP 网络的异步 I/O 原语,由 [`async-net`] 驱动(其运行在由 +//! `Runtime::block_on` 驱动的 [`async-io`] reactor 上)。这些类型替代了原先自研的 +//! FD/driver future;公开的方法名(`connect`、`bind`、`accept`、`read`、 +//! `write_all`)保持不变,使下游 crate(如 `hiver-http`)无需改动即可编译。 -#![allow(private_interfaces)] +#![allow(clippy::manual_async_fn)] use std::{ future::Future, io, - net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr}, - os::fd::{AsRawFd, FromRawFd, RawFd}, - pin::Pin, - task::{Context, Poll}, + net::{Shutdown, SocketAddr}, }; -const DUMMY_ADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); - -/// A TCP stream between a local and a remote socket -/// 本地套接字和远程套接字之间的TCP流 +/// A TCP stream between a local and a remote socket. +/// 本地套接字与远程套接字之间的 TCP 流。 +/// +/// Wraps [`async_net::TcpStream`], exposing the same inherent async methods the +/// runtime historically provided (`connect`, `read`, `write_all`, `split`, +/// `shutdown`) so existing callers compile unchanged. Underlying I/O is driven +/// by the `async-io` reactor in `Runtime::block_on`. /// -/// Provides async read/write operations with the underlying driver. -/// 使用底层驱动提供异步读/写操作。 +/// 包裹 [`async_net::TcpStream`],暴露 runtime 历史上提供的相同 inherent 异步方法 +/// (`connect`、`read`、`write_all`、`split`、`shutdown`),使现有调用方无需改动即可 +/// 编译。底层 I/O 由 `Runtime::block_on` 中的 `async-io` reactor 驱动。 pub struct TcpStream { - /// The raw file descriptor / 原始文件描述符 - fd: std::os::fd::OwnedFd, + inner: async_net::TcpStream, } impl TcpStream { - /// Create a new TcpStream from a raw file descriptor - /// 从原始文件描述符创建新的TcpStream - /// - /// # Safety / 安全性 - /// - /// The fd must be valid and owned by the caller. - /// fd必须有效且由调用者拥有。 - pub(crate) unsafe fn from_raw_fd(fd: RawFd) -> io::Result + /// Create a new TcpStream connected to the specified address. + /// 创建连接到指定地址的新 TcpStream。 + pub fn connect(addr: &str) -> impl Future> { - // Set non-blocking mode - // 设置非阻塞模式 - #[cfg(unix)] - unsafe { - let flags = libc::fcntl(fd, libc::F_GETFL); - if flags < 0 - { - return Err(io::Error::last_os_error()); - } - if libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) < 0 - { - return Err(io::Error::last_os_error()); - } - } - - Ok(Self { - // SAFETY: Caller guarantees ownership - // 安全性:调用者保证所有权 - fd: unsafe { std::os::fd::OwnedFd::from_raw_fd(fd) }, - }) - } - - /// Connect to a remote address - /// 连接到远程地址 - /// - /// # Example / 示例 - /// - /// ```rust,no_run,ignore - /// use hiver_runtime::io::TcpStream; - /// - /// async fn connect() -> std::io::Result<()> { - /// let stream = TcpStream::connect("127.0.0.1:8080").await?; - /// Ok(()) - /// } - /// ``` - pub fn connect(addr: &str) -> ConnectFuture + let addr = addr.to_string(); + async move { + let inner = async_net::TcpStream::connect(addr).await?; + Ok(Self { inner }) + } + } + + /// Read data from the stream into `buf`, returning the number of bytes + /// read. Returns `Ok(0)` when the peer has closed the connection (EOF). + /// 从流中读取数据到 `buf`,返回读取的字节数。对端关闭连接时返回 `Ok(0)`(EOF)。 + pub fn read<'a, 'b>( + &'a mut self, + buf: &'b mut [u8], + ) -> impl Future> + 'a + where + 'b: 'a, { - let Ok(addr) = addr.parse::() - else - { - // Try to resolve as hostname - // For now, return error - DNS resolution will be added later - return ConnectFuture::Error(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid address format, use IP:PORT", - )); - }; - - ConnectFuture::Connecting(Box::new(ConnectingState { - addr, - fd: None, - started: false, - })) - } - - /// Read some bytes from the stream - /// 从流中读取一些字节 - /// - /// Returns the number of bytes read. May return 0 if the stream is closed. - /// 返回读取的字节数。如果流已关闭,可能返回0。 - pub fn read<'a, 'b>(&'a mut self, buf: &'b mut [u8]) -> ReadFuture<'a, 'b> - { - ReadFuture { - stream: Some(self), - buf, - pos: 0, - } - } - - /// Write all bytes to the stream - /// 将所有字节写入流 - /// - /// This will keep writing until all bytes have been written or an error occurs. - /// 将持续写入,直到所有字节都已写入或发生错误。 - pub fn write_all<'a, 'b>(&'a mut self, buf: &'b [u8]) -> WriteAllFuture<'a, 'b> + // Delegate to the AsyncRead trait via async-net's poll-based read. + // 经由 async-net 的基于 poll 的 read 委托给 AsyncRead trait。 + use futures_lite::AsyncReadExt; + async move { self.inner.read(buf).await } + } + + /// Write all of `buf` to the stream. + /// 将 `buf` 全部写入流。 + pub fn write_all<'a, 'b>( + &'a mut self, + buf: &'b [u8], + ) -> impl Future> + 'a + where + 'b: 'a, { - WriteAllFuture { - stream: Some(self), - buf, - pos: 0, - } + use futures_lite::AsyncWriteExt; + async move { self.inner.write_all(buf).await } } - /// Split the stream into read and write halves - /// 将流拆分为读写两半 - /// - /// Note: This is a placeholder. The actual implementation will use - /// Arc-based splitting like Tokio for true split read/write. - /// 注意:这是占位符。实际实现将使用类似Tokio的基于Arc的拆分来实现真正的读写分离。 + /// Split the stream into separate read and write halves. + /// 将流拆分为独立的读、写两半。 /// - /// # Note / 注意 + /// Each half clones the underlying socket handle (async-net's `TcpStream` + /// is cheaply clonable — it is `Arc`-backed internally), so both can be + /// held and moved independently. Full-duplex I/O is safe at the kernel + /// level. This replaces the old self-built `unsafe` split that aliased + /// `&mut`. /// - /// This is a simplified split implementation. Both halves reference the same - /// underlying socket. The caller must coordinate read/write operations. - /// 这是简化的 split 实现。两个半部引用同一个底层 socket。 - /// 调用者必须协调读/写操作。 - pub fn split(&mut self) -> (ReadHalf<'_>, WriteHalf<'_>) + /// 每一半克隆底层 socket 句柄(async-net 的 `TcpStream` 可廉价克隆——内部由 + /// `Arc` 支撑),故两者可独立持有与移动。全双工 I/O 在内核层面安全。这替代了 + /// 旧的、别名 `&mut` 的自研 `unsafe` split。 + #[must_use] + pub fn split(&mut self) -> (ReadHalf, WriteHalf) { - // SAFETY: Both halves reference the same stream via raw pointer. - // This is safe because TCP sockets support full-duplex I/O — - // reads and writes can proceed concurrently at the kernel level. - // The caller must not perform conflicting operations on the same half. - // 安全:两个半部通过裸指针引用同一个流。 - // 这是安全的,因为 TCP socket 支持全双工 I/O — - // 读和写可以在内核级别并发进行。 - // 调用者不得在同一个半部上执行冲突操作。 - unsafe { - let ptr = self as *mut TcpStream; - (ReadHalf { _stream: &mut *ptr }, WriteHalf { _stream: &mut *ptr }) - } + ( + ReadHalf { + inner: self.inner.clone(), + }, + WriteHalf { + inner: self.inner.clone(), + }, + ) } - /// Shutdown the stream - /// 关闭流 + /// Shut down the read, write, or both halves of the connection. + /// 关闭连接的读、写或全部两半。 pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { - #[cfg(unix)] - unsafe { - let how = match how - { - Shutdown::Read => libc::SHUT_RD, - Shutdown::Write => libc::SHUT_WR, - Shutdown::Both => libc::SHUT_RDWR, - }; - if libc::shutdown(self.as_raw_fd(), how) < 0 - { - return Err(io::Error::last_os_error()); - } - } - #[cfg(not(unix))] - { - let _ = how; - return Err(io::Error::new( - io::ErrorKind::Unsupported, - "Shutdown not supported on this platform", - )); - } - Ok(()) - } -} - -impl AsRawFd for TcpStream -{ - fn as_raw_fd(&self) -> RawFd - { - self.fd.as_raw_fd() + self.inner.shutdown(how) } -} - -/// Future for connecting to a remote address -/// 连接到远程地址的future -pub enum ConnectFuture -{ - /// Error state / 错误状态 - Error(io::Error), - /// Connecting state / 连接中状态 - /// Boxed to reduce enum size / 使用Box减小枚举大小 - Connecting(Box), - /// Done state / 完成状态 - Done, -} - -struct ConnectingState -{ - addr: SocketAddr, - fd: Option, - started: bool, -} - -impl Future for ConnectFuture -{ - type Output = io::Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll + /// Returns the remote socket address of this peer. + /// 返回对端的远程套接字地址。 + pub fn peer_addr(&self) -> io::Result { - match &mut *self - { - ConnectFuture::Error(e) => - { - let e = std::mem::replace(e, io::Error::other("")); - Poll::Ready(Err(e)) - }, - ConnectFuture::Done => panic!("ConnectFuture polled after completion"), - ConnectFuture::Connecting(state) => - { - if !state.started - { - state.started = true; - - // Create socket and start connect - // 创建套接字并启动connect - let fd: RawFd = create_socket(state.addr.is_ipv4()); - - if fd < 0 - { - return Poll::Ready(Err(io::Error::last_os_error())); - } - - // Start connect (socket is already non-blocking from create_socket) - // 启动 connect(create_socket 已设置非阻塞) - let result = do_connect(fd, state.addr); - - if result < 0 - { - let err = io::Error::last_os_error(); - if err.kind() != io::ErrorKind::WouldBlock - { - unsafe { libc::close(fd) }; - return Poll::Ready(Err(err)); - } - // Async connect in progress — store fd for later polling - // 异步 connect 进行中 — 存储 fd 用于后续轮询 - state.fd = Some(fd); - return Poll::Pending; - } - - // Connected immediately - // 立即连接 - state.fd = Some(fd); - } - - // Check if async connect has completed using poll() - // 使用 poll() 检查异步连接是否完成 - if let Some(fd) = state.fd - { - let mut pfd = libc::pollfd { - fd, - events: libc::POLLOUT, - revents: 0, - }; - let ready = unsafe { libc::poll(&mut pfd, 1, 0) }; - - if ready < 0 - { - let fd = state.fd.take().unwrap(); - unsafe { libc::close(fd) }; - return Poll::Ready(Err(io::Error::last_os_error())); - } - - if ready == 0 - { - // Not ready yet — register waker for future notification - // 尚未就绪 — 注册 waker 以便未来通知 - cx.waker().wake_by_ref(); - return Poll::Pending; - } - - // Socket is writable — check for connection error via SO_ERROR - // Socket 可写 — 通过 SO_ERROR 检查连接错误 - let mut err_val: libc::c_int = 0; - let mut err_len: libc::socklen_t = size_of::() as libc::socklen_t; - unsafe { - libc::getsockopt( - fd, - libc::SOL_SOCKET, - libc::SO_ERROR, - &mut err_val as *mut _ as *mut _, - &mut err_len, - ); - } - if err_val != 0 - { - let fd = state.fd.take().unwrap(); - unsafe { libc::close(fd) }; - return Poll::Ready(Err(io::Error::from_raw_os_error(err_val))); - } - - // Connected successfully - // 连接成功 - let fd = state.fd.take().unwrap(); - let stream = match unsafe { TcpStream::from_raw_fd(fd) } - { - Ok(s) => s, - Err(e) => return Poll::Ready(Err(e)), - }; - *self = ConnectFuture::Done; - Poll::Ready(Ok(stream)) - } - else - { - Poll::Pending - } - }, - } + self.inner.peer_addr() } -} - -/// Helper to create a non-blocking socket -/// 创建非阻塞套接字的辅助函数 -#[cfg(unix)] -fn create_socket(ipv4: bool) -> RawFd -{ - unsafe { - let domain = if ipv4 { libc::AF_INET } else { libc::AF_INET6 }; - - #[cfg(target_os = "linux")] - let fd = libc::socket(domain, libc::SOCK_STREAM | libc::SOCK_CLOEXEC, 0); - - #[cfg(not(target_os = "linux"))] - let fd = libc::socket(domain, libc::SOCK_STREAM, 0); - - if fd < 0 - { - return fd; - } - - #[cfg(not(target_os = "linux"))] - { - // Set close-on-exec for macOS/BSD - if libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) < 0 - { - libc::close(fd); - return -1; - } - } - - // Set non-blocking - let flags = libc::fcntl(fd, libc::F_GETFL); - if libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) < 0 - { - libc::close(fd); - return -1; - } - - fd - } -} - -/// Helper to start a connect -/// 启动connect的辅助函数 -#[cfg(unix)] -fn do_connect(fd: RawFd, addr: SocketAddr) -> i32 -{ - unsafe { - if addr.is_ipv4() - { - if let SocketAddr::V4(v4) = addr - { - #[cfg(target_os = "linux")] - let sockaddr = libc::sockaddr_in { - sin_family: libc::AF_INET as u16, - sin_port: v4.port().to_be(), - sin_addr: libc::in_addr { - s_addr: u32::from_ne_bytes(v4.ip().octets()), - }, - sin_zero: [0; 8], - }; - #[cfg(not(target_os = "linux"))] - let sockaddr = libc::sockaddr_in { - sin_len: size_of::() as u8, - sin_family: libc::AF_INET as u8, - sin_port: v4.port().to_be(), - sin_addr: libc::in_addr { - s_addr: u32::from_ne_bytes(v4.ip().octets()), - }, - sin_zero: [0; 8], - }; - - libc::connect( - fd, - &sockaddr as *const _ as *const libc::sockaddr, - size_of::() as libc::socklen_t, - ) - } - else - { - -1 - } - } - else - { - if let SocketAddr::V6(v6) = addr - { - #[cfg(target_os = "linux")] - let sockaddr = libc::sockaddr_in6 { - sin6_family: libc::AF_INET6 as u16, - sin6_port: v6.port().to_be(), - sin6_flowinfo: v6.flowinfo(), - sin6_addr: libc::in6_addr { - s6_addr: v6.ip().octets(), - }, - sin6_scope_id: v6.scope_id(), - }; - - #[cfg(not(target_os = "linux"))] - let sockaddr = libc::sockaddr_in6 { - sin6_len: size_of::() as u8, - sin6_family: libc::AF_INET6 as u8, - sin6_port: v6.port().to_be(), - sin6_flowinfo: v6.flowinfo(), - sin6_addr: libc::in6_addr { - s6_addr: v6.ip().octets(), - }, - sin6_scope_id: v6.scope_id(), - }; - - libc::connect( - fd, - &sockaddr as *const _ as *const libc::sockaddr, - size_of::() as libc::socklen_t, - ) - } - else - { - -1 - } - } - } -} - -/// Future for reading from a TcpStream -/// 从TcpStream读取的future -pub struct ReadFuture<'a, 'b> -{ - stream: Option<&'a mut TcpStream>, - buf: &'b mut [u8], - pos: usize, -} - -impl Future for ReadFuture<'_, '_> -{ - type Output = io::Result; - - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll + /// Returns the local socket address. + /// 返回本地套接字地址。 + pub fn local_addr(&self) -> io::Result { - // Extract all needed values upfront to avoid borrow issues - // 提前提取所有需要的值以避免借用问题 - let stream_fd; - let buf_ptr; - let buf_len; - - { - let stream = self.stream.as_mut().unwrap(); - stream_fd = stream.as_raw_fd(); - let pos = self.pos; - buf_ptr = self.buf[pos..].as_mut_ptr(); - buf_len = self.buf[pos..].len(); - } - - #[cfg(unix)] - { - let result = unsafe { libc::read(stream_fd, buf_ptr as *mut _, buf_len) }; - - if result < 0 - { - let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::WouldBlock - { - // Would block - should register with driver - // 会阻塞 - 应该向驱动注册 - // For now, just return Pending - // 目前只返回Pending - return Poll::Pending; - } - return Poll::Ready(Err(err)); - } - - let n = result as usize; - if n == 0 - { - return Poll::Ready(Ok(0)); // EOF - } - - self.pos += n; - Poll::Ready(Ok(n)) - } - - #[cfg(not(unix))] - { - let _ = (stream_fd, buf_ptr, buf_len); - Poll::Ready(Err(io::Error::new( - io::ErrorKind::Unsupported, - "TCP read not yet implemented on this platform", - ))) - } + self.inner.local_addr() } } -/// Future for writing all bytes to a TcpStream -/// 向TcpStream写入所有字节的future -pub struct WriteAllFuture<'a, 'b> -{ - stream: Option<&'a mut TcpStream>, - buf: &'b [u8], - pos: usize, -} - -impl Future for WriteAllFuture<'_, '_> +impl From for TcpStream { - type Output = io::Result<()>; - - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll + fn from(inner: async_net::TcpStream) -> Self { - while self.pos < self.buf.len() - { - let stream = self.stream.as_mut().unwrap(); - - #[cfg(unix)] - { - let result = unsafe { - libc::write( - stream.as_raw_fd(), - self.buf[self.pos..].as_ptr() as *const _, - self.buf[self.pos..].len(), - ) - }; - - if result < 0 - { - let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::WouldBlock - { - return Poll::Pending; - } - return Poll::Ready(Err(err)); - } - - let n = result as usize; - if n == 0 - { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::WriteZero, - "write zero byte", - ))); - } - - self.pos += n; - } - - #[cfg(not(unix))] - { - let _ = stream; - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::Unsupported, - "TCP write not yet implemented on this platform", - ))); - } - } - - Poll::Ready(Ok(())) + Self { inner } } } -/// Read half of a TcpStream -/// TcpStream的读半部 -pub struct ReadHalf<'a> +/// Read half of a [`TcpStream`], sharing the underlying socket via a clone of +/// the async-net handle. +/// [`TcpStream`] 的读半部,经由 async-net 句柄的克隆共享底层 socket。 +pub struct ReadHalf { - _stream: &'a mut TcpStream, + #[allow(dead_code)] + inner: async_net::TcpStream, } -/// Write half of a TcpStream -/// TcpStream的写半部 -pub struct WriteHalf<'a> +/// Write half of a [`TcpStream`], sharing the underlying socket via a clone of +/// the async-net handle. +/// [`TcpStream`] 的写半部,经由 async-net 句柄的克隆共享底层 socket。 +pub struct WriteHalf { - _stream: &'a mut TcpStream, + #[allow(dead_code)] + inner: async_net::TcpStream, } -/// A TCP socket listener -/// TCP套接字监听器 -/// -/// Listens for incoming connections on a specific address. -/// 在特定地址上监听传入的连接。 +/// A TCP socket server, listening for connections. +/// 监听连接的 TCP socket 服务端。 pub struct TcpListener { - /// The raw file descriptor / 原始文件描述符 - fd: std::os::fd::OwnedFd, + inner: async_net::TcpListener, } impl TcpListener { - /// Create a new TCP listener bound to the specified address - /// 创建绑定到指定地址的新TCP监听器 - /// - /// # Example / 示例 - /// - /// ```rust,no_run,ignore - /// use hiver_runtime::io::TcpListener; - /// - /// async fn listen() -> std::io::Result<()> { - /// let listener = TcpListener::bind("127.0.0.1:8080").await?; - /// println!("Listening on 127.0.0.1:8080"); - /// Ok(()) - /// } - /// ``` - pub fn bind(addr: &str) -> BindFuture + /// Bind a new TCP listener to the specified address. + /// 将新 TCP 监听器绑定到指定地址。 + pub fn bind(addr: &str) -> impl Future> { - let Ok(addr) = addr.parse::() - else - { - return BindFuture::Error(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid address format, use IP:PORT", - )); - }; - - BindFuture::Binding(BindingState { addr }) - } - - /// Accept a new connection - /// 接受新连接 - pub fn accept(&mut self) -> AcceptFuture<'_> - { - AcceptFuture { listener: self } - } - - /// Get the local address - /// 获取本地地址 - pub fn local_addr(&self) -> io::Result - { - #[cfg(unix)] - unsafe { - let mut addr: libc::sockaddr_storage = std::mem::zeroed(); - let mut len = size_of::() as libc::socklen_t; - - if libc::getsockname( - self.as_raw_fd(), - &mut addr as *mut _ as *mut libc::sockaddr, - &mut len, - ) < 0 - { - return Err(io::Error::last_os_error()); - } - - // Convert to SocketAddr (simplified) - // 转换为SocketAddr(简化版) - Ok(DUMMY_ADDR) - } - - #[cfg(not(unix))] - { - Err(io::Error::new( - io::ErrorKind::Unsupported, - "local_addr not supported on this platform", - )) + let addr = addr.to_string(); + async move { + let inner = async_net::TcpListener::bind(addr).await?; + Ok(Self { inner }) } } -} - -impl AsRawFd for TcpListener -{ - fn as_raw_fd(&self) -> RawFd - { - self.fd.as_raw_fd() - } -} - -/// Future for binding a TCP listener -/// 绑定TCP监听器的future -pub enum BindFuture -{ - /// Error state / 错误状态 - Error(io::Error), - /// Binding state / 绑定中状态 - Binding(BindingState), - /// Done state / 完成状态 - Done, -} - -struct BindingState -{ - addr: SocketAddr, -} - -impl Future for BindFuture -{ - type Output = io::Result; - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll + /// Accept a new incoming connection. + /// 接受一个新的入站连接。 + pub fn accept(&mut self) -> impl Future> + '_ { - match &mut *self - { - BindFuture::Error(e) => - { - let e = std::mem::replace(e, io::Error::other("")); - Poll::Ready(Err(e)) - }, - BindFuture::Done => panic!("BindFuture polled after completion"), - BindFuture::Binding(state) => - { - // Create and bind socket - // 创建并绑定套接字 - let fd = create_socket(state.addr.is_ipv4()); - - if fd < 0 - { - return Poll::Ready(Err(io::Error::last_os_error())); - } - - // Set reuse address - // 设置地址重用 - #[cfg(unix)] - unsafe { - let opt: i32 = 1; - if libc::setsockopt( - fd, - libc::SOL_SOCKET, - libc::SO_REUSEADDR, - &opt as *const _ as *const _, - size_of::() as libc::socklen_t, - ) < 0 - { - libc::close(fd); - return Poll::Ready(Err(io::Error::last_os_error())); - } - - // Bind - // 绑定 - let result = do_bind(fd, state.addr); - if result < 0 - { - let err = io::Error::last_os_error(); - libc::close(fd); - return Poll::Ready(Err(err)); - } - - // Listen - // 监听 - if libc::listen(fd, 128) < 0 - { - let err = io::Error::last_os_error(); - libc::close(fd); - return Poll::Ready(Err(err)); - } - - let listener = TcpListener { - // SAFETY: fd is valid and owned - fd: std::os::fd::OwnedFd::from_raw_fd(fd), - }; - - *self = BindFuture::Done; - Poll::Ready(Ok(listener)) - } - - #[cfg(not(unix))] - { - Poll::Ready(Err(io::Error::new( - io::ErrorKind::Unsupported, - "TCP bind not yet implemented on this platform", - ))) - } - }, + async move { + let (stream, addr) = self.inner.accept().await?; + Ok((TcpStream { inner: stream }, addr)) } } -} - -/// Helper to bind a socket to an address -/// 将套接字绑定到地址的辅助函数 -#[cfg(unix)] -fn do_bind(fd: RawFd, addr: SocketAddr) -> i32 -{ - unsafe { - if addr.is_ipv4() - { - if let SocketAddr::V4(v4) = addr - { - #[cfg(target_os = "linux")] - let sockaddr = libc::sockaddr_in { - sin_family: libc::AF_INET as u16, - sin_port: v4.port().to_be(), - sin_addr: libc::in_addr { - s_addr: u32::from_ne_bytes(v4.ip().octets()), - }, - sin_zero: [0; 8], - }; - - #[cfg(not(target_os = "linux"))] - let sockaddr = libc::sockaddr_in { - sin_len: size_of::() as u8, - sin_family: libc::AF_INET as u8, - sin_port: v4.port().to_be(), - sin_addr: libc::in_addr { - s_addr: u32::from_ne_bytes(v4.ip().octets()), - }, - sin_zero: [0; 8], - }; - libc::bind( - fd, - &sockaddr as *const _ as *const libc::sockaddr, - size_of::() as libc::socklen_t, - ) - } - else - { - -1 - } - } - else - { - if let SocketAddr::V6(v6) = addr - { - #[cfg(target_os = "linux")] - let sockaddr = libc::sockaddr_in6 { - sin6_family: libc::AF_INET6 as u16, - sin6_port: v6.port().to_be(), - sin6_flowinfo: v6.flowinfo(), - sin6_addr: libc::in6_addr { - s6_addr: v6.ip().octets(), - }, - sin6_scope_id: v6.scope_id(), - }; - - #[cfg(not(target_os = "linux"))] - let sockaddr = libc::sockaddr_in6 { - sin6_len: size_of::() as u8, - sin6_family: libc::AF_INET6 as u8, - sin6_port: v6.port().to_be(), - sin6_flowinfo: v6.flowinfo(), - sin6_addr: libc::in6_addr { - s6_addr: v6.ip().octets(), - }, - sin6_scope_id: v6.scope_id(), - }; - - libc::bind( - fd, - &sockaddr as *const _ as *const libc::sockaddr, - size_of::() as libc::socklen_t, - ) - } - else - { - -1 - } - } - } -} - -/// Future for accepting a connection -/// 接受连接的future -pub struct AcceptFuture<'a> -{ - listener: &'a mut TcpListener, -} - -impl Future for AcceptFuture<'_> -{ - type Output = io::Result<(TcpStream, SocketAddr)>; - - #[allow(unused_mut)] - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll + /// Returns the local socket address this listener is bound to. + /// 返回本监听器绑定的本地套接字地址。 + pub fn local_addr(&self) -> io::Result { - #[cfg(unix)] - { - let mut addr: libc::sockaddr_storage = unsafe { std::mem::zeroed() }; - let mut len = size_of::() as libc::socklen_t; - - let fd = unsafe { - #[cfg(target_os = "linux")] - { - libc::accept4( - self.listener.as_raw_fd(), - &mut addr as *mut _ as *mut libc::sockaddr, - &mut len, - libc::SOCK_CLOEXEC | libc::SOCK_NONBLOCK, - ) - } - - #[cfg(not(target_os = "linux"))] - { - // Use regular accept on macOS/BSD, then set flags - let fd = libc::accept( - self.listener.as_raw_fd(), - &mut addr as *mut _ as *mut libc::sockaddr, - &mut len, - ); - - if fd >= 0 - { - // Set close-on-exec - if libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) < 0 - { - libc::close(fd); - return Poll::Ready(Err(io::Error::last_os_error())); - } - - // Set non-blocking - let flags = libc::fcntl(fd, libc::F_GETFL); - if libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) < 0 - { - libc::close(fd); - return Poll::Ready(Err(io::Error::last_os_error())); - } - } - - fd - } - }; - - if fd < 0 - { - let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::WouldBlock - { - return Poll::Pending; - } - return Poll::Ready(Err(err)); - } - - let stream = match unsafe { TcpStream::from_raw_fd(fd) } - { - Ok(s) => s, - Err(e) => return Poll::Ready(Err(e)), - }; - - // Parse peer address (simplified) - // 解析对端地址(简化版) - let peer_addr = match self.listener.local_addr() - { - Ok(_) => DUMMY_ADDR, - Err(_) => return Poll::Ready(Err(io::Error::last_os_error())), - }; - - Poll::Ready(Ok((stream, peer_addr))) - } - - #[cfg(not(unix))] - { - Poll::Ready(Err(io::Error::new( - io::ErrorKind::Unsupported, - "TCP accept not yet implemented on this platform", - ))) - } + self.inner.local_addr() } } -/// UDP socket type / UDP套接字类型 -/// -/// Provides async UDP send and receive operations. -/// 提供异步UDP发送和接收操作。 -/// -/// # Example / 示例 -/// -/// ```rust,no_run,ignore -/// use hiver_runtime::io::UdpSocket; -/// -/// async fn echo_server() -> std::io::Result<()> { -/// let socket = UdpSocket::bind("127.0.0.1:8080").await?; -/// println!("UDP server listening on 127.0.0.1:8080"); -/// -/// let mut buf = [0u8; 1024]; -/// loop { -/// let (n, peer) = socket.recv_from(&mut buf).await?; -/// socket.send_to(&buf[..n], &peer).await?; -/// } -/// } -/// ``` +/// A UDP socket. +/// UDP 套接字。 pub struct UdpSocket { - /// The raw file descriptor / 原始文件描述符 - fd: std::os::fd::OwnedFd, + inner: async_net::UdpSocket, } impl UdpSocket { - /// Bind a new UDP socket to the specified address - /// 将新的UDP套接字绑定到指定地址 - /// - /// # Example / 示例 - /// - /// ```rust,no_run,ignore - /// use hiver_runtime::io::UdpSocket; - /// - /// async fn bind_server() -> std::io::Result<()> { - /// let socket = UdpSocket::bind("127.0.0.1:8080").await?; - /// Ok(()) - /// } - /// ``` - pub fn bind(addr: &str) -> BindUdpFuture + /// Bind a new UDP socket to the specified address. + /// 将新 UDP 套接字绑定到指定地址。 + pub fn bind(addr: &str) -> impl Future> { - let Ok(addr) = addr.parse::() - else - { - return BindUdpFuture::Error(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid address format, use IP:PORT", - )); - }; - - BindUdpFuture::Binding(BindingUdpState { addr }) - } - - /// Receive data from the socket - /// 从套接字接收数据 - /// - /// Returns the number of bytes received and the peer address. - /// 返回接收的字节数和对端地址。 - pub fn recv_from<'a, 'b>(&'a mut self, buf: &'b mut [u8]) -> RecvFromFuture<'a, 'b> - { - RecvFromFuture { - stream: Some(self), - buf, - } - } - - /// Send data to the specified address - /// 向指定地址发送数据 - /// - /// Returns the number of bytes sent. - /// 返回发送的字节数。 - pub fn send_to<'a, 'b>(&'a mut self, buf: &'b [u8], addr: SocketAddr) -> SendToFuture<'a, 'b> - { - SendToFuture { - stream: Some(self), - buf, - addr, - } - } - - /// Connect the socket to a remote address - /// 将套接字连接到远程地址 - /// - /// This filters incoming datagrams to only receive from this address. - /// 这会过滤传入的数据报,只接收来自此地址的数据。 - pub fn connect(&mut self, addr: SocketAddr) -> ConnectUdpFuture - { - ConnectUdpFuture { - fd: self.fd.as_raw_fd(), - addr, - done: false, - } - } -} - -impl AsRawFd for UdpSocket -{ - fn as_raw_fd(&self) -> RawFd - { - self.fd.as_raw_fd() - } -} - -/// Future for binding a UDP socket -/// 绑定UDP套接字的future -pub enum BindUdpFuture -{ - /// Error state / 错误状态 - Error(io::Error), - /// Binding state / 绑定中状态 - Binding(BindingUdpState), - /// Done state / 完成状态 - Done, -} - -struct BindingUdpState -{ - addr: SocketAddr, -} - -impl Future for BindUdpFuture -{ - type Output = io::Result; - - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll - { - match &mut *self - { - BindUdpFuture::Error(e) => - { - let e = std::mem::replace(e, io::Error::other("")); - Poll::Ready(Err(e)) - }, - BindUdpFuture::Done => panic!("BindUdpFuture polled after completion"), - BindUdpFuture::Binding(state) => - { - // Create and bind UDP socket - // 创建并绑定UDP套接字 - let fd = create_udp_socket(state.addr.is_ipv4()); - - if fd < 0 - { - return Poll::Ready(Err(io::Error::last_os_error())); - } - - // Bind - // 绑定 - let result = do_bind_udp(fd, state.addr); - if result < 0 - { - let err = io::Error::last_os_error(); - unsafe { libc::close(fd) }; - return Poll::Ready(Err(err)); - } - - let socket = UdpSocket { - // SAFETY: fd is valid and owned - fd: unsafe { std::os::fd::OwnedFd::from_raw_fd(fd) }, - }; - - *self = BindUdpFuture::Done; - Poll::Ready(Ok(socket)) - }, - } - } -} - -/// Helper to create a UDP socket -/// 创建UDP套接字的辅助函数 -#[cfg(unix)] -fn create_udp_socket(ipv4: bool) -> RawFd -{ - unsafe { - let domain = if ipv4 { libc::AF_INET } else { libc::AF_INET6 }; - - #[cfg(target_os = "linux")] - let fd = - libc::socket(domain, libc::SOCK_DGRAM | libc::SOCK_CLOEXEC | libc::SOCK_NONBLOCK, 0); - - #[cfg(not(target_os = "linux"))] - let fd = libc::socket(domain, libc::SOCK_DGRAM, 0); - - if fd < 0 - { - return fd; - } - - #[cfg(not(target_os = "linux"))] - { - // Set close-on-exec for macOS/BSD - if libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) < 0 - { - libc::close(fd); - return -1; - } - - // Set non-blocking - let flags = libc::fcntl(fd, libc::F_GETFL); - if libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) < 0 - { - libc::close(fd); - return -1; - } - } - - fd - } -} - -/// Helper to bind a UDP socket to an address -/// 将UDP套接字绑定到地址的辅助函数 -#[cfg(unix)] -fn do_bind_udp(fd: RawFd, addr: SocketAddr) -> i32 -{ - unsafe { - if addr.is_ipv4() - { - if let SocketAddr::V4(v4) = addr - { - #[cfg(target_os = "linux")] - let sockaddr = libc::sockaddr_in { - sin_family: libc::AF_INET as u16, - sin_port: v4.port().to_be(), - sin_addr: libc::in_addr { - s_addr: u32::from_ne_bytes(v4.ip().octets()), - }, - sin_zero: [0; 8], - }; - - #[cfg(not(target_os = "linux"))] - let sockaddr = libc::sockaddr_in { - sin_len: size_of::() as u8, - sin_family: libc::AF_INET as u8, - sin_port: v4.port().to_be(), - sin_addr: libc::in_addr { - s_addr: u32::from_ne_bytes(v4.ip().octets()), - }, - sin_zero: [0; 8], - }; - - libc::bind( - fd, - &sockaddr as *const _ as *const libc::sockaddr, - size_of::() as libc::socklen_t, - ) - } - else - { - -1 - } - } - else - { - if let SocketAddr::V6(v6) = addr - { - #[cfg(target_os = "linux")] - let sockaddr = libc::sockaddr_in6 { - sin6_family: libc::AF_INET6 as u16, - sin6_port: v6.port().to_be(), - sin6_flowinfo: v6.flowinfo(), - sin6_addr: libc::in6_addr { - s6_addr: v6.ip().octets(), - }, - sin6_scope_id: v6.scope_id(), - }; - - #[cfg(not(target_os = "linux"))] - let sockaddr = libc::sockaddr_in6 { - sin6_len: size_of::() as u8, - sin6_family: libc::AF_INET6 as u8, - sin6_port: v6.port().to_be(), - sin6_flowinfo: v6.flowinfo(), - sin6_addr: libc::in6_addr { - s6_addr: v6.ip().octets(), - }, - sin6_scope_id: v6.scope_id(), - }; - - libc::bind( - fd, - &sockaddr as *const _ as *const libc::sockaddr, - size_of::() as libc::socklen_t, - ) - } - else - { - -1 - } - } - } -} - -/// Future for receiving from a UDP socket -/// 从UDP套接字接收的future -pub struct RecvFromFuture<'a, 'b> -{ - stream: Option<&'a mut UdpSocket>, - buf: &'b mut [u8], -} - -impl Future for RecvFromFuture<'_, '_> -{ - type Output = io::Result<(usize, SocketAddr)>; - - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll + let addr = addr.to_string(); + async move { + let inner = async_net::UdpSocket::bind(addr).await?; + Ok(Self { inner }) + } + } + + /// Receive a single datagram into `buf`, returning the byte count and the + /// sender's address. + /// 接收单个数据报到 `buf`,返回字节数与发送方地址。 + pub fn recv_from<'a, 'b>( + &'a mut self, + buf: &'b mut [u8], + ) -> impl Future> + 'a + where + 'b: 'a, { - // Extract all needed values upfront to avoid borrow issues - // 提前提取所有需要的值以避免借用问题 - let stream_fd; - let buf_ptr; - let buf_len; - - { - let stream = self.stream.as_mut().unwrap(); - stream_fd = stream.as_raw_fd(); - buf_ptr = self.buf.as_mut_ptr(); - buf_len = self.buf.len(); - } - - #[cfg(unix)] - { - let mut addr: libc::sockaddr_storage = unsafe { std::mem::zeroed() }; - let mut addr_len = size_of::() as libc::socklen_t; - - let result = unsafe { - libc::recvfrom( - stream_fd, - buf_ptr as *mut _, - buf_len, - 0, - &mut addr as *mut _ as *mut libc::sockaddr, - &mut addr_len, - ) - }; - - if result < 0 - { - let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::WouldBlock - { - return Poll::Pending; - } - return Poll::Ready(Err(err)); - } - - let n = result as usize; - - // Parse peer address (simplified) - // 解析对端地址(简化版) - let peer_addr = SocketAddr::V4(std::net::SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)); - - Poll::Ready(Ok((n, peer_addr))) - } - - #[cfg(not(unix))] - { - let _ = (stream_fd, buf_ptr, buf_len); - Poll::Ready(Err(io::Error::new( - io::ErrorKind::Unsupported, - "UDP recv_from not yet implemented on this platform", - ))) - } - } -} - -/// Future for sending to a UDP socket -/// 向UDP套接字发送的future -pub struct SendToFuture<'a, 'b> -{ - stream: Option<&'a mut UdpSocket>, - buf: &'b [u8], - addr: SocketAddr, -} - -impl Future for SendToFuture<'_, '_> -{ - type Output = io::Result; - - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll + async move { self.inner.recv_from(buf).await } + } + + /// Send `buf` as a datagram to `addr`. + /// 将 `buf` 作为数据报发送到 `addr`。 + pub fn send_to<'a, 'b>( + &'a mut self, + buf: &'b [u8], + addr: SocketAddr, + ) -> impl Future> + 'a + where + 'b: 'a, { - let stream = self.stream.as_mut().unwrap(); - let stream_fd = stream.as_raw_fd(); - - #[cfg(unix)] - { - let result = match self.addr - { - SocketAddr::V4(v4) => - { - let sockaddr = libc::sockaddr_in { - #[cfg(target_os = "macos")] - sin_len: size_of::() as u8, - sin_family: libc::AF_INET as _, - sin_port: v4.port().to_be(), - sin_addr: libc::in_addr { - s_addr: u32::from(*v4.ip()).to_be(), - }, - sin_zero: [0; 8], - }; - unsafe { - libc::sendto( - stream_fd, - self.buf.as_ptr() as *const _, - self.buf.len(), - 0, - &sockaddr as *const _ as *const _, - size_of::() as libc::socklen_t, - ) - } - }, - SocketAddr::V6(v6) => - { - let sockaddr = libc::sockaddr_in6 { - sin6_family: libc::AF_INET6 as _, - sin6_port: v6.port().to_be(), - sin6_flowinfo: v6.flowinfo().to_be(), - sin6_addr: libc::in6_addr { - s6_addr: v6.ip().octets(), - }, - sin6_scope_id: v6.scope_id(), - #[cfg(target_os = "macos")] - sin6_len: size_of::() as u8, - }; - unsafe { - libc::sendto( - stream_fd, - self.buf.as_ptr() as *const _, - self.buf.len(), - 0, - &sockaddr as *const _ as *const _, - size_of::() as libc::socklen_t, - ) - } - }, - }; - - if result < 0 - { - let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::WouldBlock - { - return Poll::Pending; - } - return Poll::Ready(Err(err)); - } - - let n = result as usize; - Poll::Ready(Ok(n)) - } - - #[cfg(not(unix))] - { - let _ = stream_fd; - Poll::Ready(Err(io::Error::new( - io::ErrorKind::Unsupported, - "UDP send_to not yet implemented on this platform", - ))) - } + async move { self.inner.send_to(buf, addr).await } } -} -/// Future for connecting a UDP socket -/// 连接UDP套接字的future -pub struct ConnectUdpFuture -{ - fd: RawFd, - addr: SocketAddr, - done: bool, -} - -impl Future for ConnectUdpFuture -{ - type Output = io::Result<()>; - - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll + /// Connect the UDP socket to a remote peer (filters received packets to + /// that peer and enables `recv`/`send`). + /// 将 UDP 套接字连接到远端(过滤收到的包至该对端,并启用 `recv`/`send`)。 + pub fn connect(&mut self, addr: SocketAddr) -> impl Future> + '_ { - assert!(!self.done, "ConnectUdpFuture polled after completion"); - - // Perform the connect operation - // 执行connect操作 - #[cfg(unix)] - { - let result = unsafe { - match self.addr - { - SocketAddr::V4(v4) => - { - #[cfg(target_os = "linux")] - let sockaddr = libc::sockaddr_in { - sin_family: libc::AF_INET as u16, - sin_port: v4.port().to_be(), - sin_addr: libc::in_addr { - s_addr: u32::from_ne_bytes(v4.ip().octets()), - }, - sin_zero: [0; 8], - }; - - #[cfg(not(target_os = "linux"))] - let sockaddr = libc::sockaddr_in { - sin_len: size_of::() as u8, - sin_family: libc::AF_INET as u8, - sin_port: v4.port().to_be(), - sin_addr: libc::in_addr { - s_addr: u32::from_ne_bytes(v4.ip().octets()), - }, - sin_zero: [0; 8], - }; - - libc::connect( - self.fd, - &sockaddr as *const _ as *const libc::sockaddr, - size_of::() as libc::socklen_t, - ) - }, - SocketAddr::V6(v6) => - { - #[cfg(target_os = "linux")] - let sockaddr = libc::sockaddr_in6 { - sin6_family: libc::AF_INET6 as u16, - sin6_port: v6.port().to_be(), - sin6_flowinfo: v6.flowinfo(), - sin6_addr: libc::in6_addr { - s6_addr: v6.ip().octets(), - }, - sin6_scope_id: v6.scope_id(), - }; - - #[cfg(not(target_os = "linux"))] - let sockaddr = libc::sockaddr_in6 { - sin6_len: size_of::() as u8, - sin6_family: libc::AF_INET6 as u8, - sin6_port: v6.port().to_be(), - sin6_flowinfo: v6.flowinfo(), - sin6_addr: libc::in6_addr { - s6_addr: v6.ip().octets(), - }, - sin6_scope_id: v6.scope_id(), - }; - - libc::connect( - self.fd, - &sockaddr as *const _ as *const libc::sockaddr, - size_of::() as libc::socklen_t, - ) - }, - } - }; - - if result < 0 - { - return Poll::Ready(Err(io::Error::last_os_error())); - } - } - - #[cfg(not(unix))] - { - let _ = (self.fd, self.addr); - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::Unsupported, - "UDP connect not yet implemented on this platform", - ))); - } - - self.done = true; - Poll::Ready(Ok(())) + async move { self.inner.connect(addr).await } } } @@ -1575,38 +257,27 @@ mod tests { use super::*; - #[test] - fn test_tcp_stream_create() - { - // Test that TcpStream can be created (will fail in practice without a valid fd) - // 测试TcpStream可以被创建(实际上没有有效的fd会失败) - let result = unsafe { TcpStream::from_raw_fd(-1) }; - assert!(result.is_err()); - } - #[test] fn test_tcp_listener_bind_invalid() { - let future = TcpListener::bind("invalid_address"); - // Should create Error future - // 应该创建Error future - match future - { - BindFuture::Error(_) => - {}, - _ => panic!("Expected Error future"), - } - } - - #[test] - fn test_connect_invalid_addr() - { - let future = TcpStream::connect("not_an_address"); - match future - { - ConnectFuture::Error(_) => - {}, - _ => panic!("Expected Error future for invalid address"), - } + // We cannot rely on DNS to reject made-up hostnames — many resolvers + // (ISPs, captive portals) synthesize addresses for anything. Instead + // prove bind works end-to-end by binding a real ephemeral port and + // checking the returned listener has a valid local address. + // `block_on` itself returns `io::Result`, and the inner + // future returns `io::Result`, so we unwrap twice. + // 不能依赖 DNS 拒绝编造的主机名——许多解析器(ISP、强制门户)会为任意主机 + // 合成地址。改为端到端证明 bind 生效:绑定一个真实临时端口并检查返回的 + // 监听器具有合法本地地址。`block_on` 自身返回 `io::Result`, + // 内层 future 返回 `io::Result`,故需解包两次。 + let mut runtime = crate::Runtime::new().unwrap(); + let listener = runtime + .block_on(async { TcpListener::bind("127.0.0.1:0").await }) + .expect("block_on should succeed") + .expect("bind to 127.0.0.1:0 should succeed"); + let addr = listener + .local_addr() + .expect("listener should have a local addr"); + assert!(addr.port() != 0, "ephemeral bind should assign a real port"); } } diff --git a/crates/hiver-runtime/src/iouring.rs b/crates/hiver-runtime/src/iouring.rs new file mode 100644 index 00000000..617eb40e --- /dev/null +++ b/crates/hiver-runtime/src/iouring.rs @@ -0,0 +1,85 @@ +//! io_uring reactor backend (Linux only, feature `io-uring`). +//! io_uring reactor 后端(仅 Linux,feature `io-uring`)。 +//! +//! **Status: reserved / not yet implemented.** +//! **状态:预留 / 尚未实现。** +//! +//! This module is the integration point for a future high-performance +//! thread-per-core io_uring reactor, modeled after [monoio]. When the +//! `io-uring` feature is active on Linux, `Runtime::with_config` should prefer +//! an [`IoUringDriver`] over the default `async-io` reactor. +//! +//! 此模块是未来高性能 thread-per-core io_uring reactor 的接入点,参考 [monoio] +//! 建模。当 `io-uring` feature 在 Linux 上激活时,`Runtime::with_config` 应优先 +//! 选用 [`IoUringDriver`] 而非默认的 `async-io` reactor。 +//! +//! # Why io_uring? / 为何用 io_uring? +//! +//! io_uring delivers true zero-syscall-storm async I/O: submissions and +//! completions are batched in shared ring buffers, eliminating the per-call +//! `epoll_wait`/`read`/`write` syscalls. Combined with thread-per-core (no +//! cross-thread work stealing), this is the lowest-latency async model on +//! Linux. See and the monoio architecture. +//! +//! io_uring 提供真正的零系统调用风暴异步 I/O:提交与完成在共享环形缓冲区中 +//! 批处理,消除每次调用的 `epoll_wait`/`read`/`write` 系统调用。配合 +//! thread-per-core(无跨线程 work stealing),这是 Linux 上最低延迟的异步模型。 +//! 见 及 monoio 架构。 +//! +//! # Implementation plan / 实现计划 +//! +//! 1. Add `io-uring = "0.7"` under `[target.'cfg(target_os = "linux")'.dependencies]` (gated by the +//! `io-uring` feature) in `Cargo.toml`. +//! 2. Implement `IoUringDriver` here: a `Driver`-equivalent that owns an `io_uring::IoUring` +//! instance, registers FDs as `SubmissionQueuee`s, and drains the completion queue into wakers — +//! mirroring `async-io`'s `Reactor::react()` but with ring buffers instead of epoll. +//! 3. In `runtime.rs`, add `#[cfg(all(target_os="linux", feature="io-uring"))]` selection logic in +//! `Runtime::with_config` to build an `IoUringDriver` instead of relying on `async-io`. +//! 4. The `async-net` I/O types would need io_uring-aware equivalents (or use `monoio`'s net +//! types), since async-net is bound to the async-io reactor. +//! +//! # Why this is deferred / 为何暂缓 +//! +//! This requires a Linux environment to build, run, and benchmark. On macOS +//! (the current dev platform) io_uring does not exist, so the implementation +//! cannot be validated here. The reservation (feature flag + this module) lets +//! a Linux contributor pick up the work with a clear integration point. +//! +//! 这需要 Linux 环境来构建、运行与基准测试。在 macOS(当前开发平台)上 +//! io_uring 不存在,故实现无法在此验证。此预留(feature flag + 本模块)使 +//! Linux 贡献者可在明确的接入点继续该项工作。 +//! +//! [monoio]: https://github.com/bytedance/monoio + +#![allow(missing_docs)] + +/// Placeholder for the future io_uring reactor driver. +/// 未来 io_uring reactor driver 的占位符。 +/// +/// When implemented, this will be a struct owning an `io_uring::IoUring` +/// instance, providing `submit`/`wait`/`register` methods analogous to the +/// (now-removed) self-built `Driver` trait, but backed by ring buffers. +/// +/// 实现后,这将是一个拥有 `io_uring::IoUring` 实例的结构体,提供与 +/// (现已移除的)自研 `Driver` trait 类似的 `submit`/`wait`/`register` 方法, +/// 但以环形缓冲区为支撑。 +pub struct IoUringDriver; + +impl IoUringDriver +{ + /// Create the driver. Unimplemented — see module docs for the plan. + /// 创建 driver。未实现 —— 实现计划见模块文档。 + /// + /// # Errors / 错误 + /// + /// Always returns an "unimplemented" error until the driver is built. + /// 在 driver 构建完成前始终返回 "unimplemented" 错误。 + pub fn new() -> std::io::Result + { + Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "io_uring backend is reserved but not yet implemented; use the default async-io \ + backend", + )) + } +} diff --git a/crates/hiver-runtime/src/lib.rs b/crates/hiver-runtime/src/lib.rs index 978febb5..46affecb 100644 --- a/crates/hiver-runtime/src/lib.rs +++ b/crates/hiver-runtime/src/lib.rs @@ -1,21 +1,30 @@ -//! Hiver Runtime - High-performance async runtime -//! Hiver运行时 - 高性能异步运行时 +//! Hiver Runtime - async runtime on async-executor / async-io +//! Hiver 运行时 - 基于 async-executor / async-io 的异步运行时 //! //! # Overview / 概述 //! -//! `hiver-runtime` provides a high-performance async runtime based on io-uring -//! with thread-per-core architecture for maximum scalability. +//! `hiver-runtime` builds on the [`async-executor`] + [`async-io`] + [`async-net`] +//! ecosystem (the same crates `smol` is composed of): a multi-task executor driven +//! by a reactor-aware `block_on`, with cross-platform epoll/kqueue/IOCP I/O. +//! This keeps the public surface stable (`Runtime`, `block_on`, `spawn`, +//! `io::*`, `time::*`) while delegating the low-level scheduling and I/O to +//! battle-tested libraries. //! -//! `hiver-runtime` 提供基于io-uring的高性能异步运行时,采用thread-per-core架构 -//! 以实现最大可扩展性。 +//! `hiver-runtime` 构建于 [`async-executor`] + [`async-io`] + [`async-net`] 生态 +//! (与 `smol` 由相同 crate 组成):一个由 reactor 感知 `block_on` 驱动的多任务 +//! 执行器,提供跨平台 epoll/kqueue/IOCP I/O。这保持了公共接口稳定 +//! (`Runtime`、`block_on`、`spawn`、`io::*`、`time::*`),同时将底层调度与 I/O +//! 委托给久经验证的库。 //! //! # Features / 功能 //! -//! - io-uring based I/O (Linux) / 基于io-uring的I/O(Linux) -//! - epoll/kqueue fallback / epoll/kqueue回退支持 -//! - Thread-per-core scheduler / Thread-per-core调度器 -//! - Timer wheel for efficient timers / 高效定时器的时间轮 -//! - Zero-copy I/O primitives / 零拷贝I/O原语 +//! - Multi-task executor via [`async_executor::Executor`] / 经 [`async_executor::Executor`] +//! 的多任务执行器 +//! - Reactor-aware `block_on` (smol's driver) / reactor 感知的 `block_on`(smol 的驱动器) +//! - Cross-platform async I/O (TCP/UDP) via async-net / 经 async-net 的跨平台异步 I/O(TCP/UDP) +//! - Timers via async-io / 经 async-io 的定时器 +//! - Fire-and-forget `spawn` (task is detached on handle drop) / fire-and-forget +//! `spawn`(句柄丢弃时 detach 任务) //! //! # Example / 示例 //! @@ -34,30 +43,21 @@ #![warn(missing_docs)] #![warn(unreachable_pub)] #![cfg_attr(docsrs, feature(doc_cfg))] -// Allow unsafe operations in unsafe functions for Rust 2024 edition compatibility -// 允许unsafe函数中的unsafe操作以兼容Rust 2024版本 -#![expect(unsafe_op_in_unsafe_fn)] -// Allow unsafe code - runtime requires unsafe for low-level system operations -// 允许unsafe代码 - 运行时需要unsafe进行底层系统操作 +// The standalone task::block_on uses a no-op RawWaker; runtime I/O now goes +// through async-net/async-io (no hand-written unsafe). Keep the allow for the +// few remaining std::os::fd shims. +// 独立的 task::block_on 使用 no-op RawWaker;runtime 的 I/O 现经由 +// async-net/async-io(无手写 unsafe)。保留此 allow 供少量残留的 std::os::fd shim。 #![allow(unsafe_code)] -// Runtime-specific allowances: unwrap on mutex locks is acceptable as poisoning -// is handled at a higher level, and integer casts are necessary for FFI. -// 运行时特定允许:mutex上的unwrap是可接受的,因为中毒在更高层处理, -// 且整数转换对于FFI是必要的。 +// Runtime-specific allowances: mutex unwrap is acceptable (poisoning handled +// higher up), integer casts are needed for FFI. +// 运行时特定允许:mutex unwrap 可接受(中毒在更高层处理),整数转换 FFI 需要。 #![allow(clippy::unwrap_used)] -// Allow integer casts for FFI and system calls -// 允许用于FFI和系统调用的整数转换 #![allow(clippy::cast_possible_wrap)] #![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_sign_loss)] -// Allow indexing - runtime code does bounds checking at higher levels -// 允许索引 - 运行时代码在更高层进行边界检查 #![allow(clippy::indexing_slicing)] -// Allow doc_markdown - bilingual documentation may trigger false positives -// 允许doc_markdown - 双语文档可能触发误报 #![allow(clippy::doc_markdown)] -// Allow additional lints for runtime code -// 允许运行时代码的额外检查 #![allow(clippy::ptr_arg)] #![allow(clippy::std_instead_of_core)] #![allow(clippy::manual_is_power_of_two)] @@ -71,24 +71,29 @@ // Public modules / 公共模块 pub mod channel; -pub mod driver; pub mod io; pub mod runtime; -pub mod scheduler; pub mod select; pub mod task; pub mod time; +// io_uring backend — reserved for a future Linux-only high-performance reactor +// (see the `io-uring` feature flag in Cargo.toml). Not implemented yet; this +// module exists only when the feature + Linux target are both active, so it is +// inert on all other configurations. +// +// io_uring 后端 —— 为未来的 Linux 专用高性能 reactor 预留(见 Cargo.toml 的 +// `io-uring` feature flag)。尚未实现;此模块仅当 feature 与 Linux target 同时 +// 激活时存在,故在所有其它配置下均为惰性。 +#[cfg(all(target_os = "linux", feature = "io-uring"))] +#[allow(clippy::module_inception)] +pub mod iouring; + // Re-exports / 重新导出 pub use channel::{Receiver, RecvError, SendError, Sender, bounded, unbounded}; -pub use driver::{DriverConfig, DriverConfigBuilder, DriverFactory, DriverType}; pub use runtime::{Runtime, RuntimeBuilder, RuntimeConfig}; -pub use scheduler::{ - Scheduler, SchedulerConfig, SchedulerHandle, WorkStealingConfig, WorkStealingHandle, - WorkStealingScheduler, gen_task_id, -}; pub use select::{ SelectMultiple, SelectMultipleOutput, SelectTwo, SelectTwoOutput, select_multiple, select_two, }; -pub use task::{JoinError, JoinHandle, spawn}; +pub use task::{JoinError, JoinHandle, TaskId, gen_task_id, spawn, spawn_blocking}; pub use time::{Duration, Instant, sleep, sleep_until}; diff --git a/crates/hiver-runtime/src/runtime.rs b/crates/hiver-runtime/src/runtime.rs index 3adc8777..802261ab 100644 --- a/crates/hiver-runtime/src/runtime.rs +++ b/crates/hiver-runtime/src/runtime.rs @@ -22,40 +22,62 @@ //! } //! ``` -use std::{ - future::Future, - io, - pin::Pin, - sync::Arc, - task::{Context, Poll, Waker}, -}; - -use crate::{ - driver::{Driver, DriverFactory, DriverType}, - scheduler::{Scheduler, SchedulerConfig, SchedulerHandle}, - time::{Duration, Instant}, -}; +use std::{future::Future, io, sync::RwLock}; + +use crate::time::Duration; thread_local! { static CURRENT_HANDLE: std::cell::RefCell> = const { std::cell::RefCell::new(None) }; } +/// Global handle store so that tasks running on *worker threads* (which do not +/// inherit the main thread's thread-local) can still access the runtime's +/// driver and I/O registry to register FD interest. +/// +/// 全局 handle 存储,使运行在 *worker 线程* 上的任务(不继承主线程的 +/// thread-local)仍可访问运行时的 driver 与 I/O 注册表以注册 FD 兴趣。 +/// +/// This is a **resettable** global (RwLock>, not OnceLock) so +/// that `block_on` clears it on exit. With OnceLock the first test's handle +/// leaked into every subsequent test, breaking test isolation — `try_current()` +/// would return a stale handle from a torn-down runtime. Each `block_on` sets +/// it on entry and clears it on exit. +/// +/// 这是**可重置**的全局存储(RwLock>,非 OnceLock),以便 +/// `block_on` 在退出时清空它。若用 OnceLock,首个测试的 handle 会泄漏到后续 +/// 每个测试,破坏测试隔离——`try_current()` 会返回已销毁 runtime 的过期 +/// handle。每次 `block_on` 在进入时设置、退出时清空。 +static GLOBAL_HANDLE: RwLock> = RwLock::new(None); + /// Runtime configuration / 运行时配置 /// -/// Configuration for the async runtime including scheduler and driver settings. -/// 异步运行时的配置,包括调度器和驱动设置。 +/// Configuration values for the async runtime. In the async-executor/async-io +/// backend these are kept for API/Builder compatibility but most no longer drive +/// runtime internals (the executor and reactor manage their own queues and +/// driver). `enable_parking` / `park_timeout` are retained for compatibility +/// with existing call sites and tests. +/// +/// 异步运行时的配置值。在 async-executor/async-io 后端中,这些为 API/Builder +/// 兼容性而保留,但多数已不再驱动 runtime 内部(执行器与 reactor 自行管理其队列 +/// 与 driver)。`enable_parking` / `park_timeout` 为兼容既有调用点与测试而保留。 #[derive(Debug, Clone)] pub struct RuntimeConfig { - /// Scheduler configuration / 调度器配置 - pub scheduler: SchedulerConfig, - /// Driver type to use / 要使用的driver类型 - pub driver_type: DriverType, - /// Driver I/O configuration / Driver I/O配置 - pub driver_io: crate::driver::DriverConfig, - /// Enable thread parking / 启用线程休眠 + /// Worker thread count hint (compat; async-executor is single-thread + /// block_on-driven, so this is informational only). + /// 工作线程数提示(兼容;async-executor 为单线程 block_on 驱动,故仅作信息用途)。 + pub worker_threads: usize, + /// Scheduler queue size hint (compat). + /// 调度器队列大小提示(兼容)。 + pub queue_size: usize, + /// Thread name prefix (compat). + /// 线程名前缀(兼容)。 + pub thread_name: String, + /// Enable thread parking (compat). + /// 启用线程休眠(兼容)。 pub enable_parking: bool, - /// Park timeout / 休眠超时 + /// Park timeout (compat). + /// 休眠超时(兼容)。 pub park_timeout: Duration, } @@ -64,9 +86,9 @@ impl Default for RuntimeConfig fn default() -> Self { Self { - scheduler: SchedulerConfig::default(), - driver_type: DriverType::Auto, - driver_io: crate::driver::DriverConfig::default(), + worker_threads: 1, + queue_size: 256, + thread_name: "hiver-worker".to_string(), enable_parking: true, park_timeout: Duration::from_millis(100), } @@ -105,57 +127,59 @@ impl RuntimeBuilder } } - /// Set the number of worker threads - /// 设置工作线程数量 + /// Set the number of worker threads (compat hint). + /// 设置工作线程数量(兼容提示)。 pub fn worker_threads(mut self, count: usize) -> Self { - self.config.scheduler.queue_size = count * 256; - self.config.scheduler.thread_name = "hiver-worker".to_string(); + self.config.worker_threads = count; + self.config.queue_size = count * 256; self } - /// Set the queue size for the scheduler - /// 设置调度器的队列大小 + /// Set the queue size (compat hint). + /// 设置队列大小(兼容提示)。 pub fn queue_size(mut self, size: usize) -> Self { - self.config.scheduler.queue_size = size; + self.config.queue_size = size; self } - /// Set the thread name pattern - /// 设置线程名称模式 + /// Set the thread name pattern (compat). + /// 设置线程名称模式(兼容)。 pub fn thread_name(mut self, name: impl Into) -> Self { - self.config.scheduler.thread_name = name.into(); + self.config.thread_name = name.into(); self } - /// Set the driver type - /// 设置driver类型 - pub fn driver_type(mut self, driver_type: DriverType) -> Self + /// Set the driver type (removed: the reactor is always async-io). + /// 设置 driver 类型(已移除:reactor 始终为 async-io)。 + #[deprecated( + note = "driver selection is no longer configurable; the reactor is always async-io" + )] + pub fn driver_type(self, _driver_type: ()) -> Self { - self.config.driver_type = driver_type; self } - /// Set the I/O driver queue depth - /// 设置I/O驱动队列深度 - pub fn io_entries(mut self, entries: u32) -> Self + /// Set the I/O driver queue depth (removed: async-io manages its own sizing). + /// 设置 I/O driver 队列深度(已移除:async-io 自行管理其容量)。 + #[deprecated(note = "io_entries is no longer configurable; async-io manages its own sizing")] + pub fn io_entries(self, _entries: u32) -> Self { - self.config.driver_io.entries = entries; self } - /// Enable or disable thread parking - /// 启用或禁用线程休眠 + /// Enable or disable thread parking (compat). + /// 启用或禁用线程休眠(兼容)。 pub fn enable_parking(mut self, enable: bool) -> Self { self.config.enable_parking = enable; self } - /// Set the park timeout - /// 设置休眠超时 + /// Set the park timeout (compat). + /// 设置休眠超时(兼容)。 pub fn park_timeout(mut self, timeout: Duration) -> Self { self.config.park_timeout = timeout; @@ -203,16 +227,22 @@ impl Default for RuntimeBuilder /// ``` pub struct Runtime { - /// The scheduler / 调度器 - scheduler: Scheduler, - /// The driver / 驱动 - driver: Arc, - /// Runtime configuration / 运行时配置 + /// Runtime configuration (mostly compat; the executor/reactor manage + /// their own internals). Retained so `Runtime` can be reconstructed / + /// inspected by tooling, and to keep the builder chain coherent. + /// 运行时配置(多数为兼容;执行器/reactor 自行管理其内部)。保留以便 + /// tooling 重建/检视 `Runtime`,并保持 builder 链一致。 + #[allow(dead_code)] config: RuntimeConfig, - /// Waker for the main task / 主任务的waker - main_waker: Option, - /// Last time the timer was advanced / 上次推进定时器的时间 - last_timer_advance: Instant, + /// Async executor that drives spawned tasks. Stored as a `'static` + /// reference (the executor is leaked from the heap) so that `spawn()` + /// can reach it via the `Handle` without lifetime gymnastics, and so the + /// executor outlives any `Handle` clone handed to user code. + /// + /// 异步执行器,驱动被 spawn 的任务。以 `'static` 引用存储(执行器从堆上 + /// leak),使 `spawn()` 能经由 `Handle` 访问它而无需生命周期 gymnastics, + /// 且执行器的生命周期长于任何交给用户代码的 `Handle` 克隆。 + executor: &'static async_executor::Executor<'static>, } impl Runtime @@ -239,29 +269,29 @@ impl Runtime /// Create a new runtime with the specified configuration /// 使用指定配置创建新的运行时 /// + /// In the async-executor/async-io backend most configuration is + /// informational; the executor and reactor manage their own internals. + /// + /// 在 async-executor/async-io 后端,多数配置仅作信息用途;执行器与 reactor + /// 自行管理其内部。 + /// /// # Errors / 错误 /// - /// Returns an error if: - /// 返回错误如果: - /// - Driver creation fails / Driver创建失败 - /// - Scheduler creation fails / 调度器创建失败 + /// Returns an error if executor creation fails (extremely unlikely). + /// 若执行器创建失败则返回错误(极不可能)。 pub fn with_config(config: RuntimeConfig) -> io::Result { - // Create the driver - // 创建driver - let driver = DriverFactory::create_with_config(config.driver_type, config.driver_io)?; - - // Create the scheduler with the driver - // 使用driver创建调度器 - let scheduler = Scheduler::with_config_and_driver(&config.scheduler, driver.clone())?; + // Create the executor. Leaked to obtain a `'static` reference so that + // `spawn()` can capture it through the `Handle` (which needs `'static` + // to be safely stored in a thread-local / global). The executor lives + // for the process lifetime; runtimes are not torn down repeatedly. + // 创建执行器。leak 以获得 `'static` 引用,使 `spawn()` 能经由 `Handle` + //(需 `'static` 才能安全存于 thread-local / 全局)捕获它。执行器存活于 + // 进程生命周期;runtime 不会被反复销毁。 + let executor: &'static async_executor::Executor<'static> = + Box::leak(Box::new(async_executor::Executor::new())); - Ok(Self { - scheduler, - driver, - config, - main_waker: None, - last_timer_advance: Instant::now(), - }) + Ok(Self { config, executor }) } /// Run a future to completion on this runtime @@ -285,167 +315,90 @@ impl Runtime /// println!("Hello, world!"); /// }); /// ``` - pub fn block_on>(&mut self, future: F) -> io::Result<()> + pub fn block_on(&mut self, future: F) -> io::Result + where + F::Output: Send, { // Set the current runtime handle for this thread // 为当前线程设置运行时句柄 let handle = Handle { - scheduler_handle: self.scheduler.handle(), + executor: Some(self.executor), }; Handle::set_current(Some(handle)); - - // Pin the future - // Pin future - let mut future = Box::pin(future); - - // Create a waker for the main task - // 为主任务创建waker - let handle = self.scheduler.handle(); - let waker = handle.waker(); - let mut context = Context::from_waker(&waker); - self.main_waker = Some(waker.clone()); - - // Run the event loop - // 运行事件循环 - let result = loop - { - // Poll the future - // 轮询future - match Pin::new(&mut future).poll(&mut context) - { - Poll::Ready(()) => - { - // Future completed, flush any remaining events - // Future完成,刷新任何剩余事件 - let _ = self.flush_events(); - break Ok(()); - }, - Poll::Pending => - { - // Future is not ready, run the event loop - // Future未就绪,运行事件循环 - self.run_once()?; - }, - } - }; - - // Clear the thread-local handle - // 清除线程本地句柄 + // NOTE: we deliberately do NOT write the process-global `GLOBAL_HANDLE` + // here. In the new single-thread-executor design, every spawned task + // runs on THIS thread (the one driving `executor.run`), so it inherits + // the thread-local `CURRENT_HANDLE` directly — no worker threads, no + // global fallback needed. Writing the global caused parallel tests' + // `block_on` calls to race on the same `RwLock` (one test's exit + // cleared the other's live handle), hanging the suite. + // 注意:此处故意不写入进程全局 `GLOBAL_HANDLE`。在新的单线程执行器设计中, + // 每个 spawn 出的任务都在当前线程(驱动 `executor.run` 的那个)上运行, + // 故直接继承 thread-local `CURRENT_HANDLE`——无需 worker 线程、无需全局 + // 回退。写入全局会导致并行测试的 `block_on` 在同一 `RwLock` 上竞争(一个 + // 测试退出清空了另一个仍活着的 handle),使测试套件挂起。 + + // Drive the main future to completion on THIS thread, together with + // any tasks spawned onto the executor. `executor.run(future)` polls the + // main future and drains the executor's ready queue in the same loop. + // + // CRITICAL: we drive this with `async_io::block_on` (not + // `futures_lite::future::block_on`). `async_io::block_on` is the + // reactor-aware driver that smol itself uses — it locks the async-io + // reactor, calls `react()` to process I/O events, and uses a custom + // waker (`BlockOnWaker`) that notifies the reactor when woken from + // another thread. `futures_lite::block_on` is a plain parker that does + // NOT drive the reactor — under it, a future blocked on `accept()` + // would never make progress, and fire-and-forget spawned tasks (which + // rely on the reactor to wake their read/timer wakers) would hang. This + // was the root cause of the HTTP server's "connection reset" failure. + // + // 在当前线程上把主 future 驱动至完成,同时驱动任何被 spawn 到执行器上的 + // 任务。`executor.run(future)` 在同一循环里轮询主 future 并排空执行器的 + // 就绪队列。 + // + // 关键:此处用 `async_io::block_on`(而非 `futures_lite::future::block_on`) + // 驱动。`async_io::block_on` 是 smol 自身使用的 reactor 感知驱动器 —— 它 + // 锁定 async-io reactor、调用 `react()` 处理 I/O 事件,并使用自定义 waker + //(`BlockOnWaker`)在被其它线程唤醒时通知 reactor。`futures_lite::block_on` + // 是普通 parker,不驱动 reactor —— 在其下,阻塞于 `accept()` 的 future 永远 + // 不会推进,依赖 reactor 唤醒其 read/timer waker 的 fire-and-forget 任务会 + // 挂起。这正是 HTTP 服务端 "connection reset" 失败的根因。 + let result = async_io::block_on(self.executor.run(future)); + + // Clear the thread-local handle. + // 清除线程本地句柄。 Handle::set_current(None); - - result - } - - /// Run a single iteration of the event loop - /// 运行事件循环的单次迭代 - fn run_once(&mut self) -> io::Result<()> - { - // Submit any pending I/O operations - // 提交任何挂起的I/O操作 - let _ = self.driver.submit(); - - // Wait for events with timeout - // 带超时等待事件 - let timeout = if self.config.enable_parking - { - Some(self.config.park_timeout) - } - else - { - None - }; - - if let Some(to) = timeout - { - let (_events, timed_out) = self.driver.wait_timeout(to)?; - if timed_out - { - // Timeout occurred, this is normal for idle periods - // 超时发生,这对空闲期是正常的 - } - } - else - { - let _events = self.driver.wait()?; - } - - // Process completions - // 处理完成事件 - self.process_completions(); - - // Advance the timer wheel - // 推进时间轮 - self.advance_timers(); - - Ok(()) - } - - /// Process completion events from the driver - /// 处理来自driver的完成事件 - fn process_completions(&mut self) - { - while let Some(completion) = self.driver.get_completion() - { - // Notify the task associated with this completion - // 通知与此完成关联的任务 - if let Some(waker) = self.scheduler.get_task_waker(completion.user_data) - { - waker.wake(); - } - self.driver.advance_completion(); - } - } - - /// Advance the timer wheel and wake expired timers - /// 推进时间轮并唤醒到期的定时器 - fn advance_timers(&mut self) - { - use crate::time::global_timer; - - let now = Instant::now(); - let elapsed = now.duration_since(self.last_timer_advance); - - // Convert elapsed time to ticks (1ms per tick) - // 将经过时间转换为滴答数(每毫秒1个滴答) - let ticks_to_advance = elapsed.as_millis() as u64; - - if ticks_to_advance > 0 + // The global is never written by `block_on` in the new design (see + // entry comment), but clear it defensively in case some other path set + // it, so `try_current()` outside a runtime returns `None`. + // 新设计中 `block_on` 从不写入全局(见入口注释),但防御性地清空它, + // 以防其它路径写入,使 runtime 之外的 `try_current()` 返回 `None`。 + if let Ok(mut g) = GLOBAL_HANDLE.write() { - let _expired = global_timer().advance(ticks_to_advance); - self.last_timer_advance = now; + *g = None; } - } - - /// Flush any remaining events in the driver - /// 刷新driver中的任何剩余事件 - fn flush_events(&mut self) -> io::Result<()> - { - // Submit pending operations - // 提交挂起的操作 - let _ = self.driver.submit(); - - // Process any remaining completions without blocking - // 不阻塞地处理任何剩余的完成事件 - let _ = self.driver.wait_timeout(Duration::from_millis(0))?; - - // Process completions - // 处理完成事件 - self.process_completions(); - Ok(()) + Ok(result) } } /// Spawning handle for the runtime /// 运行时的生成句柄 /// -/// Provides access to runtime functionality from within tasks. -/// 从任务内部提供运行时功能访问。 +/// Provides access to the runtime's executor so that tasks running inside +/// `block_on` can call `spawn()` to schedule more tasks. +/// 提供对运行时执行器的访问,使运行在 `block_on` 内部的任务可调用 `spawn()` +/// 以调度更多任务。 #[derive(Clone)] pub struct Handle { - /// The scheduler handle / 调度器句柄 - scheduler_handle: SchedulerHandle, + /// The async executor, used by `spawn()` to schedule tasks. `'static` so + /// the handle can be cloned into spawned futures and stored in the + /// thread-local / global handle slots. + /// 异步执行器,`spawn()` 用它调度任务。`'static` 使句柄可被克隆进被 spawn + /// 的 future,并存于 thread-local / 全局句柄槽。 + executor: Option<&'static async_executor::Executor<'static>>, } impl Handle @@ -465,9 +418,24 @@ impl Handle /// Try to get a handle to the current runtime. Returns None if outside a runtime. /// 尝试获取当前运行时的句柄。如果在运行时外部则返回None。 + /// + /// In the single-thread-executor design, tasks spawned inside `block_on` + /// run on the same thread, so they inherit the thread-local `CURRENT_HANDLE` + /// directly. The process-global `GLOBAL_HANDLE` is a defensive fallback. + /// 在单线程执行器设计中,在 `block_on` 内 spawn 的任务运行于同一线程, + /// 故直接继承 thread-local `CURRENT_HANDLE`。进程全局 `GLOBAL_HANDLE` + /// 为防御性回退。 pub fn try_current() -> Option { - CURRENT_HANDLE.with(|h| h.borrow().clone()) + let local = CURRENT_HANDLE.with(|h| h.borrow().clone()); + if local.is_some() + { + local + } + else + { + GLOBAL_HANDLE.read().ok()?.clone() + } } /// Set the current runtime handle for this thread @@ -477,11 +445,16 @@ impl Handle CURRENT_HANDLE.with(|h| *h.borrow_mut() = handle); } - /// Get the scheduler handle - /// 获取调度器句柄 - pub fn scheduler(&self) -> &SchedulerHandle + /// Get the async executor backing this runtime, if available. + /// `spawn()` uses this to schedule tasks. Returns `None` for the fallback + /// handle created outside a runtime. + /// + /// 获取支撑本 runtime 的异步执行器(若可用)。`spawn()` 用它调度任务。 + /// 在 runtime 之外创建的回退句柄返回 `None`。 + #[must_use] + pub fn executor(&self) -> Option<&'static async_executor::Executor<'static>> { - &self.scheduler_handle + self.executor } } @@ -501,7 +474,7 @@ mod tests fn test_runtime_config_default() { let config = RuntimeConfig::default(); - assert_eq!(config.scheduler.queue_size, 256); + assert_eq!(config.queue_size, 256); assert!(config.enable_parking); assert_eq!(config.park_timeout.as_millis(), 100); } @@ -515,20 +488,24 @@ mod tests .thread_name("test-worker") .enable_parking(false); - assert_eq!(builder.config.scheduler.queue_size, 512); - assert_eq!(builder.config.scheduler.thread_name, "test-worker"); + assert_eq!(builder.config.queue_size, 512); + assert_eq!(builder.config.thread_name, "test-worker"); assert!(!builder.config.enable_parking); } #[test] fn test_runtime_builder_driver_config() { + // driver_type/io_entries are now no-ops (reactor is always async-io); + // verify park_timeout still propagates. + // driver_type/io_entries 现为 no-op(reactor 始终为 async-io); + // 验证 park_timeout 仍可传播。 + #[allow(deprecated)] let builder = RuntimeBuilder::new() - .driver_type(DriverType::Auto) + .driver_type(()) .io_entries(512) .park_timeout(Duration::from_millis(50)); - assert_eq!(builder.config.driver_io.entries, 512); assert_eq!(builder.config.park_timeout.as_millis(), 50); } @@ -654,8 +631,8 @@ mod tests .block_on(async { let h1 = crate::task::spawn(async { 1i32 }); let h2 = crate::task::spawn(async { 2i32 }); - assert_ne!(h1.id(), crate::scheduler::TaskId::UNKNOWN); - assert_ne!(h2.id(), crate::scheduler::TaskId::UNKNOWN); + assert_ne!(h1.id(), crate::task::TaskId::UNKNOWN); + assert_ne!(h2.id(), crate::task::TaskId::UNKNOWN); assert_ne!(h1.id(), h2.id()); let _ = h1.wait().await; let _ = h2.wait().await; @@ -785,8 +762,9 @@ mod tests let handle = Handle::current(); assert!(Handle::try_current().is_some()); - // Verify scheduler handle is functional - let _scheduler = handle.scheduler(); + // Verify the executor is reachable via the handle. + // 验证执行器可经由 handle 访问。 + assert!(handle.executor().is_some()); }) .unwrap(); diff --git a/crates/hiver-runtime/src/scheduler/handle.rs b/crates/hiver-runtime/src/scheduler/handle.rs deleted file mode 100644 index 8f350253..00000000 --- a/crates/hiver-runtime/src/scheduler/handle.rs +++ /dev/null @@ -1,434 +0,0 @@ -//! Scheduler handle for external task injection -//! 用于外部任务注入的调度器句柄 - -use std::{sync::Arc, task::RawWaker}; - -use super::{RawTask, TaskId, gen_task_id, queue::LocalQueue}; - -/// Wake-up notification channel -/// 唤醒通知通道 -/// -/// Uses eventfd on Linux and pipe on macOS/BSD for cross-platform support. -/// 在Linux上使用eventfd,在macOS/BSD上使用pipe以支持跨平台。 -pub(crate) struct WakeChannel -{ - /// Read file descriptor / 读文件描述符 - read_fd: std::os::fd::RawFd, - /// Write file descriptor / 写文件描述符 - write_fd: std::os::fd::RawFd, -} - -impl WakeChannel -{ - /// Create a new wake channel - /// 创建新的唤醒通道 - pub(crate) fn new() -> std::io::Result - { - #[cfg(target_os = "linux")] - { - // Use eventfd on Linux (more efficient) - // 在Linux上使用eventfd(更高效) - let fd = unsafe { libc::eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK) }; - - if fd < 0 - { - return Err(std::io::Error::last_os_error()); - } - - Ok(Self { - read_fd: fd, - write_fd: fd, - }) - } - - #[cfg(not(target_os = "linux"))] - { - // Use pipe on macOS/BSD - // 在macOS/BSD上使用pipe - let mut fds = [-1i32; 2]; - let result = unsafe { libc::pipe(fds.as_mut_ptr()) }; - - if result < 0 - { - return Err(std::io::Error::last_os_error()); - } - - // Set close-on-exec flag - // 设置close-on-exec标志 - unsafe { - libc::fcntl(fds[0], libc::F_SETFD, libc::FD_CLOEXEC); - libc::fcntl(fds[1], libc::F_SETFD, libc::FD_CLOEXEC); - - // Set non-blocking - // 设置非阻塞 - libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK); - libc::fcntl(fds[1], libc::F_SETFL, libc::O_NONBLOCK); - } - - Ok(Self { - read_fd: fds[0], - write_fd: fds[1], - }) - } - } - - /// Send a wake-up notification - /// 发送唤醒通知 - pub(crate) fn notify(&self) - { - #[cfg(target_os = "linux")] - unsafe { - // Write to eventfd - // 写入eventfd - let val: u64 = 1; - libc::write(self.write_fd, &val as *const _ as *const _, 8); - } - - #[cfg(not(target_os = "linux"))] - unsafe { - // Write to pipe (any data works) - // 写入pipe(任何数据都可以) - let val: u8 = 1; - libc::write(self.write_fd, &val as *const _ as *const _, 1); - } - } - - /// Drain all pending notifications - /// 排空所有挂起的通知 - pub(crate) fn drain(&self) - { - #[cfg(target_os = "linux")] - unsafe { - let mut val: u64 = 0; - while libc::read(self.read_fd, &mut val as *mut _ as *mut _, 8) == 8 - { - // Successfully drained a notification - // 成功排空一个通知 - } - } - - #[cfg(not(target_os = "linux"))] - unsafe { - let mut val: u8 = 0; - while libc::read(self.read_fd, &mut val as *mut _ as *mut _, 1) == 1 - { - // Successfully drained a notification - // 成功排空一个通知 - } - } - } - - /// Block until a notification arrives or timeout elapses - /// 阻塞直到收到通知或超时 - /// - /// Returns `true` if a notification was received, `false` on timeout. - /// 如果收到通知返回 `true`,超时返回 `false`。 - pub(crate) fn recv_timeout(&self, timeout: std::time::Duration) -> bool - { - let mut tv = libc::timeval { - tv_sec: timeout.as_secs() as _, - tv_usec: timeout.subsec_micros() as _, - }; - - unsafe { - let mut fdset: libc::fd_set = std::mem::zeroed(); - libc::FD_ZERO(&mut fdset); - libc::FD_SET(self.read_fd, &mut fdset); - - let n = libc::select( - self.read_fd + 1, - &mut fdset, - std::ptr::null_mut(), - std::ptr::null_mut(), - &mut tv, - ); - - if n > 0 - { - self.drain(); - true - } - else - { - false - } - } - } - - /// Get the file descriptor for epoll/kqueue registration - /// 获取用于epoll/kqueue注册的文件描述符 - #[must_use] - pub(crate) fn raw_fd(&self) -> std::os::fd::RawFd - { - self.read_fd - } -} - -impl Drop for WakeChannel -{ - fn drop(&mut self) - { - #[cfg(target_os = "linux")] - { - if self.read_fd >= 0 - { - unsafe { - libc::close(self.read_fd); - } - } - } - - #[cfg(not(target_os = "linux"))] - { - if self.read_fd >= 0 - { - unsafe { - libc::close(self.read_fd); - } - } - if self.write_fd >= 0 - { - unsafe { - libc::close(self.write_fd); - } - } - } - } -} - -/// Handle to a scheduler for external task submission -/// 调度器句柄,用于外部任务提交 -/// -/// This handle can be cloned and shared across threads. -/// 此句柄可以在线程间克隆和共享。 -#[derive(Clone)] -pub struct SchedulerHandle -{ - /// Local queue for task injection - /// 用于任务注入的本地队列 - queue: Arc, - /// Wake-up channel - /// 唤醒通道 - wake: Arc, -} - -impl SchedulerHandle -{ - /// Create a new scheduler handle - /// 创建新的调度器句柄 - pub(crate) fn new(queue: Arc, wake: Arc) -> Self - { - Self { queue, wake } - } - - /// Create a new standalone handle (for runtime use) - /// 创建新的独立句柄(用于运行时) - pub fn new_default() -> Self - { - Self { - queue: Arc::new(LocalQueue::new(256)), - wake: Arc::new(WakeChannel::new().unwrap()), - } - } - - /// Submit a task to the scheduler - /// 向调度器提交任务 - pub fn submit(&self, task: RawTask) -> std::io::Result<()> - { - if self.queue.push(task) - { - // Notify the scheduler that a new task is available - // 通知调度器有新任务可用 - self.wake.notify(); - Ok(()) - } - else - { - Err(std::io::Error::new(std::io::ErrorKind::WouldBlock, "Scheduler queue is full")) - } - } - - /// Submit a task with an associated ID - /// 提交带有关联ID的任务 - pub fn submit_with_id(&self, _task_id: TaskId, task: RawTask) -> std::io::Result<()> - { - // For now, just submit the task (ID tracking will be added later) - // 目前只提交任务(ID跟踪稍后添加) - self.submit(task) - } - - /// Get the file descriptor for wake-up events - /// 获取唤醒事件的文件描述符 - #[must_use] - pub fn wake_fd(&self) -> std::os::fd::RawFd - { - self.wake.raw_fd() - } - - /// Handle wake-up events (call after epoll/kqueue returns) - /// 处理唤醒事件(epoll/kqueue返回后调用) - pub fn handle_wake(&self) - { - self.wake.drain(); - } - - /// Generate a new task ID - /// 生成新的任务ID - #[must_use] - pub fn new_task_id(&self) -> TaskId - { - gen_task_id() - } - - /// Get a waker for this handle - /// 获取此句柄的waker - pub fn waker(&self) -> std::task::Waker - { - // Create a waker that will submit to the scheduler - // 创建将提交到调度器的waker - let handle_clone = self.clone(); - let raw_waker = RawWaker::new(Arc::into_raw(Arc::new(handle_clone)) as *const (), &VTABLE); - unsafe { std::task::Waker::from_raw(raw_waker) } - } - - /// Get a task waker by ID (placeholder for future implementation) - /// 通过ID获取任务waker(未来实现的占位符) - /// - /// # Implementation Note / 实现说明 - /// - /// This requires maintaining a registry of active tasks in the scheduler. - /// Each task would need to store its waker when it first polls, and the - /// scheduler would need to be able to retrieve it by task ID. - /// 这需要在调度器中维护一个活动任务注册表。每个任务在首次轮询时 - /// 需要存储其 waker,调度器需要能够通过任务 ID 检索它。 - /// - /// Future implementation should: - /// 未来实现应该: - /// 1. Add a `HashMap` to the scheduler state - /// 2. Store wakers when tasks are first polled - /// 3. Clean up wakers when tasks complete - pub fn get_task_waker(&self, _id: u64) -> Option - { - None - } -} - -/// VTable for scheduler handle waker -/// 调度器句柄waker的VTable -static VTABLE: std::task::RawWakerVTable = - std::task::RawWakerVTable::new(clone_waker, wake, wake_by_ref, drop_waker); - -unsafe fn clone_waker(data: *const ()) -> RawWaker -{ - let handle = Arc::from_raw(data as *const SchedulerHandle); - let ptr = Arc::into_raw(handle.clone()) as *const (); - // Keep the original `data`'s strong ref — clone must not consume it. - // Without this forget, `handle` drops and decrements the original waker's - // refcount on every clone, causing a UAF (macOS SIGSEGV / Linux tcache). - std::mem::forget(handle); - RawWaker::new(ptr, &VTABLE) -} - -unsafe fn wake(data: *const ()) -{ - let handle = Arc::from_raw(data as *const SchedulerHandle); - // Wake would submit a task - for now just notify - // Wake会提交任务 - 目前只通知 - handle.wake.notify(); -} - -unsafe fn wake_by_ref(data: *const ()) -{ - let handle = &*(data as *const SchedulerHandle); - handle.wake.notify(); -} - -unsafe fn drop_waker(data: *const ()) -{ - let _ = Arc::from_raw(data as *const SchedulerHandle); -} - -#[cfg(test)] -#[allow( - clippy::indexing_slicing, - clippy::float_cmp, - clippy::module_inception, - clippy::items_after_statements, - clippy::assertions_on_constants -)] -mod tests -{ - use super::*; - - #[test] - fn test_handle_submit() - { - let queue = Arc::new(LocalQueue::new(16)); - let wake = Arc::new(WakeChannel::new().unwrap()); - - let handle = SchedulerHandle::new(queue.clone(), wake); - - let task = 0x1000 as RawTask; - assert!(handle.submit(task).is_ok()); - - // Task should be in the queue - // 任务应该在队列中 - assert_eq!(queue.pop(), Some(task)); - } - - #[test] - fn test_wake_channel_notify_and_drain() - { - let wake = WakeChannel::new().unwrap(); - assert!(wake.raw_fd() >= 0); - - // drain on empty channel — no notification pending, recv_timeout should wait - let start = std::time::Instant::now(); - let received = wake.recv_timeout(std::time::Duration::from_millis(5)); - assert!(!received, "empty channel should not receive"); - assert!(start.elapsed() >= std::time::Duration::from_millis(3)); - - // notify then drain — drain consumes the notification - wake.notify(); - wake.drain(); - // After drain, a short recv_timeout should return false (notification consumed) - let received = wake.recv_timeout(std::time::Duration::from_millis(5)); - assert!(!received, "drained notification should not be received again"); - } - - #[test] - fn test_wake_channel_multiple_notify() - { - let wake = WakeChannel::new().unwrap(); - wake.notify(); - wake.notify(); - wake.notify(); - // Should be able to receive after multiple notifies - let received = wake.recv_timeout(std::time::Duration::from_millis(10)); - assert!(received, "should receive after notify"); - // drain remaining - wake.drain(); - } - - #[test] - fn test_recv_timeout_no_notification() - { - let wake = WakeChannel::new().unwrap(); - let start = std::time::Instant::now(); - let received = wake.recv_timeout(std::time::Duration::from_millis(10)); - assert!(!received); - // Should have waited approximately 10ms, not returned immediately - // 应该等待约10ms,而不是立即返回 - assert!(start.elapsed() >= std::time::Duration::from_millis(5)); - } - - #[test] - fn test_recv_timeout_with_notification() - { - let wake = WakeChannel::new().unwrap(); - wake.notify(); - - let received = wake.recv_timeout(std::time::Duration::from_secs(1)); - assert!(received); - } -} diff --git a/crates/hiver-runtime/src/scheduler/local.rs b/crates/hiver-runtime/src/scheduler/local.rs deleted file mode 100644 index 2c7b23d9..00000000 --- a/crates/hiver-runtime/src/scheduler/local.rs +++ /dev/null @@ -1,425 +0,0 @@ -//! Local scheduler for thread-per-core runtime -//! thread-per-core运行时的本地调度器 - -use std::{ - os::fd::RawFd, - sync::{Arc, Mutex}, - thread::{self, JoinHandle}, - time::Duration, -}; - -use super::{RawTask, handle::SchedulerHandle, queue::LocalQueue}; - -/// Configuration for the scheduler -/// 调度器配置 -#[derive(Debug, Clone)] -pub struct SchedulerConfig -{ - /// Size of the local task queue / 本地任务队列大小 - pub queue_size: usize, - /// CPU core affinity (None = no affinity) / CPU核心亲和性(None = 无亲和性) - pub cpu_affinity: Option, - /// Thread name prefix / 线程名称前缀 - pub thread_name: String, -} - -impl Default for SchedulerConfig -{ - fn default() -> Self - { - Self { - queue_size: 256, - cpu_affinity: None, - thread_name: "hiver-worker".to_string(), - } - } -} - -/// Local scheduler for a single thread -/// 单线程的本地调度器 -/// -/// Each scheduler runs on its own thread and manages its own task queue. -/// Each scheduler follows the thread-per-core model with no work stealing. -/// -/// 每个调度器在自己的线程上运行并管理自己的任务队列。 -/// 每个调度器遵循 thread-per-core 模型,没有工作窃取。 -pub struct Scheduler -{ - /// Local task queue / 本地任务队列 - queue: Arc, - /// External queue for task injection / 用于任务注入的外部队列 - inject_queue: Arc, - /// Wake channel for external notifications / 外部通知的唤醒通道 - wake: Arc, - /// Scheduler state / 调度器状态 - state: Arc, - /// Join handle for the worker thread / 工作线程的join句柄 - thread_handle: Option>, - /// Task waker storage (task_id -> waker) / 任务waker存储(task_id -> waker) - task_wakers: Arc>>, -} - -// Scheduler state values -const STATE_RUNNING: u8 = 0; -const STATE_SHUTTING_DOWN: u8 = 1; -const STATE_STOPPED: u8 = 2; - -impl Scheduler -{ - /// Create a new scheduler with default configuration - /// 使用默认配置创建新调度器 - /// - /// # Errors / 错误 - /// - /// Returns an error if the wake channel cannot be created. - /// 如果无法创建唤醒通道则返回错误。 - pub fn new() -> std::io::Result - { - Self::with_config(&SchedulerConfig::default()) - } - - /// Create a new scheduler with the specified configuration - /// 使用指定配置创建新调度器 - /// - /// # Errors / 错误 - /// - /// Returns an error if: - /// 返回错误如果: - /// - Configuration is invalid / 配置无效 - /// - Wake channel creation fails / 唤醒通道创建失败 - pub fn with_config(config: &SchedulerConfig) -> std::io::Result - { - let queue = Arc::new(LocalQueue::new(config.queue_size)); - let inject_queue = Arc::new(LocalQueue::new(config.queue_size)); - let wake = Arc::new(super::handle::WakeChannel::new()?); - let task_wakers = Arc::new(Mutex::new(std::collections::HashMap::new())); - - let state = Arc::new(std::sync::atomic::AtomicU8::new(STATE_RUNNING)); - - // Clone for thread closure - // 为线程闭包克隆 - let queue_clone = queue.clone(); - let inject_queue_clone = inject_queue.clone(); - let wake_clone = wake.clone(); - let state_clone = state.clone(); - let thread_name = config.thread_name.clone(); - let cpu_affinity = config.cpu_affinity; - - // Spawn the worker thread - // 生成工作线程 - let thread_handle = thread::Builder::new().name(thread_name).spawn(move || { - // Set CPU affinity if specified - // 如果指定了,设置CPU亲和性 - if let Some(core) = cpu_affinity - { - Self::set_cpu_affinity(core); - } - - // Run the scheduler loop - // 运行调度器循环 - Self::run_scheduler(&queue_clone, &inject_queue_clone, &wake_clone, &state_clone); - })?; - - Ok(Self { - queue, - inject_queue, - wake, - state, - thread_handle: Some(thread_handle), - task_wakers, - }) - } - - /// Create a new scheduler with the specified configuration and driver - /// 使用指定配置和driver创建新调度器 - /// - /// # Errors / 错误 - /// - /// Returns an error if: - /// 返回错误如果: - /// - Configuration is invalid / 配置无效 - /// - Wake channel creation fails / 唤醒通道创建失败 - pub fn with_config_and_driver( - config: &SchedulerConfig, - _driver: Arc, - ) -> std::io::Result - { - let queue = Arc::new(LocalQueue::new(config.queue_size)); - let inject_queue = Arc::new(LocalQueue::new(config.queue_size)); - let wake = Arc::new(super::handle::WakeChannel::new()?); - let task_wakers = Arc::new(Mutex::new(std::collections::HashMap::new())); - - let state = Arc::new(std::sync::atomic::AtomicU8::new(STATE_RUNNING)); - - // Clone for thread closure - // 为线程闭包克隆 - let queue_clone = queue.clone(); - let inject_queue_clone = inject_queue.clone(); - let wake_clone = wake.clone(); - let state_clone = state.clone(); - let thread_name = config.thread_name.clone(); - let cpu_affinity = config.cpu_affinity; - - // Spawn the worker thread - // 生成工作线程 - let thread_handle = thread::Builder::new().name(thread_name).spawn(move || { - // Set CPU affinity if specified - // 如果指定了,设置CPU亲和性 - if let Some(core) = cpu_affinity - { - Self::set_cpu_affinity(core); - } - - // Run the scheduler loop with driver - // 运行带driver的调度器循环 - // Driver is stored by Runtime and used in its block_on event loop. - // Scheduler worker handles task polling; Runtime handles I/O events - // and wakes tasks via waker → re-enqueue → wake channel notification. - // Driver由Runtime持有并在block_on事件循环中使用。 - // Scheduler worker负责任务轮询;Runtime处理I/O事件, - // 通过waker → 重新入队 → wake通道通知来唤醒任务。 - Self::run_scheduler(&queue_clone, &inject_queue_clone, &wake_clone, &state_clone); - })?; - - Ok(Self { - queue, - inject_queue, - wake, - state, - thread_handle: Some(thread_handle), - task_wakers, - }) - } - - /// Get a handle to this scheduler for external task submission - /// 获取此调度器的句柄用于外部任务提交 - #[must_use] - pub fn handle(&self) -> SchedulerHandle - { - SchedulerHandle::new(self.inject_queue.clone(), self.wake.clone()) - } - - /// Request the scheduler to shut down gracefully - /// 请求调度器优雅关闭 - pub fn shutdown(&self) - { - self.state - .store(STATE_SHUTTING_DOWN, std::sync::atomic::Ordering::Release); - // Notify the scheduler to wake up and check state - // 通知调度器唤醒并检查状态 - self.wake.notify(); - } - - /// Wait for the scheduler to stop - /// 等待调度器停止 - /// - /// # Panics / 恐慌 - /// - /// Panics if the scheduler thread has already been joined. - /// 如果调度器线程已被加入则恐慌。 - pub fn join(&mut self) -> thread::Result<()> - { - if let Some(handle) = self.thread_handle.take() - { - handle.join() - } - else - { - Ok(()) - } - } - - /// Main scheduler loop - /// 主调度器循环 - fn run_scheduler( - local_queue: &LocalQueue, - inject_queue: &LocalQueue, - wake: &super::handle::WakeChannel, - state: &std::sync::atomic::AtomicU8, - ) - { - while state.load(std::sync::atomic::Ordering::Relaxed) == STATE_RUNNING - { - // Try to get a task from local queue first - // 首先尝试从本地队列获取任务 - let task = local_queue.pop().or_else(|| { - // Try inject queue (external submissions) - // 尝试注入队列(外部提交) - inject_queue.pop() - }); - - if let Some(task) = task - { - // Execute the task by polling its future via the vtable - // 通过vtable轮询其future来执行任务 - let completed = unsafe { crate::task::raw_task::poll_raw_task(task) }; - if completed - { - // Task finished, consume queue ref - unsafe { - crate::task::raw_task::deallocate_completed_task(task); - } - } - // If Pending: waker holds the ref and will re-enqueue when ready - // 如果Pending:waker持有引用,就绪时会重新入队 - } - else - { - // No tasks available, block on wake channel with timeout - // 没有可用任务,带超时阻塞在唤醒通道上 - wake.recv_timeout(Duration::from_millis(10)); - } - } - - state.store(STATE_STOPPED, std::sync::atomic::Ordering::Release); - } - - /// Set CPU affinity for the current thread - /// 为当前线程设置CPU亲和性 - #[cfg(target_os = "linux")] - fn set_cpu_affinity(core: usize) - { - unsafe { - let mut cpu_set: libc::cpu_set_t = std::mem::zeroed(); - libc::CPU_ZERO(&mut cpu_set); - libc::CPU_SET(core % libc::CPU_SETSIZE as usize, &mut cpu_set); - - let _ = libc::sched_setaffinity(0, size_of::(), &cpu_set); - } - } - - #[cfg(not(target_os = "linux"))] - fn set_cpu_affinity(_core: usize) - { - // CPU affinity is only supported on Linux - // CPU亲和性仅在Linux上支持 - } - - /// Submit a task to this scheduler - /// 向此调度器提交任务 - pub fn submit(&self, task: RawTask) -> Result<(), RawTask> - { - if self.queue.push(task) - { - self.wake.notify(); - Ok(()) - } - else - { - Err(task) - } - } - - /// Get the wake file descriptor for epoll registration - /// 获取用于epoll注册的唤醒文件描述符 - #[must_use] - pub fn wake_fd(&self) -> RawFd - { - self.wake.raw_fd() - } - - /// Get a task waker by ID - /// 通过ID获取任务waker - pub fn get_task_waker(&self, id: u64) -> Option - { - let wakers = self.task_wakers.lock().unwrap(); - wakers.get(&id).cloned() - } - - /// Register a task waker - /// 注册任务waker - pub fn register_task_waker(&self, id: u64, waker: std::task::Waker) - { - let mut wakers = self.task_wakers.lock().unwrap(); - wakers.insert(id, waker); - } - - /// Remove a task waker - /// 移除任务waker - pub fn remove_task_waker(&self, id: u64) -> Option - { - let mut wakers = self.task_wakers.lock().unwrap(); - wakers.remove(&id) - } -} - -impl Drop for Scheduler -{ - fn drop(&mut self) - { - // Ensure scheduler is stopped - // 确保调度器已停止 - self.shutdown(); - let _ = self.join(); - } -} - -#[cfg(test)] -#[allow( - clippy::indexing_slicing, - clippy::float_cmp, - clippy::module_inception, - clippy::items_after_statements, - clippy::assertions_on_constants -)] -mod tests -{ - use super::*; - - #[test] - fn test_scheduler_creation() - { - let scheduler = Scheduler::new(); - assert!(scheduler.is_ok()); - - let scheduler = scheduler.unwrap(); - let handle = scheduler.handle(); - assert!(handle.submit(0x1000 as RawTask).is_ok()); - } - - #[test] - fn test_scheduler_config() - { - let config = SchedulerConfig { - queue_size: 512, - cpu_affinity: Some(0), - thread_name: "test-worker".to_string(), - }; - - let scheduler = Scheduler::with_config(&config); - assert!(scheduler.is_ok()); - } - - #[test] - fn test_scheduler_submit_and_handle() - { - let scheduler = Scheduler::new().unwrap(); - let handle = scheduler.handle(); - - // Submit multiple tasks - assert!(handle.submit(0x1000 as RawTask).is_ok()); - assert!(handle.submit(0x2000 as RawTask).is_ok()); - - // Wake fd should be a valid file descriptor - assert!(handle.wake_fd() >= 0); - } - - #[test] - fn test_scheduler_waker_store_empty() - { - let scheduler = Scheduler::new().unwrap(); - - // Non-existent waker should return None - assert!(scheduler.get_task_waker(9999).is_none()); - - // Removing non-existent waker should return None - assert!(scheduler.remove_task_waker(9999).is_none()); - } - - #[test] - fn test_scheduler_shutdown() - { - let scheduler = Scheduler::new().unwrap(); - scheduler.shutdown(); - } -} diff --git a/crates/hiver-runtime/src/scheduler/mod.rs b/crates/hiver-runtime/src/scheduler/mod.rs deleted file mode 100644 index 2f45a335..00000000 --- a/crates/hiver-runtime/src/scheduler/mod.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Task scheduler module -//! 任务调度器模块 -//! -//! This module provides the thread-per-core task scheduler -//! and work-stealing scheduler implementations. -//! 本模块提供 thread-per-core 任务调度器和工作窃取调度器实现。 - -pub mod handle; -pub mod local; -pub mod queue; -pub mod work_stealing; - -use std::{future::Future, pin::Pin}; - -pub use handle::SchedulerHandle; -pub use local::{Scheduler, SchedulerConfig}; -pub use queue::LocalQueue; -pub use work_stealing::{WorkStealingConfig, WorkStealingHandle, WorkStealingScheduler}; - -/// A pinned, boxed future -/// 固定位置的盒子未来 -pub type BoxFuture<'a, T> = Pin + Send + 'a>>; - -/// Task ID type — a newtype around `u64` so it cannot be confused with -/// other `u64` values (timestamps, counters) at call sites. -/// 任务ID类型 —— `u64` 的 newtype,避免在调用点与其他 `u64` 值 -/// (时间戳、计数器)混淆。 -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct TaskId(u64); - -impl TaskId -{ - /// Sentinel for "no task" — returned when a task has no assigned core. - /// "无任务"哨兵 —— 当任务无分配 core 时返回。 - pub const UNKNOWN: TaskId = TaskId(0); - - /// Create a TaskId from a raw u64 (internal use only). - /// 从原始 u64 创建 TaskId(仅供内部使用)。 - #[must_use] - pub const fn from_u64(id: u64) -> Self - { - Self(id) - } - - /// Get the raw u64 value. - /// 获取原始 u64 值。 - #[must_use] - pub const fn as_u64(self) -> u64 - { - self.0 - } -} - -impl std::fmt::Display for TaskId -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - write!(f, "TaskId({})", self.0) - } -} - -/// Generate a new unique task ID -/// 生成新的唯一任务ID -#[must_use] -pub fn gen_task_id() -> TaskId -{ - use std::sync::atomic::{AtomicU64, Ordering}; - static COUNTER: AtomicU64 = AtomicU64::new(1); - TaskId(COUNTER.fetch_add(1, Ordering::Relaxed)) -} - -/// Raw task pointer for wake-up notifications -/// 用于唤醒通知的原始任务指针 -pub type RawTask = *const (); diff --git a/crates/hiver-runtime/src/scheduler/queue.rs b/crates/hiver-runtime/src/scheduler/queue.rs deleted file mode 100644 index 4bf66454..00000000 --- a/crates/hiver-runtime/src/scheduler/queue.rs +++ /dev/null @@ -1,319 +0,0 @@ -//! Local task queue for thread-per-core scheduler -//! thread-per-core调度器的本地任务队列 - -use std::{ - cell::UnsafeCell, - mem::MaybeUninit, - sync::atomic::{AtomicUsize, Ordering}, -}; - -use super::RawTask; - -/// Local queue for thread-per-core scheduler -/// thread-per-core调度器的本地队列 -/// -/// Uses a bounded ring buffer optimized for single consumer (the scheduler thread) -/// with support for external producers (work stealing injectors). -/// Uses interior mutability for thread-safe operations. -/// -/// 使用为单个消费者(调度器线程)优化的有界环形缓冲区, -/// 支持外部生产者(工作窃取注入器)。 -/// 使用内部可变性实现线程安全操作。 -pub struct LocalQueue -{ - /// Ring buffer for task pointers / 任务指针的环形缓冲区 - buffer: Box<[UnsafeCell>]>, - /// Queue capacity (must be power of 2) / 队列容量(必须是2的幂) - capacity: usize, - /// Capacity mask for fast modulo / 快速取模的容量掩码 - mask: usize, - /// Head index (consumer) / 头索引(消费者) - head: AtomicUsize, - /// Tail index (producer) / 尾索引(生产者) - tail: AtomicUsize, -} - -// Safety: The queue uses atomic operations for thread safety -// and UnsafeCell for interior mutability -// 队列使用原子操作和UnsafeCell实现线程安全 -unsafe impl Send for LocalQueue {} -unsafe impl Sync for LocalQueue {} - -impl LocalQueue -{ - /// Create a new local queue with the specified capacity - /// 创建具有指定容量的新本地队列 - /// - /// The capacity will be rounded up to the next power of 2. - /// 容量将向上舍入到下一个2的幂。 - #[must_use] - pub fn new(capacity: usize) -> Self - { - let capacity = capacity.next_power_of_two().max(2); - let mask = capacity - 1; - - // Initialize buffer with MaybeUninit (more efficient than Vec