Skip to content

Whether FreeRTOS refuses a non-owner's mutex give is argued from its source, not measured #117

Description

@bdbarnett

#107 fixed two nodes that released the pump lock without having taken it. On Windows that corrupted the CRITICAL_SECTION; glibc refuses a non-owner's unlock with EPERM (measured). That FreeRTOS refuses it as well — which is why neither ESP32 board ever showed the defect — is read from its source. A one-off probe on a board would settle it, and would say whether the boards were ever exposed.

Activity

  1. added
    needs-boardNeeds a board on the bench (P4 panel, S3-Touch-4.3, T-Embed)
    on Sep 22, 2026
  2. bdbarnett commented on Sep 22, 2026

    @bdbarnett
    ContributorAuthor

    Measured. FreeRTOS refuses it, so the boards were never exposed.

    LilyGO T-Embed S3, ESP32_GENERIC_S3/SPIRAM_OCT, MicroPython v1.29.0, ESP-IDF 5.5.4, on the pump's own xSemaphoreCreateRecursiveMutex:

    >>> import _audioif
    >>> for i in range(3): print(_audioif.lock_probe())
    (0, 0, 1)
    (0, 0, 1)
    (0, 0, 1)
    

    (held_by_other, free_for_all, owner_control), 1 = the give was accepted, 0 = refused.

    • held_by_other = 0 — the interpreter holds the mutex, a second FreeRTOS task calls xSemaphoreGiveRecursive on it: refused.
    • free_for_all = 0 — nobody holds it, a task that never took it gives it anyway: refused. That is the shape The audio pull runs off the interpreter thread, and a component's output stops changing identity #107's nodes were actually in — a release with no take anywhere, rather than a release of somebody else's take.
    • owner_control = 1 — the control. A task that took the mutex itself gives it back: accepted.

    The control is the reason this is evidence rather than a plausible number. 0 is exactly what the FreeRTOS source predicts for the first two, so a probe that never ran, or whose give was broken for some unrelated reason, would return (0, 0) and agree with the expected answer perfectly. It had to be shown able to produce a 1 before its zeros meant anything. (Its internal -1 sentinel for "the task could not be created" never fired either.)

    So: glibc EPERM, Windows CRITICAL_SECTION corruption, FreeRTOS pdFAIL. The two ESP32 boards never showed #107's defect because the RTOS refused every one of those gives, not because the bug was absent — which is worth knowing, because it means the same class of bug would stay invisible on these boards again.

    The probe is C rather than a script for a reason worth recording: this port's _thread lock is xSemaphoreCreateBinaryStatic, a binary semaphore with no owner at all, so a give from another thread always succeeds there. A Python-side experiment would have answered a different question very convincingly.

    _audioif.lock_probe(), PyDevices/audioif#14. Nothing in the audio path calls it.

    Image: sha256 10395d7b99c2f999f6ee8339a7222ac8a61fdc80c50db6ddb61006b875ca4f18, built from cmods 391403c, audioif 3c418f4 + PyDevices/audioif#13 + PyDevices/audioif#14, audiodsp 95e58b3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-boardNeeds a board on the bench (P4 panel, S3-Touch-4.3, T-Embed)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions