Skip to content

[Bugfix] NimBLEDevice::createServer can crash if stack not initialized.#1117

Merged
h2zero merged 4 commits intomasterfrom
bugfix/create-server-crash
Mar 17, 2026
Merged

[Bugfix] NimBLEDevice::createServer can crash if stack not initialized.#1117
h2zero merged 4 commits intomasterfrom
bugfix/create-server-crash

Conversation

@h2zero
Copy link
Owner

@h2zero h2zero commented Mar 17, 2026

This removes the gatts/gap reset calls from NimBLEDevice::createServer so that it can be safely used before initializing the stack.

This also deprecates NimBLEService::start the the services will now be added/created only when the server is started.

Fixes #1088

Summary by CodeRabbit

  • Refactor

    • Server startup now reports success/failure and governs service startup.
  • Deprecation

    • Calling service->start() is deprecated and becomes a no-op; services start with the server.
  • Bug Fixes

    • Advertising aborts if the server fails to start to avoid inconsistent states.
  • Examples / Behavior

    • Example sketches no longer call service->start() explicitly; helper-created services are marked for cleanup.

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Arr: server creation no longer runs GAP/GATT init; server/service start/reset now return booleans and use an internal service start path; public NimBLEService::start() is deprecated and replaced by a private start_internal() invoked by the server.

Changes

Cohort / File(s) Summary
Device / Server creation
src/NimBLEDevice.cpp
Removed calls to ble_gatts_reset(), ble_svc_gap_init(), and ble_svc_gatt_init() from NimBLEDevice::createServer() so GAP/GATT init is not performed at server creation.
Server control flow & signatures
src/NimBLEServer.cpp, src/NimBLEServer.h
start() return type changed voidbool; resetGATT() changed voidbool. start() now early-returns if already started, propagates resetGATT() failures, sets m_gattsStarted on success, and uses start_internal() for per-service startup.
Service lifecycle / API
src/NimBLEService.cpp, src/NimBLEService.h
NimBLEService::start() deprecated and implemented as an inline no-op returning true; real start logic moved to private start_internal() which rebuilds and registers service definitions unconditionally.
Advertising flow
src/NimBLEAdvertising.cpp
Advertising startup checks NimBLEServer::start() return value and aborts advertising if server start fails.
Examples / Stream
examples/*, src/NimBLEStream.cpp
Removed explicit pService->start() calls from multiple examples and NimBLEStream::begin(); streams mark services they create for deletion instead of starting them.

Sequence Diagram(s)

sequenceDiagram
  participant App as App / Setup
  participant Device as NimBLEDevice
  participant Server as NimBLEServer
  participant Service as NimBLEService
  participant Stack as BLE GATT Stack

  App->>Device: createServer()
  Device->>Server: instantiate singleton (skip GAP/GATT init)

  App->>Server: start()
  Server->>Stack: resetGATT()
  alt resetGATT success
    Server->>Server: set flags / prepare services
    loop each service
      Server->>Service: start_internal()
      Service->>Stack: ble_gatts_count_cfg / ble_gatts_add_svcs
      Stack-->>Service: registration result
    end
    Server-->>App: start() returns true
  else resetGATT failure
    Server-->>App: start() returns false
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

⚓ Avast! The server sleeps unroused by GAP or GATT,
Private starts slip in silent like a schooner at night,
True and false flags hoist where old calls once sat,
Examples trimmed of noisy starts — neat as a pirate's knot,
Yarrr, the stack waits steady till the captain gives the light.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes service deprecation and refactoring beyond the immediate bugfix, including removal of pService->start() calls across multiple examples. Consider separating the service deprecation changes into a separate PR, or clarify how the example updates directly address the createServer crash issue.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main bugfix: preventing crashes in NimBLEDevice::createServer when the BLE stack is not initialized.
Linked Issues check ✅ Passed The PR addresses issue #1088 by removing initialization calls from createServer, allowing safe use before stack initialization, though the linked issue lacks detailed error context.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/create-server-crash
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NimBLEService.h`:
- Around line 49-51: Fix the typo in the deprecation message for
NimBLEService::start(): update the __attribute__((deprecated(...))) string to
read "NimBLEService::start() has no effect. Services are started when the server
is started." so the message uses "has no effect" (remove the extra "is").

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0d9eb032-f808-40ca-b2d3-29586b60d676

📥 Commits

Reviewing files that changed from the base of the PR and between 66522ce and eed5542.

📒 Files selected for processing (4)
  • src/NimBLEDevice.cpp
  • src/NimBLEServer.cpp
  • src/NimBLEService.cpp
  • src/NimBLEService.h
💤 Files with no reviewable changes (1)
  • src/NimBLEDevice.cpp

This removes the gatts/gap reset calls from NimBLEDevice::createServer so that it can be safely used before initializing the stack.

This also deprecates NimBLEService::start the the services will now be added/created only when the server is started.
@h2zero h2zero force-pushed the bugfix/create-server-crash branch from eed5542 to 4da6670 Compare March 17, 2026 17:51
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/NimBLEService.cpp (1)

153-165: ⚠️ Potential issue | 🟡 Minor

Arrr, reset “started” state on registration failure.

After Line 153 sets service type, failure at Line 157 or Line 163 returns false but leaves isStarted() lookin’ true.

🩹 Proposed fix
     int rc = ble_gatts_count_cfg(m_pSvcDef);
     if (rc != 0) {
+        m_pSvcDef->type = 0;
         NIMBLE_LOGE(LOG_TAG, "ble_gatts_count_cfg failed, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
         return false;
     }

     rc = ble_gatts_add_svcs(m_pSvcDef);
     if (rc != 0) {
+        m_pSvcDef->type = 0;
         NIMBLE_LOGE(LOG_TAG, "ble_gatts_add_svcs, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
         return false;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NimBLEService.cpp` around lines 153 - 165, The code sets m_pSvcDef->type
then returns false on failures from ble_gatts_count_cfg or ble_gatts_add_svcs
but leaves isStarted() true; ensure you reset the service started state before
returning by clearing the started flag (e.g. call the service's setter or mutate
the m_started state via setStarted(false) or equivalent) in both failure
branches after the failing call, so that isStarted() accurately reflects the
aborted registration for m_pSvcDef.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NimBLEServer.cpp`:
- Around line 910-912: The call to pSvc->start_internal() in the service loop
currently ignores failures; update the logic in NimBLEServer (where pSvc is
iterated and getRemoved() is checked) to check the return value of
pSvc->start_internal(), and on failure handle it deterministically (e.g., log
the error with context using the server logger, mark the service as
removed/invalid via pSvc->setRemoved() or similar, and abort/return an error
from the overall startup routine so startup does not continue in a partial
state). Ensure you reference pSvc->getRemoved(), pSvc->start_internal(), and the
surrounding startup/initialization function so a failed start_internal() causes
proper cleanup and an early failure return.

---

Outside diff comments:
In `@src/NimBLEService.cpp`:
- Around line 153-165: The code sets m_pSvcDef->type then returns false on
failures from ble_gatts_count_cfg or ble_gatts_add_svcs but leaves isStarted()
true; ensure you reset the service started state before returning by clearing
the started flag (e.g. call the service's setter or mutate the m_started state
via setStarted(false) or equivalent) in both failure branches after the failing
call, so that isStarted() accurately reflects the aborted registration for
m_pSvcDef.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d3b137a8-2583-482a-8fa5-afafd7a16dd4

📥 Commits

Reviewing files that changed from the base of the PR and between eed5542 and 4da6670.

📒 Files selected for processing (4)
  • src/NimBLEDevice.cpp
  • src/NimBLEServer.cpp
  • src/NimBLEService.cpp
  • src/NimBLEService.h
💤 Files with no reviewable changes (1)
  • src/NimBLEDevice.cpp

@h2zero h2zero force-pushed the bugfix/create-server-crash branch from 771180c to 40db200 Compare March 17, 2026 18:52
@h2zero h2zero force-pushed the bugfix/create-server-crash branch from 40db200 to 402be9a Compare March 17, 2026 19:00
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/NimBLEService.cpp (1)

156-167: Ahoy! Consider usin' clearServiceDefinitions() on these error paths, cap'n.

When ble_gatts_count_cfg or ble_gatts_add_svcs fails, ye be settin' only m_pSvcDef->type = 0, but the characteristics and descriptors arrays allocated earlier (lines 106, 124) be left danglin' until the next voyage or when the ship be scuttled (destructor).

While this ain't causin' a true leak (the destructor or next start_internal() call will swab the deck), callin' clearServiceDefinitions() directly would free the treasure immediately and keep the code consistent.

🏴‍☠️ Proposed fix for immediate cleanup
     int rc = ble_gatts_count_cfg(m_pSvcDef);
     if (rc != 0) {
         NIMBLE_LOGE(LOG_TAG, "ble_gatts_count_cfg failed, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
-        m_pSvcDef->type = 0; // Clear the type to indicate the service is not started/registered.
+        clearServiceDefinitions(); // Clears type and frees allocated characteristics/descriptors.
         return false;
     }

     rc = ble_gatts_add_svcs(m_pSvcDef);
     if (rc != 0) {
         NIMBLE_LOGE(LOG_TAG, "ble_gatts_add_svcs, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
-        m_pSvcDef->type = 0; // Clear the type to indicate the service is not started/registered.
+        clearServiceDefinitions(); // Clears type and frees allocated characteristics/descriptors.
         return false;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NimBLEService.cpp` around lines 156 - 167, On error paths after
ble_gatts_count_cfg and ble_gatts_add_svcs failures, call
clearServiceDefinitions() instead of only setting m_pSvcDef->type = 0 so the
characteristic and descriptor arrays allocated earlier are freed immediately;
update the two error blocks that currently set m_pSvcDef->type = 0 (the ones
handling rc != 0 for ble_gatts_count_cfg and ble_gatts_add_svcs) to invoke
clearServiceDefinitions() and then return false.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/NimBLEService.cpp`:
- Around line 156-167: On error paths after ble_gatts_count_cfg and
ble_gatts_add_svcs failures, call clearServiceDefinitions() instead of only
setting m_pSvcDef->type = 0 so the characteristic and descriptor arrays
allocated earlier are freed immediately; update the two error blocks that
currently set m_pSvcDef->type = 0 (the ones handling rc != 0 for
ble_gatts_count_cfg and ble_gatts_add_svcs) to invoke clearServiceDefinitions()
and then return false.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b965e38c-7eaa-4ba7-aa2b-f50823b3c263

📥 Commits

Reviewing files that changed from the base of the PR and between 40db200 and 402be9a.

📒 Files selected for processing (8)
  • examples/Bluetooth_5/NimBLE_extended_server/NimBLE_extended_server.ino
  • examples/Bluetooth_5/NimBLE_multi_advertiser/NimBLE_multi_advertiser.ino
  • examples/L2CAP/L2CAP_Server/L2CAP_Server.ino
  • examples/NimBLE_Secure_Server/NimBLE_Secure_Server.ino
  • examples/NimBLE_Server/NimBLE_Server.ino
  • examples/NimBLE_Server_Whitelist/NimBLE_Server_Whitelist.ino
  • src/NimBLEService.cpp
  • src/NimBLEStream.cpp
💤 Files with no reviewable changes (6)
  • examples/NimBLE_Server_Whitelist/NimBLE_Server_Whitelist.ino
  • examples/L2CAP/L2CAP_Server/L2CAP_Server.ino
  • examples/NimBLE_Secure_Server/NimBLE_Secure_Server.ino
  • examples/Bluetooth_5/NimBLE_multi_advertiser/NimBLE_multi_advertiser.ino
  • examples/NimBLE_Server/NimBLE_Server.ino
  • examples/Bluetooth_5/NimBLE_extended_server/NimBLE_extended_server.ino
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/NimBLEStream.cpp

@h2zero h2zero force-pushed the bugfix/create-server-crash branch from 402be9a to 6034b1c Compare March 17, 2026 19:15
@h2zero h2zero merged commit b8fd564 into master Mar 17, 2026
42 checks passed
@h2zero h2zero deleted the bugfix/create-server-crash branch March 17, 2026 19:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

An error occurred while running the program in an Arduino 3.3.7 environment.

1 participant