Skip to content

feat: add native Go SSH2 client/server implementation#123

Merged
DavidObando merged 16 commits into
microsoft:mainfrom
avanderhoorn:avanderhoorn/go-support
May 1, 2026
Merged

feat: add native Go SSH2 client/server implementation#123
DavidObando merged 16 commits into
microsoft:mainfrom
avanderhoorn:avanderhoorn/go-support

Conversation

@avanderhoorn

@avanderhoorn avanderhoorn commented Mar 4, 2026

Copy link
Copy Markdown
Member

Add native Go SSH2 client/server implementation

Key overview files

Summary

This PR adds a full-parity Go implementation of the Dev Tunnels SSH library alongside
the existing C# and TypeScript implementations. A client or server built with any of
the three implementations can communicate with any other — they share the same protocol,
algorithm negotiation, extensions, and behavioral semantics.

205 files changed across source (src/go/), tests (test/go/, test/e2e/),
documentation, and CI/CD:

Area Production Lines Test Lines
ssh (core protocol) ~13,200 ~22,400
keys (key import/export) ~2,900 ~1,800
tcp (TCP + port forwarding) ~1,900 ~3,900
test/go/ (integration + interop) ~11,700
Total ~18,000 ~39,800

The test-to-production ratio is roughly 2.2:1.


Overview

The Go implementation preserves the same API shape as C# and TypeScript while adapting
to Go's concurrency model and conventions:

  • Goroutines instead of async/await. C# and TypeScript use async/await to run
    request handlers inline without blocking the dispatch loop. Go doesn't have async/await,
    so each channel gets a dedicated goroutine with a buffered request queue (capacity 16).
    This gives the same behavioral property — a slow handler on one channel never blocks
    another — using the idiomatic Go primitive.

  • Callbacks instead of events. C# uses EventHandler<T>, TypeScript uses
    vscode-jsonrpc Emitter/Event. Go uses exported func fields
    (OnAuthenticating, OnChannelOpening, OnRequest, etc.) with thread-safe
    Set*Handler() methods for late binding. This avoids pulling in an event framework
    for what amounts to single-subscriber callbacks.

  • io.ReadWriteCloser everywhere. SSH sessions accept any io.ReadWriteCloser
    — pipes, TCP connections, WebSocket adapters, anything. The Stream wrapper exposes
    channels as standard io.ReadWriteCloser so they compose with io.Copy, bufio,
    encoding/json, and the rest of the Go stdlib.

  • Zero external dependencies. The src/go/ module uses only the Go standard library.
    Crypto is handled by crypto/ecdsa, crypto/rsa, crypto/aes, etc. which delegate to
    platform-native implementations (OpenSSL on Linux, Security.framework on macOS, CNG on
    Windows). Test modules use testify for assertions.

  • PascalCase exports, camelCase internals. Follows Go naming conventions: exported
    symbols are PascalCase, unexported are camelCase. Acronyms like SSH, HMAC, AES
    are all-caps per Go convention.

  • Error values, not exceptions. All fallible operations return error. Typed errors
    (ConnectionError, ChannelError, ReconnectError) carry structured reason codes
    that map to SSH disconnect/failure reason codes.


Feature parity with C# and TypeScript

Algorithms — 100% parity

Category Algorithms (all platforms)
Key Exchange ecdh-sha2-nistp521, ecdh-sha2-nistp384, ecdh-sha2-nistp256, diffie-hellman-group16-sha512, diffie-hellman-group14-sha256, none
Public Key rsa-sha2-512, rsa-sha2-256, ecdsa-sha2-nistp521, ecdsa-sha2-nistp384, ecdsa-sha2-nistp256, none
Encryption aes256-gcm@openssh.com, aes256-cbc, aes256-ctr, none
HMAC hmac-sha2-512-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512, hmac-sha2-256, none

Features — 100% parity

Feature C# TS Go
SSH over any stream Y Y Y
Channel multiplexing Y Y Y
Extended data (stderr) Y Y Y
Auth: none, password, publickey, hostbased, keyboard-interactive Y Y Y
Session reconnection Y Y Y
Session piping (relay) Y Y Y
Channel piping Y Y Y
Port forwarding (local ↔ remote) Y Y Y
Stream-based port forwarding Y Y Y
Keep-alive (keepalive@openssh.com) Y Y Y
Key rotation (rekey) Y Y Y
Service activation pattern Y Y Y
Protocol extensions (server-sig-algs, open-channel-request, session-reconnect, session-latency) Y Y Y

Key formats — full coverage

Format C# TS Go
SSH (RFC 4253) Y Y Y
SSH2 (RFC 4716) Y Y
PKCS#1 Y Y Y
SEC1 Y Y Y
PKCS#8 (encrypted) Y Y Y
OpenSSH (encrypted) Y Y
JWK (RFC 7517) Y Y Y

Test coverage

Unit tests (~679 tests in src/go/)

Colocated with production code following Go convention. Coverage includes:

Package Test Files Focus Areas
ssh/ 55 _test.go files Session lifecycle, channel operations, auth flows, reconnection, piping, metrics, config validation, key exchange, key rotation, keep-alive, non-blocking dispatch, error handling, stress tests, benchmarks
ssh/messages/ 7 _test.go files Wire format serialization round-trips, regression tests, coverage for every message type
ssh/algorithms/ 7 _test.go files ECDH/DH key exchange, AES-GCM/CBC/CTR encryption, HMAC variants, GCM tampering detection, parity with C#/TS output
ssh/io/ 1 _test.go file BigInt encoding, SSH data reader/writer
keys/ 4 _test.go files Import/export all 7 formats, cross-format round-trips, encrypted key handling, bcrypt
tcp/ 5 _test.go files Port forwarding (16 scenarios), TCP client/server, forwarded ports collection, interop, parity tests

40 explicit parity tests verify behavioral alignment with C# and TypeScript
(named *_parity_test.go), covering encryption output, HMAC output, channel
semantics, metrics, reconnection, piping, service activation, and secure streams.

Integration tests (~158 tests in test/go/ssh-test/)

Full client/server scenarios over in-memory streams using the SessionPair test
harness (no TCP, no flakiness):

File Tests Coverage
ssh_test.go 22 Core infrastructure, version exchange, error handling
channel_request_test.go 17 Custom requests, subsystem, shell, exec, success/failure
port_forwarding_test.go 16 Remote/local forwarding, direct-tcpip, cancel, multi-hop
interop_test.go 11 Go↔C#, Go↔TS, Go↔OpenSSH over real TCP
multi_channel_stream_test.go 10 Concurrent channels, data isolation
key_exchange_test.go 10 ECDH/DH variants, none, renegotiation
auth_pubkey_test.go 9 RSA/ECDSA variants, key mismatch
channel_test.go 9 Open/close, bidirectional data, custom types
secure_stream_test.go 8 AES-GCM/CBC/CTR, HMAC variants
+ 7 more files 46 Auth, channel flow, metrics, services, keep-alive, streams

E2E tests (~48 tests in test/e2e/)

Real TCP connections with real cryptographic handshakes, orchestrated by bash scripts:

Script Tests What It Validates
e2e-go-selftest.sh 8 Go↔Go across 5 algorithm combos + 3 feature modes
e2e-interop.sh 24 Go↔C#, Go↔TS, C#↔TS across algorithm combos
e2e-go-reconnect.sh 3 Session reconnection after network failure
e2e-go-portfwd.sh 3 TCP port forwarding through SSH tunnels
e2e-go-concurrent-requests.sh 3 Non-blocking request dispatch under load
e2e-go-pipe-request.sh 1 Channel request forwarding through piped sessions
e2e-cs-selftest.sh 3 C#↔C# baseline
e2e-ts-selftest.sh 3 TS↔TS baseline

The orchestrator (e2e-validate.sh) runs all suites with a gate check
(go vet + go test -race -short) before E2E execution.


Major components — review guide

1. Core protocol (src/go/ssh/)

The heart of the implementation. Key files and what to look for:

File Lines Review Focus
session.go ~600 Session lifecycle, Connect(), Close(), version exchange. Check that Done() channel is properly closed on all exit paths.
session_dispatch.go ~400 Main message dispatch loop, KEX message queueing (RFC 4253 §7.1), custom message handler routing. This is the "event loop" — verify no message types are dropped.
session_channels.go ~300 Channel open/accept/close orchestration, per-channel request dispatch with goroutine + buffered queue. Check queue-full handling and fire-and-forget logging.
channel.go ~750 Channel data flow, window management, request handling, Pipe(). Check panic recovery doesn't deadlock (uses closeRequestsCh() not Close()). Verify Ctx is set on all RequestEventArgs.
session_encryption.go ~250 Encryption activation after KEX, algorithm selection. Verify correct algorithm pairing (cipher + HMAC + compression).
protocol.go ~350 Wire protocol: message framing, MAC verification, sequence numbers. Check that sequence number wraparound is handled.
auth_service.go ~400 Authentication state machine: none, password, publickey, hostbased, keyboard-interactive. Verify multi-attempt limiting.
key_exchange_service.go ~500 KEX negotiation, DH/ECDH exchange, host key verification. Check that key material is zeroed after use.
reconnect.go ~400 Session reconnection: token generation/verification, message cache, retransmission. Check HMAC-based token verification.
pipe.go, pipe_session.go ~350 Channel and session piping for relay scenarios. Verify payload forwarding preserves type-specific data bytes.
config.go ~270 Algorithm constants, session config, default presets. Verify preference ordering matches C#/TS.

2. Key management (src/go/keys/)

Import/export for 7 key formats. Review focus:

File Review Focus
import_export.go Auto-detection logic (PEM vs binary vs JSON). Check format detection order doesn't misclassify.
pkcs8_format.go Encrypted PKCS#8 with PBKDF2. Verify key derivation parameters.
openssh_format.go OpenSSH format with bcrypt+aes256-ctr encryption. Check padding validation.
jwk_format.go JWK import/export. Verify Base64url encoding (no padding).
bcrypt.go Custom bcrypt implementation (OpenSSH variant, different from standard bcrypt). This is security-critical.

3. TCP transport (src/go/tcp/)

TCP wrappers and port forwarding. Review focus:

File Review Focus
port_forwarding.go ~1,100 lines — the largest single file. Implements RFC 4254 §6-7. Check channel lifecycle management, TCP listener cleanup on close, and race safety in the forwarding maps.
client.go, server.go TCP dial/listen + session creation. Verify socket options (keepalive, nodelay) and error handling on connection failure.

4. Test infrastructure (test/go/ssh-test/helpers/)

File Review Focus
session_pair.go Creates connected client/server over in-memory streams. Verify cleanup in Close() — sessions before streams.
mock_network_stream.go Injects network failures. Check thread safety of disconnect signaling.

5. Interop helper (test/go/interop/go/main.go)

~1,400 line CLI binary supporting 8 test modes. Used by both integration tests
(interop_test.go) and E2E scripts. Review focus: protocol marker output
(LISTENING, AUTHENTICATED, ECHO_OK, etc.) must be deterministic and parseable.

6. CI/CD changes

File Change
.github/workflows/pr-build.yaml Added setup-go@v5, Go build/test steps, E2E test step (macOS only)
build.js Added Go to aggregate build/test commands, --race flag support
package.json Added build-go and test-go npm script aliases
.gitignore Scoped *.test pattern to src/go/ and test/go/

Public API surface and example usage

The Go library exposes three packages mirroring the C# and TypeScript structure:

Package overview

github.com/microsoft/dev-tunnels-ssh/src/go/ssh   — Core SSH protocol
github.com/microsoft/dev-tunnels-ssh/src/go/keys  — Key import/export
github.com/microsoft/dev-tunnels-ssh/src/go/tcp   — TCP wrappers + port forwarding

Core types

ssh.SessionConfig         — Algorithm lists, protocol extensions, service registrations
ssh.ClientSession         — Client-side session (Connect, Authenticate, Reconnect)
ssh.ServerSession         — Server-side session (Connect, Credentials, OnClientAuthenticated)
ssh.Channel               — Bidirectional data channel (Send, Request, Pipe, Close)
ssh.Stream                — io.ReadWriteCloser wrapper around Channel
ssh.ClientCredentials     — Username, password, public keys, provider callbacks
ssh.ServerCredentials     — Host keys, provider callbacks
ssh.KeyPair               — Interface for RSA/ECDSA key pairs

tcp.Client                — TCP SSH client (OpenSession, ReconnectSession)
tcp.Server                — TCP SSH server (AcceptSessions, Credentials)
tcp.PortForwardingService — Local/remote port forwarding, stream-based forwarding

keys.ImportKey            — Auto-detect format and import
keys.ExportPublicKey      — Export to any supported format
keys.ExportPrivateKey     — Export with optional encryption

Client example

client := tcp.NewClient(ssh.NewDefaultConfig())
defer client.Close()

ctx := context.Background()
session, _ := client.OpenSession(ctx, "localhost", 2222)

// Verify server host key
session.OnAuthenticating = func(args *ssh.AuthenticatingEventArgs) {
    // Validate args.PublicKey against known hosts
    args.AuthenticationResult = struct{}{}
}

// Authenticate
session.Authenticate(ctx, &ssh.ClientCredentials{
    Username: "user",
    Password: "password",
})

// Open channel and exchange data
ch, _ := session.OpenChannel(ctx)
ch.SetDataReceivedHandler(func(data []byte) {
    fmt.Printf("Received: %s\n", data)
    ch.AdjustWindow(uint32(len(data)))
})
ch.Send(ctx, []byte("hello"))

Server example

server := tcp.NewServer(ssh.NewDefaultConfig())

hostKey, _ := ssh.GenerateKeyPair(ssh.AlgoPKEcdsaSha2P384)
server.Credentials = &ssh.ServerCredentials{
    PublicKeys: []ssh.KeyPair{hostKey},
}

server.OnSessionAuthenticating = func(args *ssh.AuthenticatingEventArgs) {
    if args.Username == "admin" && args.Password == "secret" {
        args.AuthenticationResult = struct{}{}
    }
}

server.OnSessionOpened = func(session *ssh.ServerSession) {
    go func() {
        for {
            ch, err := session.AcceptChannel(context.Background())
            if err != nil {
                return
            }
            go func() {
                ch.SetDataReceivedHandler(func(data []byte) {
                    ch.Send(context.Background(), data) // echo
                    ch.AdjustWindow(uint32(len(data)))
                })
            }()
        }
    }()
}

server.AcceptSessions(context.Background(), 2222, "")

Stream-based (non-TCP)

clientSession := ssh.NewClientSession(ssh.NewDefaultConfig())
serverSession := ssh.NewServerSession(ssh.NewDefaultConfig())

// Connect over any io.ReadWriteCloser — pipes, WebSockets, etc.
go serverSession.Connect(ctx, serverSideStream)
clientSession.Connect(ctx, clientSideStream)

Port forwarding

config := ssh.NewDefaultConfig()
tcp.AddPortForwardingService(config)

// After connecting...
pfs := tcp.GetPortForwardingService(&session.Session)

// Stream to a remote port
stream, _ := pfs.StreamToRemotePort(ctx, "127.0.0.1", 8080)

// Forward local:3000 → remote:8080
fwd, _ := pfs.ForwardToRemotePort(ctx, 3000, "127.0.0.1", 8080)
defer fwd.Close()

Key import/export

import "github.com/microsoft/dev-tunnels-ssh/src/go/keys"

// Import from PEM (auto-detects format)
keyPair, _ := keys.ImportKey(pemBytes, "passphrase")

// Export to encrypted PKCS#8
pemData, _ := keys.ExportPrivateKey(keyPair, keys.KeyFormatPkcs8, "passphrase")

// Export public key in SSH format
pubData, _ := keys.ExportPublicKey(keyPair, keys.KeyFormatSSH)

Session reconnection

config := ssh.NewDefaultConfigWithReconnect()
client := tcp.NewClient(config)

session, _ := client.OpenSession(ctx, host, port)
// ... connection drops ...

// Reconnect preserving all channels and state
client.ReconnectSession(ctx, session, host, port)

Session piping (relay)

// Two sessions connected to the same relay server
ssh.PipeSession(ctx, sessionA, sessionB)

// Or pipe individual channels
channelA.Pipe(ctx, channelB)

How to test locally

# Build everything
npm run build-go

# Run Go unit + integration tests with race detector
npm run test-go -- --race

# Run full E2E suite (requires Go, Node.js, optionally dotnet)
./test/e2e/e2e-validate.sh

Full-parity Go implementation of the Dev Tunnels SSH library, matching
the existing C# and TypeScript implementations. Includes:

- Core SSH2 protocol: session lifecycle, key exchange, algorithm
  negotiation, channel multiplexing, and flow control
- Authentication: none, password, public key, host-based, and
  keyboard-interactive methods
- Algorithms: ECDH/DH key exchange, RSA/ECDSA public keys,
  AES-256-GCM/CBC/CTR encryption, HMAC-SHA2-256/512 with ETM variants
- Key import/export: PKCS#1, PKCS#8, SEC1, OpenSSH, SSH2, JWK formats
- TCP client/server wrappers with port forwarding service
- Session reconnection and piping (relay) support
- Non-blocking per-channel request dispatch via goroutines
- Keep-alive, metrics, custom message handlers, service activation
- ~679 unit tests, ~158 integration tests, ~48 E2E tests
- Cross-implementation interop validated against C# and TypeScript
@jasongin jasongin self-assigned this Mar 5, 2026
Comment thread test/e2e/README.md
Comment thread test/go/README.md

@DavidObando DavidObando left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added some general feedback but I'll need more time to better understand the large change.

Comment thread src/go/go.mod Outdated
Comment thread src/go/ssh/algorithms/ecdh.go Outdated
Comment thread src/go/ssh/algorithms/ecdh.go
Comment thread src/go/ssh/algorithms/ecdh.go Outdated
Comment thread src/go/ssh/session.go Outdated
Comment thread src/go/keys/bcrypt.go Outdated
Comment thread .github/workflows/pr-build.yaml
Comment thread src/go/ssh/channel.go Outdated
Comment thread src/go/ssh/protocol.go
Comment thread src/go/ssh/session.go
avanderhoorn and others added 9 commits March 6, 2026 10:11
Add a comprehensive benchmark suite that measures SSH library performance
across all three platform implementations with unified JSON output and
automated cross-platform comparison reporting.

- Define common JSON result schema for consistent cross-platform output
- Add C# benchmarks: encryption, HMAC, KEX, keygen, signatures, serialization
- Add TypeScript benchmarks: matching C# coverage with Node.js harness
- Add Go benchmarks: native Go SSH implementation benchmarks
- Build orchestrator script to coordinate builds, runs, and reporting
- Build Markdown report generator with trimmed-mean statistics
- Integrate benchmarks into the build system
- Add bench/README.md documenting usage and architecture
…ghput benchmarks

- Add --verify flag across all platforms (C#, TS, Go) with correctness checks
  for every benchmark category (encryption, HMAC, KEX, keygen, signatures,
  serialization, session setup, throughput, port forward, reconnect)
- Fix Go benchmarks to use TCP loopback (matching C#/TS) instead of io.Pipe
- Fix C#/TS throughput benchmarks to reuse sessions across runs with safety
  timeouts, eliminating backpressure deadlocks on large encrypted messages
- Align throughput duration to 2s across all platforms
- Add report validation warnings for zero/negative metrics and >10x cross-
  platform divergence, with 200x threshold for algorithm categories
- Document expected Go performance advantages and verification coverage in README
…pelining

Throughput (~1.9x encrypted small-message improvement, 23.7K → 45.2K msgs/s):

- Reuse GCM cipher buffers (aadBuf, sealBuf, tag) instead of allocating
  per-message; Sign() and SetTag() now avoid copies
- Coalesce packet + MAC into single Write() call by pre-allocating packet
  buffer with extra capacity for the MAC
- Reuse HMAC input buffers (sendHmacBuf/recvHmacBuf) across messages
- Add sync.Pool for packet buffers with security zeroing before return
- Add reusable SSHDataWriter for direct message serialization (eliminates
  ToBuffer() double-serialization)
- Use math/rand for unencrypted padding instead of crypto/rand
- Replace session.mu lock in SendMessage with atomic connected flag

Session setup (latency-sensitive setup reduced from ~1929ms to ~710ms):

- Fire-and-forget ServiceRequest in Authenticate() (skip ServiceAccept wait)
- Synchronize post-KEX goroutines (extensionInfo, reconnect) before auth
  to avoid sendMu contention that added ~200ms per round-trip
- Inline NewKeys, ExtensionInfo, and reconnect-enable sends from the
  dispatch loop instead of spawning goroutines, matching C#/TS behavior
- Pipeline version string + KexInit in Connect() (send KexInit before
  reading remote version per RFC 4253, saving one round-trip)
- Add 64KB bufio.Writer to reduce write syscall overhead

Reconnect reliability:

- Add reconnectInfoReady channel so Reconnect() waits for the remote's
  enable-reconnect to be processed before returning
- Explicitly cache the enable-reconnect message in enableReconnect() to
  solve the chicken-and-egg where the message that triggers caching wasn't
  itself cached

Benchmark verification:

- Add key size assertions to keygen and signature verification across all
  three platforms (Go, C#, TS) to catch parameter mismatches
- Fix Go keygen benchmark that was ignoring the key size parameter
- Fix data race on kexService.exchanging (bool → atomic int32)
- Rewrite latencyStream with queue-based single-drainer for non-blocking
  writes matching C#/TS behavior

Tests:

- Add unit tests for SSHDataWriter.Slice(), GenerateKeyPairWithSize(),
  sendMessageDirect(), buffer reuse with varying payload sizes, GCM/HMAC
  aliasing contracts
- Fix gcm_tampering_test to copy tag before mutating
- Fix asyncPipeRWC.Write() race condition (pre-check closeCh)
…nteractive auth

Add 4 new e2e test suites (12 tests) covering SSH features that previously
had only unit test coverage: exit-status/exit-signal/standalone signals,
SendExtendedData (stderr) bidirectional flow, key rotation with low threshold,
and multi-round-trip keyboard-interactive authentication.
…sfeedback

Address all actionable review comments from PR microsoft#123 code review:

Security & correctness:
- Upgrade Go from 1.17 to 1.21 for crypto security fixes
- Migrate ECDH from deprecated elliptic.ScalarMult to crypto/ecdh
  (constant-time, automatic IsOnCurve validation)
- Fix bcrypt magic string comments (OxychromaticBlowfishSwatDynamite)
- Add DefaultPbkdf2Iterations const alongside mutable var

Session lifecycle:
- Refactor close()/closeWithError() into unified closeImpl()
  fixing reconnect check, closeMetrics, and custom error bugs
- Move done/kexDone channel init under session mutex
- Restore currentAlgorithms in reconnect restoreState

Channel robustness:
- Derive request contexts from session done channel
- Log window adjust send failures
- Add accept queue bound (64) with resource shortage rejection
- Use SendMessage for channel-failure replies (disconnected-buffer)
- Add DroppedRequests counter to ChannelMetrics

Protocol:
- Fix reconnect cache memory leak with slice compaction

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The TCP e2e tests deadlocked because createDuplexStreams used raw
io.Pipe which has no write buffering. During SSH Connect, both sides
pipeline version string + kexInit writes in a goroutine, then wait for
the writes to finish before starting the dispatch loop. With unbuffered
pipes, both goroutines block on Flush (pipe Write) waiting for readers
that haven't started  classic deadlock.yet

Replace duplexStream with asyncPipeRWC (matching the ssh package tests)
which buffers writes through a channel + background goroutine, simulating
the kernel buffer behavior of real TCP sockets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port the race condition fix from C# PR microsoft#132:
microsoft#132

In closeImpl(), s.protocol was accessed outside the mutex (after unlock
at the end of the guard block). A concurrent reconnect could replace
s.protocol between that unlock and the subsequent use at lines 495/512,
causing the close to disconnect/close the wrong (new) connection.

Fix by capturing proto := s.protocol inside the lock, then using that
captured reference for the disconnect send and protocol close, matching
the C# fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…feedback

Davidobando/fix pr 123 review feedback
DavidObando added a commit that referenced this pull request Apr 9, 2026
Address all actionable review comments from PR #123 code review:

Security & correctness:
- Upgrade Go from 1.17 to 1.21 for crypto security fixes
- Migrate ECDH from deprecated elliptic.ScalarMult to crypto/ecdh
  (constant-time, automatic IsOnCurve validation)
- Fix bcrypt magic string comments (OxychromaticBlowfishSwatDynamite)
- Add DefaultPbkdf2Iterations const alongside mutable var

Session lifecycle:
- Refactor close()/closeWithError() into unified closeImpl()
  fixing reconnect check, closeMetrics, and custom error bugs
- Move done/kexDone channel init under session mutex
- Restore currentAlgorithms in reconnect restoreState

Channel robustness:
- Derive request contexts from session done channel
- Log window adjust send failures
- Add accept queue bound (64) with resource shortage rejection
- Use SendMessage for channel-failure replies (disconnected-buffer)
- Add DroppedRequests counter to ChannelMetrics

Protocol:
- Fix reconnect cache memory leak with slice compaction

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DavidObando
DavidObando previously approved these changes Apr 9, 2026
DavidObando and others added 2 commits April 15, 2026 21:11
- Fix deadlock in session.Connect(): close s.done channel via defer if
  Connect() returns before the dispatch loop starts, preventing closeImpl()
  from blocking forever on <-s.done.

- Fix TestMeasureSessionLatency: increase context timeout from 10s to 30s
  to accommodate -race overhead on slow CI machines (especially Windows).

- Fix TestRecordSessionContour: add WaitUntilReconnectEnabled() call before
  sending data, ensuring the reconnect extension is fully enabled on both
  sides so latency info is appended to messages.

- Fix TestClosedSessionHasNoLatency: increase context timeout from 10s to
  30s for consistency with other latency tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix flaky Go test deadlocks and latency measurement timeouts
@DavidObando
DavidObando merged commit b0f029c into microsoft:main May 1, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants