Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
08a31bd
feat: degrade to in-memory reporting on read-only/ephemeral file syst…
krassx Oct 14, 2026
184d102
fix: address review of data-directory fallback (#9)
krassx Oct 14, 2026
01e337c
fix: second review round for data-directory fallback (#9)
krassx Oct 14, 2026
cc96a41
feat(bugsee): populate utc_offset, hardware model and Windows locale
krassx Oct 14, 2026
a7410a4
fix(bugsee): address review of hostinfo (iOS model, tm_gmtoff gating,…
krassx Oct 14, 2026
11226fb
fix(core): exactly-once recovery and queue draining across processes …
krassx Oct 14, 2026
16937c8
fix(core): arbitrate cross-process claims and session liveness with O…
krassx Oct 14, 2026
31a293c
fix: keep draining existing data dirs that cannot take new files (#9)
krassx Oct 14, 2026
10271f3
fix(core): harden the file-lock claims (second review)
krassx Oct 14, 2026
716c363
test: gate unix-only helpers so Windows clippy stays clean (#9)
krassx Oct 14, 2026
69a6bc8
Merge origin/fix/issue-9 into fix/issue-14
krassx Oct 14, 2026
6e71a45
feat(native): best-effort flush of in-flight events at crash time
krassx Oct 14, 2026
9ae8a05
fix(core): queue the previous crash on the launch path before launch …
krassx Oct 14, 2026
0b78a10
fix: drain every trusted unwritable default tree, not just one; stale…
krassx Oct 14, 2026
6267bd5
Merge origin/fix/issue-9; portable clippy fixes for lock claims
krassx Oct 14, 2026
412cff1
Merge remote-tracking branch 'origin/fix/issue-9' into fix/issue-13
krassx Oct 14, 2026
0cb980b
fix(core): run launch-path recovery on a bounded helper, after the cr…
krassx Oct 14, 2026
a0a779b
Merge origin/fix/issue-14 into fix/issue-10
krassx Oct 14, 2026
1760cf2
fix(native): harden in-flight crash flush (review feedback, #13)
krassx Oct 14, 2026
ee3f808
fix(core): review round 3 on lock claims
krassx Oct 14, 2026
c4f81d6
fix(core): make the lock-less heartbeat robust to clock skew and slow…
krassx Oct 14, 2026
6dfc9c8
fix(core): keep unique crashes the launch pass cannot queue; scan new…
krassx Oct 14, 2026
ee725bc
Merge origin/fix/issue-10 into fix/issue-13: port the DYING gate onto…
krassx Oct 14, 2026
6d6faa2
Merge origin/fix/issue-14 into fix/issue-10
krassx Oct 14, 2026
4530024
fix(core): tried sessions first, and a queue-cap refusal is not an at…
krassx Oct 14, 2026
979c6e8
fix(native): address review of the in-flight flush (#13)
krassx Oct 14, 2026
1d84feb
fix: never recover the same directory twice in one launch (#9)
krassx Oct 14, 2026
3b90a28
fix(core): a lock file that vanishes under a scan is not evidence of …
krassx Oct 14, 2026
4002558
fix(core): a failed enqueue write is not a recovery attempt
krassx Oct 14, 2026
7aad047
Merge remote-tracking branch 'origin/fix/issue-14' into fix/issue-10
krassx Oct 14, 2026
b606938
fix(core): a session the launch pass defers is picked up even if the …
krassx Oct 14, 2026
12d4672
fix(core): deferred launch-pass recovery uses the recovery directory,…
krassx Oct 14, 2026
fbd6644
fix: remember every undeletable retired bundle, not just delivered on…
krassx Oct 14, 2026
5496529
Merge remote-tracking branch 'origin/fix/issue-9' into fix/issue-10
krassx Oct 14, 2026
1b91328
Merge origin/fix/issue-10 into fix/issue-13
krassx Oct 14, 2026
04a927a
Merge origin/fix/issue-16 into fix/issue-13 (libc stays a normal unix…
krassx Oct 14, 2026
bb281f1
test(core): wait on an observable condition in the full-queue test
krassx Oct 14, 2026
4893817
Merge origin/fix/issue-10 into fix/issue-13
krassx Oct 14, 2026
7ab8a90
fix(core): declare kernel32 calls once (clashing_extern_declarations …
krassx Oct 14, 2026
b6ce5e0
Merge origin/main into fix/issue-9
krassx Oct 14, 2026
e01a20a
Merge origin/fix/issue-9 into fix/issue-14
krassx Oct 14, 2026
e00b09d
Merge origin/fix/issue-14 into fix/issue-10
krassx Oct 14, 2026
3e18e85
Merge remote-tracking branch 'origin/fix/issue-10' into HEAD
krassx Oct 14, 2026
acee6db
docs: keep the corrected native feature comment from main
krassx Oct 14, 2026
1ecfca8
fix: de-duplicate the unix libc dependency after merging main (#27's …
krassx Oct 14, 2026
fbbabb2
Merge origin/fix/issue-9 into fix/issue-14
krassx Oct 14, 2026
66e4f18
Merge origin/fix/issue-14 into fix/issue-10
krassx Oct 14, 2026
4e42eda
Merge remote-tracking branch 'origin/fix/issue-10' into HEAD
krassx Oct 14, 2026
0f94b90
fix(core): make the crash-flush handshake robust on slow runners
krassx Oct 14, 2026
a89ce8a
Merge origin/main (squashes of #14, #10) into fix/issue-13
krassx Oct 14, 2026
4858ce7
docs: keep design notes generic (no private-component references) (#36)
krassx Oct 14, 2026
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
2 changes: 1 addition & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ That last case previously produced **no report whatsoever** — the SDK shut dow

## 12. Crash-time safety, best-effort flush & edge cases

**Best-effort in-flight flush:** at crash, secure the guaranteed artifact first (`PanicSnapshot` / minidump), then attempt flush, then terminate. The crashing thread never touches the queue lock/allocator — it does an async-signal-safe wake (atomic flag + `sem_post`/eventfd/self-pipe) and waits ≤~50 ms for the worker to drain→append→`write_all`→ack; no ack → give up. (This best-effort in-flight flush is a **planned** hardening step; the current worker already durably appends each record as it drains, so completed records survive without it.) Panic(unwind): usually succeeds. panic=abort: hook wakes worker before abort. Native fault: defensive only.
**Best-effort in-flight flush (implemented, `bugsee_core::crash_flush`):** a producer only queues; the worker appends later, so entries still in the channel (or mid-append) when the process dies are lost. At crash the handler first secures the guaranteed artifact (marker + frames), then does one atomic ticket increment, one non-blocking `write(2)` (Windows: `SetEvent`) to a process-wide wake handle (one pipe/event per process, never closed, so no descriptor-reuse race), and polls an atomic ack for at most 50 ms (`crash_flush::DEFAULT_BUDGET`, shared with the panic hook). It first raises a `dying` flag so the uploader and recovery start no new claims in the window (a kill mid-claim would strand it for the claim grace period). One helper thread per process (created at launch, re-created in a fork child, never in the handler) is blocked on that handle; it posts a `Flush` message through the normal FIFO channel and, when the worker acks, publishes the ticket. FIFO order means an ack implies every entry queued before the crash is appended. The crashing thread never takes the queue lock, allocates or serializes, and a stuck/dead worker or poisoned allocator costs exactly the budget (the 5 s watchdog is not involved). The pid recorded at registration makes a fork child skip the flush until its revived recorder re-registers. A fault on the worker or helper thread skips the wait; the last registered recorder is the flush target. Panic: the hook runs the same bounded flush for a main-thread panic no guard will catch (covers `panic = "abort"` builds with no native handler; with one, the SIGABRT handler flushes too); an unwinding panic is also drained by the launch guard's `Drop`. Native fault: flushed from every fatal exit of the signal handler / the Windows exception filter, after the marker is complete. **Tradeoffs:** zero hot-path cost and no new on-disk format (no spill file: a torn append is the already-tolerated torn part tail, pinned by a recovery test), but the flush needs a live worker and allocator, so a fault that corrupts the heap or hits the worker thread forfeits it, and what is drained is bounded by throughput (about 3.5k ~450-byte events in 50 ms on a debug build; the remainder is lost). A producer-side pre-serialized ring spilled by the handler would survive a dead worker but taxes every event; not built.

**Recursion guard:** `NORMAL→PANICKING→HANDLING_FATAL→SECONDARY`; second fatal → tiny marker + terminate, no re-entry.
**Quarantine:** Class A (continue) / B (restart) / C (rebuild instance) / D (disable); restart budget (1 immediate, 3/10 min, then disable) — never infinite.
Expand Down
4 changes: 2 additions & 2 deletions PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ in [`DESIGN.md`](./DESIGN.md); the wire contract is `bugsee/report-bundle-struct
| 0 — Harness | ✅ | Workspace, mock 3-step API server, bundle validator, subprocess crash runner, CI **matrix across ubuntu/macOS/windows** (build/test/clippy per platform, fmt once) + pinned-MSRV job. |
| 1 — Ingestible bundle (core) | ✅ | Sliding-window capture (time/byte/event caps), length-prefixed part streams, envelope/manifest, Zstd method-93 ZIP, signatures. |
| 2 — Handled errors + caught panics | ✅ | `catch_unwind` guards, panic snapshot, error/cause chains. Follow-ups: `tracing` Layer + `log` adapter done. Subsystem quarantine still open. |
| 3 — Fatal + next-launch + correlation | ✅ (core) | Native handler, session generations + liveness markers, next-launch recovery, panic↔SIGABRT correlation, client-side native dedup signature from frame offsets. **New:** Windows native capture — SEH/VEH handler, PE CodeView module map, marker + frame capture — so all three desktop platforms report fatal crashes. Open: best-effort in-flight flush, full out-of-process minidump. |
| 3 — Fatal + next-launch + correlation | ✅ (core) | Native handler, session generations + liveness markers, next-launch recovery, panic↔SIGABRT correlation, client-side native dedup signature from frame offsets. **New:** Windows native capture — SEH/VEH handler, PE CodeView module map, marker + frame capture — so all three desktop platforms report fatal crashes. In-flight events are now flushed at crash (bounded 50 ms, signal-safe wake of the capture worker; #13). Open: full out-of-process minidump. |
| 4 — Integrations & APM | ✅ (core) | APM transactions/spans, sysinfo telemetry, reqwest network capture. **New:** the environment is actually populated — OS version, kernel, memory, disk, locale, and app identity (`package_id`/`version`/`build`) via `LaunchOptions::app_version`. Open: sessions / release-health. |
| 5 — Hardening | ✅ (core) | Durable retry queue (backoff, retry cap, blacklist), `before_send`/`before_breadcrumb`, event sampling. **New:** `on_report_dropped` — delivery failure is observable at last; `flush()` alone never could distinguish delivered from discarded. Open: general PII scrubber, rate limits. |
| 6 — Mobile/FFI | ✅ (core) | `bugsee-ffi` C ABI (`include/bugsee.h`). Open: actual iOS/Android target builds + Swift/Kotlin wrappers. |
Expand Down Expand Up @@ -538,7 +538,7 @@ Rust and are worth evaluating before building one.
### Remaining phase work

- Phase 2: subsystem quarantine (auto-disable a repeatedly-faulting integration).
- Phase 3: best-effort in-flight flush at crash; full out-of-process minidump (desktop) — see the backend note below, which is the same machinery.
- Phase 3: full out-of-process minidump (desktop) — see the backend note below, which is the same machinery.
- Phase 4: sessions / release-health.
- Phase 5: general PII scrubber, rate limits.
- Phase 6: iOS/Android target builds; Swift/Kotlin language wrappers.
Expand Down
7 changes: 7 additions & 0 deletions crates/bugsee-core/src/claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ impl Drop for Claim {
/// Try to claim `path` (created if absent). `None` if another process, or another
/// thread of this one, holds it, or if it cannot be claimed.
pub fn try_claim(path: &Path) -> Option<Claim> {
// A dying process must not start work a kill would leave half-claimed.
if crate::crash_flush::is_dying() {
return None;
}
match acquire(path) {
Acquire::Got(claim) => Some(claim),
Acquire::Held | Acquire::Unavailable => None,
Expand All @@ -100,6 +104,9 @@ pub fn try_claim(path: &Path) -> Option<Claim> {
/// may both do the work, and the backend deduplicates. `None` means another
/// process (or thread) holds the claim.
pub fn try_claim_lenient(path: &Path) -> Option<Option<Claim>> {
if crate::crash_flush::is_dying() {
return None;
}
match acquire(path) {
Acquire::Got(claim) => Some(Some(claim)),
Acquire::Unavailable => Some(None),
Expand Down
Loading
Loading