Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa56b8b63c |
No files matched your search
+15
-10
@@ -45,6 +45,11 @@ type Conn struct {
|
|||||||
serverSfx string
|
serverSfx string
|
||||||
commands map[string]cmdHandler
|
commands map[string]cmdHandler
|
||||||
|
|
||||||
|
// pingInterval and pongDeadline are the constants of the
|
||||||
|
// same names, except in tests, which shorten them.
|
||||||
|
pingInterval time.Duration
|
||||||
|
pongDeadline time.Duration
|
||||||
|
|
||||||
// writeMu serializes writes to conn, which come from
|
// writeMu serializes writes to conn, which come from
|
||||||
// serve(), from the relay goroutine and, for KILL, from
|
// serve(), from the relay goroutine and, for KILL, from
|
||||||
// Disconnect.
|
// Disconnect.
|
||||||
@@ -95,6 +100,9 @@ func newConn(
|
|||||||
serverSfx: srvName,
|
serverSfx: srvName,
|
||||||
remoteIP: host,
|
remoteIP: host,
|
||||||
hostname: resolveHost(ctx, host),
|
hostname: resolveHost(ctx, host),
|
||||||
|
|
||||||
|
pingInterval: pingInterval,
|
||||||
|
pongDeadline: pongDeadline,
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.commands = conn.buildCommandMap()
|
conn.commands = conn.buildCommandMap()
|
||||||
@@ -213,18 +221,15 @@ func resolveHost(ctx context.Context, addr string) string {
|
|||||||
|
|
||||||
// serve is the main loop for a single IRC client connection.
|
// serve is the main loop for a single IRC client connection.
|
||||||
// Cancelling ctx when it returns stops the relay goroutine.
|
// Cancelling ctx when it returns stops the relay goroutine.
|
||||||
// When nothing has been read for interval the client is sent
|
// When nothing has been read for pingInterval the client is
|
||||||
// a PING, and when nothing is read within deadline after that
|
// sent a PING, and when nothing is read within pongDeadline
|
||||||
// the connection is closed.
|
// after that the connection is closed.
|
||||||
func (c *Conn) serve(
|
func (c *Conn) serve(ctx context.Context) {
|
||||||
ctx context.Context,
|
|
||||||
interval, deadline time.Duration,
|
|
||||||
) {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
defer c.cleanup(ctx)
|
defer c.cleanup(ctx)
|
||||||
|
|
||||||
ping := time.AfterFunc(interval, func() {
|
ping := time.AfterFunc(c.pingInterval, func() {
|
||||||
c.send("PING :" + c.serverSfx)
|
c.send("PING :" + c.serverSfx)
|
||||||
})
|
})
|
||||||
defer ping.Stop()
|
defer ping.Stop()
|
||||||
@@ -234,10 +239,10 @@ func (c *Conn) serve(
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
// Every line read starts both waits again.
|
// Every line read starts both waits again.
|
||||||
ping.Reset(interval)
|
ping.Reset(c.pingInterval)
|
||||||
|
|
||||||
_ = c.conn.SetReadDeadline(
|
_ = c.conn.SetReadDeadline(
|
||||||
time.Now().Add(interval + deadline),
|
time.Now().Add(c.pingInterval + c.pongDeadline),
|
||||||
)
|
)
|
||||||
|
|
||||||
if !scanner.Scan() {
|
if !scanner.Scan() {
|
||||||
|
|||||||
@@ -13,16 +13,13 @@ import (
|
|||||||
"sneak.berlin/go/neoirc/internal/service"
|
"sneak.berlin/go/neoirc/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewTestServer creates a Server suitable for testing, whose
|
// NewTestServer creates a Server suitable for testing.
|
||||||
// connections use interval and deadline in place of
|
|
||||||
// pingInterval and pongDeadline.
|
|
||||||
// The caller must call Stop() when finished.
|
// The caller must call Stop() when finished.
|
||||||
func NewTestServer(
|
func NewTestServer(
|
||||||
log *slog.Logger,
|
log *slog.Logger,
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
database *db.Database,
|
database *db.Database,
|
||||||
brk *broker.Broker,
|
brk *broker.Broker,
|
||||||
interval, deadline time.Duration,
|
|
||||||
) *Server {
|
) *Server {
|
||||||
globs := &globals.Globals{
|
globs := &globals.Globals{
|
||||||
Appname: "neoirc",
|
Appname: "neoirc",
|
||||||
@@ -41,9 +38,6 @@ func NewTestServer(
|
|||||||
brk: brk,
|
brk: brk,
|
||||||
svc: svc,
|
svc: svc,
|
||||||
conns: make(map[*Conn]struct{}),
|
conns: make(map[*Conn]struct{}),
|
||||||
|
|
||||||
pingInterval: interval,
|
|
||||||
pongDeadline: deadline,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,13 +64,22 @@ const KillWriteWindow = killWriteWindow
|
|||||||
// output queue when no broker notification arrives.
|
// output queue when no broker notification arrives.
|
||||||
const PollInterval = pollInterval
|
const PollInterval = pollInterval
|
||||||
|
|
||||||
// PingInterval is how long the server waits with nothing read
|
// ServeConn serves tcpConn as s serves each connection it
|
||||||
// from a connection before it sends a PING.
|
// accepts, except that interval and deadline replace
|
||||||
const PingInterval = pingInterval
|
// pingInterval and pongDeadline.
|
||||||
|
func (s *Server) ServeConn(
|
||||||
|
tcpConn net.Conn,
|
||||||
|
interval, deadline time.Duration,
|
||||||
|
) {
|
||||||
|
client := newConn(
|
||||||
|
context.Background(), tcpConn, s.log,
|
||||||
|
s.database, s.brk, s.cfg, s.svc,
|
||||||
|
)
|
||||||
|
client.pingInterval = interval
|
||||||
|
client.pongDeadline = deadline
|
||||||
|
|
||||||
// PongDeadline is how long the server waits after the PING
|
go client.serve(context.Background())
|
||||||
// for a line before it closes the connection.
|
}
|
||||||
const PongDeadline = pongDeadline
|
|
||||||
|
|
||||||
// NewTestConn returns a Conn for nick on tcpConn, without a
|
// NewTestConn returns a Conn for nick on tcpConn, without a
|
||||||
// server, database or service behind it.
|
// server, database or service behind it.
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
"sneak.berlin/go/neoirc/internal/broker"
|
"sneak.berlin/go/neoirc/internal/broker"
|
||||||
@@ -39,11 +38,6 @@ type Server struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
conns map[*Conn]struct{}
|
conns map[*Conn]struct{}
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
||||||
// pingInterval and pongDeadline are the constants of the
|
|
||||||
// same names, except in tests, which shorten them.
|
|
||||||
pingInterval time.Duration
|
|
||||||
pongDeadline time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new IRC Server and registers its lifecycle
|
// New creates a new IRC Server and registers its lifecycle
|
||||||
@@ -63,9 +57,6 @@ func New(
|
|||||||
listener: nil,
|
listener: nil,
|
||||||
cancel: nil,
|
cancel: nil,
|
||||||
mu: sync.Mutex{},
|
mu: sync.Mutex{},
|
||||||
|
|
||||||
pingInterval: pingInterval,
|
|
||||||
pongDeadline: pongDeadline,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listenAddr := params.Config.IRCListenAddr
|
listenAddr := params.Config.IRCListenAddr
|
||||||
@@ -162,7 +153,7 @@ func (s *Server) acceptLoop(ctx context.Context) {
|
|||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
client.serve(ctx, s.pingInterval, s.pongDeadline)
|
client.serve(ctx)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,22 +71,6 @@ func newTestEnvWithConfig(
|
|||||||
) *testEnv {
|
) *testEnv {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
return newTestEnvWithPingTimes(
|
|
||||||
t, cfg, ircserver.PingInterval, ircserver.PongDeadline,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newTestEnvWithPingTimes is newTestEnvWithConfig, except
|
|
||||||
// that the server sends a connection a PING after interval
|
|
||||||
// with nothing read, and closes it when nothing is read
|
|
||||||
// within deadline after that.
|
|
||||||
func newTestEnvWithPingTimes(
|
|
||||||
t *testing.T,
|
|
||||||
cfg *config.Config,
|
|
||||||
interval, deadline time.Duration,
|
|
||||||
) *testEnv {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
// A random name, so that no other test environment, not
|
// A random name, so that no other test environment, not
|
||||||
// even an earlier run of the same test, can share it.
|
// even an earlier run of the same test, can share it.
|
||||||
dsn := fmt.Sprintf(
|
dsn := fmt.Sprintf(
|
||||||
@@ -143,9 +127,7 @@ func newTestEnvWithPingTimes(
|
|||||||
&slog.HandlerOptions{Level: slog.LevelError}, //nolint:exhaustruct
|
&slog.HandlerOptions{Level: slog.LevelError}, //nolint:exhaustruct
|
||||||
))
|
))
|
||||||
|
|
||||||
srv := ircserver.NewTestServer(
|
srv := ircserver.NewTestServer(log, cfg, database, brk)
|
||||||
log, cfg, database, brk, interval, deadline,
|
|
||||||
)
|
|
||||||
|
|
||||||
err = srv.Start(addr)
|
err = srv.Start(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -193,26 +175,34 @@ func (env *testEnv) dial(t *testing.T) *testClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ping interval and PONG deadline for
|
// The ping interval and PONG deadline for dialWithPingTimes.
|
||||||
// newTestEnvWithShortPings. They are equal, so that three
|
// They are equal, so that three answered PINGs take longer
|
||||||
// answered PINGs take longer than a client that answers none
|
// than a client that answers none stays connected.
|
||||||
// stays connected.
|
|
||||||
const (
|
const (
|
||||||
testPingInterval = 300 * time.Millisecond
|
testPingInterval = 300 * time.Millisecond
|
||||||
testPongDeadline = 300 * time.Millisecond
|
testPongDeadline = 300 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
// newTestEnvWithShortPings starts an IRC server that uses
|
// dialWithPingTimes connects a client to the test server
|
||||||
// testPingInterval and testPongDeadline in place of
|
// whose connection is sent a PING after testPingInterval with
|
||||||
// pingInterval and pongDeadline.
|
// nothing read, and closed when nothing is read within
|
||||||
func newTestEnvWithShortPings(t *testing.T) *testEnv {
|
// testPongDeadline after that.
|
||||||
|
func (env *testEnv) dialWithPingTimes(t *testing.T) *testClient {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
return newTestEnvWithPingTimes(
|
serverSide, clientSide := net.Pipe()
|
||||||
t,
|
|
||||||
&config.Config{ServerName: testServerName},
|
t.Cleanup(func() { _ = clientSide.Close() })
|
||||||
testPingInterval, testPongDeadline,
|
|
||||||
|
env.srv.ServeConn(
|
||||||
|
serverSide, testPingInterval, testPongDeadline,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return &testClient{
|
||||||
|
t: t,
|
||||||
|
conn: clientSide,
|
||||||
|
scanner: bufio.NewScanner(clientSide),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isServerPing reports whether line is the PING the test
|
// isServerPing reports whether line is the PING the test
|
||||||
@@ -442,8 +432,8 @@ func TestPingPong(t *testing.T) {
|
|||||||
func TestQuietClientAnsweringPingStaysConnected(t *testing.T) {
|
func TestQuietClientAnsweringPingStaysConnected(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
env := newTestEnvWithShortPings(t)
|
env := newTestEnv(t)
|
||||||
client := env.dial(t)
|
client := env.dialWithPingTimes(t)
|
||||||
client.register("answers")
|
client.register("answers")
|
||||||
|
|
||||||
for range 3 {
|
for range 3 {
|
||||||
@@ -460,8 +450,8 @@ func TestQuietClientAnsweringPingStaysConnected(t *testing.T) {
|
|||||||
func TestQuietClientNotAnsweringPingIsClosed(t *testing.T) {
|
func TestQuietClientNotAnsweringPingIsClosed(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
env := newTestEnvWithShortPings(t)
|
env := newTestEnv(t)
|
||||||
client := env.dial(t)
|
client := env.dialWithPingTimes(t)
|
||||||
client.register("silent")
|
client.register("silent")
|
||||||
|
|
||||||
lines := client.readUntilClosed()
|
lines := client.readUntilClosed()
|
||||||
@@ -476,8 +466,8 @@ func TestQuietClientNotAnsweringPingIsClosed(t *testing.T) {
|
|||||||
func TestUnregisteredClientAnswersPing(t *testing.T) {
|
func TestUnregisteredClientAnswersPing(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
env := newTestEnvWithShortPings(t)
|
env := newTestEnv(t)
|
||||||
client := env.dial(t)
|
client := env.dialWithPingTimes(t)
|
||||||
|
|
||||||
client.readUntil(isServerPing)
|
client.readUntil(isServerPing)
|
||||||
client.send("PONG :" + testServerName)
|
client.send("PONG :" + testServerName)
|
||||||
|
|||||||
Reference in new issue
Block a user