package stdune

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file global_lock.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
let sprintf = Printf.sprintf
let lock_file = Path.Build.(relative root ".lock")

let with_timeout ~timeout f =
  let now () = Time.now () in
  let deadline = Time.add (now ()) timeout in
  let rec loop () =
    if Time.(now () >= deadline)
    then `Timed_out
    else (
      match f () with
      | `Continue -> loop ()
      | `Stop -> `Success)
  in
  loop ()
;;

let write_pid fd =
  let pid = Int.to_string (Unix.getpid ()) in
  let len = String.length pid in
  let res = Unix.write fd (Bytes.of_string pid) 0 len in
  assert (res = len)
;;

module Lock = struct
  let t =
    lazy
      (Path.ensure_build_dir_exists ();
       let fd =
         Unix.openfile
           (Path.Build.to_string lock_file)
           [ Unix.O_CREAT; O_WRONLY; O_SHARE_DELETE; O_CLOEXEC ]
           0o600
       in
       Flock.create fd)
  ;;

  let or_raise_unix ~name = function
    | Ok s -> s
    | Error error ->
      Code_error.raise
        "lock"
        [ "name", Dyn.string name; "error", Dyn.string (Unix.error_message error) ]
  ;;

  let lock () =
    let t = Lazy.force t in
    let res = Flock.lock_non_block t Exclusive |> or_raise_unix ~name:"lock" in
    (match res with
     | `Failure -> ()
     | `Success ->
       let fd = Flock.fd t in
       Unix.ftruncate fd 0;
       write_pid fd);
    res
  ;;

  let unlock () =
    let lock = Lazy.force t in
    Unix.ftruncate (Flock.fd lock) 0;
    Flock.unlock lock |> or_raise_unix ~name:"unlock"
  ;;
end

let locked = ref false

module Lock_held_by = struct
  type t =
    | Pid_from_lockfile of int
    | Unknown

  let read_lock_file () =
    match Io.read_file (Path.build lock_file) with
    | exception _ -> Unknown
    | pid ->
      (match int_of_string_opt pid with
       | Some pid -> Pid_from_lockfile pid
       | None ->
         User_error.raise
           [ Pp.textf
               "Unexpected contents of build directory global lock file (%s). Expected \
                an integer PID. Found: %s"
               (Path.Build.to_string_maybe_quoted lock_file)
               pid
           ]
           ~hints:
             [ Pp.textf "Try deleting %s" (Path.Build.to_string_maybe_quoted lock_file) ])
  ;;
end

let lock ~timeout =
  match
    (* If Config hasn't been initialized yet, default to `Enabled behavior.
       This allows the lock to be acquired early (e.g., before creating trace
       files) to prevent corruption. *)
    if Config.is_initialized () then Config.(get global_lock) else `Enabled
  with
  | `Disabled -> Ok ()
  | `Enabled ->
    if !locked
    then Ok ()
    else (
      let res =
        match timeout with
        | None -> Lock.lock ()
        | Some timeout ->
          (match
             with_timeout ~timeout (fun () ->
               match Lock.lock () with
               | `Success -> `Stop
               | `Failure -> `Continue)
           with
           | `Timed_out -> `Failure
           | `Success -> `Success)
      in
      match res with
      | `Success ->
        locked := true;
        Ok ()
      | `Failure ->
        let lock_held_by = Lock_held_by.read_lock_file () in
        Error lock_held_by)
;;

let lock_exn ~timeout =
  match lock ~timeout with
  | Ok () -> ()
  | Error lock_held_by ->
    User_error.raise
      [ Pp.textf
          "A running dune%s instance has locked the build directory. If this is not the \
           case, please delete %S."
          (match lock_held_by with
           | Unknown -> ""
           | Pid_from_lockfile pid -> sprintf " (pid: %d)" pid)
          (Path.Build.to_string_maybe_quoted lock_file)
      ]
;;

let unlock () =
  if !locked
  then (
    Lock.unlock ();
    locked := false)
;;

let at_exit = At_exit.at_exit At_exit.main unlock