| 388 | } |
| 389 | |
| 390 | func (c *counterInt64) TryUpdateIfStale(ctx context.Context, staleness time.Duration, executeNewValueFn func(ctx context.Context) (int64, error)) (int64, error) { |
| 391 | ctx, span := common.StartSpan(ctx, "CounterInt64.TryUpdateIfStale", |
| 392 | trace.WithAttributes( |
| 393 | attribute.String("key", c.key), |
| 394 | attribute.Int64("staleness_ms", staleness.Milliseconds()), |
| 395 | ), |
| 396 | ) |
| 397 | defer span.End() |
| 398 | |
| 399 | if !c.IsStale(staleness) { |
| 400 | span.SetAttributes(attribute.Bool("skipped_not_stale", true)) |
| 401 | return c.value.Load(), nil |
| 402 | } |
| 403 | |
| 404 | // Track time waiting for mutex |
| 405 | _, mutexSpan := common.StartSpan(ctx, "CounterInt64.TryUpdateIfStale.AcquireMutex") |
| 406 | c.updateMu.Lock() |
| 407 | mutexSpan.End() |
| 408 | defer c.updateMu.Unlock() |
| 409 | |
| 410 | // Double-check staleness after acquiring mutex |
| 411 | if !c.IsStale(staleness) { |
| 412 | span.SetAttributes(attribute.Bool("skipped_not_stale_after_mutex", true)) |
| 413 | return c.value.Load(), nil |
| 414 | } |
| 415 | |
| 416 | initialVal := c.value.Load() |
| 417 | span.SetAttributes(attribute.Int64("initial_value", initialVal)) |
| 418 | // IMPORTANT: Foreground path MUST be local-only and bounded by updateMaxWait. |
| 419 | // Do NOT acquire distributed locks or do any remote I/O here, otherwise degraded shared state |
| 420 | // (e.g. Redis lock acquisition) can block normal request flow. |
| 421 | span.SetAttributes(attribute.Bool("foreground_remote_io_disabled", true)) |
| 422 | |
| 423 | // Execute the refresh function (e.g., RPC call to get latest block) in background |
| 424 | resultCh := make(chan refreshResult, 1) |
| 425 | go func() { |
| 426 | fnCtx, fnCancel := context.WithTimeout(c.registry.appCtx, c.registry.fallbackTimeout) |
| 427 | defer fnCancel() |
| 428 | |
| 429 | // Create a span for the actual RPC/refresh call - this is usually what takes time |
| 430 | _, fnSpan := common.StartSpan(ctx, "CounterInt64.TryUpdateIfStale.ExecuteRefresh", |
| 431 | trace.WithAttributes( |
| 432 | attribute.String("key", c.key), |
| 433 | attribute.Int64("timeout_ms", c.registry.fallbackTimeout.Milliseconds()), |
| 434 | ), |
| 435 | ) |
| 436 | value, err := executeNewValueFn(fnCtx) |
| 437 | if err != nil { |
| 438 | fnSpan.SetAttributes(attribute.String("error", err.Error())) |
| 439 | } else { |
| 440 | fnSpan.SetAttributes(attribute.Int64("result_value", value)) |
| 441 | } |
| 442 | fnSpan.End() |
| 443 | |
| 444 | resultCh <- refreshResult{val: value, err: err} |
| 445 | }() |
| 446 | |
| 447 | timer := time.NewTimer(c.registry.updateMaxWait) |