Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions .github/SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Security Policy

## Reporting a Vulnerability

Use GitHub's Security Advisories feature to report security concerns
privately.

Expect a response within 7 days. If the issue is confirmed, a fix
will be released as a patch version.

## Scope

seqlock is a small sequence lock library with no network stack, no
external dependencies, and no dynamic memory allocation. The primary
attack surface is race conditions in the single-writer/multi-reader
contract and integer overflow in sequence counter calculations.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

# Object files
*.o
*.ko
*.obj
*.elf

Expand Down Expand Up @@ -34,7 +33,6 @@
*.app
*.i*86
*.x86_64
*.hex

# Debug files
*.dSYM/
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Run TSAN on an x86-64 host (or a 4K-page AArch64). TSAN cannot initialise on a
### CI / source of truth

- CI definitions live in `.github/workflows/ci.yml`.
- A dedicated `tsan` job runs the thread sanitizer; ASAN and TSAN are mutually
- A dedicated `tsan` job runs the thread sanitiser; ASAN and TSAN are mutually
exclusive, so it is separate from the ASAN/UBSan matrix.
- Prefer running the same commands locally as CI runs.
- If `pre-commit` is configured later, run it before committing.
Expand Down Expand Up @@ -69,7 +69,7 @@ Run TSAN on an x86-64 host (or a 4K-page AArch64). TSAN cannot initialise on a
- Match the conventions in the existing files.
- All public identifiers use the `seqlock_` / `SEQLOCK_` prefix.
- Keep public headers minimal and stable; do not expose internal helpers.
- Prefer explicit fixed-width integer types when ABI or serialization matters.
- Prefer explicit fixed-width integer types when ABI or serialisation matters.
- No dynamic memory, no syscalls, no blocking anywhere in the library.
- Compile-time validation via `_Static_assert` (lock-free counter, non-empty and
non-over-aligned payload); the primitive must fail to compile rather than
Expand Down Expand Up @@ -121,5 +121,5 @@ Run TSAN on an x86-64 host (or a 4K-page AArch64). TSAN cannot initialise on a
Unity or other external test dependencies.
- The multi-threaded torn-read test (`tests/test_seqlock_mt.c`) doubles as the
TSAN gate; its payload carries a self-checking invariant so any torn read is
detected at runtime, independent of the sanitizer.
detected at runtime, independent of the sanitiser.
- Add a test case for each bug fix and each new feature.
51 changes: 8 additions & 43 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,20 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Changed

- Added security policy, SPDX headers, and cleaned ignore rules while leaving the existing special coverage/concurrency handling unchanged.

## [0.1.1] - 2026-06-03

### Fixed

- `seqlock_dep` now links the static archive via
`seqlock_lib.get_static_lib()` instead of the `both_libraries` object,
which resolved to the shared library. Consumers vendoring seqlock as a
static subproject no longer pull in a `libseqlock.so` runtime dependency,
matching the static archive already advertised by the generated
`pkg-config` file
- `seqlock_dep` now links the static archive via `seqlock_lib.get_static_lib()` so static subproject consumers no longer pull in a shared-library runtime dependency.

## [0.1.0] - 2026-05-27

Initial release.

### Added

- Single-writer / multi-reader sequence lock with lock-free reads and a
wait-free writer, publishing a multi-word value as an internally consistent
snapshot (no torn tuples)
- `SEQLOCK_DEFINE(name, type)` typed-macro API generating a cell type and its
`init` / `store` / `load` / `try_load` operations, storing the payload by
value with no dynamic allocation
- Type-erased core (`seqlock_core_init` / `_store` / `_load` / `_try_load`)
compiled once in `seqlock.c`, with the typed operations as thin inline shims
- Bounded `try_load(max_retries)` variant that gives up after a caller-set
number of attempts instead of spinning
- C11 memory-ordering implementation correct on weakly-ordered CPUs, routed
through `seqlock_conf.h` macros with a GCC/Clang `__atomic` path, a C11
`<stdatomic.h>` path, and a uniprocessor fallback
- ThreadSanitizer-clean concurrency handling: a race-free relaxed-atomic
payload copy auto-selected under TSAN, and a fast benign-race-annotated
`memcpy` otherwise (`SEQLOCK_ATOMIC_PAYLOAD_COPY`)
- User-overridable configuration via `seqlock_conf.h`: `SEQLOCK_COUNTER_TYPE`,
`SEQLOCK_DEFAULT_MAX_RETRIES`, `SEQLOCK_CELL_ALIGNED`, `SEQLOCK_CACHELINE`,
and the `SEQLOCK_CPU_RELAX()` reader-retry spin hint (`pause` on x86,
`yield` on AArch64)
- Compile-time validation: `_Static_assert` rejects a non-unsigned or
non-lock-free counter type (on both the `__atomic` and C11 backends), a
zero-size payload, and an over-aligned payload
- Meson build as `both_libraries` with version/soversion, `pkg-config`
generation, an optional generated `seqlock_version.h`, and subproject support
- CI (GitHub Actions) for Ubuntu and macOS with address and
undefined-behaviour sanitisers, a dedicated ThreadSanitizer job, and a
release build
- Unit test suite covering single-threaded round-trip, pointer validation,
the even-counter invariant, bounded `try_load`, counter wrap, the C11
`<stdatomic.h>` backend, a concurrent self-checking torn-read test (also the
TSAN gate), and a multi-reader soak; validated natively on AArch64

[0.1.1]: https://github.com/aajll/seqlock/releases/tag/v0.1.1
[0.1.0]: https://github.com/aajll/seqlock/releases/tag/v0.1.0
- Initial sequence-lock primitive with typed macro API, type-erased core, bounded `try_load`, weak-memory-safe ordering, TSAN-aware payload copying, user-overridable configuration, compile-time validation, Meson packaging, CI, and concurrency tests.
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ have only advisory deviations and want to keep it that way.
add Unity or other external test dependencies.
- The concurrent torn-read test (`tests/test_seqlock_mt.c`) doubles as the
TSAN gate. Its payload carries a self-checking invariant so a torn read is
caught at runtime, independent of the sanitizer. Keep new concurrency tests
caught at runtime, independent of the sanitiser. Keep new concurrency tests
deterministic in their pass/fail criterion (assert an invariant, not a
timing).
- Concurrency tests shrink their iteration budget under a sanitizer
(`SEQLOCK_TEST_SANITIZED` in `tests/seqlock_test.h`): sanitizers add a
- Concurrency tests shrink their iteration budget under a sanitiser
(`SEQLOCK_TEST_SANITIZED` in `tests/seqlock_test.h`): sanitisers add a
10-50x slowdown and race detection comes from interleavings, not volume.
Keep the full budget for native builds. Counts are `-D`-overridable.
- The type-erased core (`seqlock_core_*`) is exercised directly by
Expand Down
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Single-writer / multi-reader sequence lock for lock-free, internally consistent
- **Configurable**: counter width, per-cell cache-line alignment, retry default, and copy strategy are all overridable through `seqlock_conf.h`
- **Compliance-aware design goals**: static allocation, explicit per-function contracts, compile-time validation, and a small auditable codebase targeting IEC 61508 and MISRA C:2012 principles

## Using the Library
## Installation

### As a Meson subproject

Expand Down Expand Up @@ -71,23 +71,6 @@ The generated version header is available as `seqlock_version.h` in the
build tree and as `<seqlock/seqlock_version.h>` after install. It is optional
and is not included by the library or its tests.

## Building

```sh
# Library only (release)
meson setup build --buildtype=release -Dbuild_tests=false
meson compile -C build

# With unit tests
meson setup build --buildtype=debug -Dbuild_tests=true
meson compile -C build
meson test -C build --verbose

# Concurrency check under ThreadSanitizer (x86-64 host; see Notes)
meson setup build-tsan --buildtype=debug -Dbuild_tests=true -Db_sanitize=thread
meson test -C build-tsan --verbose
```

## Quick Start

```c
Expand Down Expand Up @@ -135,6 +118,23 @@ int main(void)
}
```

## Building

```sh
# Library only (release)
meson setup build --buildtype=release -Dbuild_tests=false
meson compile -C build

# With unit tests
meson setup build --buildtype=debug -Dbuild_tests=true
meson compile -C build
meson test -C build --verbose

# Concurrency check under ThreadSanitizer (x86-64 host; see Notes)
meson setup build-tsan --buildtype=debug -Dbuild_tests=true -Db_sanitize=thread
meson test -C build-tsan --verbose
```

## API Reference

### Configuration Macros
Expand Down
8 changes: 5 additions & 3 deletions config/seqlock_version.h.in
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/**
* @file seqlock_version.h
* @brief Version information for the library.
* SPDX-License-Identifier: MIT
*
* Generated by Meson.
* @file: seqlock_version.h
*
* @brief
* Version information for the library. Generated by Meson.
*/

#ifndef SEQLOCK_VERSION_H_
Expand Down
8 changes: 6 additions & 2 deletions include/seqlock.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/**
* @file seqlock.h
* @brief Single-writer / multi-reader sequence lock (seqlock) primitive.
* SPDX-License-Identifier: MIT
*
* @file: seqlock.h
*
* @brief
* Single-writer / multi-reader sequence lock (seqlock) primitive.
*
* @details
* A seqlock publishes a multi-word value as an internally consistent
Expand Down
8 changes: 6 additions & 2 deletions include/seqlock_conf.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/**
* @file seqlock_conf.h
* @brief Compile-time configuration for the seqlock library.
* SPDX-License-Identifier: MIT
*
* @file: seqlock_conf.h
*
* @brief
* Compile-time configuration for the seqlock library.
*
* @details
* Every option below is wrapped in an `#ifndef` guard, so a consumer can
Expand Down
8 changes: 6 additions & 2 deletions src/seqlock.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/**
* @file seqlock.c
* @brief Type-erased core of the seqlock primitive.
* SPDX-License-Identifier: MIT
*
* @file: seqlock.c
*
* @brief
* Type-erased core of the seqlock primitive.
*
* @details
* All correctness-critical logic lives here, once, operating on raw
Expand Down
Loading