mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-12 23:09:33 +01:00
* volume: skip VolumeDelete when its RPC was cancelled before the delete A caller that cancels (a lost tiering lease, an expired deadline) still had its admitted delete run in the Go server, including one that waited behind the copy guard. The Rust server drops a cancelled handler, so return the context error before the delete starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * volume: wait until the queued delete blocks on the guard before cancelling Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
3.6 KiB
Go
100 lines
3.6 KiB
Go
package weed_server
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestVolumeStatusErrorDistinguishesAbsentFromTransportFailure(t *testing.T) {
|
|
notFound := volumeStatusError(fmt.Errorf("delete volume 17 not found on disk: %w", storage.ErrVolumeNotFound))
|
|
assert.Equal(t, codes.NotFound, status.Code(notFound))
|
|
assert.Contains(t, notFound.Error(), "not found", "the store message must survive for callers that match on it")
|
|
|
|
transport := errors.New("connection reset")
|
|
require.ErrorIs(t, volumeStatusError(transport), transport)
|
|
assert.NotEqual(t, codes.NotFound, status.Code(transport))
|
|
|
|
notEmpty := volumeStatusError(storage.ErrVolumeNotEmpty)
|
|
assert.Equal(t, codes.FailedPrecondition, status.Code(notEmpty))
|
|
assert.Contains(t, notEmpty.Error(), "volume not empty")
|
|
|
|
noSpace := volumeStatusError(fmt.Errorf("compact volume 17: %w", storage.ErrInsufficientSpace))
|
|
assert.Equal(t, codes.ResourceExhausted, status.Code(noSpace))
|
|
assert.Contains(t, noSpace.Error(), "insufficient free space")
|
|
}
|
|
|
|
func TestVolumeDeleteMapsAbsentStoreVolumeToNotFound(t *testing.T) {
|
|
vs := &VolumeServer{store: newTraversalTestStore(t.TempDir())}
|
|
|
|
_, err := vs.VolumeDelete(context.Background(), &volume_server_pb.VolumeDeleteRequest{VolumeId: 17})
|
|
assert.Equal(t, codes.NotFound, status.Code(err), err)
|
|
}
|
|
|
|
func TestVolumeDeleteSkipsCancelledRPC(t *testing.T) {
|
|
vid := needle.VolumeId(1)
|
|
vs := newVacuumCommitTestServer(t, 0, vid)
|
|
|
|
cancelled, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
_, err := vs.VolumeDelete(cancelled, &volume_server_pb.VolumeDeleteRequest{VolumeId: uint32(vid)})
|
|
assert.Equal(t, codes.Canceled, status.Code(err), err)
|
|
require.NotNil(t, vs.store.GetVolume(vid), "a cancelled delete must leave the volume")
|
|
|
|
expired, cancelExpired := context.WithDeadline(context.Background(), time.Now().Add(-time.Second))
|
|
defer cancelExpired()
|
|
_, err = vs.VolumeDelete(expired, &volume_server_pb.VolumeDeleteRequest{VolumeId: uint32(vid)})
|
|
assert.Equal(t, codes.DeadlineExceeded, status.Code(err), err)
|
|
require.NotNil(t, vs.store.GetVolume(vid), "an expired delete must leave the volume")
|
|
|
|
_, err = vs.VolumeDelete(context.Background(), &volume_server_pb.VolumeDeleteRequest{VolumeId: uint32(vid)})
|
|
require.NoError(t, err)
|
|
assert.Nil(t, vs.store.GetVolume(vid))
|
|
}
|
|
|
|
func TestVolumeDeleteCancelledWhileQueuedBehindGuard(t *testing.T) {
|
|
vid := needle.VolumeId(1)
|
|
vs := newVacuumCommitTestServer(t, 0, vid)
|
|
|
|
guard := vs.volumeCopyGuard(uint32(vid))
|
|
guard.Lock()
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
_, err := vs.VolumeDelete(ctx, &volume_server_pb.VolumeDeleteRequest{VolumeId: uint32(vid)})
|
|
done <- err
|
|
}()
|
|
require.Eventually(t, func() bool {
|
|
buf := make([]byte, 1<<20)
|
|
for _, g := range bytes.Split(buf[:runtime.Stack(buf, true)], []byte("\n\n")) {
|
|
if bytes.Contains(g, []byte("(*VolumeServer).VolumeDelete")) && bytes.Contains(g, []byte("sync.(*Mutex).Lock")) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}, 10*time.Second, time.Millisecond)
|
|
cancel()
|
|
guard.Unlock()
|
|
|
|
select {
|
|
case err := <-done:
|
|
assert.Equal(t, codes.Canceled, status.Code(err), err)
|
|
case <-time.After(10 * time.Second):
|
|
t.Fatal("VolumeDelete did not return")
|
|
}
|
|
assert.NotNil(t, vs.store.GetVolume(vid), "a delete cancelled while queued must leave the volume")
|
|
}
|