Skip to content

Fix sst tunnel crashing with concurrent map writes under load - #7017

Open
portlandhodl wants to merge 1 commit into
anomalyco:devfrom
portlandhodl:fix-tunnel-concurrent-map-write
Open

portlandhodl wants to merge 1 commit into
anomalyco:devfrom
portlandhodl:fix-tunnel-concurrent-map-write

Conversation

@portlandhodl

@portlandhodl portlandhodl commented Sep 29, 2026 •

Copy link
Copy Markdown

Problem

sst tunnel dies when a client opens several connections at once, e.g. a Postgres connection pool through an sst.aws.Vpc bastion:

fatal error: concurrent map read and map write

github.com/charmbracelet/lipgloss.(*Style).set          set.go:30
github.com/charmbracelet/lipgloss.Style.Bold            set.go:36
github.com/sst/sst/v3/pkg/tunnel.StartProxy.func1       pkg/tunnel/proxy.go:32
github.com/armon/go-socks5.(*Server).handleConnect

go-socks5 runs each connection's Dial in its own goroutine, and the proxy logs each one with ui.GetColor(addr).Bold(true). GetColor returns an entry from the global ui.Colors by value, but lipgloss v0.9.1 styles keep their rules in a map that is shared across value copies, so Bold(true) writes into the shared global map. Concurrent connections race on it and the Go runtime kills the process, which drops the tunnel.

Fix

GetColor now returns Colors[i].Copy(), which deep-copies the rules map, so callers can chain setters without touching shared state. proxy.go is the only caller today, but fixing it in GetColor covers any future ones too.

Testing

Added cmd/sst/mosaic/ui/ui_test.go:

  • TestGetColorConcurrentUse: 32 goroutines calling GetColor(addr).Bold(true).Render(...)
  • TestGetColorDoesNotMutateColors: checks that Colors isn't changed through GetColor

go test -race ./cmd/sst/mosaic/ui/:

  • without the fix: the test binary crashes with fatal error: concurrent map read and map write (28 DATA RACE reports)
  • with the fix: ok

🤖 Generated with Claude Code

The tunnel proxy logs each connection with ui.GetColor(addr).Bold(true)
from a separate goroutine per SOCKS connection. GetColor returned the
global Colors entry by value, but lipgloss (v0.9.1) styles share their
rules map across value copies, so Bold(true) wrote into the shared map.
When a client opened several connections at once (e.g. a Postgres pool)
the process died with "fatal error: concurrent map writes".

Return a deep copy from GetColor so callers can chain setters safely.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant