Skip to content

RegisterDecoder races with concurrent stream decoding #886

Description

@sylvesterkaczmarek

Summary

ssestream.RegisterDecoder mutates the package-level decoder registry while ssestream.NewDecoder reads the same map without synchronization. Because both functions are public and no init-only restriction is documented, registering a decoder while requests are opening streams can race and can terminate the process with fatal error: concurrent map writes.

Reproduction

On current main at 66688d6990b364431e7698d9466180bd9db3dc93, run concurrent RegisterDecoder calls alongside NewDecoder calls under the race detector:

var wg sync.WaitGroup
for i := 0; i < 4; i++ {
    wg.Add(1)
    go func(worker int) {
        defer wg.Done()
        for j := 0; j < 500; j++ {
            ssestream.RegisterDecoder(
                fmt.Sprintf("application/x-race-%d-%d", worker, j),
                func(io.ReadCloser) ssestream.Decoder { return nil },
            )
        }
    }(i)
}
for i := 0; i < 4; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        for j := 0; j < 500; j++ {
            decoder := ssestream.NewDecoder(&http.Response{
                Header: http.Header{"Content-Type": []string{"text/event-stream"}},
                Body: io.NopCloser(strings.NewReader("")),
            })
            _ = decoder.Close()
        }
    }()
}
wg.Wait()

go test -race reports reads in NewDecoder racing with writes in RegisterDecoder; the reproduction can also terminate with fatal error: concurrent map writes.

Expected behavior

The public decoder registry should be safe to read while another goroutine registers a decoder. A small read/write lock around registry access is sufficient; decoder execution itself should remain outside the lock.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions