The log lock is a binary semaphore, so when the radio task preempted loop()
mid-log every log from async_tcp waited the full 20ms for a holder that
couldn't let go until the radio job ended. Waiting now stops as soon as
the holder is preempted on another core. The rule lives in
LogLockPolicy.h with unit tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>