Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-04-30 - Map Allocation Overhead in Statusline Plugins
**Learning:** In performance-sensitive areas like concurrent plugin execution in the status line, calling functions like `colors.ColorMap()` repeatedly causes expensive map allocations per plugin execution, increasing memory usage and GC pressure.
**Action:** Cache static data maps at the instance level (e.g., in the `StatusLine` struct) during initialization to ensure thread-safety, minimize allocations, and improve plugin dispatch performance.
12 changes: 11 additions & 1 deletion internal/statusline/statusline.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type StatusLine struct {
isIdle bool
bashPlugins []plugin.Plugin // Cached discovered bash plugins
bashPluginsOnce sync.Once
colorsMap map[string]string // Cached static colors map to avoid allocations
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Sharing a single map instance across concurrent plugin executions introduces a potential data race. In Go, maps are not thread-safe for concurrent read/write operations. Since renderLine executes plugins in parallel goroutines, if any native plugin modifies the Colors map, or if one plugin is being serialized to JSON (a read operation) while another modifies it (a write operation), the application will panic. While plugins are generally expected to treat this map as read-only, the previous implementation using colors.ColorMap() provided isolation by returning a fresh map for each execution. Please ensure that all consumers treat this map as immutable.

Suggested change
colorsMap map[string]string // Cached static colors map to avoid allocations
colorsMap map[string]string // Cached static colors map (read-only) to avoid allocations

colorsMapOnce sync.Once
}

// New creates a new StatusLine renderer
Expand All @@ -48,6 +50,14 @@ func New(input Input, cfg config.Config) *StatusLine {
}
}

// getColorsMap returns a cached colors map for plugin execution
func (sl *StatusLine) getColorsMap() map[string]string {
sl.colorsMapOnce.Do(func() {
sl.colorsMap = colors.ColorMap()
})
return sl.colorsMap
}

// discoverBashPlugins discovers bash plugins once and caches them
func (sl *StatusLine) discoverBashPlugins() []plugin.Plugin {
sl.bashPluginsOnce.Do(func() {
Expand Down Expand Up @@ -634,7 +644,7 @@ func (sl *StatusLine) runPlugin(name string) string {
ContextWindowSize: sl.input.Context.ContextWindow,
},
Config: sl.getPluginConfig(name),
Colors: colors.ColorMap(),
Colors: sl.getColorsMap(), // Use cached instance-level map to avoid allocations per execution
}

// Try native plugin first (much faster - no subprocess)
Expand Down
Loading