Socket.IO for Go v3.0.0 is a major release that brings the following key improvements:
| Feature | Description |
|---|---|
| Monorepo Consolidation | All previously separate repositories (engine.io-go-parser, engine.io, socket.io-go-parser, socket.io-client-go, socket.io-go-redis) have been merged into a single monorepo with 9 versioned submodules |
| Unified Version Management | All modules share a single version definition in pkg/version, ensuring consistency across the entire ecosystem |
| Protocol Alignment | Aligned with the Socket.IO v4+ protocol for improved compatibility with the JavaScript ecosystem |
| Thread Safety | Comprehensive concurrency fixes including atomic socket flags, mutex-protected middleware, copy-on-write patterns, and goroutine leak prevention |
| Type Safety | Generic types.Atomic[T] replacing atomic.Value, types.Optional[T] for null safety, strongly typed Handshake fields |
| New Utility Packages | pkg/slices for safe slice operations, pkg/queue for ordered message delivery, pkg/request for HTTP client |
| Redis Cluster Support | Sharded broadcast operator, CROSSSLOT error fixes, and dynamic channel subscription management |
| DoS Prevention | HTTP body size limits on polling transport, configurable attachment count limits |
| Go 1.26.0 | Minimum Go version requirement updated to Go 1.26.0 |
github.com/zishang520/socket.io/
βββ v3 # Root: shared types, interfaces
βββ parsers/
β βββ engine/v3 # Engine.IO protocol parser
β βββ socket/v3 # Socket.IO protocol parser
βββ servers/
β βββ engine/v3 # Engine.IO server
β βββ socket/v3 # Socket.IO server
βββ clients/
β βββ engine/v3 # Engine.IO client
β βββ socket/v3 # Socket.IO client
βββ adapters/
βββ adapter/v3 # Base adapter interface
βββ redis/v3 # Redis adapter (+ emitter)
We recommend reviewing this entire upgrade guide to understand all changes. The upgrade process consolidates dependencies and updates import paths to align with the Socket.IO v4+ protocol, introducing improved performance and updated APIs.
Dependency Structure Consolidation
All Socket.IO related packages are now consolidated under the main github.com/zishang520/socket.io/ repository with versioned submodules.
Likelihood Of Impact: Very High
This change affects every import in your application. All imports must be updated to use the new v3 paths.
Protocol Compatibility Update
Socket.IO v3 aligns with the Socket.IO v4+ protocol, which means compatibility changes for all client connections.
Likelihood Of Impact: Very High
Your client-side Socket.IO library must be upgraded to version 4.x or higher. Clients using older versions (v2.x or v3.x) will not be able to connect to the v3 server.
# Update your frontend dependency
npm install socket.io-client@^4.0.0Action Required:
- Coordinate with your frontend team to upgrade client libraries
- Test all client connections after upgrade
- Ensure backward compatibility strategy if gradual rollout is needed
Import Path Restructuring
Every Socket.IO import path requires updating to the new v3 structure. This affects 8 major package categories.
Likelihood Of Impact: Very High
All package imports across your entire codebase must be systematically updated. This includes:
- Engine.IO Parser (
parsers/engine/v3) - Socket.IO Parser (
parsers/socket/v3) - Engine.IO Server (
servers/engine/v3) - Socket.IO Server (
servers/socket/v3) - Redis Adapter (
adapters/redis/v3) - Valkey Adapter (
adapters/valkey/v3) β new in v3 - Engine.IO Client (
clients/engine/v3) - Socket.IO Client (
clients/socket/v3) - Common Types and Utils (
v3/pkg)
See the Import Path Updates section for complete mapping tables.
Redis Adapter Type Changes
The Redis adapter has replaced types.String with types.Atomic[string] for better type safety.
Likelihood Of Impact: High (if using Redis adapter)
// Before
import "github.com/zishang520/socket.io-go-redis/types"
var s types.String
// After
import "github.com/zishang520/socket.io/v3/pkg/types"
var s types.Atomic[string]Socket Handshake Type Updates
The socket.Handshake structure now uses more strongly typed fields:
Likelihood Of Impact: Medium
// Before
type Handshake struct {
Headers map[string][]string
Query map[string][]string
Auth any
}
// After
type Handshake struct {
Headers types.IncomingHttpHeaders // provides Header() method
Query types.ParsedUrlQuery // provides Query() method
Auth map[string]any
}Access patterns must be updated:
// Before
headers := socket.Handshake().Headers
userAgent := headers["user-agent"][0]
// After
headers := socket.Handshake().Headers.Header()
userAgent := headers.Get("User-Agent")HttpContext API Refactoring
Several methods and properties of *types.HttpContext have been renamed or refactored from properties to methods.
Likelihood Of Impact: Medium
// Before
func example(ctx *types.HttpContext) {
headers := ctx.ResponseHeaders
host := ctx.GetHost()
method := ctx.GetMethod()
values := ctx.Gets("foo")
value := ctx.Get("bar")
path := ctx.GetPathInfo()
}
// After
func example(ctx *types.HttpContext) {
headers := ctx.ResponseHeaders()
host := ctx.Host()
method := ctx.Method()
values, _ := ctx.Query().Gets("foo")
value, _ := ctx.Query().Get("bar")
path := ctx.PathInfo()
}Changes summary:
ResponseHeadersβResponseHeaders()(property to method)GetHost()βHost()GetMethod()βMethod()Gets(key)βQuery().Gets(key)Get(key)βQuery().Get(key)GetPathInfo()βPathInfo()
New/Updated methods:
| Method | Description |
|---|---|
Path() |
Returns cleaned path (without leading/trailing slashes) |
UserAgent() |
Returns User-Agent header value |
Secure() |
Returns true if TLS connection |
SetStatusCode(code) |
Now returns error for validation |
IsDone() |
Check if response has been written |
Done() |
Returns <-chan struct{} instead of <-chan Void |
Config GetRaw* Method Changes
All GetRaw* methods now return types.Optional[T] instead of pointer types for better null safety:
Likelihood Of Impact: Medium
// Before
func configExample(config ConnectionStateRecoveryInterface) {
if duration := config.GetRawMaxDisconnectionDuration(); duration != nil {
fmt.Printf("Duration: %d", *duration)
}
}
// After
func configExample(config ConnectionStateRecoveryInterface) {
if duration := config.GetRawMaxDisconnectionDuration(); duration != nil {
fmt.Printf("Duration: %d", duration.Get())
}
}ParameterBag Package Migration
ParameterBag has been moved from the utils package to the types package.
Likelihood Of Impact: Medium
// Before
import "github.com/zishang520/socket.io/v3/pkg/utils"
func example() {
var bag *utils.ParameterBag
bag = utils.NewParameterBag(nil)
}
// After
import "github.com/zishang520/socket.io/v3/pkg/types"
func example() {
var bag *types.ParameterBag
bag = types.NewParameterBag(nil)
}Adapter Utility Functions Reorganization
Utility functions like SliceMap and Tap have been moved from the adapter package to dedicated pkg subpackages.
Likelihood Of Impact: Medium
// Before
import "github.com/zishang520/socket.io/adapters/adapter/v3"
func example() {
adapter.SliceMap(/**/)
adapter.Tap(/**/)
}
// After
import (
"github.com/zishang520/socket.io/v3/pkg/slices"
"github.com/zishang520/socket.io/v3/pkg/utils"
)
func example() {
slices.Map(/**/)
utils.Tap(/**/)
}Changes summary:
adapter.SliceMapβslices.Map(moved topkg/slices)adapter.Tapβutils.Tap(moved topkg/utils)
New functions in pkg/slices:
The new pkg/slices package provides additional utility functions:
| Function | Description |
|---|---|
Get(s, idx) |
Safely retrieves an element with bounds checking |
GetAny[O](vals, idx) |
Retrieves and type-asserts from []any |
TryGet(s, idx) |
Returns zero value if out of bounds |
TryGetAny[O](vals, idx) |
Type-asserts from []any or returns zero |
GetWithDefault(s, idx, def) |
Returns default value if out of bounds |
GetPtr(s, idx) |
Returns pointer to element or nil |
Slice(s, start) |
Safe sub-slice with bounds checking |
First(s) / Last(s) |
Get first/last element safely |
Filter(s, predicate) |
Filter elements by predicate |
Map(vals, transform) |
Transform each element |
Reduce(vals, initial, reducer) |
Reduce to single value |
IsEmpty(s) |
Check if slice is nil or empty |
IsValidIndex(s, idx) |
Check if index is valid |
ExtendedError Type Consolidation
The ExtendedError type has been consolidated from separate implementations in clients/socket and servers/socket packages into a single shared implementation in pkg/types. This eliminates code duplication and provides a consistent error type across the entire codebase.
Likelihood Of Impact: Medium
// Before (client-side)
import "github.com/zishang520/socket.io-client-go/socket"
err := socket.NewExtendedError("connection failed", nil)
// Before (server-side)
import "github.com/zishang520/socket.io/v2/socket"
err := socket.NewExtendedError("middleware error", map[string]any{"code": 401})
data := err.Data() // Note: server-side had Data() method
// After (unified)
import "github.com/zishang520/socket.io/v3/pkg/types"
err := types.NewExtendedError("error message", map[string]any{"code": 401})
data := err.Data // Now uses direct field accessKey changes:
clients/socket.ExtendedErrorβtypes.ExtendedErrorservers/socket.ExtendedErrorβtypes.ExtendedError(type alias maintained for backward compatibility)- Server-side
Data()method replaced withDatafield for consistency - Both client and server now share the same
ExtendedErrorimplementation
Note: The server-side socket package retains a type alias for ExtendedError and a wrapper function NewExtendedError for backward compatibility, so existing server code may continue to work without changes. However, client-side code must update imports.
Redis SubscriptionMode Type Migration
The SubscriptionMode type has been moved from adapters/redis/adapter package to the root adapters/redis package for better organization and sharing between adapter and emitter.
Likelihood Of Impact: Medium (if using Redis sharded adapter)
// Before
import "github.com/zishang520/socket.io/adapters/redis/v3/adapter"
opts := adapter.NewShardedRedisAdapterOptions()
opts.SetSubscriptionMode(adapter.DynamicSubscriptionMode)
// After
import (
"github.com/zishang520/socket.io/adapters/redis/v3"
"github.com/zishang520/socket.io/adapters/redis/v3/adapter"
)
opts := adapter.NewShardedRedisAdapterOptions()
opts.SetSubscriptionMode(redis.DynamicSubscriptionMode)Key changes:
| Before | After |
|---|---|
adapter.SubscriptionMode |
redis.SubscriptionMode |
adapter.StaticSubscriptionMode |
redis.StaticSubscriptionMode |
adapter.DynamicSubscriptionMode |
redis.DynamicSubscriptionMode |
adapter.DynamicPrivateSubscriptionMode |
redis.DynamicPrivateSubscriptionMode |
New additions:
redis.DefaultSubscriptionMode- Default mode constantredis.PrivateRoomIdLength- Length constant for private room detectionredis.ShouldUseDynamicChannel(mode, room)- Shared helper function
Emitter options extended:
The EmitterOptions now supports sharded Pub/Sub configuration:
emitterOpts := emitter.NewEmitterOptions()
emitterOpts.SetSharded(true)
emitterOpts.SetSubscriptionMode(redis.DynamicSubscriptionMode)Debug Logging Improvements
Debug logging has been updated to provide more consistent output across all packages.
Likelihood Of Impact: Low
No code changes required, but log output format may differ slightly.
Internal Type Reorganization
Some internal types have been reorganized for better code maintainability. These changes should not affect public API usage but may impact code that relies on internal types.
Likelihood Of Impact: Low
If you're importing internal packages, review your imports after upgrading.
Update your go.mod to require the Socket.IO v3 packages:
go get github.com/zishang520/socket.io/v3@latest
go get github.com/zishang520/socket.io/parsers/engine/v3@latest
go get github.com/zishang520/socket.io/parsers/socket/v3@latest
go get github.com/zishang520/socket.io/servers/engine/v3@latest
go get github.com/zishang520/socket.io/servers/socket/v3@latest
go get github.com/zishang520/socket.io/adapters/adapter/v3@latest
go get github.com/zishang520/socket.io/adapters/redis/v3@latest
go get github.com/zishang520/socket.io/clients/engine/v3@latest
go get github.com/zishang520/socket.io/clients/socket/v3@latestClean up your dependencies after updating:
go mod tidyExample go.mod entries:
require (
github.com/zishang520/socket.io/v3 v3.0.0
github.com/zishang520/socket.io/parsers/engine/v3 v3.0.0
github.com/zishang520/socket.io/parsers/socket/v3 v3.0.0
github.com/zishang520/socket.io/servers/engine/v3 v3.0.0
github.com/zishang520/socket.io/servers/socket/v3 v3.0.0
github.com/zishang520/socket.io/adapters/adapter/v3 v3.0.0
github.com/zishang520/socket.io/adapters/redis/v3 v3.0.0
github.com/zishang520/socket.io/clients/engine/v3 v3.0.0
github.com/zishang520/socket.io/clients/socket/v3 v3.0.0
)Update all Socket.IO import paths throughout your application using the following reference tables:
| v1/v2 Import | v3 Import |
|---|---|
github.com/zishang520/engine.io-go-parser/packet |
github.com/zishang520/socket.io/parsers/engine/v3/packet |
github.com/zishang520/engine.io-go-parser/parser |
github.com/zishang520/socket.io/parsers/engine/v3/parser |
github.com/zishang520/engine.io-go-parser/types |
github.com/zishang520/socket.io/v3/pkg/types |
github.com/zishang520/engine.io-go-parser/utils |
github.com/zishang520/socket.io/v3/pkg/utils |
| v1/v2 Import | v3 Import |
|---|---|
github.com/zishang520/socket.io-go-parser/parser |
github.com/zishang520/socket.io/parsers/socket/v3/parser |
github.com/zishang520/socket.io-go-parser/v2/parser |
github.com/zishang520/socket.io/parsers/socket/v3/parser |
| v1/v2 Import | v3 Import |
|---|---|
github.com/zishang520/engine.io/config |
github.com/zishang520/socket.io/servers/engine/v3/config |
github.com/zishang520/engine.io/v2/config |
github.com/zishang520/socket.io/servers/engine/v3/config |
github.com/zishang520/engine.io/engine |
github.com/zishang520/socket.io/servers/engine/v3 |
github.com/zishang520/engine.io/v2/engine |
github.com/zishang520/socket.io/servers/engine/v3 |
github.com/zishang520/engine.io/errors |
github.com/zishang520/socket.io/servers/engine/v3/errors |
github.com/zishang520/engine.io/v2/errors |
github.com/zishang520/socket.io/servers/engine/v3/errors |
github.com/zishang520/engine.io/events |
github.com/zishang520/socket.io/v3/pkg/events |
github.com/zishang520/engine.io/v2/events |
github.com/zishang520/socket.io/v3/pkg/events |
github.com/zishang520/engine.io/log |
github.com/zishang520/socket.io/v3/pkg/log |
github.com/zishang520/engine.io/v2/log |
github.com/zishang520/socket.io/v3/pkg/log |
github.com/zishang520/engine.io/transports |
github.com/zishang520/socket.io/servers/engine/v3/transports |
github.com/zishang520/engine.io/v2/transports |
github.com/zishang520/socket.io/servers/engine/v3/transports |
github.com/zishang520/engine.io/types |
github.com/zishang520/socket.io/v3/pkg/types |
github.com/zishang520/engine.io/v2/types |
github.com/zishang520/socket.io/v3/pkg/types |
github.com/zishang520/engine.io/utils |
github.com/zishang520/socket.io/v3/pkg/utils |
github.com/zishang520/engine.io/v2/utils |
github.com/zishang520/socket.io/v3/pkg/utils |
github.com/zishang520/engine.io/v2/webtransport |
github.com/zishang520/socket.io/v3/pkg/webtransport |
| v1/v2 Import | v3 Import |
|---|---|
github.com/zishang520/socket.io/socket |
github.com/zishang520/socket.io/servers/socket/v3 |
github.com/zishang520/socket.io/v2/socket |
github.com/zishang520/socket.io/servers/socket/v3 |
github.com/zishang520/socket.io/v2/adapter |
github.com/zishang520/socket.io/adapters/adapter/v3 |
| v1 Import | v3 Import |
|---|---|
github.com/zishang520/socket.io-go-redis/adapter |
github.com/zishang520/socket.io/adapters/redis/v3/adapter |
github.com/zishang520/socket.io-go-redis/emitter |
github.com/zishang520/socket.io/adapters/redis/v3/emitter |
github.com/zishang520/socket.io-go-redis/types |
github.com/zishang520/socket.io/adapters/redis/v3 |
| Before (adapter subpackage) | After (redis root package) |
|---|---|
adapter.SubscriptionMode |
redis.SubscriptionMode |
adapter.StaticSubscriptionMode |
redis.StaticSubscriptionMode |
adapter.DynamicSubscriptionMode |
redis.DynamicSubscriptionMode |
adapter.DynamicPrivateSubscriptionMode |
redis.DynamicPrivateSubscriptionMode |
The Valkey adapter is a new, independent module introduced in v3. It mirrors the adapters/redis module but uses the valkey-go client.
go get github.com/zishang520/socket.io/adapters/valkey/v3@latest| Package | Import Path |
|---|---|
| Root types & client | github.com/zishang520/socket.io/adapters/valkey/v3 |
| Classic / Sharded / Streams adapters | github.com/zishang520/socket.io/adapters/valkey/v3/adapter |
| Emitter | github.com/zishang520/socket.io/adapters/valkey/v3/emitter |
Example go.mod:
require (
github.com/zishang520/socket.io/adapters/valkey/v3 v3.x.y
)Usage:
import (
"context"
vk "github.com/valkey-io/valkey-go"
valkey "github.com/zishang520/socket.io/adapters/valkey/v3"
vkadapter "github.com/zishang520/socket.io/adapters/valkey/v3/adapter"
)
client, _ := vk.NewClient(vk.ClientOption{InitAddress: []string{"localhost:6379"}})
valkeyClient := valkey.NewValkeyClient(context.Background(), client)
server.SetAdapter(&vkadapter.ValkeyAdapterBuilder{Valkey: valkeyClient})Read/write separation (recommended for production):
pubClient, _ := vk.NewClient(vk.ClientOption{InitAddress: []string{"master:6379"}})
subClient, _ := vk.NewClient(vk.ClientOption{InitAddress: []string{"replica:6380"}})
valkeyClient := valkey.NewValkeyClientWithSub(context.Background(), pubClient, subClient)
server.SetAdapter(&vkadapter.ValkeyAdapterBuilder{Valkey: valkeyClient})| v1 Import | v3 Import |
|---|---|
github.com/zishang520/engine.io-client-go/engine |
github.com/zishang520/socket.io/clients/engine/v3 |
github.com/zishang520/engine.io-client-go/request |
github.com/zishang520/socket.io/v3/pkg/request |
github.com/zishang520/engine.io-client-go/transports |
github.com/zishang520/socket.io/clients/engine/v3/transports |
| v1 Import | v3 Import |
|---|---|
github.com/zishang520/socket.io-client-go/socket |
github.com/zishang520/socket.io/clients/socket/v3 |
github.com/zishang520/socket.io-client-go/utils |
github.com/zishang520/socket.io/v3/pkg/utils |
| Old Import | v3 Import |
|---|---|
clients/socket.ExtendedError |
github.com/zishang520/socket.io/v3/pkg/types.ExtendedError |
servers/socket.ExtendedError |
github.com/zishang520/socket.io/v3/pkg/types.ExtendedError |
Tip: Use
grep -r "github.com/zishang520" .to find all old imports, then use find-and-replace to update them systematically.
Socket.IO v3 aligns with the Socket.IO v4+ protocol. Ensure your client-side Socket.IO library is updated to version 4.x or higher.
npm install socket.io-client@^4.0.0If you're using the Redis adapter, you must replace all instances of types.String with types.Atomic[string]:
// Before
import "github.com/zishang520/socket.io-go-redis/types"
func example() {
var roomName types.String
roomName.Store("lobby")
value := roomName.Load()
}
// After
import "github.com/zishang520/socket.io/v3/pkg/types"
func example() {
var roomName types.Atomic[string]
roomName.Store("lobby")
value := roomName.Load()
}Update code that accesses handshake headers and query parameters:
// Before
func handleConnection(socket *socket.Socket) {
headers := socket.Handshake().Headers
userAgent := headers["user-agent"][0]
query := socket.Handshake().Query
token := query["token"][0]
}
// After
func handleConnection(socket *socket.Socket) {
headers := socket.Handshake().Headers.Header()
userAgent := headers.Get("User-Agent")
query := socket.Handshake().Query.Query()
token := query.Get("token")
}Update code that uses GetRaw* configuration methods:
// Before
func configExample(config ConnectionStateRecoveryInterface) {
if duration := config.GetRawMaxDisconnectionDuration(); duration != nil {
fmt.Printf("Duration: %d", *duration)
}
}
// After
func configExample(config ConnectionStateRecoveryInterface) {
if duration := config.GetRawMaxDisconnectionDuration(); duration != nil {
fmt.Printf("Duration: %d", duration.Get())
}
}Update *utils.ParameterBag to *types.ParameterBag:
// Before
import "github.com/zishang520/socket.io/v3/pkg/utils"
var bag *utils.ParameterBag = utils.NewParameterBag(nil)
// After
import "github.com/zishang520/socket.io/v3/pkg/types"
var bag *types.ParameterBag = types.NewParameterBag(nil)Transport upgrade methods now return []string instead of *types.Set[string]:
// Before
upgrades := transport.Upgrades() // *types.Set[string]
// After
upgrades := transport.Upgrades() // []string| Before | After |
|---|---|
ctx.ResponseHeaders |
ctx.ResponseHeaders() |
ctx.GetHost() |
ctx.Host() |
ctx.GetMethod() |
ctx.Method() |
ctx.Gets("key") |
ctx.Query().Gets("key") |
ctx.Get("key") |
ctx.Query().Get("key") |
ctx.GetPathInfo() |
ctx.PathInfo() |
| Before | After |
|---|---|
adapter.SliceMap(...) |
slices.Map(...) |
adapter.Tap(...) |
utils.Tap(...) |
Server-side Data() method is now a field:
// Before
data := err.Data()
// After
data := err.DataIf using the sharded Redis adapter, update SubscriptionMode imports:
// Before
import "github.com/zishang520/socket.io/adapters/redis/v3/adapter"
opts.SetSubscriptionMode(adapter.DynamicSubscriptionMode)
// After
import "github.com/zishang520/socket.io/adapters/redis/v3"
opts.SetSubscriptionMode(redis.DynamicSubscriptionMode)Here is a minimal server example after upgrading to v3:
package main
import (
"fmt"
"net/http"
server "github.com/zishang520/socket.io/servers/socket/v3"
)
func main() {
io := server.NewServer(nil, nil)
io.On("connection", func(args ...any) {
socket := args[0].(*server.Socket)
fmt.Printf("connected: %s\n", socket.Id())
socket.On("message", func(args ...any) {
fmt.Printf("received: %v\n", args)
socket.Emit("message", args...)
})
socket.On("disconnect", func(args ...any) {
fmt.Printf("disconnected: %s\n", socket.Id())
})
})
http.Handle("/socket.io/", io.ServeHandler(nil))
fmt.Println("server listening on :3000")
http.ListenAndServe(":3000", nil)
}After completing the upgrade, thoroughly test your application:
go test ./...- Client connections and disconnections
- Event emission and reception
- Namespace and room operations
- Redis adapter broadcasting (if applicable)
Set the DEBUG environment variable to enable verbose logging:
# Linux / macOS
DEBUG=socket.io:* go run main.go
# Windows PowerShell
$env:DEBUG="socket.io:*"; go run main.goEnsure your frontend uses Socket.IO client v4.x or higher.
npm install socket.io-client@^4.0.0The examples/benchmark module provides a built-in benchmark test for validating performance:
cd examples/benchmark
go run main.gogo mod tidy
go clean -modcache
go mod downloadnpm install socket.io-client@^4.0.0If you encounter errors with Data() method calls on ExtendedError:
// Before (server-side)
data := err.Data()
// After
data := err.Data- GitHub Issues β for confirmed bugs or feature requests
- GitHub Discussions β for general questions and help
- Go Package Documentation β API reference
- Socket.IO Protocol Documentation β protocol specification
- Socket.IO Go Repository β source code and examples
Released on 2026-04-13
This is the first stable release of Socket.IO for Go v3. It includes all changes from the alpha, beta, and RC phases.
- Monorepo consolidation: 6 separate repositories merged into one monorepo with 9 versioned Go submodules
- Unified versioning: Single version source at
pkg/version/version.goshared by all modules - Go 1.26.0 minimum: Takes advantage of the latest Go features
- Protocol alignment: Compatible with Socket.IO v4+ JavaScript clients
- Thread safety overhaul: Atomic socket flags (copy-on-write), mutex-protected middleware,
sync.OnceValuefor lazy initialization, goroutine leak prevention viaruntime.SetFinalizer - Type safety improvements: Generic
types.Atomic[T],types.Optional[T]for null safety, strongly typedHandshakefields (IncomingHttpHeaders,ParsedUrlQuery) - New packages:
pkg/slices(safe slice operations),pkg/queue(sequential task queue for message ordering),pkg/request(HTTP client) - Redis Cluster support: Sharded broadcast operator, CROSSSLOT error fixes, dynamic channel subscriptions, pagination for session restoration
- Security hardening: HTTP body size limits on polling (DoS prevention), configurable attachment count limits (default 10), immutable packet encoding
- Code quality: golangci-lint integration,
errcheckviolations resolved, magic numbers replaced with named constants, standardized debug logging
For a complete migration guide from v1/v2, see Upgrading from v1/v2 to v3.
See the individual RC/beta/alpha release notes below for detailed per-release changes.
Released from commit
cc50fc2
Parser: ERROR_PACKET Removed from Public API
Likelihood Of Impact: Low (only if directly referencing ERROR_PACKET)
The shared mutable ERROR_PACKET singleton has been removed from the public API to prevent data race conditions. It has been replaced with an internal newErrorPacket() factory function that creates a fresh instance each time, avoiding shared mutable state across goroutines.
// Before (no longer works)
import "github.com/zishang520/socket.io/parsers/engine/v3/parser"
var errPkt = parser.ERROR_PACKET
// After (use alternatives)
// If you need error packet creation, use the public parser APIs
// that internally create error packets as neededImpact: This is unlikely to affect most applications since ERROR_PACKET was an internal constant. If you were using it directly, you should rely on the public parser API methods instead.
Socket Packet Encoder: Encode() No Longer Mutates Input
Likelihood Of Impact: Low
The Encode() method in the Socket.IO packet encoder now creates a copy of the packet before mutation, preventing unintended side effects on the caller's packet object.
// Before - Encode() modified the input packet's Type field
import "github.com/zishang520/socket.io/parsers/socket/v3/parser"
pkt := &packet.Packet{Type: parser.EVENT, Data: binaryData}
encoded := encoder.Encode(pkt)
// pkt.Type would now be BINARY_EVENT (mutated!)
// After - Input packet is not modified
pkt := &packet.Packet{Type: parser.EVENT, Data: binaryData}
encoded := encoder.Encode(pkt)
// pkt.Type remains EVENT (not mutated)Impact: This is a behavior fix that makes code more predictable. If your code was relying on the side effect of Encode() mutating the input packet, you need to update it to handle packets immutably.
Socket.IO Parser: Configurable Attachment Count Limit
Likelihood Of Impact: Low
The attachment limit has been reduced from a hardcoded 1000 to a configurable per-decoder instance default of 10 (aligned with the upstream Node.js implementation). The limit is now controlled via DecoderOptions instead of a package-level constant.
import "github.com/zishang520/socket.io/parsers/socket/v3/parser"
// Default - limited to 10 attachments per packet
decoder := parser.NewDecoder()
// Custom limit for applications that need more attachments
decoder := parser.NewDecoder(&parser.DecoderOptions{
MaxAttachments: 50,
})Packets exceeding the limit will be rejected with parser.ErrTooManyAttachments.
Impact: Applications sending more than 10 attachments in a single packet will now be rejected. If you encounter this error, split large payloads into multiple packets or configure a higher limit.
Engine.IO Polling: HTTP Body Size Limit
Likelihood Of Impact: Medium (only if sending very large payloads via polling)
The polling transport now enforces MaxHttpBufferSize limit on request body reads to prevent unbounded memory consumption (DoS prevention).
// Before - No limit on body size
// Large payloads could cause excessive memory usage
// After - Limited by MaxHttpBufferSize (default 1 MB)
// Large payloads exceeding the limit are truncated/rejectedImpact: If you're sending payloads larger than MaxHttpBufferSize (default 1 MB) via polling transport, they will be truncated or rejected. Use WebSocket/WebTransport for larger messages or increase the limit:
import "github.com/zishang520/socket.io/servers/engine/v3/config"
opts := config.DefaultServerOptions()
opts.SetMaxHttpBufferSize(10 * 1024 * 1024) // 10 MBWebSocket/WebTransport: Send Loop Behavior
Likelihood Of Impact: Very Low
Fixed send loop early return bug that was previously dropping remaining packets in queue when an encoded frame was sent successfully.
// Before - Send loop would return after first packet, dropping queue
// Packet 1: sent
// Packet 2, 3, ...: dropped (never sent)
// After - Send loop continues processing all queued packets
// All packets in queue are sent correctlyImpact: This is a bug fix that improves reliability. Previously, only the first queued packet would be sent; now all queued packets are sent as expected. No code changes required.
Middleware Thread Safety
Likelihood Of Impact: Very Low (only if modifying middleware during runtime)
Engine.IO base server now protects middleware slice with sync.RWMutex for concurrent-safe reading and modification.
// Before - Unsafe concurrent middleware modification
go server.Use(middleware1) // Racing writes
go server.Use(middleware2) // Could panic or miss middleware
// After - Thread-safe middleware operations
go server.Use(middleware1) // Safe
go server.Use(middleware2) // SafeImpact: This is a thread safety fix. No code changes required.
Socket Flags: Concurrent Mutation Safety
Likelihood Of Impact: Very Low
Socket flag mutations (Compress, Volatile, Timeout) now use atomic.Pointer with copy-on-write to prevent race conditions.
// Before - Racing flag mutations could cause data races
go socket.Compress(true)
go socket.Volatile()
// Data race condition possible
// After - All flag mutations are thread-safe
go socket.Compress(true)
go socket.Volatile()
// Safe concurrent mutationsImpact: This is a thread safety fix. No code changes required.
Queue: Goroutine Leak Prevention
Likelihood Of Impact: Very Low
The task queue now uses runtime.SetFinalizer() to prevent goroutine leaks when queue instances are garbage collected.
Impact: This is a resource leak fix. Applications with long-running queues may see reduced goroutine count. No code changes required.
Message Ordering and OOM Prevention
Likelihood Of Impact: Very Low
Resolves #116. A new sequential task queue (pkg/queue) preserves message ordering and prevents OOM under high concurrency. Both client and server transports now use this queue for send operations.
Impact: This is a reliability fix. No code changes required.
- Debug logging standardized across all packages using
pkg/log - Magic numbers replaced with named constants throughout the codebase
- Client constants extracted and network monitoring leak fixed
- Go minimum version is now 1.26.0
Released from commit
5b988b6
- Go 1.26.0 required: Minimum Go version bumped to 1.26.0
- golangci-lint integration: Linting is now integrated into the build system via
Makefiles - Improved error handling:
errcheckviolations resolved across the entire codebase, replacing error suppression with proper handling or explicitio.Closerpatterns
- Enhanced polling mechanism and added pagination for session restoration
- Improved dynamic channel subscription management in the sharded Redis adapter
- Added
MessageTypevalidation and improved error handling
- Fixed nil pointer dereference caused by race condition in Engine.IO (
76a0015) - Fixed
Peekmethod added toBuffertype with integer overflow protection (ef32276,5d3ea31)
Released from commit
e854211
ExtendedError Type Consolidation
Likelihood Of Impact: Medium
The ExtendedError type has been consolidated from separate implementations in clients/socket and servers/socket packages into a single shared implementation in pkg/types. This eliminates code duplication and provides a consistent error type across the entire codebase.
// Before (client-side)
import "github.com/zishang520/socket.io-client-go/socket"
err := socket.NewExtendedError("connection failed", nil)
// Before (server-side)
import "github.com/zishang520/socket.io/v2/socket"
err := socket.NewExtendedError("middleware error", map[string]any{"code": 401})
data := err.Data() // Note: server-side had Data() method
// After (unified)
import "github.com/zishang520/socket.io/v3/pkg/types"
err := types.NewExtendedError("error message", map[string]any{"code": 401})
data := err.Data // Now uses direct field accessKey changes:
clients/socket.ExtendedErrorβtypes.ExtendedErrorservers/socket.ExtendedErrorβtypes.ExtendedError(type alias maintained for backward compatibility)- Server-side
Data()method replaced withDatafield for consistency - Both client and server now share the same
ExtendedErrorimplementation
Note: The server-side socket package retains a type alias for ExtendedError and a wrapper function NewExtendedError for backward compatibility, so existing server code may continue to work without changes. However, client-side code must update imports.
Redis SubscriptionMode Type Migration
Likelihood Of Impact: Medium (if using Redis sharded adapter)
The SubscriptionMode type has been moved from adapters/redis/adapter package to the root adapters/redis package for better organization and sharing between adapter and emitter.
// Before
import "github.com/zishang520/socket.io/adapters/redis/v3/adapter"
opts := adapter.NewShardedRedisAdapterOptions()
opts.SetSubscriptionMode(adapter.DynamicSubscriptionMode)
// After
import (
"github.com/zishang520/socket.io/adapters/redis/v3"
"github.com/zishang520/socket.io/adapters/redis/v3/adapter"
)
opts := adapter.NewShardedRedisAdapterOptions()
opts.SetSubscriptionMode(redis.DynamicSubscriptionMode)Key changes:
| Before | After |
|---|---|
adapter.SubscriptionMode |
redis.SubscriptionMode |
adapter.StaticSubscriptionMode |
redis.StaticSubscriptionMode |
adapter.DynamicSubscriptionMode |
redis.DynamicSubscriptionMode |
adapter.DynamicPrivateSubscriptionMode |
redis.DynamicPrivateSubscriptionMode |
New additions:
redis.DefaultSubscriptionMode- Default mode constantredis.PrivateRoomIdLength- Length constant for private room detectionredis.ShouldUseDynamicChannel(mode, room)- Shared helper function
Emitter options extended:
emitterOpts := emitter.NewEmitterOptions()
emitterOpts.SetSharded(true)
emitterOpts.SetSubscriptionMode(redis.DynamicSubscriptionMode)- Added sharded broadcast operator for Redis Cluster support (
d83b4db) - Fixed timeout when fetching sockets from empty rooms (
d5cfa20) - Fixed Redis Cluster CROSSSLOT errors by managing separate PubSub clients per channel (
2629cc1) - Improved binary packet handling and code organization
Released from commit
b2f5457
Adapter Utility Functions Reorganization
Likelihood Of Impact: Medium
Utility functions SliceMap and Tap have been moved from the adapter package to dedicated pkg subpackages.
// Before
import "github.com/zishang520/socket.io/adapters/adapter/v3"
func example() {
adapter.SliceMap(/**/)
adapter.Tap(/**/)
}
// After
import (
"github.com/zishang520/socket.io/v3/pkg/slices"
"github.com/zishang520/socket.io/v3/pkg/utils"
)
func example() {
slices.Map(/**/)
utils.Tap(/**/)
}Changes summary:
adapter.SliceMapβslices.Map(moved topkg/slices)adapter.Tapβutils.Tap(moved topkg/utils)
New functions in pkg/slices:
The new pkg/slices package provides additional utility functions:
| Function | Description |
|---|---|
Get(s, idx) |
Safely retrieves an element with bounds checking |
GetAny[O](vals, idx) |
Retrieves and type-asserts from []any |
TryGet(s, idx) |
Returns zero value if out of bounds |
TryGetAny[O](vals, idx) |
Type-asserts from []any or returns zero |
GetWithDefault(s, idx, def) |
Returns default value if out of bounds |
GetPtr(s, idx) |
Returns pointer to element or nil |
Slice(s, start) |
Safe sub-slice with bounds checking |
First(s) / Last(s) |
Get first/last element safely |
Filter(s, predicate) |
Filter elements by predicate |
Map(vals, transform) |
Transform each element |
Reduce(vals, initial, reducer) |
Reduce to single value |
IsEmpty(s) |
Check if slice is nil or empty |
IsValidIndex(s, idx) |
Check if index is valid |
HttpContext API Refactoring
Likelihood Of Impact: Medium
Several methods and properties of *types.HttpContext have been renamed or refactored from properties to methods. All lazy-loaded methods now use sync.OnceValue for thread safety.
// Before
func example(ctx *types.HttpContext) {
headers := ctx.ResponseHeaders
host := ctx.GetHost()
method := ctx.GetMethod()
values := ctx.Gets("foo")
value := ctx.Get("bar")
path := ctx.GetPathInfo()
}
// After
func example(ctx *types.HttpContext) {
headers := ctx.ResponseHeaders()
host := ctx.Host()
method := ctx.Method()
values, _ := ctx.Query().Gets("foo")
value, _ := ctx.Query().Get("bar")
path := ctx.PathInfo()
}Changes summary:
ResponseHeadersβResponseHeaders()(property to method)GetHost()βHost()GetMethod()βMethod()Gets(key)βQuery().Gets(key)Get(key)βQuery().Get(key)GetPathInfo()βPathInfo()
New/Updated methods:
| Method | Description |
|---|---|
Path() |
Returns cleaned path (without leading/trailing slashes) |
UserAgent() |
Returns User-Agent header value |
Secure() |
Returns true if TLS connection |
SetStatusCode(code) |
Now returns error for validation |
IsDone() |
Check if response has been written |
Done() |
Returns <-chan struct{} instead of <-chan Void |
ParameterBag Package Migration
Likelihood Of Impact: Medium
ParameterBag has been moved from the utils package to the types package.
// Before
import "github.com/zishang520/socket.io/v3/pkg/utils"
func example() {
var bag *utils.ParameterBag
bag = utils.NewParameterBag(nil)
}
// After
import "github.com/zishang520/socket.io/v3/pkg/types"
func example() {
var bag *types.ParameterBag
bag = types.NewParameterBag(nil)
}Released from commit
d7c93b5
Socket Handshake Type Updates
Likelihood Of Impact: Medium
The socket.Handshake structure now uses more strongly typed fields:
// Before
type Handshake struct {
Headers map[string][]string
Query map[string][]string
Auth any
}
// After
type Handshake struct {
Headers types.IncomingHttpHeaders // provides Header() method
Query types.ParsedUrlQuery // provides Query() method
Auth map[string]any
}Access patterns must be updated:
// Before
headers := socket.Handshake().Headers
userAgent := headers["user-agent"][0]
// After
headers := socket.Handshake().Headers.Header()
userAgent := headers.Get("User-Agent")Auth Parameter Standardization
Likelihood Of Impact: Medium
The Auth field in Handshake is now standardized to map[string]any instead of any. This provides a consistent type for authentication data.
// Before
auth := socket.Handshake().Auth // type: any
if authMap, ok := auth.(map[string]any); ok {
token := authMap["token"]
}
// After
auth := socket.Handshake().Auth // type: map[string]any
token := auth["token"]Optional[T] Enhancements
Likelihood Of Impact: Low
The Optional[T] interface now includes IsPresent() and IsEmpty() methods, and Some.Get() handles nil receiver gracefully.
if duration := config.GetRawMaxDisconnectionDuration(); duration != nil && duration.IsPresent() {
fmt.Printf("Duration: %d", duration.Get())
}Released from commit
540c239
- Fixed panic when client sends nil payload in Socket.IO parser (
80fe0b9)
- Replaced
GetRaw*method calls with direct property access for better readability (ce8f623)
Released from commit
01f5eca
Config GetRaw* Method Changes
Likelihood Of Impact: Medium
All GetRaw* methods now return types.Optional[T] instead of pointer types for better null safety:
// Before
func configExample(config ConnectionStateRecoveryInterface) {
if duration := config.GetRawMaxDisconnectionDuration(); duration != nil {
fmt.Printf("Duration: %d", *duration)
}
}
// After
func configExample(config ConnectionStateRecoveryInterface) {
if duration := config.GetRawMaxDisconnectionDuration(); duration != nil {
fmt.Printf("Duration: %d", duration.Get())
}
}- Fixed HTTP/2 connection goroutine leaks in
HTTPClient.Close()(069619b) - Fixed timer goroutine leaks adapted from upstream (
ff5d935)
Alpha releases covering the initial v3 restructuring
- Dependency Consolidation: All previously separate repositories (
engine.io-go-parser,engine.io,socket.io-go-parser,socket.io-client-go,socket.io-go-redis) have been merged into a single monorepo with versioned submodules - Import Path Restructuring: All package import paths updated to the new
github.com/zishang520/socket.io/namespace (see Import Path Updates) - Type-safe Atomic Types:
atomic.Valuereplaced with generictypes.Atomic[T]for type safety (7389549) - Redis Adapter Type Updates:
types.Stringreplaced withtypes.Atomic[string] - Server Options Refactoring: Consolidated server options interfaces and structures for improved clarity (
a396fef) - Transport Upgrade Methods: Updated to return
[]stringinstead of*types.Set[string](f3c4cd8) - Version Management: Added
cmd/socket.iomodule with version command and per-module version files
| Recommendation | Details |
|---|---|
| Backup First | Always backup your codebase before upgrading |
| Go Version | Ensure you're using Go 1.26.0 or higher |
| Staged Rollout | Consider upgrading non-critical components first |
| Client Coordination | Coordinate with frontend team for Socket.IO client v4.x+ compatibility |
| Security Updates | v3.0.0 includes important DoS prevention and data race fixes |
| Vendor Directory | If using go mod vendor, run go mod vendor after updating dependencies |
| IDE Support | Restart your IDE/language server after updating imports for accurate code completion |