# Multi-Worker Quick Reference

## TL;DR

**Problem**: Using 4 workers causes duplicate emails and race conditions.

**Solution**: File-based locking ensures only ONE worker runs schedulers.

## Commands

```bash
# Development (1 worker - auto primary)
uvicorn alpine_bits_python.api:app --reload

# Production (4 workers - one becomes primary)
uvicorn alpine_bits_python.api:app --workers 4 --host 0.0.0.0 --port 8000

# Test worker coordination
uv run python test_worker_coordination.py

# Run all tests
uv run pytest tests/ -v
```

## Check Which Worker is Primary

Look for startup logs:

```
[INFO] Worker startup: pid=1001, primary=True   ← PRIMARY
[INFO] Worker startup: pid=1002, primary=False  ← SECONDARY
[INFO] Worker startup: pid=1003, primary=False  ← SECONDARY
[INFO] Worker startup: pid=1004, primary=False  ← SECONDARY
[INFO] Daily report scheduler started           ← Only on PRIMARY
```

## Lock File

**Location**: `/tmp/alpinebits_primary_worker.lock`

**Check lock status**:
```bash
# See which PID holds the lock
cat /tmp/alpinebits_primary_worker.lock
# Output: 1001

# Verify process is running
ps aux | grep 1001
```

**Clean stale lock** (if needed):
```bash
rm /tmp/alpinebits_primary_worker.lock
# Then restart application
```

## What Runs Where

| Service | Primary Worker | Secondary Workers |
|---------|---------------|-------------------|
| HTTP requests | ✓ Yes | ✓ Yes |
| Email scheduler | ✓ Yes | ✗ No |
| Error alerts | ✓ Yes | ✓ Yes (all workers can send) |
| DB migrations | ✓ Yes | ✗ No |
| Customer hashing | ✓ Yes | ✗ No |

## Troubleshooting

### All workers think they're primary
**Cause**: Lock file not accessible
**Fix**: Check permissions on `/tmp/` or change lock location

### No worker becomes primary
**Cause**: Stale lock file
**Fix**: `rm /tmp/alpinebits_primary_worker.lock` and restart

### Still getting duplicate emails
**Check**: Are you seeing duplicate **scheduled reports** or **error alerts**?
- Scheduled reports should only come from primary ✓
- Error alerts can come from any worker (by design) ✓

## Code Example

```python
from alpine_bits_python.worker_coordination import is_primary_worker

async def lifespan(app: FastAPI):
    # Acquire lock - only one worker succeeds
    is_primary, worker_lock = is_primary_worker()

    if is_primary:
        # Start singleton services
        scheduler.start()

    # All workers handle requests
    yield

    # Release lock on shutdown
    if worker_lock:
        worker_lock.release()
```

## Documentation

- **Full guide**: `docs/MULTI_WORKER_DEPLOYMENT.md`
- **Solution summary**: `SOLUTION_SUMMARY.md`
- **Implementation**: `src/alpine_bits_python/worker_coordination.py`
- **Test script**: `test_worker_coordination.py`
