Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4a89c19
fix: Make safety infrastructure coroutine-compliant and fix example e…
casibbald Jul 10, 2025
ba035a8
fix: Added spawn_safe method to SafeBuilder
casibbald Jul 10, 2025
1a40cbc
fix: update rust to nightly
casibbald Jul 10, 2025
db0567a
fix: platform-specific test skipping due to OS-level I/O handling dif…
casibbald Jul 10, 2025
07cb45a
fix: correct for platform independence
casibbald Jul 10, 2025
eaca046
fix: fmt
casibbald Jul 10, 2025
54dff3f
fix: Windows-specific version of the test (test_rwlock_write_canceled…
casibbald Jul 10, 2025
ee88116
feat: additional tests
casibbald Jul 10, 2025
87619b6
feat: Complete Task 1.1 Safe Coroutine Spawning with comprehensive te…
casibbald Jul 10, 2025
8c49f39
feat: Add comprehensive test coverage for UDP, io/mod, and crossbeam_…
casibbald Jul 10, 2025
2e6974d
Add flagship One Billion Row Challenge example
casibbald Jul 10, 2025
72d64e8
Backup ultra-optimized 1BRC version
casibbald Jul 10, 2025
1abd51e
Ultra-optimize 1BRC for 4-second target
casibbald Jul 10, 2025
ba87ad2
Configs for configuration test isolation issues that were causing CI …
casibbald Jul 11, 2025
5a591f8
tooling
casibbald Jul 11, 2025
c516b01
Updates for windows compatibility
casibbald Jul 11, 2025
9b58806
More windows fixes
casibbald Jul 11, 2025
b3f5788
More windows fixes
casibbald Jul 11, 2025
d13d931
Integration tests
casibbald Jul 11, 2025
8a2ace2
chore: address fmt issues
casibbald Dec 5, 2025
232329c
chore: address fmt issues
casibbald Dec 5, 2025
de818f0
fix: resolve all clippy warnings in examples and tests
casibbald Dec 5, 2025
bec672f
chore: address fmt issues
casibbald Dec 5, 2025
0fdeecb
fix: remove 12 production unwrap() calls for JSF compliance
casibbald Dec 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 60 additions & 7 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,74 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Cache cargo registry
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: cargo fmt --check
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Run cargo clippy
- name: Run cargo clippy (strict)
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
- name: Run cargo clippy no default features
args: --all-targets --all-features -- -D warnings -D clippy::correctness -D clippy::suspicious -D clippy::complexity -D clippy::perf -D clippy::style
- name: Run cargo clippy no default features (strict)
uses: actions-rs/cargo@v1
with:
command: clippy
args: --no-default-features -- -D warnings
args: --no-default-features --all-targets -- -D warnings -D clippy::correctness -D clippy::suspicious -D clippy::complexity -D clippy::perf -D clippy::style
- name: Run cargo clippy with selected pedantic lints
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets --all-features -- -D warnings -D clippy::cast_lossless -D clippy::redundant_closure_for_method_calls -D clippy::uninlined_format_args -D clippy::manual_is_multiple_of -D clippy::needless_continue -D clippy::needless_for_each

security:
name: Security audit and additional checks
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
- name: Cache cargo registry
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-security-${{ hashFiles('**/Cargo.lock') }}
- name: Run cargo check with strict flags
uses: actions-rs/cargo@v1
with:
command: check
args: --all-targets --all-features
- name: Check documentation
uses: actions-rs/cargo@v1
with:
command: doc
args: --all-features --no-deps

test:
name: Test ${{ matrix.rust }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
Expand All @@ -55,13 +100,21 @@ jobs:
- macOS-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install Rust (${{ matrix.rust }})
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
- name: Cache cargo registry
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-${{ matrix.rust }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
/target/
.vscode/
Cargo.lock
~/
71 changes: 71 additions & 0 deletions .nextest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# .nextest.toml
# Nextest configuration for May coroutine library

[profile.default]
# Don't fail fast - we want to see all test results
fail-fast = false

# Show slow tests (>60s) - important for coroutine timing tests
status-level = "slow"

# Retry flaky tests once - coroutines can have timing issues
retries = 1

# Run tests with reduced parallelism for coroutine stability
test-threads = 4

# Timeout for individual tests (important for coroutine tests that might hang)
slow-timeout = { period = "120s", terminate-after = 2 }

# Global timeout for the entire test run
final-status-level = "slow"

[profile.default.junit]
# Generate JUnit XML for CI/CD integration
path = "target/nextest/junit.xml"

# Test groups for different types of tests
[[profile.default.overrides]]
filter = 'test(safe_spawn)'
test-threads = 2
slow-timeout = { period = "30s" }

[[profile.default.overrides]]
filter = 'test(integration)'
test-threads = 1
slow-timeout = { period = "60s" }

[[profile.default.overrides]]
filter = 'test(bench)'
test-threads = 1
retries = 0
slow-timeout = { period = "180s" }

# Coroutine-specific test overrides
[[profile.default.overrides]]
filter = 'test(coroutine) or test(spawn) or test(yield)'
test-threads = 2
slow-timeout = { period = "45s" }

# I/O tests need more time and less concurrency
[[profile.default.overrides]]
filter = 'test(io) or test(net) or test(tcp) or test(udp)'
test-threads = 1
slow-timeout = { period = "90s" }

# Sync primitive tests
[[profile.default.overrides]]
filter = 'test(mutex) or test(rwlock) or test(channel) or test(mpsc) or test(mpmc)'
test-threads = 2
slow-timeout = { period = "30s" }

# Coverage profile for code coverage analysis
[profile.coverage]
# Minimal parallelism for stable coverage collection
test-threads = 1
retries = 0
fail-fast = true
slow-timeout = { period = "180s", terminate-after = 1 }

[profile.coverage.junit]
path = "target/nextest/coverage-junit.xml"
16 changes: 16 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ tempfile = "3"
native-tls = "0.2"
tungstenite = "0.28"
serde_derive = "1.0"
rand = "0.8"
serde_json = "1.0"
csv = "1.3"
# High-performance 1BRC libraries
memmap2 = "0.9" # Memory mapping for zero-copy file access
rayon = "1.8" # Data parallelism
ahash = "0.8" # Fastest hash function
bstr = "1.9" # Fast byte string operations
memchr = "2.7" # SIMD-accelerated string searching
simdutf8 = "0.1" # SIMD UTF-8 validation
lexical = "6.1" # Fastest number parsing
rustc-hash = "1.1" # Fast hash map
fxhash = "0.2" # Another fast hash function
itoa = "1.0" # Fast integer to string
ryu = "1.0" # Fast float to string
num_cpus = "1.16" # CPU core detection

[features]
default = ["io_cancel", "io_timeout", "work_steal"]
Expand Down
131 changes: 115 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ May is a high-performance library for programming stackful coroutines with which
----------

## Features
* **Safe coroutine spawning** with compile-time and runtime safety guarantees (no unsafe blocks required);
* The stackful coroutine implementation is based on [generator][generator];
* Support schedule on a configurable number of threads for multi-core systems;
* Support coroutine version of a local storage ([CLS][cls]);
Expand All @@ -39,6 +40,7 @@ May is a high-performance library for programming stackful coroutines with which
* Support graceful panic handling that will not affect other coroutines;
* Support scoped coroutine creation;
* Support general selection for all the coroutine API;
* **Comprehensive safety infrastructure** with TLS safety and stack overflow protection;
* All the coroutine API are compatible with the standard library semantics;
* All the coroutine API can be safely called in multi-threaded context;
* Both stable, beta, and nightly channels are supported;
Expand All @@ -47,7 +49,61 @@ May is a high-performance library for programming stackful coroutines with which
----------

## Usage
A naive echo server implemented with May:

### Safe Coroutine Spawning (Recommended)
The new safe API eliminates the need for unsafe blocks and provides comprehensive safety guarantees:

```rust
use may::coroutine::{spawn_safe, SafeBuilder, SafetyLevel};
use may::net::TcpListener;
use std::io::{Read, Write};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind("127.0.0.1:8000")?;

while let Ok((mut stream, _)) = listener.accept() {
// Safe coroutine spawning - no unsafe blocks required!
spawn_safe(move || -> Result<(), std::io::Error> {
let mut buf = vec![0; 1024 * 16];
while let Ok(n) = stream.read(&mut buf) {
if n == 0 {
break;
}
stream.write_all(&buf[0..n])?;
}
Ok(())
})?;
}
Ok(())
}
```

### Advanced Configuration
For fine-tuned control over safety and performance:

```rust
use may::coroutine::{SafeBuilder, SafetyLevel};

fn main() -> Result<(), Box<dyn std::error::Error>> {
// Configure coroutine with specific safety level
let handle = SafeBuilder::new()
.safety_level(SafetyLevel::Strict)
.stack_size(1024 * 1024)
.name("worker-coroutine")
.spawn_safe(|| {
println!("Safe coroutine with custom configuration!");
42
})?;

let result = handle.join()?;
println!("Result: {}", result);
Ok(())
}
```

### Traditional API (Still Supported)
The traditional `go!` macro is still available for backward compatibility:

```rust
#[macro_use]
extern crate may;
Expand All @@ -59,7 +115,7 @@ fn main() {
let listener = TcpListener::bind("127.0.0.1:8000").unwrap();
while let Ok((mut stream, _)) = listener.accept() {
go!(move || {
let mut buf = vec![0; 1024 * 16]; // alloc in heap!
let mut buf = vec![0; 1024 * 16];
while let Ok(n) = stream.read(&mut buf) {
if n == 0 {
break;
Expand All @@ -69,7 +125,6 @@ fn main() {
});
}
}

```

----------
Expand All @@ -87,6 +142,9 @@ fn main() {
* [A simple HTTPS][https_sever]
* [WebSockets][websocket]

### Safety Examples
* [Safe coroutine spawning][safe_spawn] - Demonstrates the new safe APIs


----------

Expand All @@ -95,25 +153,65 @@ You can refer to https://tfb-status.techempower.com/ to get the latest [may_mini

----------

## Safety Features

May now includes comprehensive safety infrastructure to help you write safer coroutine code:

### Safety Levels
- **Strict**: Maximum safety with runtime validation and TLS monitoring
- **Balanced**: Good safety with minimal performance overhead (recommended)
- **Permissive**: Basic safety for performance-critical code
- **Development**: Enhanced debugging and validation for development

### Automatic Safety Checks
```rust
use may::coroutine::{spawn_safe, SafetyLevel, SafeBuilder};

// The safe API automatically handles:
// - TLS access validation
// - Stack overflow detection
// - Blocking operation monitoring
// - Configuration validation

let handle = SafeBuilder::new()
.safety_level(SafetyLevel::Strict)
.spawn_safe(|| {
// Your code here is automatically monitored for safety violations
println!("Safe coroutine execution!");
42
})?;
```

### Safety Violation Handling
The safety system provides detailed error reporting:
```rust
use may::safety::SafetyViolation;

match spawn_safe(|| { /* your code */ }) {
Ok(handle) => { /* success */ }
Err(SafetyViolation::TlsAccess { description, .. }) => {
eprintln!("TLS safety violation: {}", description);
}
Err(SafetyViolation::StackOverflow { current_usage, max_size, .. }) => {
eprintln!("Stack overflow risk: {}/{} bytes", current_usage, max_size);
}
// ... other safety violations
}
```

## Caveat
There is a detailed [document][caveat] that describes May's main restrictions. In general, there are four things you should follow when writing programs that use coroutines:
* Don't call thread-blocking API (It will hurt the performance);
* Carefully use Thread Local Storage (access TLS in coroutine might trigger undefined behavior).
There is a detailed [document][caveat] that describes May's main restrictions. With the new safe APIs, many of these concerns are automatically handled:

> It's considered **unsafe** with the following pattern:
> ```rust
> set_tls();
> // Or another coroutine API that would cause scheduling:
> coroutine::yield_now();
> use_tls();
> ```
> but it's **safe** if your code is not sensitive about the previous state of TLS. Or there is no coroutines scheduling between **set** TLS and **use** TLS.
### Traditional Concerns (Automatically Handled by Safe APIs)
* ✅ **TLS Safety**: The safe API automatically detects and prevents unsafe TLS access patterns
* ✅ **Stack Overflow**: Runtime monitoring helps detect potential stack overflow conditions

### Still Important to Consider
* Don't call thread-blocking API (It will hurt the performance);
* Don't run CPU bound tasks for long time, but it's ok if you don't care about fairness;
* Don't exceed the coroutine stack. There is a guard page for each coroutine stack. When stack overflow occurs, it will trigger segment fault error.

**Note:**
> The first three rules are common when using cooperative asynchronous libraries in Rust. Even using a futures-based system also have these limitations. So what you should really focus on is a coroutine stack size, make sure it's big enough for your applications.
> When using the new `spawn_safe` API with appropriate safety levels, most traditional coroutine safety concerns are automatically monitored and reported. For maximum safety, use `SafetyLevel::Strict` during development and testing.

----------

Expand Down Expand Up @@ -143,3 +241,4 @@ May is licensed under either of the following, at your option:
[caveat]:docs/may_caveat.md
[stack]:docs/tune_stack_size.md
[may_minihttp]:https://github.com/Xudong-Huang/may_minihttp
[safe_spawn]:examples/safe_spawn.rs
Loading
Loading