# Story 1.19: Consistent Backpressure Semantics Across Call Contexts

**Status:** Complete
**Priority:** High
**Depends on:** None

---

## Context / Background

The GPT-5.2 audit identified that backpressure behavior differs unexpectedly based on call context. When `SyncLoggerFacade` is called from the same thread as the worker loop, it immediately drops events on a full queue, ignoring the `drop_on_full=False` setting.

**Current behavior in `src/fapilog/core/logger.py:711-735`:**

```python
# If on the worker loop thread, do non-blocking enqueue only
if (
    self._loop_thread_ident is not None
    and self._loop_thread_ident == threading.get_ident()
):
    if self._queue.try_enqueue(payload):
        # ... success path
    else:
        self._dropped += 1  # Drops regardless of drop_on_full setting
```

**Problem:**
- Same-thread path uses `try_enqueue()` (non-blocking) and drops immediately
- Cross-thread path uses `_async_enqueue()` which honors backpressure wait timeout
- This violates the principle of least surprise for users configuring `drop_on_full=False`

**Why this design exists:**
- Same-thread blocking would deadlock (can't wait on own loop)
- Current behavior is intentional but undocumented

---

## Scope (In / Out)

### In Scope

- Document the current behavior explicitly in docstrings and user docs
- Add a diagnostic warning when same-thread drop occurs with `drop_on_full=False`
- Update reliability-defaults.md to explain this edge case
- Consider adding a configuration option for same-thread behavior

### Out of Scope

- Changing the fundamental same-thread non-blocking behavior (would cause deadlocks)
- Implementing async waiting in same-thread context (not possible)

---

## Acceptance Criteria

### AC1: Behavior Documented in Code

**Description:** The same-thread drop behavior is clearly documented in the `_enqueue` method docstring.

**Validation:**
```python
# src/fapilog/core/logger.py
def _enqueue(self, ...):
    """
    ...
    Note: When called from the worker loop thread (same-thread context),
    events are dropped immediately if the queue is full, regardless of
    `drop_on_full` setting. This prevents deadlock.
    ...
    """
```

### AC2: Diagnostic Warning for drop_on_full=False Mismatch

**Description:** When `drop_on_full=False` but same-thread drop occurs, emit a specific diagnostic.

**Validation:**
```python
# Should emit diagnostic like:
# {"category": "backpressure", "message": "same-thread drop despite drop_on_full=False", ...}
```

### AC3: User Documentation Updated

**Description:** `docs/user-guide/reliability-defaults.md` explains same-thread behavior.

**Validation:**
- Document contains section explaining same-thread context behavior
- Recommends using `AsyncLoggerFacade` in async contexts

### AC4: Existing Tests Still Pass

**Description:** Test `tests/integration/test_worker_lifecycle.py` AC5 continues to validate this behavior.

**Validation:**
```bash
pytest tests/integration/test_worker_lifecycle.py -k "AC5" -v
```

---

## Implementation Notes

### File Changes

```
src/fapilog/core/logger.py (MODIFIED - add docstring, enhance diagnostic)
docs/user-guide/reliability-defaults.md (MODIFIED - document edge case)
docs/architecture/async-sync-boundary.md (MODIFIED - reference this behavior)
```

### Key Code Change

```python
# In _enqueue same-thread branch, enhance the diagnostic:
else:
    self._dropped += 1
    try:
        from .diagnostics import warn

        warn(
            "backpressure",
            "drop on full (same-thread)" + (
                " - note: drop_on_full=False cannot be honored in same-thread context"
                if not self._drop_on_full else ""
            ),
            drop_total=self._dropped,
            drop_on_full_setting=self._drop_on_full,
            queue_hwm=self._queue_high_watermark,
            capacity=self._queue.capacity,
        )
    except Exception:
        pass
```

---

## Tasks

### Phase 1: Documentation

- [ ] Add docstring to `_enqueue` explaining same-thread behavior
- [ ] Update `reliability-defaults.md` with same-thread section
- [ ] Update `async-sync-boundary.md` to reference this

### Phase 2: Enhanced Diagnostics

- [ ] Modify same-thread drop diagnostic to note `drop_on_full` mismatch
- [ ] Add `drop_on_full_setting` to diagnostic metadata

### Phase 3: Validation

- [ ] Verify existing tests still pass
- [ ] Add test for enhanced diagnostic output (optional)

---

## Tests

### Existing Tests

- `tests/integration/test_worker_lifecycle.py` AC5 already validates same-thread drop behavior

### New Tests (Optional)

- `tests/unit/test_logger_diagnostics.py`
  - Test diagnostic includes `drop_on_full_setting` when applicable

---

## Definition of Done

### Code Complete

- [ ] Docstring added to `_enqueue` method
- [ ] Diagnostic enhanced with setting context
- [ ] User documentation updated

### Quality Assurance

- [ ] Existing tests pass
- [ ] `ruff check` passes
- [ ] `mypy` passes

### Documentation

- [ ] reliability-defaults.md updated
- [ ] async-sync-boundary.md updated
- [ ] CHANGELOG updated

---

## Risks / Rollback

### Risks

1. **Risk:** Users expect different behavior after reading docs
   - **Mitigation:** Clearly state this is intentional to prevent deadlock

2. **Risk:** Breaking existing diagnostics consumers
   - **Mitigation:** Only add fields, don't change existing structure

### Rollback Plan

If issues occur:
1. Revert documentation changes
2. Revert diagnostic changes

---

## Related Stories

- **Related:** Story 7.10 - Worker lifecycle coverage (tests same-thread behavior)

---

## Code Review

**Date:** 2026-01-17
**Reviewer:** Claude
**Verdict:** OK to PR

### Summary

Documented same-thread backpressure drop behavior in code docstrings and user guides. Enhanced diagnostic warning when `drop_on_full=False` cannot be honored.

### AC Verification

| Criterion | Evidence |
|-----------|----------|
| AC1: Behavior Documented in Code | `src/fapilog/core/logger.py:703-722` - Docstring explains same-thread drop, deadlock prevention |
| AC2: Diagnostic Warning | `src/fapilog/core/logger.py:759-768` - Message includes mismatch note, adds `drop_on_full_setting` field |
| AC3: User Documentation Updated | `docs/user-guide/reliability-defaults.md:11-20`, `docs/architecture/async-sync-boundary.md:165-175` |
| AC4: Existing Tests Still Pass | All 20 tests in `test_worker_lifecycle.py` pass |

### Quality Gates

- [x] ruff check passed
- [x] ruff format passed
- [x] mypy passed
- [x] diff-cover >= 90% (100%)
- [x] No weak assertions
- [x] No dead code

---

## Change Log

| Date | Change | Author |
|------|--------|--------|
| 2026-01-17 | Code review passed, status Complete | Claude |
| 2026-01-16 | Initial draft from GPT-5.2 audit | Claude |
