Skip to content

client.Destroy() Hangs in Streaming Mode (~25% of the time) #243

@lzrf0cuz

Description

@lzrf0cuz

client.Destroy() Hangs in Streaming Mode (~25% of the time)

Summary

client.Destroy() hangs for 10+ seconds approximately 25% of the time when using streaming mode. The SSE reader goroutine remains blocked on a network read and never exits.

Reproduction

main.go:

package main

import (
	"log"
	"os"

	"github.com/splitio/go-client/v6/splitio/client"
	"github.com/splitio/go-client/v6/splitio/conf"
)

func main() {
	apiKey := os.Getenv("SPLIT_API_KEY")
	if apiKey == "" {
		log.Fatal("SPLIT_API_KEY required")
	}

	cfg := conf.Default()
	factory, err := client.NewSplitFactory(apiKey, cfg)
	if err != nil {
		log.Fatal(err)
	}

	splitClient := factory.Client()
	if err := splitClient.BlockUntilReady(10); err != nil {
		log.Fatal(err)
	}

	log.Println("Split SDK ready")

	// Evaluate a treatment
	treatment := splitClient.Treatment("user-123", "my-feature", nil)
	log.Printf("Treatment: %s\n", treatment)

	log.Println("Calling Destroy()...")

	// THIS HANGS ~25% OF THE TIME
	splitClient.Destroy()

	log.Println("Destroy() completed")
}

Run 20 times to reproduce:

export SPLIT_API_KEY="your-api-key"

for i in {1..20}; do
  echo "=== Run $i ==="
  timeout 15s go run main.go
  if [ $? -eq 124 ]; then
    echo "TIMEOUT - HANG DETECTED"
  fi
done

Expected Behavior

Destroy() completes in ~100-200ms:

Split SDK ready
Treatment: control
Calling Destroy()...
SSE streaming exiting
Stopped streaming
Destroy() completed

Actual Behavior (25% of runs)

Hangs for 10+ seconds:

Split SDK ready
Treatment: control
Calling Destroy()...
Stopping all synchronization tasks
SSE client stopped or shutdown in progress. Ignoring.
[hangs until timeout kills it]

Test Results

Metric Value
Total runs 20
Hangs detected 5
Failure rate 25%
Hang duration 10+ seconds (or up to 1 hour without timeout)

Stack Trace During Hang

SSE reader goroutine blocked waiting for server data:

goroutine 103 [sync.Cond.Wait]:
bufio.(*Reader).ReadString()
	/opt/homebrew/opt/go/libexec/src/bufio/bufio.go:502
github.com/splitio/go-toolkit/v5/sse.(*Client).readEvents()
	go-toolkit/v5@v5.4.1/sse/sse.go:55

Main goroutine waiting for shutdown:

goroutine 54 [sync.Cond.Wait]:
github.com/splitio/go-toolkit/v5/struct/traits/lifecycle.(*Manager).AwaitShutdownComplete()
	go-toolkit/v5@v5.4.1/struct/traits/lifecycle/lifecycle.go:85
github.com/splitio/go-split-commons/v8/synchronizer.(*ManagerImpl).Stop()
	go-split-commons/v8@v8.0.0/synchronizer/manager.go:184
github.com/splitio/go-client/v6/splitio/client.(*SplitFactory).Destroy()
	go-client/v6@v6.8.1/splitio/client/factory.go:252

The SSE reader never receives the shutdown signal and waits indefinitely for server data. The HTTP/2 connection remains open until timeout (up to 1 hour).

Environment

  • SDK: github.com/splitio/go-client/v6@v6.8.1
  • Commons: github.com/splitio/go-split-commons/v8@v8.0.0
  • Toolkit: github.com/splitio/go-toolkit/v5@v5.4.1
  • Go: 1.25.4
  • Mode: Streaming (default with valid API key)

Impact

  • Production application shutdowns hang 25% of the time
  • Goroutine and connection leaks

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