What Is the Singleflight Package in CasaOS? A Complete Guide to Duplicate Call Suppression

The singleflight package in CasaOS provides duplicate function call suppression, ensuring that only the first goroutine executes an expensive operation while subsequent concurrent callers wait and share the same result.

The singleflight package in CasaOS is a lightweight implementation of the Go concurrency pattern originally found in golang.org/x/sync/singleflight. Located in pkg/singleflight/singleflight.go, this utility prevents redundant work by coordinating concurrent requests for the same resource across the CasaOS codebase.

How the Singleflight Package Works in CasaOS

The singleflight pattern solves the "thundering herd" problem where multiple goroutines simultaneously request the same expensive data. Instead of executing the operation multiple times, the singleflight package in CasaOS guarantees that only one execution occurs per unique key, with all waiting callers receiving the identical result.

Core Mechanism: In-Flight Deduplication

When a CasaOS component calls Group.Do(key, fn), the package checks if a function call with that key is already in progress. If so, the caller blocks until the active call completes. If not, the caller becomes the leader and executes the function. This mechanism is critical for operations like fetching Docker container metadata or probing network devices where duplicate I/O would waste system resources.

Result Sharing and the Shared Flag

The Result struct returned by singleflight operations contains three fields: Val (the returned value), Err (any error that occurred), and Shared (a boolean indicating whether the result was reused from an in-flight call). The Shared flag allows CasaOS components to log or metricize cache hits versus fresh fetches.

Key Features of the CasaOS Singleflight Implementation

According to the source code in IceWhaleTech/CasaOS, the implementation offers these capabilities:

  • Generic type support: The Group[T any] type uses Go generics to work with any return type safely.
  • Panic propagation: Panics in the executing function are captured as panicError and re-thrown to all waiting callers.
  • Runtime.Goexit handling: The package correctly propagates runtime.Goexit calls from the executing goroutine.
  • Explicit invalidation: The Forget(key) method removes a key from the internal map, forcing the next request to execute the function fresh.
  • Channel-based API: DoChan provides a non-blocking alternative that returns a channel for result retrieval.

CasaOS Singleflight Usage Examples

Basic Synchronous Calls with Do()

The most common pattern uses Do() to synchronize access to expensive operations. In pkg/singleflight/singleflight.go, the Do method signature accepts a string key and a parameterless function.

package main

import (
    "github.com/IceWhaleTech/CasaOS/pkg/singleflight"
)

// Create a global group instance
var deviceInfoGroup singleflight.Group[DeviceInfo]

type DeviceInfo struct {
    Name  string
    Temp  float64
}

// fetchDeviceInfo simulates an expensive hardware query
func fetchDeviceInfo(id string) (DeviceInfo, error) {
    // Implementation that talks to the device...
    return DeviceInfo{Name: "sda", Temp: 42.0}, nil
}

// GetDeviceInfo ensures concurrent requests for the same id share one fetch
func GetDeviceInfo(id string) (DeviceInfo, error, bool) {
    return deviceInfoGroup.Do(id, func() (DeviceInfo, error) {
        return fetchDeviceInfo(id)
    })
}

Asynchronous Processing with DoChan()

For non-blocking scenarios, CasaOS components use DoChan(), which returns immediately with a channel that will receive the Result when the operation completes.

// GetDeviceInfoAsync returns a channel instead of blocking
func GetDeviceInfoAsync(id string) <-chan singleflight.Result[DeviceInfo] {
    return deviceInfoGroup.DoChan(id, func() (DeviceInfo, error) {
        return fetchDeviceInfo(id)
    })
}

// Consumer handles the result asynchronously
func consumer(id string) {
    ch := GetDeviceInfoAsync(id)
    res := <-ch  // blocks here until fetch completes
    
    if res.Err != nil {
        return
    }
    // res.Shared indicates if this was a shared result
    _ = res.Shared
    _ = res.Val
}

Cache Invalidation with Forget()

To force a refresh of cached data, perhaps after a configuration change, call Forget() with the specific key. This removes the entry from the internal m map in singleflight.go.

// InvalidateDeviceCache forces the next call to re-fetch
func InvalidateDeviceCache(id string) {
    deviceInfoGroup.Forget(id)
}

Implementation Details and Source Code Location

The core implementation resides in pkg/singleflight/singleflight.go within the CasaOS repository. The Group struct maintains a map of call objects, each containing a sync.WaitGroup to block duplicate callers and a slot for the result.

The implementation handles edge cases carefully:

  1. Panic recovery: The doCall function wraps execution in a deferred recovery that stores panics as panicError types, then re-panics them in each waiting caller's goroutine.
  2. Key collision: The mu mutex protects the internal map, ensuring thread-safe access to the in-flight call registry.
  3. Memory management: Once a call completes and all waiters wake, the entry is deleted from the map unless Forget was invoked.

Summary

  • The singleflight package in CasaOS prevents duplicate execution of expensive operations across concurrent goroutines.
  • Located in pkg/singleflight/singleflight.go, it implements the standard Go singleflight pattern with generic type support.
  • Use Do() for blocking access, DoChan() for channel-based concurrency, and Forget() to invalidate cached results.
  • The Shared flag in the result indicates whether a caller received a value from an in-flight operation or triggered a fresh execution.
  • Panics and runtime.Goexit are propagated correctly to all waiting callers.

Frequently Asked Questions

What is the purpose of singleflight in CasaOS?

The singleflight package in CasaOS eliminates redundant work by ensuring that only one goroutine executes a function for a given key, while all concurrent requesters wait and receive the same result. This prevents duplicate I/O operations when multiple components simultaneously request the same device status or configuration data.

How does singleflight differ from a mutex?

While a mutex prevents concurrent access to a critical section, singleflight specifically suppresses duplicate function executions. A mutex would block callers but still require each to execute the function sequentially; singleflight shares the result of a single execution with all waiters, reducing total work from N executions to 1.

When should I use DoChan instead of Do?

Use DoChan when you need non-blocking behavior or want to integrate with a select statement for timeouts or cancellation. Do blocks the caller until the result is ready, while DoChan returns immediately with a channel that receives the Result when the operation completes.

How do I force a refresh of cached data when using singleflight?

Call Forget(key) on the Group instance to remove a specific key from the internal map. This forces the next call with that key to execute the function fresh, rather than waiting for or receiving an existing in-flight result. This is useful for cache invalidation scenarios in CasaOS hardware polling routines.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →