carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
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.
15const 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.
20func 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).
35func 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.
49func 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