Restore GAP device name after GATT reset#426
Conversation
ESP-IDF NimBLE can reset the GAP Device Name when the standard GAP service is reinitialized during GATT server startup. NimBLEDevice::init(deviceName) calls NimBLEDevice::setDeviceName(deviceName), but that happens before the server is started. Later, starting advertising starts the server and resets/re-registers the GAP service, causing the C GAP name backing storage to be rebuilt from CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME. In projects where that Kconfig default remains "nimble", the advertised Local Name can be correct while the Generic Access Device Name characteristic reports "nimble".
The failing sequence is:
-> pAdvertising->start()
-> NimBLEAdvertising::start()
-> NimBLEDevice::getServer()->start()
-> NimBLEServer::resetGATT()
-> ble_gatts_reset()
-> ble_svc_gap_init()
-> ESP-IDF NimBLE resets GAP name to CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME
-> "nimble"
-> ble_svc_gatt_init()
Fix this by saving the device name supplied through NimBLEDevice::setDeviceName() and reapplying it immediately after ble_svc_gap_init() in NimBLEServer::resetGATT(). This preserves the C++ wrapper contract that the configured device name remains effective even when the wrapper internally resets the GATT/GAP service tables.
📝 WalkthroughWalkthrough
ChangesDevice Name Persistence Across GATT Reset
Estimated code review effort🎯 2 (Simple) | ⏱️ ~5 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/NimBLEServer.cpp`:
- Around line 894-896: The static members NimBLEDevice::m_deviceName and
NimBLEDevice::m_deviceNameSet are accessed without synchronization in the
resetGATT() method, creating a potential race condition if another thread calls
setDeviceName() concurrently. To fix this, add a static mutex member (like
m_deviceNameMutex) to the NimBLEDevice class, then protect all accesses to
m_deviceName and m_deviceNameSet with std::lock_guard in both the
setDeviceName() method and the resetGATT() method. In resetGATT(), acquire the
lock, cache the values of m_deviceNameSet and m_deviceName into local variables,
release the lock, then perform the conditional call to setDeviceName() with the
cached name outside the critical section.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a7ecf420-5f09-4fa4-8875-aea742681393
📒 Files selected for processing (3)
src/NimBLEDevice.cppsrc/NimBLEDevice.hsrc/NimBLEServer.cpp
| if (NimBLEDevice::m_deviceNameSet && !NimBLEDevice::setDeviceName(NimBLEDevice::m_deviceName)) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Shiver me timbers! A potential race condition be lurkin' in these waters!
Arr matey, while yer logic fer reapplyin' the cached device name be sound as a Spanish doubloon, there be a concurrency concern that could make yer ship run aground:
The static members NimBLEDevice::m_deviceName and NimBLEDevice::m_deviceNameSet be accessed here without any synchronization. If some scallywag calls NimBLEDevice::setDeviceName() from another thread whilst this resetGATT() be executin', ye could have yerself a classic race condition! One thread be writin' to m_deviceName while another be readin' from it - that be a recipe fer trouble on the high seas.
Now, in most BLE applications, these operations be serialized by the nature o' the code flow. But from a library design standpoint, there be no enforcement o' this serialization, savvy?
🏴☠️ Possible solutions fer this concurrency concern
Option 1: Document that BLE operations must be serialized by the application (low effort, shifts responsibility to user)
Option 2: Add a mutex to protect access to the static members (higher effort, makes library thread-safe by design)
// In NimBLEDevice.h, add a static mutex
static std::mutex m_deviceNameMutex;
// In setDeviceName:
{
std::lock_guard<std::mutex> lock(m_deviceNameMutex);
m_deviceName = deviceName;
m_deviceNameSet = true;
}
// In resetGATT:
std::string cachedName;
bool nameWasSet;
{
std::lock_guard<std::mutex> lock(NimBLEDevice::m_deviceNameMutex);
cachedName = NimBLEDevice::m_deviceName;
nameWasSet = NimBLEDevice::m_deviceNameSet;
}
if (nameWasSet && !NimBLEDevice::setDeviceName(cachedName)) {
return false;
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/NimBLEServer.cpp` around lines 894 - 896, The static members
NimBLEDevice::m_deviceName and NimBLEDevice::m_deviceNameSet are accessed
without synchronization in the resetGATT() method, creating a potential race
condition if another thread calls setDeviceName() concurrently. To fix this, add
a static mutex member (like m_deviceNameMutex) to the NimBLEDevice class, then
protect all accesses to m_deviceName and m_deviceNameSet with std::lock_guard in
both the setDeviceName() method and the resetGATT() method. In resetGATT(),
acquire the lock, cache the values of m_deviceNameSet and m_deviceName into
local variables, release the lock, then perform the conditional call to
setDeviceName() with the cached name outside the critical section.
NOTE: Codex 5.5 created this patch and writeup etc but I reviewed it and it looks like what I would have done by hand, and tested it here and it does resolve the issue and explains why I've been seeing my device misnamed for a bit. Not sure how or when I broke it, or if the issue was library side or a nimble change but figured others might be hitting this issue.
ESP-IDF NimBLE can reset the GAP Device Name when the standard GAP service is reinitialized during GATT server startup. NimBLEDevice::init(deviceName) calls NimBLEDevice::setDeviceName(deviceName), but that happens before the server is started. Later, starting advertising starts the server and resets/re-registers the GAP service, causing the C GAP name backing storage to be rebuilt from CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME. In projects where that Kconfig default remains "nimble", the advertised Local Name can be correct while the Generic Access Device Name characteristic reports "nimble".
The failing sequence is:
-> pAdvertising->start()
-> NimBLEAdvertising::start()
-> NimBLEDevice::getServer()->start()
-> NimBLEServer::resetGATT()
-> ble_gatts_reset()
-> ble_svc_gap_init()
-> ESP-IDF NimBLE resets GAP name to CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME
-> "nimble"
-> ble_svc_gatt_init()
Fix this by saving the device name supplied through NimBLEDevice::setDeviceName() and reapplying it immediately after ble_svc_gap_init() in NimBLEServer::resetGATT(). This preserves the C++ wrapper contract that the configured device name remains effective even when the wrapper internally resets the GATT/GAP service tables.
Summary by CodeRabbit