Skip to content

lightning: synchronize sdk service access#4185

Draft
bznein wants to merge 1 commit into
BitBoxSwiss:staging-sparkfrom
bznein:race_condition_breeze
Draft

lightning: synchronize sdk service access#4185
bznein wants to merge 1 commit into
BitBoxSwiss:staging-sparkfrom
bznein:race_condition_breeze

Conversation

@bznein

@bznein bznein commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Guard Breez SDK service reads and reconnects with a mutex, and route payment operations through the active SDK helper so disconnects cannot race direct service access.

Before asking for reviews, here is a check list of the most common things you might need to consider:

  • updating the Changelog
  • writing unit tests
  • checking if your changes affect other coins or tokens in unintended ways
  • testing on multiple environments (Qt, Android, ...)
  • having an AI review your changes

@bznein

bznein commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR introduces thread-safe access to the Breez SDK service instance by adding a sync.Mutex to the Lightning struct. A new activeSDK() helper centralizes initialization validation while holding the lock and returns either the active SDK or an initialization error. All SDK lifecycle operations (Disconnect, connect) and data-access methods now acquire the mutex-protected SDK reference through activeSDK() instead of directly accessing the SDK field. This pattern is applied consistently across the core API methods (CheckActive, Balance) and all payment methods (ParsePaymentInput, PreparePayment, SendPayment, BoardingAddress, ReceivePayment, ListPayments).

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/lightning/lightning.go`:
- Around line 139-147: activeSDK() returns the sdkService pointer after
releasing the mutex, creating a race where Disconnect() can call Destroy() and
nil out sdkService while the caller still uses it; change sdkServiceMu from
sync.Mutex to sync.RWMutex and replace activeSDK() with a helper that acquires
an RLock for the duration of the SDK operation (either expose a new
withActiveSDK(fn(*breez_sdk_spark.BreezSdk) error) that holds RLock/RUnlock
around calling fn or require callers to take RLock/RUnlock themselves), update
Balance(), other callers, connect() and Disconnect() to use RLock for reads and
Lock for mutations, and ensure Disconnect() calls Destroy() while holding the
exclusive Lock to prevent use-after-destroy of sdkService.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 831949bd-1be3-4397-b5c1-a5fa2ee22ad1

📥 Commits

Reviewing files that changed from the base of the PR and between 363be55 and e57e87a.

📒 Files selected for processing (2)
  • backend/lightning/lightning.go
  • backend/lightning/payments.go

Comment thread backend/lightning/lightning.go Outdated
Comment on lines +139 to +147
func (lightning *Lightning) activeSDK() (*breez_sdk_spark.BreezSdk, error) {
lightning.sdkServiceMu.Lock()
defer lightning.sdkServiceMu.Unlock()

if lightning.Account() == nil || lightning.sdkService == nil {
return nil, errp.New("Lightning not initialized")
}
return lightning.sdkService, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

activeSDK() releases the lock before the SDK is actually used, leaving a race window.

The mutex is released when activeSDK() returns (via defer), but the caller then uses the returned SDK pointer without holding the lock. If Disconnect() is called concurrently between activeSDK() returning and the SDK method call, the SDK will be destroyed while still in use:

  1. Thread A: sdkService, _ := activeSDK() → lock acquired, SDK returned, lock released
  2. Thread B: Disconnect() → acquires lock, calls Destroy(), sets sdkService = nil
  3. Thread A: sdkService.GetInfo(...)use-after-destroy

Consider using sync.RWMutex with a read lock held for the entire SDK operation duration:

🔒 Suggested pattern using RWMutex
-	sdkServiceMu sync.Mutex
+	sdkServiceMu sync.RWMutex

Then in Balance() and similar methods:

 func (lightning *Lightning) Balance() (*accounts.Balance, error) {
-	sdkService, err := lightning.activeSDK()
-	if err != nil {
+	lightning.sdkServiceMu.RLock()
+	defer lightning.sdkServiceMu.RUnlock()
+
+	if lightning.Account() == nil || lightning.sdkService == nil {
+		return nil, errp.New("Lightning not initialized")
+	}
-		return nil, err
-	}

 	ensureSynced := false
-	info, err := sdkService.GetInfo(breez_sdk_spark.GetInfoRequest{
+	info, err := lightning.sdkService.GetInfo(breez_sdk_spark.GetInfoRequest{

And Disconnect() / connect() would use Lock() for exclusive access.

Also applies to: 177-184

🤖 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 `@backend/lightning/lightning.go` around lines 139 - 147, activeSDK() returns
the sdkService pointer after releasing the mutex, creating a race where
Disconnect() can call Destroy() and nil out sdkService while the caller still
uses it; change sdkServiceMu from sync.Mutex to sync.RWMutex and replace
activeSDK() with a helper that acquires an RLock for the duration of the SDK
operation (either expose a new withActiveSDK(fn(*breez_sdk_spark.BreezSdk)
error) that holds RLock/RUnlock around calling fn or require callers to take
RLock/RUnlock themselves), update Balance(), other callers, connect() and
Disconnect() to use RLock for reads and Lock for mutations, and ensure
Disconnect() calls Destroy() while holding the exclusive Lock to prevent
use-after-destroy of sdkService.

Guard Breez SDK service reads and reconnects with a mutex, and route payment
operations through the active SDK helper so disconnects cannot race direct
service access.
@bznein bznein force-pushed the race_condition_breeze branch from e57e87a to a4fc12d Compare June 9, 2026 11:21
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.

1 participant