Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ name: CI

on:
push:
branches: [main, master]
branches: [master]
pull_request:
branches: [main, master]
branches: [master]

jobs:
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.23', '1.24']

steps:
- name: Checkout code
Expand All @@ -21,7 +18,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
go-version-file: 'go.mod'

- name: Download dependencies
run: go mod download
Expand All @@ -31,7 +28,6 @@ jobs:

- name: Upload coverage
uses: codecov/codecov-action@v4
if: matrix.go-version == '1.24'
with:
files: ./coverage.out
fail_ci_if_error: false
Expand All @@ -47,7 +43,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version-file: 'go.mod'

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v6
Expand All @@ -66,10 +62,10 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version-file: 'go.mod'

- name: Build
run: go build -v ./...

- name: Build example
run: go build -v ./example/...
run: go build -v -o bin/echo ./example/...
29 changes: 27 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Build output
bin/

# Binaries for programs and plugins
*.exe
*.exe~
Expand All @@ -8,6 +11,28 @@
# Test binary, build with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
# Output of the go coverage tool
*.out
.idea
coverage.out

# Profiling
*.prof

# Debug binary (dlv)
__debug_bin*

# Go workspace
go.work
go.work.sum

# IDE
.idea/
.vscode/
.claude/

# macOS
.DS_Store

# Environment
.env
.env.local
26 changes: 9 additions & 17 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: "2"

run:
timeout: 5m
modules-download-mode: readonly
Expand All @@ -14,24 +12,18 @@ linters:
- misspell
- unconvert
- unparam
exclusions:
presets:
- std-error-handling
rules:
- path: _test\.go
linters:
- unparam
- errcheck

formatters:
enable:
- gofmt
- goimports
settings:
goimports:
local-prefixes:
- github.com/Zereker/socket

issues:
exclude-rules:
- path: _test\.go
linters:
- unparam
- errcheck

linters-settings:
misspell:
locale: US
goimports:
local-prefixes: github.com/Zereker/socket
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ A simple, high-performance TCP server framework for Go.
- **Simple API** - Easy to use with functional options pattern
- **Custom Codec** - Pluggable message encoding/decoding via `io.Reader`
- **Graceful Shutdown** - Context-based cancellation support
- **Heartbeat** - Automatic read/write deadline management
- **Idle Timeout** - Automatic read/write deadline management for connection health
- **Error Handling** - Flexible error handling with `Disconnect` or `Continue` actions
- **Structured Logging** - Built-in `slog` integration

Expand Down Expand Up @@ -99,11 +99,13 @@ func main() {
| `CustomCodecOption(codec)` | Set message codec (required) | - |
| `OnMessageOption(handler)` | Set message handler (required) | - |
| `OnErrorOption(handler)` | Set error handler | Disconnect on error |
| `HeartbeatOption(duration)` | Set heartbeat interval | 30s |
| `IdleTimeoutOption(duration)` | Set idle timeout for read/write deadlines | 30s |
| `BufferSizeOption(size)` | Set send channel buffer size | 1 |
| `MessageMaxSize(size)` | Set max message size | 1MB |
| `LoggerOption(logger)` | Set custom logger | slog default |

> **Note:** The idle timeout sets TCP read/write deadlines but does not send ping/pong packets. For active connection health checking, implement heartbeat messages in your application protocol.

## Error Handling

Control how errors are handled with `OnErrorOption`:
Expand Down Expand Up @@ -134,21 +136,40 @@ addr := conn.Addr()

## Write Methods

Three ways to send messages:
Three ways to send messages with different blocking behaviors:

```go
// Non-blocking write, returns ErrBufferFull if channel is full
conn.Write(msg)
// Non-blocking write (fire-and-forget)
// Returns ErrBufferFull immediately if channel is full
// Best for: non-critical data, custom backpressure handling
err := conn.Write(msg)
if errors.Is(err, socket.ErrBufferFull) {
// Handle backpressure: drop, retry, or use blocking write
}

// Blocking write with context cancellation
// Waits for buffer space, respects context timeout/cancellation
// Best for: critical messages that must be delivered
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
conn.WriteBlocking(ctx, msg)

// Write with timeout
// Waits up to the specified duration for buffer space
// Best for: simple timeout without context management
conn.WriteTimeout(msg, 5*time.Second)
```

All write methods return `ErrConnectionClosed` if the connection is closed.

### Backpressure Handling

When `ErrBufferFull` is returned, it indicates the receiver is not consuming messages fast enough. Strategies:
- **Drop**: Acceptable for metrics, heartbeats, or non-critical updates
- **Retry with backoff**: For important but delay-tolerant messages
- **Switch to blocking**: Use `WriteBlocking` when delivery is critical
- **Flow control**: Implement application-level rate limiting

## Custom Logger

Implement the `Logger` interface or use `slog`:
Expand Down
Loading