microsoft/typescript-go · warning

CPU profiling already in progress

Error message

CPU profiling already in progress

What it means

Returned by CPUProfiler.StartCPUProfile when a profile session already exists (c.session != nil). The profiler is a single-slot resource guarded by a mutex, so a second concurrent or overlapping start request is rejected rather than queueing. This protects the one-writer semantics of runtime/pprof CPU profiles.

Source

Thrown at internal/pprof/pprof.go:80

		fmt.Fprintf(p.logWriter, "Memory profile: %v\n", p.memFilePath)
	}

	fmt.Fprintf(p.logWriter, "CPU profile: %v\n", p.cpuFilePath)
}

// CPUProfiler manages on-demand CPU profiling.
type CPUProfiler struct {
	mu      sync.Mutex
	session *ProfileSession
}

// StartCPUProfile starts CPU profiling, writing to the specified directory when stopped.
func (c *CPUProfiler) StartCPUProfile(profileDir string) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.session != nil {
		return errors.New("CPU profiling already in progress")
	}

	if err := os.MkdirAll(profileDir, 0o755); err != nil {
		return fmt.Errorf("failed to create profile directory: %w", err)
	}

	cpuProfilePath := filepath.Join(profileDir, fmt.Sprintf("%d-%d-cpuprofile.pb.gz", os.Getpid(), time.Now().UnixMilli()))
	cpuFile, err := os.Create(cpuProfilePath)
	if err != nil {
		return fmt.Errorf("failed to create CPU profile file: %w", err)
	}

	if err := pprof.StartCPUProfile(cpuFile); err != nil {
		cpuFile.Close()
		os.Remove(cpuProfilePath)
		return fmt.Errorf("failed to start CPU profile: %w", err)
	}

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Call StopCPUProfile (or the corresponding stop request) before starting again
  2. Treat this error as an idempotency signal: optionally stop-then-restart in one sequence
  3. Track profiling state on the caller side so concurrent triggers are serialized

Example fix

// before
if err := profiler.StartCPUProfile(dir); err != nil { return err }

// after
if err := profiler.StartCPUProfile(dir); err != nil {
	if strings.Contains(err.Error(), "already in progress") {
		_, _ = profiler.StopCPUProfile() // reclaim, then retry once
		return profiler.StartCPUProfile(dir)
	}
	return err
}
Defensive patterns

Strategy: validation

Validate before calling

// track state on the caller and only start when idle
if !profilingActive.Swap(true) {
	if err := profiler.StartCPUProfile(dir); err != nil {
		profilingActive.Store(false)
		return err
	}
}

Try / catch

if err := profiler.StartCPUProfile(dir); err != nil {
	if errors.Is(err, errProfilingActive) || strings.Contains(err.Error(), "already in progress") {
		_, _ = profiler.StopCPUProfile()
		return profiler.StartCPUProfile(dir)
	}
	return err
}

Prevention

When it happens

Trigger: Calling the start-CPU-profile API/endpoint twice without an intervening stop; a client reconnect and re-issues the start request believing the old session died; two monitoring tools attached to the same server both starting profiles.

Common situations: Automation scripts that retry the start call on timeout; orphaned session after the stop request was lost; misconfigured dashboards polling a profiling endpoint with start instead of status.

Related errors


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/459d9ee1a0f6252a. Report an issue: GitHub.