Skip to content

Commit f82df20

Browse files
authored
add random backoff to avoid livelock (#1647)
Signed-off-by: Hayim.Shaul@ibm.com <hayimsha@fhe03.vpc.cloud9.ibm.com>
1 parent 06bf470 commit f82df20

10 files changed

Lines changed: 1652 additions & 95 deletions

File tree

docs/configuration.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,31 @@ token:
292292
Security: 256
293293
SW:
294294
Hash: SHA2
295+
# Auditor lock configuration for enrollment ID locking during audit operations
296+
# These settings control the retry behavior when multiple auditors compete for locks
297+
auditor:
298+
lock:
299+
# maxRetries is the maximum number of retry attempts for lock acquisition
300+
# Default: 10
301+
maxRetries: 10
302+
303+
# initialBackoff is the initial backoff delay before the first retry
304+
# Default: 10ms
305+
initialBackoff: 10ms
306+
307+
# maxBackoff is the maximum backoff delay between retries
308+
# Default: 5s
309+
maxBackoff: 5s
310+
311+
# backoffMultiplier is the exponential backoff multiplier
312+
# Each retry delay is multiplied by this factor
313+
# Default: 2.0
314+
backoffMultiplier: 2.0
315+
316+
# jitterFactor is the randomization factor to prevent thundering herd (0.0 to 1.0)
317+
# Adds random jitter to break symmetry when multiple auditors retry simultaneously
318+
# Default: 0.3 (30%)
319+
jitterFactor: 0.3
295320
Security: 256
296321
```
297322
@@ -420,6 +445,57 @@ Default values:
420445
- Increase `workerCount` to 8-16 to improve parallel processing
421446
- Decrease `scanInterval` to 2-3s for faster recovery detection
422447

448+
---
449+
450+
### Optional: token.tms.<name>.auditor.lock
451+
452+
If not specified, the default configuration is:
453+
454+
```yaml
455+
token:
456+
tms:
457+
<name>:
458+
auditor:
459+
lock:
460+
maxRetries: 10
461+
initialBackoff: 10ms
462+
maxBackoff: 5s
463+
backoffMultiplier: 2.0
464+
jitterFactor: 0.3
465+
```
466+
467+
Default values:
468+
469+
- maxRetries: 10
470+
- initialBackoff: 10ms
471+
- maxBackoff: 5s
472+
- backoffMultiplier: 2.0
473+
- jitterFactor: 0.3
474+
475+
**Parameter Descriptions:**
476+
477+
- **maxRetries**: Maximum number of retry attempts when acquiring locks on enrollment IDs during audit operations
478+
- **initialBackoff**: Initial delay before the first retry attempt
479+
- **maxBackoff**: Maximum delay between retry attempts (exponential backoff is capped at this value)
480+
- **backoffMultiplier**: Factor by which the backoff delay increases after each retry (exponential growth)
481+
- **jitterFactor**: Randomization factor (0.0 to 1.0) added to backoff delays to prevent multiple auditors from retrying simultaneously (prevents thundering herd problem)
482+
483+
**Tuning Recommendations:**
484+
485+
1. **For High-Contention Environments:**
486+
- Increase `maxRetries` to 15-20 to handle more lock conflicts
487+
- Increase `maxBackoff` to 10s to spread out retry attempts
488+
- Keep `jitterFactor` at 0.3 or higher to maintain randomization
489+
490+
2. **For Low-Latency Requirements:**
491+
- Decrease `initialBackoff` to 5ms for faster initial retries
492+
- Decrease `maxBackoff` to 2s to avoid long waits
493+
- Increase `backoffMultiplier` to 3.0 for faster exponential growth
494+
495+
3. **For Resource-Constrained Environments:**
496+
- Decrease `maxRetries` to 5 to fail faster
497+
- Keep default backoff settings to balance retry attempts with resource usage
498+
423499
2. **For Resource-Constrained Environments:**
424500
- Decrease `batchSize` to 50 to reduce memory usage
425501
- Decrease `workerCount` to 2 to reduce CPU load

token/services/auditor/auditor.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/hyperledger-labs/fabric-token-sdk/token/services/tokens"
2323
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx/dep"
2424
"github.com/hyperledger-labs/fabric-token-sdk/token/services/ttx/finality"
25+
"github.com/hyperledger-labs/fabric-token-sdk/token/services/utils"
2526
token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token"
2627
"go.opentelemetry.io/otel/trace"
2728
)
@@ -81,9 +82,11 @@ type Service struct {
8182
metricsProvider metrics.Provider
8283
metrics *Metrics
8384
checkService CheckService
85+
lockConfig *LockConfig
8486
}
8587

8688
// NewService creates a new auditor Service with the provided dependencies.
89+
// If lockConfig is nil, default lock configuration will be used.
8790
func NewService(
8891
tmsID token.TMSID,
8992
networkProvider NetworkProvider,
@@ -93,7 +96,12 @@ func NewService(
9396
finalityTracer trace.Tracer,
9497
metricsProvider metrics.Provider,
9598
checkService CheckService,
99+
lockConfig *LockConfig,
96100
) *Service {
101+
if lockConfig == nil {
102+
lockConfig = DefaultLockConfig()
103+
}
104+
97105
return &Service{
98106
tmsID: tmsID,
99107
networkProvider: networkProvider,
@@ -104,6 +112,7 @@ func NewService(
104112
metricsProvider: metricsProvider,
105113
metrics: newMetrics(metricsProvider),
106114
checkService: checkService,
115+
lockConfig: lockConfig,
107116
}
108117
}
109118

@@ -113,7 +122,8 @@ func (a *Service) Validate(ctx context.Context, request *token.Request) error {
113122
}
114123

115124
// Audit extracts the list of inputs and outputs from the passed transaction.
116-
// In addition, Audit acquires locks on the enrollment IDs involved in the transaction.
125+
// In addition, the Audit locks the enrollment named ids with retry logic and exponential backoff
126+
// to prevent livelock conditions.
117127
// The caller MUST call Release() to unlock these enrollment IDs after processing.
118128
//
119129
// IMPORTANT: The defer Release() statement MUST be placed immediately after checking
@@ -141,18 +151,47 @@ func (a *Service) Audit(ctx context.Context, tx Transaction) (*token.InputStream
141151
var eids []string
142152
eids = append(eids, record.Inputs.EnrollmentIDs()...)
143153
eids = append(eids, record.Outputs.EnrollmentIDs()...)
144-
logger.DebugfContext(ctx, "audit transaction [%s], acquire locks", tx.ID())
145-
if err := a.auditDB.AcquireLocks(ctx, string(request.Anchor), eids...); err != nil {
154+
155+
// Acquire locks with retry and exponential backoff to prevent livelock
156+
logger.DebugfContext(ctx, "audit transaction [%s], acquire locks with retry", tx.ID())
157+
if err := a.acquireLocksWithRetry(ctx, string(request.Anchor), eids); err != nil {
146158
a.metrics.AuditLockConflicts.Add(1)
147159

148160
return nil, nil, err
149161
}
162+
150163
logger.DebugfContext(ctx, "audit transaction [%s], acquire locks done", tx.ID())
151164
a.metrics.AuditDuration.Observe(time.Since(start).Seconds())
152165

153166
return record.Inputs, record.Outputs, nil
154167
}
155168

169+
// acquireLocksWithRetry attempts to acquire locks with exponential backoff and randomized jitter
170+
// to prevent livelock conditions when multiple auditors compete for the same enrollment IDs.
171+
// This implements the mitigation strategy for deadlock/livelock prevention.
172+
func (a *Service) acquireLocksWithRetry(ctx context.Context, anchor string, eids []string) error {
173+
// Create a retry runner with jitter support
174+
retryRunner := utils.NewRetryRunnerWithJitter(
175+
logger,
176+
a.lockConfig.MaxRetries,
177+
a.lockConfig.InitialBackoff,
178+
a.lockConfig.MaxBackoff,
179+
a.lockConfig.BackoffMultiplier,
180+
a.lockConfig.JitterFactor,
181+
)
182+
183+
// Use the retry runner to acquire locks
184+
err := retryRunner.RunWithContext(ctx, func() error {
185+
return a.auditDB.AcquireLocks(ctx, anchor, eids...)
186+
})
187+
188+
if err != nil {
189+
return errors.WithMessagef(err, "failed to acquire locks for anchor [%s]", anchor)
190+
}
191+
192+
return nil
193+
}
194+
156195
// Append adds the passed transaction to the auditor database.
157196
// It also releases the locks acquired by Audit.
158197
func (a *Service) Append(ctx context.Context, tx Transaction) error {

0 commit comments

Comments
 (0)