Serve the read verbs on the serving controller's own connection, and name every connection
Each verb ran as a process that dialled the bus, so hundreds of short connections an hour, all named mesh-controller, hid any client reconnecting in a loop (hq issue 327). D15 now says a user whose connections keep dropping.
This commit is contained in:
@@ -28,6 +28,8 @@ import (
|
||||
type JetStream struct {
|
||||
conn *nats.Conn
|
||||
js nats.JetStreamContext
|
||||
// borrowed is a connection lent by its owner (Borrow): closing it is the owner's.
|
||||
borrowed bool
|
||||
// Note is how this says something it decided not to fail over. Nil is silent, which is only
|
||||
// right for a caller that has no way to report; the controller sets it.
|
||||
Note func(string, ...any)
|
||||
@@ -40,9 +42,17 @@ func (j *JetStream) note(format string, args ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionName is what a connection this process dials says it is, in the server's list of
|
||||
// connections: the controller's role and what it is doing (novox/hq issue 327). Every connection was
|
||||
// named `mesh-controller` — the serving controller's, each verb's own process, the build agents' — so the
|
||||
// server's list could not say which was which. The process sets it once, at its start; an option a
|
||||
// caller passes to Dial names one connection otherwise.
|
||||
var ConnectionName = "mesh-controller"
|
||||
|
||||
// Dial connects and returns the controller's JetStream handle.
|
||||
func Dial(url string, opts ...nats.Option) (*JetStream, error) {
|
||||
opts = append(opts, nats.Name("mesh-controller"), nats.Timeout(10*time.Second))
|
||||
// The name first, so a name the caller gives is the one that stands.
|
||||
opts = append([]nats.Option{nats.Name(ConnectionName), nats.Timeout(10 * time.Second)}, opts...)
|
||||
// **Pinned, not named.** The bus presents the mesh's own certificate, which names nothing a
|
||||
// public verifier would accept (design 25 §4: a host pins the server's exact certificate and
|
||||
// checks nothing else, and so does this). Without this, the first connection failed with
|
||||
@@ -130,11 +140,20 @@ func (j *JetStream) Conn() *nats.Conn { return j.conn }
|
||||
func (j *JetStream) Context() nats.JetStreamContext { return j.js }
|
||||
|
||||
func (j *JetStream) Close() {
|
||||
if j.conn != nil {
|
||||
if j.conn != nil && !j.borrowed {
|
||||
j.conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Borrow is the same connection for a caller that will close what it was handed when it is done: its
|
||||
// Close closes nothing, and the connection stays its owner's (novox/hq issue 327). How the serving
|
||||
// controller lends its own connection to work that would otherwise dial one of its own.
|
||||
func Borrow(j *JetStream) *JetStream {
|
||||
lent := *j
|
||||
lent.borrowed = true
|
||||
return &lent
|
||||
}
|
||||
|
||||
// EnsureStream creates the stream if it is absent and brings it to match if it is present.
|
||||
//
|
||||
// **Idempotent, because the controller asserts on every start** rather than creating once at
|
||||
|
||||
@@ -3,6 +3,8 @@ package broker
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/testbus"
|
||||
)
|
||||
|
||||
@@ -66,3 +68,32 @@ func TestAgainstARealServer(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// A connection says what it is in the server's list (novox/hq issue 327): the process's name, unless the
|
||||
// caller names this one; and a lent connection's Close leaves its owner's open.
|
||||
func TestAConnectionIsNamedAndALentOneIsNotClosed(t *testing.T) {
|
||||
url := testbus.URL(t)
|
||||
was := ConnectionName
|
||||
ConnectionName = "mesh-controller verb conditions"
|
||||
defer func() { ConnectionName = was }()
|
||||
named, err := Dial(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer named.Close()
|
||||
if got := named.Conn().Opts.Name; got != "mesh-controller verb conditions" {
|
||||
t.Errorf("named %q", got)
|
||||
}
|
||||
lease, err := Dial(url, nats.Name("mesh-controller serving lease"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer lease.Close()
|
||||
if got := lease.Conn().Opts.Name; got != "mesh-controller serving lease" {
|
||||
t.Errorf("a name the caller gave became %q", got)
|
||||
}
|
||||
Borrow(named).Close()
|
||||
if !named.Conn().IsConnected() {
|
||||
t.Error("closing a lent connection closed its owner's")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,12 @@ func BuildsOverNATSOn(address, seat string) (Builders, error) {
|
||||
return &natsBuilds{js: js, owned: true, seat: seat}, nil
|
||||
}
|
||||
|
||||
// BuildsOn is the asking side on a connection the caller holds, which Close leaves open: the serving
|
||||
// controller asks on its own rather than dialling one per build (novox/hq issue 327).
|
||||
func BuildsOn(js *broker.JetStream, seat string) Builders {
|
||||
return &natsBuilds{js: js, seat: seat}
|
||||
}
|
||||
|
||||
// role is the seat asked: what the asker was made for, or the current build role for one made
|
||||
// without saying (a test building the struct by hand).
|
||||
func (b *natsBuilds) role() string {
|
||||
|
||||
Reference in New Issue
Block a user