go-client icon indicating copy to clipboard operation
go-client copied to clipboard

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

Open lzrf0cuz opened this issue 1 month ago • 0 comments

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/[email protected]/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/[email protected]/struct/traits/lifecycle/lifecycle.go:85
github.com/splitio/go-split-commons/v8/synchronizer.(*ManagerImpl).Stop()
	go-split-commons/[email protected]/synchronizer/manager.go:184
github.com/splitio/go-client/v6/splitio/client.(*SplitFactory).Destroy()
	go-client/[email protected]/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

Impact

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

lzrf0cuz avatar Nov 20 '25 22:11 lzrf0cuz