Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
317 lines
11 KiB
Go
317 lines
11 KiB
Go
package fuse_test
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto/rand"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"os"
|
||
"path/filepath"
|
||
"runtime"
|
||
"sync"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
// mountDebugPort holds the debug/pprof port the test passed to the
|
||
// mount process via -debug.port. It is set once at TestWriteBufferCap
|
||
// entry and consulted from the write-timeout paths to fetch the mount
|
||
// process's goroutine dump (the test's own dumpAllGoroutines only
|
||
// covers the test process).
|
||
var mountDebugPort int
|
||
|
||
// fetchMountGoroutines pulls a full goroutine dump from the mount
|
||
// process's pprof endpoint. If the mount debug port isn't configured
|
||
// or the HTTP call fails, a short explanation is returned instead of
|
||
// an error — this is diagnostic best-effort, not a test assertion.
|
||
func fetchMountGoroutines() string {
|
||
if mountDebugPort == 0 {
|
||
return "(mount debug port not configured)"
|
||
}
|
||
url := fmt.Sprintf("http://127.0.0.1:%d/debug/pprof/goroutine?debug=2", mountDebugPort)
|
||
client := &http.Client{Timeout: 10 * time.Second}
|
||
resp, err := client.Get(url)
|
||
if err != nil {
|
||
return fmt.Sprintf("(failed to reach mount pprof at %s: %v)", url, err)
|
||
}
|
||
defer resp.Body.Close()
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return fmt.Sprintf("(failed to read mount pprof body: %v)", err)
|
||
}
|
||
return string(body)
|
||
}
|
||
|
||
// dumpAllGoroutines returns a full stack trace of every live goroutine.
|
||
// Used on write-timeout to give CI actionable diagnosis if the write
|
||
// buffer cap ever re-regresses into a hang.
|
||
func dumpAllGoroutines() string {
|
||
buf := make([]byte, 1<<20)
|
||
for {
|
||
n := runtime.Stack(buf, true)
|
||
if n < len(buf) {
|
||
return string(buf[:n])
|
||
}
|
||
buf = make([]byte, 2*len(buf))
|
||
}
|
||
}
|
||
|
||
// writeBufferCapConfig returns a TestConfig that exercises the new
|
||
// -writeBufferSizeMB flag. The cap is set below the aggregate in-flight
|
||
// write demand of the subtests below, so every new chunk has to pass
|
||
// through the Reserve/Release backpressure path at least some of the
|
||
// time. The cap is intentionally NOT minimal — over-tight settings
|
||
// interact with the per-file writable-chunk limit and the FUSE MaxWrite
|
||
// batching in ways that starve single-handle writers on slow CI.
|
||
//
|
||
// Also enables the mount's pprof debug endpoint so the test can fetch
|
||
// mount-process goroutine dumps on write-timeout, which is the only
|
||
// way to actually diagnose a backpressure deadlock (the test process
|
||
// itself is just blocked in syscall.Write waiting on FUSE).
|
||
func writeBufferCapConfig(debugPort int) *TestConfig {
|
||
return &TestConfig{
|
||
Collection: "",
|
||
Replication: "000",
|
||
ChunkSizeMB: 2, // 2 MiB chunks
|
||
CacheSizeMB: 100, // read cache (unrelated)
|
||
NumVolumes: 3,
|
||
EnableDebug: false,
|
||
// Route glog to stderr so the framework's process log capture
|
||
// actually contains something — by default weed sends glog to
|
||
// /tmp/weed.* files which the CI artifact upload step never
|
||
// sees. Critical for diagnosing upload/saveToStorage errors
|
||
// on Linux runs.
|
||
MountGlobalOptions: []string{
|
||
"-logtostderr=true",
|
||
"-v=2",
|
||
},
|
||
MountOptions: []string{
|
||
// 16 MiB total write buffer ⇒ up to 8 chunks in flight
|
||
// across every open file handle on this mount. Large
|
||
// enough to avoid starving a single handle on a slow
|
||
// CI runner, small enough that the concurrent test
|
||
// below still has to drain through it.
|
||
"-writeBufferSizeMB=16",
|
||
"-debug=true",
|
||
fmt.Sprintf("-debug.port=%d", debugPort),
|
||
},
|
||
SkipCleanup: false,
|
||
}
|
||
}
|
||
|
||
// writeWithTimeout wraps os.WriteFile with a hard deadline so a stuck
|
||
// write fails the test fast instead of consuming the full job budget.
|
||
// This is belt-and-braces around the 45-minute workflow timeout and
|
||
// makes write-buffer regressions surface as an actionable failure.
|
||
func writeWithTimeout(t *testing.T, path string, data []byte, timeout time.Duration) {
|
||
t.Helper()
|
||
done := make(chan error, 1)
|
||
go func() { done <- os.WriteFile(path, data, 0644) }()
|
||
select {
|
||
case err := <-done:
|
||
if err != nil {
|
||
// Dump mount goroutines on any write error, not just
|
||
// timeout — upload failures that surface via close()
|
||
// as EIO leave the mount process running but in an
|
||
// informative state (pending sealed chunks, error
|
||
// counters, etc).
|
||
t.Logf("write %s failed (%v) — dumping MOUNT goroutines:\n%s", path, err, fetchMountGoroutines())
|
||
}
|
||
require.NoError(t, err, "write %s", path)
|
||
case <-time.After(timeout):
|
||
t.Logf("write %s did not finish within %v — dumping TEST goroutines:\n%s", path, timeout, dumpAllGoroutines())
|
||
t.Logf("dumping MOUNT goroutines:\n%s", fetchMountGoroutines())
|
||
t.Fatalf("write %s timed out — write buffer cap is likely leaking or deadlocking", path)
|
||
}
|
||
}
|
||
|
||
// runSubtestWithWatchdog runs body on the current (subtest main)
|
||
// goroutine and starts a watcher goroutine that logs diagnostics and
|
||
// fails the test if body doesn't return within timeout.
|
||
//
|
||
// body must run on the main goroutine because test helpers inside it
|
||
// (require.NoError, writeWithTimeout's own t.Fatalf on its internal
|
||
// timeout) need t.Fatal / t.FailNow, which Go's testing docs restrict
|
||
// to the goroutine running the test function. The watcher goroutine
|
||
// only calls goroutine-safe t methods (t.Log, t.Logf, t.Errorf) so it
|
||
// can mark the test failed and dump diagnostics without violating
|
||
// that contract. If body is stuck past timeout the watcher still
|
||
// surfaces the wedge (test + mount goroutine dumps + a FAIL mark);
|
||
// body itself gets unblocked either by its own inner writeWithTimeout
|
||
// firing t.Fatalf or by Go test's global -timeout.
|
||
func runSubtestWithWatchdog(t *testing.T, timeout time.Duration, body func(t *testing.T)) {
|
||
t.Helper()
|
||
stop := make(chan struct{})
|
||
defer close(stop)
|
||
go func() {
|
||
select {
|
||
case <-stop:
|
||
return
|
||
case <-time.After(timeout):
|
||
t.Logf("subtest exceeded %v watchdog — dumping TEST goroutines:\n%s", timeout, dumpAllGoroutines())
|
||
t.Logf("dumping MOUNT goroutines:\n%s", fetchMountGoroutines())
|
||
t.Errorf("subtest exceeded %v watchdog — see goroutine dumps above", timeout)
|
||
}
|
||
}()
|
||
body(t)
|
||
}
|
||
|
||
// TestWriteBufferCap exercises the end-to-end write-buffer cap on a
|
||
// real FUSE mount. Without the cap, a volume-server stall would let
|
||
// the swap file grow without bound (issue #8777). With the cap, writers
|
||
// must serialize through a bounded budget while still producing correct
|
||
// output — that correctness (and the absence of deadlocks) is what
|
||
// this test verifies.
|
||
//
|
||
// Note: this test deliberately does not assert that Reserve *blocked*
|
||
// at some observed used-byte peak. The mount runs as a subprocess so
|
||
// its in-process WriteBufferAccountant state is not reachable from the
|
||
// test without adding a metrics/RPC surface to the mount binary. The
|
||
// deterministic peak-vs-cap assertion instead lives in the in-package
|
||
// unit test TestWriteBufferCap_SharedAcrossPipelines, which drives a
|
||
// controlled gated uploader and samples Used() throughout the run.
|
||
func TestWriteBufferCap(t *testing.T) {
|
||
mountDebugPort = freePort(t)
|
||
config := writeBufferCapConfig(mountDebugPort)
|
||
framework := NewFuseTestFramework(t, config)
|
||
defer framework.Cleanup()
|
||
|
||
require.NoError(t, framework.Setup(config))
|
||
|
||
const subtestTimeout = 3 * time.Minute
|
||
|
||
t.Run("ConcurrentWritesUnderCap", func(t *testing.T) {
|
||
runSubtestWithWatchdog(t, subtestTimeout, func(t *testing.T) {
|
||
testConcurrentWritesUnderCap(t, framework)
|
||
})
|
||
})
|
||
|
||
t.Run("LargeFileUnderCap", func(t *testing.T) {
|
||
runSubtestWithWatchdog(t, subtestTimeout, func(t *testing.T) {
|
||
testLargeFileUnderCap(t, framework)
|
||
})
|
||
})
|
||
|
||
t.Run("DoesNotDeadlockAfterPressure", func(t *testing.T) {
|
||
runSubtestWithWatchdog(t, subtestTimeout, func(t *testing.T) {
|
||
testWriteBufferNoDeadlockAfterPressure(t, framework)
|
||
})
|
||
})
|
||
}
|
||
|
||
// testConcurrentWritesUnderCap opens several files in parallel with
|
||
// aggregate demand that exceeds the 16 MiB write buffer cap, then
|
||
// verifies every byte survived the round trip.
|
||
func testConcurrentWritesUnderCap(t *testing.T, framework *FuseTestFramework) {
|
||
const (
|
||
numFiles = 4
|
||
fileSize = 8 * 1024 * 1024 // 8 MiB per file ⇒ 32 MiB total vs 16 MiB cap
|
||
)
|
||
|
||
dir := "write_buffer_cap_concurrent"
|
||
framework.CreateTestDir(dir)
|
||
|
||
payloads := make([][]byte, numFiles)
|
||
for i := range payloads {
|
||
buf := make([]byte, fileSize)
|
||
_, err := rand.Read(buf)
|
||
require.NoError(t, err)
|
||
payloads[i] = buf
|
||
}
|
||
|
||
start := time.Now()
|
||
var wg sync.WaitGroup
|
||
errs := make(chan error, numFiles)
|
||
timedOut := make(chan struct{}, numFiles)
|
||
for i := 0; i < numFiles; i++ {
|
||
i := i
|
||
wg.Add(1)
|
||
go func() {
|
||
defer wg.Done()
|
||
name := fmt.Sprintf("file_%02d.bin", i)
|
||
path := filepath.Join(framework.GetMountPoint(), dir, name)
|
||
done := make(chan error, 1)
|
||
go func() { done <- os.WriteFile(path, payloads[i], 0644) }()
|
||
select {
|
||
case err := <-done:
|
||
if err != nil {
|
||
errs <- fmt.Errorf("writer %d: %w", i, err)
|
||
}
|
||
case <-time.After(90 * time.Second):
|
||
timedOut <- struct{}{}
|
||
errs <- fmt.Errorf("writer %d: timed out after 90s", i)
|
||
}
|
||
}()
|
||
}
|
||
wg.Wait()
|
||
close(errs)
|
||
// If any writer timed out, dump every live goroutine so CI shows the
|
||
// wedge instead of just a walltime.
|
||
select {
|
||
case <-timedOut:
|
||
t.Logf("at least one concurrent writer timed out — dumping goroutines:\n%s", dumpAllGoroutines())
|
||
default:
|
||
}
|
||
for err := range errs {
|
||
t.Fatal(err)
|
||
}
|
||
t.Logf("wrote %d × %d MiB under 16 MiB cap in %v", numFiles, fileSize/(1024*1024), time.Since(start))
|
||
|
||
for i := 0; i < numFiles; i++ {
|
||
name := fmt.Sprintf("file_%02d.bin", i)
|
||
path := filepath.Join(framework.GetMountPoint(), dir, name)
|
||
got, err := os.ReadFile(path)
|
||
require.NoError(t, err, "read %s", name)
|
||
require.Equal(t, len(payloads[i]), len(got), "size mismatch for %s", name)
|
||
if !bytes.Equal(payloads[i], got) {
|
||
t.Fatalf("content mismatch for %s", name)
|
||
}
|
||
}
|
||
}
|
||
|
||
// testLargeFileUnderCap writes a single file whose size exceeds the
|
||
// 16 MiB cap through a single handle, verifying that the pipeline
|
||
// drains its own earlier chunks and makes forward progress rather than
|
||
// self-deadlocking when the global budget is already full of its own
|
||
// earlier sealed chunks.
|
||
func testLargeFileUnderCap(t *testing.T, framework *FuseTestFramework) {
|
||
const fileSize = 20 * 1024 * 1024 // 20 MiB ⇒ 10 chunks vs 8-slot budget
|
||
|
||
payload := make([]byte, fileSize)
|
||
_, err := rand.Read(payload)
|
||
require.NoError(t, err)
|
||
|
||
name := "write_buffer_cap_large.bin"
|
||
path := filepath.Join(framework.GetMountPoint(), name)
|
||
|
||
start := time.Now()
|
||
writeWithTimeout(t, path, payload, 90*time.Second)
|
||
t.Logf("wrote %d MiB through one handle under 16 MiB cap in %v", fileSize/(1024*1024), time.Since(start))
|
||
|
||
got, err := os.ReadFile(path)
|
||
require.NoError(t, err)
|
||
require.Equal(t, len(payload), len(got))
|
||
if !bytes.Equal(payload, got) {
|
||
t.Fatal("content mismatch on large single-handle write")
|
||
}
|
||
}
|
||
|
||
// testWriteBufferNoDeadlockAfterPressure verifies the mount is still
|
||
// healthy after being driven against the cap. A budget-slot leak would
|
||
// eventually cause every new chunk allocation to hang; a quick canary
|
||
// write catches that as a hard failure.
|
||
func testWriteBufferNoDeadlockAfterPressure(t *testing.T, framework *FuseTestFramework) {
|
||
name := "write_buffer_cap_canary.txt"
|
||
path := filepath.Join(framework.GetMountPoint(), name)
|
||
content := []byte("write buffer cap canary — mount still healthy")
|
||
|
||
writeWithTimeout(t, path, content, 30*time.Second)
|
||
|
||
got, err := os.ReadFile(path)
|
||
require.NoError(t, err)
|
||
require.Equal(t, content, got)
|
||
}
|