# Contributing to giong
Thanks for taking an interest. This file explains how to build and test giong, what a
change has to satisfy before it is merged, and how to write commits.
## Your first contribution
New here? Welcome. A good way in:
1. Pick an issue labelled
[`good first issue`](https://github.com/giong-run/Giong/labels/good%20first%20issue)
or [`help wanted`](https://github.com/giong-run/Giong/labels/help%20wanted), or fix a
bug you found yourself.
2. Comment on the issue to say you are working on it, so nobody duplicates the work. A
maintainer will assign it to you. If you stop, say so; that is fine.
3. Set up as described below, reproduce the problem with a test, then fix it.
4. Open a pull request early, as a draft if it is not finished. Questions in a draft are
welcome and are the fastest way to get help.
Pull requests from forks are the normal route. CI on a first-time contributor's pull request
starts once a maintainer approves the run, so do not worry if the checks wait at first.
Labels you will see: `bug`, `enhancement`, `documentation`, `good first issue`,
`help wanted`, `confirmed` (a maintainer reproduced it) and the `area: …` labels, which
name the part of the code involved (`driver`, `fs`, `net`, `http`, `tls`).
## Before you start
- For anything larger than a small fix, **[open an issue](https://github.com/giong-run/Giong/issues) first** and describe
what you want to change. It avoids duplicated work and patches that do not fit the design.
- Check [ROADMAP.md](ROADMAP.md), including its non-goals.
- Bugs: include the kernel version (`uname -r`), the Rust version, and the smallest
program that shows the problem.
## Setting up
You need **Linux with kernel 6.0 or newer** and a stable Rust toolchain. giong does not
build on Windows or macOS; use WSL2 with a 6.x kernel, a virtual machine, or a container
started with `--security-opt seccomp=unconfined` (Docker's default profile blocks io_uring).
```bash
cargo build --workspace --all-features
cargo test --workspace --all-features
cargo run --release --example http
```
The repository is a Cargo workspace of three crates: `giong` (the runtime, at the root),
`giong-http` and `giong-tls`. The servers under `bench/` are separate workspaces.
## Checks every change must pass
Continuous integration runs these, and so should you:
```bash
cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps
scripts/check.sh test # the whole suite in release mode, five times over
scripts/check.sh fallback # the same on the io_uring setups of kernels older than 6.1
```
If you touched `unsafe` code, buffer handling, the driver or anything that crosses
threads, also run:
```bash
scripts/check.sh tsan # ThreadSanitizer; needs nightly with the rust-src component
scripts/valgrind.sh # needs valgrind; takes a few minutes
```
## Rules for the code
### Tests
- A bug fix comes with a test that fails without the fix.
- A new feature comes with tests for its normal path **and** for what happens when it is
interrupted (see cancellation below).
- Tests must not depend on tight timing. Use generous upper bounds; CI machines are slow.
### `unsafe`
giong talks to the kernel, so some `unsafe` is unavoidable. Keep it small and justified.
- Every `unsafe` block has a `// SAFETY:` comment stating why it is sound.
- Prefer a safe design over a faster unsafe one unless a benchmark shows the difference
matters.
- New `unsafe` code needs a test that would catch its misuse, and a clean valgrind run.
### Cancellation safety
This is the rule that is easiest to break. io_uring keeps using the memory an operation
points at until the kernel reports completion, whatever happens to the Rust future.
- Any new operation must keep its buffers alive until its final completion arrives, even
if its future is dropped first. Use `Op`, which already does this, rather than
submitting entries by hand.
- State in the documentation of every new `async fn` whether it is cancel safe, meaning
whether dropping it can lose data.
- Add a test that drops the operation while it is in flight and then checks
`giong::stats()` for leaked operations and buffers. `tests/cancel.rs` has examples.
### Performance claims
- A change described as faster needs numbers: before and after, from `bench/run.sh` or a
benchmark you add, with the machine and kernel stated.
- Report the median of several runs and the spread, not the best run.
- Say what you did not measure.
### Dependencies and API
- New dependencies need a reason. The runtime depends on three crates; `giong-tls` adds
`rustls`, and `giong-http` adds nothing.
- Public items need documentation. Breaking changes to the public API need an issue first
and an entry in the changelog.
## Commits
Commits follow [Conventional Commits](https://www.conventionalcommits.org/):
```
<type>(<scope>): <summary in the imperative, lower case, no full stop>
<why the change is needed and what it does, wrapped at 72 columns>
<footers, e.g. "Fixes #12" or "BREAKING CHANGE: ...">
```
**Types:** `feat`, `fix`, `perf`, `refactor`, `test`, `docs`, `bench`, `ci`, `chore`.
**Scopes:** `runtime`, `executor`, `driver`, `net`, `fs`, `time`, `sync`, `buf`,
`http`, `tls`, `examples`, `bench`. Leave the scope out when a change spans several.
Examples:
```
fix(driver): cancel a paused multishot receive when its stream is dropped
feat(net): add UdpSocket
perf(time): skip the timer wheel when no timer is armed
docs: explain how to run under WSL2
```
- One logical change per commit. Formatting-only changes go in their own commit.
- A breaking change is marked with `!` after the type or scope (`feat(net)!: ...`) and a
`BREAKING CHANGE:` footer.
- The body explains *why*. The diff already shows *what*.
## Pull requests
1. Branch from `main`. Name the branch `<type>/<short-description>`, for example
`fix/recv-pause-on-drop`.
2. Keep the pull request focused; several small ones are reviewed faster than one large one.
3. Add a line under `## [Unreleased]` in [CHANGELOG.md](CHANGELOG.md) for anything a user
would notice.
4. Fill in the pull request template. Say how you tested the change.
5. All checks must be green. A maintainer reviews and merges; pull requests are
squash-merged, so the pull request title must itself be a valid commit summary.
## Releases
Maintainers cut releases: move the `Unreleased` entries under a new version heading with
the date, bump `version` in the three `Cargo.toml` files (the crates share one version
number, and `giong-http` and `giong-tls` name the `giong` version they need), and merge
that change. Then tag the merge commit and push the tag:
```bash
git tag -a vX.Y.Z -m "giong X.Y.Z"
git push origin vX.Y.Z
```
The [release workflow](.github/workflows/release.yml) checks that the tag matches the
crate versions and that `CHANGELOG.md` has a section for it, publishes the three crates
to crates.io through Trusted Publishing, and creates the GitHub Release from that
section. A tag with a suffix such as `v0.3.0-rc.1` becomes a pre-release. If a run fails
part way, fix the cause and re-run it: crates already published are skipped.
## Security
Please do not open a public issue for a vulnerability such as a memory-safety bug that
can be triggered remotely. Report it privately at
<https://github.com/giong-run/Giong/security/advisories/new> so it can be fixed before it is disclosed.
## Conduct
Everyone taking part is expected to follow the [code of conduct](CODE_OF_CONDUCT.md). In
short: be respectful, assume good faith, and criticise code, not people. Report problems to
conduct@giong.run.
## Licensing of contributions
giong is dual-licensed under MIT and Apache-2.0. Unless you state otherwise, any
contribution you submit is licensed under the same terms, without additional conditions.