ISSUE-4417: FuriEventFlag: furi_event_flag_clear() notifies the event loop before the ISR-deferred clear is applied#4418
Open
herbenderbler wants to merge 1 commit into
Conversation
herbenderbler
requested review from
DrZlo13,
gsurkov,
hedger and
turbospok
as code owners
July 15, 2026 22:50
furi_event_flag_clear() from ISR context queued the clear for the timer daemon via xEventGroupClearBitsFromISR() but fired the FuriEventLoopEventOut notification synchronously from the ISR. The event loop thread (priority 16) outranks the timer daemon (priority 2), so it evaluated the level against the still-unmodified bits and dropped the event; the daemon applied the clear afterwards with no re-notification. Replace the stock deferred-clear callback with one that clears the bits and then notifies the event loop, so the loop observes the cleared bits. ISR-path yield, error handling and return value are unchanged; task-context behaviour is unchanged. Fixes flipperdevices#4417. Mirrors the furi_event_flag_set() fix from flipperdevices#4358.
herbenderbler
force-pushed
the
ISSUE-4417/furieventflag-fix
branch
from
July 15, 2026 22:55
c57038a to
f5033d3
Compare
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's New
Problem (fixes #4417)
furi_event_flag_clear()has the same deferred-operation/early-notify defect on its ISR path that #4336 reported forfuri_event_flag_set()(fixed by #4358). Whenfuri_event_flag_clear()is called from interrupt context and an app subscribes viafuri_event_loop_subscribe_event_flag()withFuriEventLoopEventOut, the event loop is notified before the clear is applied, evaluates the level against stale bits, and drops the event permanently.This defect is latent: every in-tree caller of
furi_event_flag_clear()runs in task context, so the ISR branch is currently unreachable. It is still worth fixing becausefuri_event_flag_clear()is part of the public FAP API (api_symbols.csv), so any external app or future in-firmware ISR can hit it — and with #4358 documentingfuri_event_flag_set()as ISR-safe, the symmetric expectation forclearis natural. Filed as #4417, split out of #4358 per the discussion there.Cause
FreeRTOS's
xEventGroupClearBitsFromISR()does not clear the bits in the ISR — it is a thin wrapper aroundxTimerPendFunctionCallFromISR()that queues the clear for the timer daemon. The old code then calledfuri_event_loop_link_notify(..., FuriEventLoopEventOut)immediately, from the ISR. The event loop thread (priority 16) outranks the timer daemon (priority 2), so it woke first, evaluated theFuriEventLoopEventOutlevel while the bits were still uncleared, found the level false, and dropped the item from its waiting list. The daemon then applied the clear with no further notification, so the event was lost entirely. Not a narrow race — the deterministic outcome given the priority gap.Fix
Notify only after the bits are actually cleared. Since
xEventGroupClearBitsFromISR()is literallyxTimerPendFunctionCallFromISR(vEventGroupClearBitsCallback, ...), the fix swaps in our own pended callback that:xEventGroupClearBits()furi_event_loop_link_notify(..., FuriEventLoopEventOut)if any of the requested bits were actually setBy the time the event loop runs, the bits are cleared and the level evaluation observes the correct value. Everything else is preserved: the timer command queue is unchanged (if it is full,
FuriStatusErrorResourceis still returned), the pre-existingportYIELD_FROM_ISR(pdTRUE)and its ordering rationale (FreeRTOS-Kernel#93) still apply unchanged — the pended callback goes through the same timer command queue as the stock deferred clear, so relative ordering of ISR-issued event-group operations is FIFO-preserved. The ISR-path return value is still the flag state read at call time. Task-context behaviour is unchanged.One deliberate difference from the set-side callback in #4358, found while verifying the approach: #4417 suggests the callback must keep the instance alive because "
xEventGroupClearBits()can yield internally viaxTaskResumeAll()" — that turns out not to be true. UnlikexEventGroupSetBits(),xEventGroupClearBits()is a plain critical section (lib/FreeRTOS-Kernel/event_groups.c): clearing bits can never satisfy a waiter, so it cannot wake or yield to a task that might free the flag. The callback still wraps both calls invTaskSuspendAll()/xTaskResumeAll(), but for a different (and accurately commented) reason: it keeps clear + notify atomic with respect to other tasks, matching the task-context path, which performs both underFURI_CRITICAL, and keeps the two pended callbacks structurally identical once #4358 lands.Relationship to #4358: independent change, branched off
dev; neither PR depends on the other to build or pass. Both touchfuri/core/event_flag.c, so whichever merges second will need a trivial conflict resolution (the two added includes, and two separate callback insertions).Regression test
None added in this PR — deliberately. Exercising this path requires running
furi_event_flag_clear()in ISR context, and the only in-tree mechanism for that is thefuri_hal_interrupt_trigger_pending()helper plus the ISR test harness introduced by #4358, which has not merged yet. Duplicating that infrastructure here would guarantee API-CSV and test-file conflicts between the two PRs.Once #4358 lands,
test_furi_event_loop_event_flag_from_isr()is a direct template: subscribeFuriEventLoopEventOutinstead ofFuriEventLoopEventIn, pre-set the bits, and callfuri_event_flag_clear()from the software-pending LPTIM2 ISR. I will follow up with that test in this PR (if still open) or a successor.API surface
No change to the public FAP API. No entry is added, removed or changed in either CSV —
targets/f7/api_symbols.csvandtargets/f18/api_symbols.csvare untouched and stay at version 88.0, in lockstep withdev(fbt reportsAPI version 88.0 is up to datefor both targets). The only header change is a doc comment onfuri_event_flag_clear().Verification
Verified on physical hardware (Flipper Zero,
f7), gated oninfo devicereporting the exact fix commit:firmware.commit.hash/firmware.commit.dirtyf5033d35/false— device runs exactly the committed fixfirmware.api.major/minor88.0unit_testssuiteFailed tests: 0, 156.9 s. The runner's non-failingLeakedstat read 2440 bytes (it varies run-to-run: an earlier pass of the same diff read 3104); not attributable to this diff (no allocations added, and the modified ISR branch is unreachable in the suite).The suite exercises the task-context
furi_event_flag_clear()path (theFuriApiLockre-lock in the RPC tests and the BT service both sit on it) and the event-loop event-flag subscriptions intest_furi_event_loop, confirming no regression in the paths this change touches. The ISR branch itself has no in-tree reproducer yet — see Regression test above; on-device A/B of the ISR path will come with the follow-up test.Also verified:
./fbt FIRMWARE_APP_SET=unit_tests firmware_allbuilds clean (f7); this is the image flashed for the run above (viaflash_usb_full)../fbt TARGET_HW=18 firmware_allbuilds clean (f18 is built by CI).Documentation
furi/core/event_flag.h— states thatfuri_event_flag_clear()is ISR-safe and that with aFuriEventLoopEventOutsubscription the event loop is notified only after the clear is applied. (The correspondingfuri_event_loop.hsubscribe-side note is left to ISSUE-4336: EventLoop receives callbacks one event late when setting FuriEventFlag from interrupts #4358 to avoid a doc-comment collision.)Checklist (For Reviewer)