Files
versitygw/backend/posix/objlock_windows.go
T
Ben McClelland 9c363b280a fix: make conditional publish lock modes explicit
Conditional PUTs require a lock primitive that excludes competing gateway
processes sharing a backend filesystem. A successful flock call does not prove
that property: some clustered filesystem configurations accept flock but scope
it to one node, silently leaving cross-gateway check-and-publish races open.

Add an object-lock mode that lets operators select flock or fcntl for
filesystems where that primitive is cluster-coherent, local for the existing
per-process behavior, or none to reject conditional writes with NotImplemented.
Keep the legacy disable flag as an alias for local.

Shared lock modes now verify the selected primitive on the root lock filesystem
during startup and fail closed if it cannot be used. Runtime lock failures no
longer silently downgrade to process-local exclusion. The startup check cannot
establish cross-node coherence, so that remains an explicit operator
requirement.

ScoutFS defaults to none since posix locks are not cluster consistent, but
allow setting local for single node deployments.

Fixes #2351
2026-09-10 19:01:56 -07:00

64 lines
1.8 KiB
Go

// Copyright 2026 Versity Software
// This file is licensed under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//go:build windows
package posix
import (
"context"
"errors"
"fmt"
"os"
"time"
"golang.org/x/sys/windows"
)
// lockFileExclusive takes an exclusive lock on f via LockFileEx, polling with
// backoff so that context cancellation is honored while waiting. The lock is
// released when f is closed or the process exits.
func lockFileExclusive(ctx context.Context, f *os.File, mode ObjectLockMode) error {
if mode != ObjectLockModeFlock {
return errors.New("object lock mode fcntl is not supported on Windows")
}
backoff := objLockInitialBackoff
for {
ol := new(windows.Overlapped)
err := windows.LockFileEx(windows.Handle(f.Fd()),
windows.LOCKFILE_EXCLUSIVE_LOCK|windows.LOCKFILE_FAIL_IMMEDIATELY,
0, 1, 0, ol)
if err == nil {
return nil
}
if !errors.Is(err, windows.ERROR_LOCK_VIOLATION) {
return err
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(backoff):
}
backoff = min(backoff*2, objLockMaxBackoff)
}
}
func validateObjectLockMode(mode ObjectLockMode) error {
if mode == ObjectLockModeFcntl {
return fmt.Errorf("object lock mode %q is not supported on Windows; use flock, local, or none", mode)
}
return nil
}