fs: serialize littlefs access with a recursive mutex#2449
Draft
faisal-shah wants to merge 1 commit into
Draft
Conversation
littlefs is not thread-safe, yet FS is called from three different FreeRTOS tasks: the SystemTask (fs.Init, controller loads at boot), the display task (settings and alarm saves from the settings screens, watchface resource loading) and the BLE host task (FSService performs full littlefs I/O inside GATT access callbacks, and DFU writes during a BLE file transfer can run while the user navigates settings screens). Nothing serializes these calls today. The SpiMaster semaphore only serializes individual bus transactions; the shared lfs_t state (caches, open-file list, lookahead) is unprotected, so a BLE filesystem transfer racing a settings save can corrupt filesystem state. Take a recursive mutex in every public FS method and expose a RAII FS::Lock so callers can hold the lock across multi-call sequences. The recursive type matters: a caller holding FS::Lock still goes through the public methods. Holding the lock across a sequence is needed wherever an open file handle must not race a Rename or Delete of the same file, since lfs_dir_commit invalidates such handles in the mlist. Cost: one FreeRTOS recursive mutex and one pointer, no API change. Callers that never overlapped are unaffected; overlapping callers now briefly wait instead of interleaving inside littlefs.
|
Build size and comparison to main:
|
Author
|
The |
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.
Problem
littlefs is not thread-safe, but
Controllers::FSis called from three different FreeRTOS tasks with no serialization:FSServiceruns full littlefs I/O (open/read/write/delete/mkdir/rename) directly inside GATT access callbacks (FSService.cpp).SettingDisplay.cpp,Alarm.cppviaSaveSettings()/SaveAlarm()), watchface resource loading.fs.Init()and controller loads at boot.The
SpiMastersemaphore only serializes individual SPI bus transactions. The sharedlfs_tstate (block caches, open-file mlist, lookahead buffer) is unprotected, so e.g. a companion app uploading resources over the BLE FS service while the user flips a setting has two tasks inside littlefs at once, which can corrupt filesystem state.Fix
Add a FreeRTOS recursive mutex to
FS, taken in every public method, and expose a RAIIFS::Lockso callers can hold the lock across multi-call sequences. The recursive type is what makes the RAII guard composable: a caller holdingFS::Lockstill goes through the locked public methods.Holding the lock across a sequence matters wherever an open file handle must not race a
RenameorDeleteof the same file:lfs_dir_commitinvalidates such handles in the mlist (lfs.c, mlist update onLFS_TYPE_DELETE), so an unlocked reader can otherwise end up on reclaimed blocks.Existing call sites need no changes.
Cost
One recursive mutex (created in the constructor) and one handle pointer. No API change. Tasks that never overlapped are unaffected; overlapping tasks now briefly block instead of interleaving inside littlefs.
Testing
main(arm-gcc 10.3, no size surprises: +~900 B text).xSemaphoreCreateRecursiveMutex/TakeRecursive/GiveRecursiveimplementations to build this; I have that as a small companion change for InfiniSim and can PR it there if this direction is acceptable.Marked draft for feedback on the approach; happy to adjust (e.g. per-method granularity or a different guard shape) if maintainers prefer.