mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-16 12:16:36 +00:00
* log buffer: wake the interval loop on shutdown instead of sleeping through it loopInterval parked in time.Sleep(flushInterval) and only re-checked IsStopping when it woke, so a buffer shut down early kept both loop goroutines - and the PreviousBufferCount+1 slabs of BufferSize they reach - alive for up to a full interval afterwards. Select on shutdownCh against a ticker instead, and give the loops a WaitGroup so a test can observe that they exit. * test: release the filers the server tests build Every helper here left its filer's meta log buffer running, so each test pinned PreviousBufferCount+1 buffers of BufferSize for the rest of the run: ~3.5GB of live heap across the package, which overruns the address space on linux/386 and kills the 32-bit job with an out-of-memory throw. Thread the test through the helpers so the buffer is shut down on cleanup, and shut the subscribe harness's filer down outright - its deletion loop keeps the whole filer reachable otherwise. That harness quiesces its flush path first, since Filer.Shutdown closes the store a flush still in flight would write through.
69 lines
2.0 KiB
Go
69 lines
2.0 KiB
Go
package weed_server
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
)
|
|
|
|
// Concurrent OExcl creates for the same path must yield exactly one winner. The
|
|
// filer's CreateEntry is a FindEntry-then-Insert; without the per-path lock both
|
|
// racers observe "not found" and both insert. The exclusive entry lock makes the
|
|
// check-then-act atomic so the losers see ErrEntryAlreadyExists.
|
|
func TestCreateEntryOExclSerialized(t *testing.T) {
|
|
store := newRenameTestStore()
|
|
store.findDelay = 5 * time.Millisecond
|
|
f := newRenameTestFiler(t, store)
|
|
f.DirBucketsPath = "/buckets"
|
|
|
|
fs := &FilerServer{
|
|
filer: f,
|
|
option: &FilerOption{},
|
|
entryLockTable: util.NewLockTable[util.FullPath](),
|
|
}
|
|
|
|
const racers = 8
|
|
var success, alreadyExists, unexpected int32
|
|
var wg sync.WaitGroup
|
|
start := make(chan struct{})
|
|
for i := 0; i < racers; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
<-start
|
|
resp, err := fs.CreateEntry(context.Background(), &filer_pb.CreateEntryRequest{
|
|
Directory: "/test",
|
|
OExcl: true,
|
|
SkipCheckParentDirectory: true,
|
|
Entry: &filer_pb.Entry{
|
|
Name: "obj",
|
|
Attributes: &filer_pb.FuseAttributes{Mtime: 1700000000, FileMode: 0644, Inode: 1},
|
|
},
|
|
})
|
|
switch {
|
|
case err != nil:
|
|
atomic.AddInt32(&unexpected, 1)
|
|
case resp.Error == "":
|
|
atomic.AddInt32(&success, 1)
|
|
case resp.ErrorCode == filer_pb.FilerError_ENTRY_ALREADY_EXISTS:
|
|
atomic.AddInt32(&alreadyExists, 1)
|
|
default:
|
|
atomic.AddInt32(&unexpected, 1)
|
|
}
|
|
}()
|
|
}
|
|
close(start)
|
|
wg.Wait()
|
|
|
|
// Exactly one winner; every loser fails with ENTRY_ALREADY_EXISTS and nothing
|
|
// else, so an unrelated failure can't masquerade as a passing test.
|
|
if success != 1 || alreadyExists != racers-1 || unexpected != 0 {
|
|
t.Fatalf("winners=%d already_exists=%d unexpected=%d (racers=%d)", success, alreadyExists, unexpected, racers)
|
|
}
|
|
}
|