mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
A backup checkpoints one Base's write-ahead log inside one transaction. A tenant's Base is separate handles with its own log, in neither, so its file was being copied mid-write beside a log copied at another instant — a torn copy rather than a smaller one. The archive names what it leaves out, in the zip's own comment, where every listing tool already prints it. Restore spares exactly what the archive omits. That symmetry is the whole thing: an archive that skips the tenants and a restore that does not spare them deletes every tenant. Both are stated in the package that owns the directory, so core stays ignorant of tenancy. The rate limiter refuses a request it cannot key. A forwarded request arrives with no client address, and a limiter that admits what it cannot count is not one. The way to be counted already exists: a gateway that sets a forwarded header restores the client, through the trusted-proxy settings. Opening a Base holds the registry's lock over the map alone. It had spanned provision, construct, bootstrap and declare, and a read waits behind a waiting writer, so one org's first request stalled every other request in the process — measured at 209ms against a 101ms open. Co-authored-by: Hanzo Dev <[email protected]>
153 lines
3.6 KiB
Go
153 lines
3.6 KiB
Go
package org
|
|
|
|
import (
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hanzoai/base/core"
|
|
"github.com/hanzoai/base/tests"
|
|
)
|
|
|
|
// Opening an org's Base is a migration run on a fresh file, and it is by far
|
|
// the longest thing the registry does. This pins that it happens off the path
|
|
// every other org takes: while one org is being opened for the first time, a
|
|
// lookup of an already-open Base answers at once.
|
|
//
|
|
// The lock the registry needs is the one over its map. Holding it across the
|
|
// open instead makes any org's first request the whole process's latency.
|
|
func TestOpeningOneBaseDoesNotStallAnother(t *testing.T) {
|
|
app, err := tests.NewTestApp()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer app.Cleanup()
|
|
|
|
b := newBases(&plugin{app: app, orgDB: NewOrgDB(app, "")})
|
|
defer b.close()
|
|
|
|
if _, err := b.base("warm"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Time one cold open, so what follows is measured against this machine
|
|
// rather than against a constant.
|
|
start := time.Now()
|
|
if _, err := b.base("first"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cold := time.Since(start)
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
if _, err := b.base("second"); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}()
|
|
|
|
var worst time.Duration
|
|
start = time.Now()
|
|
loop:
|
|
for {
|
|
select {
|
|
case <-done:
|
|
break loop
|
|
default:
|
|
}
|
|
at := time.Now()
|
|
if _, err := b.base("warm"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if d := time.Since(at); d > worst {
|
|
worst = d
|
|
}
|
|
}
|
|
overlap := time.Since(start)
|
|
|
|
// If the second open finished before anything could be measured against
|
|
// it, the run proves nothing and says so rather than passing.
|
|
if overlap < cold/2 {
|
|
t.Fatalf("the second open finished in %v against a cold open of %v — too fast to observe", overlap, cold)
|
|
}
|
|
if worst > cold/2 {
|
|
t.Fatalf("a lookup of an open Base waited %v while another org opened, which takes %v", worst, cold)
|
|
}
|
|
}
|
|
|
|
// Two callers naming the same org get the same Base, and it is opened once.
|
|
// The registry is what makes an org's Base a single thing; opening it twice
|
|
// would put two sets of handles on one file.
|
|
func TestOneBasePerOrg(t *testing.T) {
|
|
app, err := tests.NewTestApp()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer app.Cleanup()
|
|
|
|
b := newBases(&plugin{app: app, orgDB: NewOrgDB(app, "")})
|
|
defer b.close()
|
|
|
|
const callers = 8
|
|
got := make([]core.App, callers)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(callers)
|
|
for i := range got {
|
|
go func() {
|
|
defer wg.Done()
|
|
app, err := b.base("acme")
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
got[i] = app
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
|
|
for i, app := range got {
|
|
if app == nil {
|
|
t.Fatalf("caller %d got no Base", i)
|
|
}
|
|
if app != got[0] {
|
|
t.Fatalf("caller %d got a second Base for one org", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A Base that could not be opened is not remembered as one. Keeping the failure
|
|
// would answer every later request for that org with it, so a transient reason
|
|
// would outlive itself and last the life of the process.
|
|
func TestAFailedOpenIsNotRemembered(t *testing.T) {
|
|
app, err := tests.NewTestApp()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer app.Cleanup()
|
|
|
|
orgDB := NewOrgDB(app, "")
|
|
b := newBases(&plugin{app: app, orgDB: orgDB})
|
|
defer b.close()
|
|
|
|
// A file where the org's directory belongs: nothing can be provisioned
|
|
// there, and the open fails.
|
|
if err := os.MkdirAll(orgDB.OrgsDir(), 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(orgDB.OrgDir("acme"), []byte("not a directory"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := b.base("acme"); err == nil {
|
|
t.Fatal("opening a Base over a file succeeded")
|
|
}
|
|
|
|
b.mu.RLock()
|
|
_, remembered := b.open["acme"]
|
|
b.mu.RUnlock()
|
|
if remembered {
|
|
t.Fatal("a Base that failed to open was remembered")
|
|
}
|
|
}
|