Skip to content

fix(config): make DefaultPaths() thread-safe with sync.Once #94

@CalvinAllen

Description

@CalvinAllen

Make config.DefaultPaths() thread-safe and eliminate redundant filesystem calls.

Problem

DefaultPaths() is called repeatedly without proper synchronization:

// Current: not thread-safe, potential race condition
func DefaultPaths() *Paths {
    if defaultPaths == nil {
        defaultPaths = initPaths() // calls getRootDir() which calls os.UserHomeDir()
    }
    return defaultPaths
}

Issues:

  1. Race condition: If called from multiple goroutines, defaultPaths check and assignment is not atomic
  2. Repeated calls: os.UserHomeDir() may be called multiple times before caching takes effect
  3. No env var caching: DTVEM_ROOT environment variable is checked on every initialization
  4. Excessive path rebuilding: Every call to DefaultPaths().Shims, DefaultPaths().Bin, etc. in different parts of the codebase relies on proper caching

Impact: Every shim invocation calls this multiple times (e.g., in mapShimToRuntime, findInSystemPath). While currently single-threaded, this is a latent bug that will manifest if concurrency is added.

Also Addresses: Excessive filepath.Join() and Path Resolution

This fix also resolves the concern about repeated filepath.Join() calls throughout the shim:

shimDir := config.DefaultPaths().Shims  // Called here
// Later in mapShimToRuntime, another call to DefaultPaths()
// And potentially in findInSystemPath

With sync.Once, the Paths struct (containing Shims, Bin, Runtimes, Cache) is computed exactly once. All subsequent calls to DefaultPaths() return the cached struct with pre-computed absolute paths - no repeated filepath.Join() or os.Getwd() calls.

Recommended Solution

Use sync.Once for thread-safe, guaranteed single initialization:

var (
    defaultPaths *Paths
    pathsOnce    sync.Once
)

func DefaultPaths() *Paths {
    pathsOnce.Do(func() {
        defaultPaths = initPaths()
    })
    return defaultPaths
}

func initPaths() *Paths {
    // Cache env var lookup once
    root := os.Getenv("DTVEM_ROOT")
    if root == "" {
        home, err := os.UserHomeDir()
        if err != nil {
            // Handle error appropriately
            panic(fmt.Sprintf("failed to get home directory: %v", err))
        }
        root = filepath.Join(home, ".dtvem")
    }

    return &Paths{
        Root:     root,
        Bin:      filepath.Join(root, "bin"),
        Shims:    filepath.Join(root, "shims"),
        Runtimes: filepath.Join(root, "runtimes"),
        Cache:    filepath.Join(root, "cache"),
    }
}

Implementation Tasks

  • Add sync.Once variable for path initialization
  • Refactor DefaultPaths() to use sync.Once
  • Cache DTVEM_ROOT env var in the single initialization
  • Cache os.UserHomeDir() result
  • Ensure all path fields use absolute paths (no relative path resolution needed)
  • Add unit tests for concurrent access
  • Add unit tests for DTVEM_ROOT override
  • Run race detector (go test -race) to verify fix

Expected Impact

  • Eliminates race condition in path initialization
  • Guarantees os.UserHomeDir() is called exactly once
  • filepath.Join() for path construction happens once at startup
  • Environment variable lookup happens once at startup
  • Safe for future concurrent usage

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions