| 1 | package release |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "fmt" |
| 7 | "os" |
| 8 | "path/filepath" |
| 9 | "time" |
| 10 | |
| 11 | "golang.org/x/sys/unix" |
| 12 | ) |
| 13 | |
| 14 | // fencePoll is how often a wait for the carry fence asks again. |
| 15 | const fencePoll = 20 * time.Millisecond |
| 16 | |
| 17 | // lockConsumedPublish is root's side of the carry fence for host: the lock |
| 18 | // file is created if it is missing (root owns the consumed directory), and |
| 19 | // held exclusively until unlock. |
| 20 | func lockConsumedPublish(ctx context.Context, optDir, host string) (func(), error) { |
| 21 | if err := os.MkdirAll(filepath.Join(optDir, "consumed"), 0o755); err != nil { |
| 22 | return nil, err |
| 23 | } |
| 24 | f, err := os.OpenFile(ConsumedLockPath(optDir, host), os.O_RDWR|os.O_CREATE, 0o644) |
| 25 | if err != nil { |
| 26 | return nil, err |
| 27 | } |
| 28 | return flockWait(ctx, f) |
| 29 | } |
| 30 | |
| 31 | // lockConsumedCarry is the cut's side for host. The agent may only be able |
| 32 | // to read the consumed directory; flock takes an exclusive lock on a |
| 33 | // read-only descriptor all the same. A lock file that does not exist is no |
| 34 | // lock to take (FenceCarry). |
| 35 | func lockConsumedCarry(ctx context.Context, optDir, host string) (func(), error) { |
| 36 | f, err := os.Open(ConsumedLockPath(optDir, host)) |
| 37 | if errors.Is(err, os.ErrNotExist) { |
| 38 | return func() {}, nil |
| 39 | } |
| 40 | if err != nil { |
| 41 | return nil, err |
| 42 | } |
| 43 | return flockWait(ctx, f) |
| 44 | } |
| 45 | |
| 46 | // flockWait takes an exclusive flock on f, asking again every fencePoll |
| 47 | // until ctx ends or consumedFenceWait passes. f is closed on failure, and by |
| 48 | // the unlock it returns. |
| 49 | func flockWait(ctx context.Context, f *os.File) (func(), error) { |
| 50 | fd := int(f.Fd()) |
| 51 | deadline := time.Now().Add(consumedFenceWait) |
| 52 | for { |
| 53 | err := unix.Flock(fd, unix.LOCK_EX|unix.LOCK_NB) |
| 54 | if err == nil { |
| 55 | return func() { |
| 56 | _ = unix.Flock(fd, unix.LOCK_UN) |
| 57 | _ = f.Close() |
| 58 | }, nil |
| 59 | } |
| 60 | if !errors.Is(err, unix.EWOULDBLOCK) && !errors.Is(err, unix.EINTR) { |
| 61 | _ = f.Close() |
| 62 | return nil, fmt.Errorf("lock %s: %w", f.Name(), err) |
| 63 | } |
| 64 | if !time.Now().Before(deadline) { |
| 65 | _ = f.Close() |
| 66 | return nil, fmt.Errorf("lock %s: still held after %s", f.Name(), consumedFenceWait) |
| 67 | } |
| 68 | select { |
| 69 | case <-ctx.Done(): |
| 70 | _ = f.Close() |
| 71 | return nil, fmt.Errorf("lock %s: %w", f.Name(), ctx.Err()) |
| 72 | case <-time.After(fencePoll): |
| 73 | } |
| 74 | } |
| 75 | } |
| 76 | |