Skip to main content

git_policy_cli/
real_index_lock.rs

1//! What:
2//!  The real `index.lock` a landing holds,
3//!  created exclusively,
4//!  identified by device,
5//!  inode and
6//!       filesystem,
7//!  announced through `index~pid.lock`,
8//!  and released only while it is still the file
9//!       that was created.
10//! Why:
11//!  The landing's index install depends on the exact lock file it created;
12//!  a replaced lock must
13//!      never be deleted,
14//!  and a lock this process did not create must never be released
15//!      (`src/policy-engine/commit-landing-index-lock.ts`).
16//!
17//! In TS you'd write (pseudocode):
18//! ```ts
19//! await using lock = await openRealIndexLock({ realIndexPath, timeoutMs });
20//! ```
21
22/// The lock identity record the transaction journal stores, and the numbered record's name.
23use super::transaction_journal::{IndexLockRecord, LockIdentity, index_lock_record_filename};
24/// The record's exact bytes.
25use super::transaction_journal_encode::encode_index_lock;
26/// Debug and warning text to the diagnostic log.
27use super::diagnostic_log::{debug, warn};
28/// The recovery install of a recorded post-index through the exact owned lock, and the release of that lock.
29use super::recovery_files::{LockRelease, install_recovered_index, lock_release_name, release_owned_lock};
30/// Recovery failures, which the install reports.
31use super::recovery_error::RecoveryError;
32#[cfg(unix)]
33use super::transaction_journal::FileIdentity;
34/// The waiting coordination and its failure.
35use super::index_writer_coordination::{CoordinationFailure, wait_for_lock};
36/// The filesystem identity of the worktree's volume.
37#[cfg(unix)]
38use super::filesystem_identity::filesystem_identity;
39/// Announcing this process through the PID file, the journal record, and the lock's mode.
40use super::journal_record::{RecordSync, write_record};
41use super::private_storage::{protect_path, write_private_file_unsynced};
42/// `Path` and `PathBuf` are borrowed and owned filesystem paths.
43use std::path::{Path, PathBuf};
44
45/// Module tag for diagnostics.
46const TAG: &str = "acquireRealIndexLock";
47
48/// What:
49///  A real `index.lock` created by this process.
50/// Why:
51///   Holding the identity lets the release prove the file is still the one created.
52///
53/// In TS you'd write (pseudocode):
54/// ```ts
55/// type RealIndexLock = { lockPath: string; identity: LockIdentity; installIndex(source): Promise<void> };
56/// ```
57#[derive(Debug, Eq, PartialEq)]
58pub struct RealIndexLock {
59    /// The lock path,
60    ///  `<real index>.lock`.
61    lock_path: PathBuf,
62    /// The real index the lock guards,
63    ///  which an install replaces.
64    real_index: PathBuf,
65    /// The PID file path,
66    ///  `<real index>~pid.lock`.
67    pid_path: PathBuf,
68    /// The identity recorded when the lock was created.
69    identity: LockIdentity,
70}
71
72/// What:
73///  The lock path of a real index.
74/// Why:
75///   Git names the lock beside the index.
76///
77/// In TS you'd write (pseudocode):
78/// ```ts
79/// `${realIndexPath}.lock`
80/// ```
81pub fn real_lock_path(real_index: &Path) -> PathBuf {
82    let mut name: std::ffi::OsString = real_index.as_os_str().to_owned();
83    name.push(".lock");
84    return PathBuf::from(name);
85}
86
87/// What:
88///  The PID file path of a real index.
89/// Why:
90///   Git's `core.lockfilePid` file lives beside the lock.
91///
92/// In TS you'd write (pseudocode):
93/// ```ts
94/// lockPidPath(realIndexPath) // `${realIndexPath}~pid.lock`
95/// ```
96pub fn real_pid_path(real_index: &Path) -> PathBuf {
97    let mut name: std::ffi::OsString = real_index.as_os_str().to_owned();
98    name.push("~pid.lock");
99    return PathBuf::from(name);
100}
101
102impl RealIndexLock {
103    /// What:
104    ///  The identity recorded for the lock.
105    /// Why:
106    ///   The landing journal stores it.
107    ///
108    /// In TS you'd write (pseudocode):
109    /// ```ts
110    /// lock.identity
111    /// ```
112    pub fn identity(&self) -> &LockIdentity {
113        return &self.identity;
114    }
115
116    /// What:
117    ///  Whether the lock path still names the file this process created.
118    /// Why:
119    ///   A release or install must not act on a replaced lock.
120    ///
121    /// In TS you'd write (pseudocode):
122    /// ```ts
123    /// await assertLockIdentity({ lockPath, identity });
124    /// ```
125    pub fn assert_identity(&self) -> Result<(), String> {
126        return same_file_identity(self.lock_path.as_path(), &self.identity);
127    }
128
129    /// What:
130    ///  Release the lock and this process's PID file,
131    ///  refusing a lock that was replaced.
132    /// Why:
133    ///   A replaced lock belongs to another process and stays in place.
134    ///
135    /// In TS you'd write (pseudocode):
136    /// ```ts
137    /// await lock[Symbol.asyncDispose]();
138    /// ```
139    pub fn release(self) -> Result<(), String> {
140        self.assert_identity()?;
141        if let Err(error) = std::fs::remove_file(&self.lock_path) {
142            return Err(error.to_string());
143        }
144        return remove_own_pid_file(self.pid_path.as_path());
145    }
146
147    /// What:
148    ///  Install a computed index through the held lock,
149    ///  consuming the lock,
150    ///  then remove this process's PID file.
151    /// Why:
152    ///   `installIndex`:
153    ///  the bytes are written into the lock this process created,
154    ///  the lock is linked under a private name beside the post-index,
155    ///  and that name is renamed over the real index,
156    ///  so the new index keeps the lock's owner.
157    ///  The lock is not released here on failure:
158    ///  once the landing record is durable,
159    ///  recovery completes the install through the same lock.
160    ///
161    /// In TS you'd write (pseudocode):
162    /// ```ts
163    /// await lock.installIndex(postIndexPath);
164    /// ```
165    pub fn install(self, post_index: &Path) -> Result<(), RecoveryError> {
166        install_recovered_index(
167            self.lock_path.as_path(),
168            self.real_index.as_path(),
169            post_index,
170            &self.identity,
171        )?;
172        debug(TAG, format!("installed {} through {}", self.real_index.display(), self.lock_path.display()).as_str());
173        return remove_own_pid_file(self.pid_path.as_path()).map_err(RecoveryError);
174    }
175
176    /// What:
177    ///  Release a lock a landing leaves without installing:
178    ///  the lock is removed only while it is still the file this process created,
179    ///  and the PID file goes either way.
180    /// Why:
181    ///   The incumbent's disposal warns when the lock is gone or another owner's now
182    ///  and still removes its own PID file,
183    ///  and throws only for an operating-system error.
184    ///
185    /// In TS you'd write (pseudocode):
186    /// ```ts
187    /// await lock[Symbol.asyncDispose]();
188    /// ```
189    pub fn release_after_landing(self) -> Result<(), String> {
190        match release_owned_lock(&self.identity, self.lock_path.as_path()) {
191            Ok(LockRelease::Released) => {}
192            Ok(other) => warn(
193                TAG,
194                format!("index lock was not this transaction's at release ({}): {}", lock_release_name(other), self.lock_path.display()).as_str(),
195            ),
196            Err(error) => return Err(error.0),
197        }
198        return remove_own_pid_file(self.pid_path.as_path());
199    }
200}
201
202/// What:
203///  Whether a path is a regular file with the recorded device and inode,
204///  without following links.
205/// Why:
206///   Following a link would compare another file's identity.
207///
208/// In TS you'd write (pseudocode):
209/// ```ts
210/// const metadata = await lstat(path, { bigint: true });
211/// ```
212#[cfg(unix)]
213fn same_file_identity(path: &Path, identity: &LockIdentity) -> Result<(), String> {
214    use std::os::unix::fs::MetadataExt;
215    let metadata: std::fs::Metadata = match std::fs::symlink_metadata(path) {
216        Ok(found) => found,
217        Err(error) => return Err(error.to_string()),
218    };
219    if !metadata.is_file()
220        || metadata.dev().to_string() != identity.file.device
221        || metadata.ino().to_string() != identity.file.inode
222    {
223        return Err(format!("the index lock identity changed: {}", path.display()));
224    }
225    return Ok(());
226}
227
228/// What:
229///  On platforms without an inode,
230///  the identity cannot be checked,
231///  so the lock is never trusted.
232/// Why:
233///   The incumbent refuses a landing without a filesystem identity too.
234///
235/// In TS you'd write (pseudocode):
236/// ```ts
237/// throw new TypeError('filesystem identity unavailable');
238/// ```
239// UNTESTED ON WINDOWS: no gate compiles or runs this branch (the crate does not build for Windows; README, Requirements).
240#[cfg(not(unix))]
241fn same_file_identity(path: &Path, _identity: &LockIdentity) -> Result<(), String> {
242    return Err(format!("the index lock identity cannot be checked here: {}", path.display()));
243}
244
245/// What:
246///  Create the lock file exclusively with private permissions.
247/// Why:
248///   `O_EXCL` is the mutual exclusion;
249///  an existing file means another owner holds the lock.
250///
251/// In TS you'd write (pseudocode):
252/// ```ts
253/// open(lockPath, O_CREAT | O_EXCL | O_RDWR, 0o600);
254/// ```
255#[cfg(unix)]
256fn create_exclusive(path: &Path) -> Result<bool, String> {
257    use std::os::unix::fs::OpenOptionsExt;
258    let mut options: std::fs::OpenOptions = std::fs::OpenOptions::new();
259    options.write(true).create_new(true).mode(0o600);
260    match options.open(path) {
261        Ok(_) => return Ok(true),
262        Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => return Ok(false),
263        Err(error) => return Err(error.to_string()),
264    }
265}
266
267/// What:
268///  Create the lock file exclusively,
269///  on platforms without Unix modes.
270/// Why:
271///   Private permissions are not applied there;
272///  the identity check still refuses the lock.
273///
274/// In TS you'd write (pseudocode):
275/// ```ts
276/// open(lockPath, O_CREAT | O_EXCL | O_RDWR);
277/// ```
278// UNTESTED ON WINDOWS: no gate compiles or runs this branch (the crate does not build for Windows; README, Requirements).
279#[cfg(not(unix))]
280fn create_exclusive(path: &Path) -> Result<bool, String> {
281    let mut options: std::fs::OpenOptions = std::fs::OpenOptions::new();
282    options.write(true).create_new(true);
283    match options.open(path) {
284        Ok(_) => return Ok(true),
285        Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => return Ok(false),
286        Err(error) => return Err(error.to_string()),
287    }
288}
289
290/// What:
291///  The identity of a freshly created lock file.
292/// Why:
293///   The journal records it;
294///  a platform without device and inode numbers records nothing.
295///
296/// In TS you'd write (pseudocode):
297/// ```ts
298/// { file: { device: String(stat.dev), inode: String(stat.ino) }, fsId: await resolveFsId(dirname(path)) };
299/// ```
300#[cfg(unix)]
301fn created_identity(lock_path: &Path, real_index: &Path) -> Result<LockIdentity, String> {
302    use std::os::unix::fs::MetadataExt;
303    let metadata: std::fs::Metadata = match std::fs::symlink_metadata(lock_path) {
304        Ok(found) => found,
305        Err(error) => return Err(error.to_string()),
306    };
307    let directory: &Path = real_index.parent().unwrap_or(Path::new("."));
308    let fs_id: String = match filesystem_identity(directory) {
309        Some(found) => found,
310        None => return Err(String::from("the filesystem identity of the worktree is unavailable")),
311    };
312    return Ok(LockIdentity {
313        file: FileIdentity {
314            device: metadata.dev().to_string(),
315            inode: metadata.ino().to_string(),
316        },
317        fs_id,
318    });
319}
320
321/// What:
322///  On platforms without device and inode numbers,
323///  no identity is recorded and the landing stops.
324/// Why:
325///   The Windows volume serial is not exposed by the standard library,
326///  as the design notes say.
327///
328/// In TS you'd write (pseudocode):
329/// ```ts
330/// throw new TypeError('filesystem identity unavailable');
331/// ```
332// UNTESTED ON WINDOWS: no gate compiles or runs this branch (the crate does not build for Windows; README, Requirements).
333#[cfg(not(unix))]
334fn created_identity(lock_path: &Path, _real_index: &Path) -> Result<LockIdentity, String> {
335    return Err(format!("the index lock identity cannot be recorded here: {}", lock_path.display()));
336}
337
338/// What:
339///  Remove the PID file when it still names this process.
340/// Why:
341///   Another process may have announced itself meanwhile.
342///
343/// In TS you'd write (pseudocode):
344/// ```ts
345/// if ((await readFile(pidPath, 'utf8')) === `pid ${process.pid}\n`) await rm(pidPath);
346/// ```
347fn remove_own_pid_file(pid_path: &Path) -> Result<(), String> {
348    let own: String = format!("pid {}\n", std::process::id());
349    match std::fs::read_to_string(pid_path) {
350        Ok(text) => {
351            if text == own && let Err(error) = std::fs::remove_file(pid_path) {
352                return Err(error.to_string());
353            }
354            return Ok(());
355        }
356        Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
357        Err(error) => return Err(error.to_string()),
358    }
359}
360
361/// What:
362///  Create the lock exclusively,
363///  waiting for a foreign holder under the classification rules,
364///  and read the identity of the file created.
365/// Why:
366///   `openLockWaiting` plus the identity step of the landing;
367///  a lock whose identity cannot be recorded is removed again,
368///  because nothing proves it is ours.
369///
370/// In TS you'd write (pseudocode):
371/// ```ts
372/// const handle = await openLockWaiting({ realIndexPath, timeoutMs });
373/// ```
374fn create_identified_lock(
375    real_index: &Path,
376    timeout_ms: u64,
377    consequence: &str,
378) -> Result<(PathBuf, LockIdentity), CoordinationFailure> {
379    let lock_path: PathBuf = real_lock_path(real_index);
380    loop {
381        match create_exclusive(lock_path.as_path()) {
382            Ok(true) => break,
383            Ok(false) => wait_for_lock(real_index, timeout_ms, consequence)?,
384            Err(reason) => return Err(CoordinationFailure::Stop(reason)),
385        }
386    }
387    match created_identity(lock_path.as_path(), real_index) {
388        Ok(identity) => return Ok((lock_path, identity)),
389        Err(reason) => {
390            let _ = std::fs::remove_file(lock_path.as_path());
391            return Err(CoordinationFailure::Stop(reason));
392        }
393    }
394}
395
396/// What:
397///  Take the real index lock for a landing so that its journal record exists before the lock does:
398///  create a candidate file in the transaction directory,
399///  journal the candidate's identity,
400///  then publish the candidate as `index.lock` by a hard link,
401///  which is as exclusive as `O_EXCL` and gives the lock the candidate's inode.
402///  `Ok(None)` means the filesystem refused the link and nothing was left behind,
403///  so the caller creates the lock in place and journals it afterwards.
404/// Why:
405///   A kill between the creation of `index.lock` and the write of its record leaves a lock that nothing proves
406///  ours, which every later command refuses to remove (the lock may be Git's); no window like that remains when the
407///  record comes first. A candidate that outlives a kill lies in the transaction directory, which recovery removes.
408///
409/// In TS you'd write (pseudocode):
410/// ```ts
411/// await writeCandidate(); await journalIdentity(candidate); await link(candidate, indexLock); await rm(candidate);
412/// ```
413fn create_journaled_lock(
414    real_index: &Path,
415    transaction_directory: &Path,
416    attempt: i64,
417    timeout_ms: u64,
418    consequence: &str,
419) -> Result<Option<(PathBuf, LockIdentity)>, CoordinationFailure> {
420    let lock_path: PathBuf = real_lock_path(real_index);
421    let candidate: PathBuf = transaction_directory.join(format!("index-lock-{attempt}.candidate"));
422    match create_exclusive(candidate.as_path()) {
423        Ok(true) => {}
424        Ok(false) => return Err(CoordinationFailure::Stop(format!("{} already exists", candidate.display()))),
425        Err(reason) => return Err(CoordinationFailure::Stop(reason)),
426    }
427    let identity: LockIdentity = match created_identity(candidate.as_path(), real_index) {
428        Ok(found) => found,
429        Err(reason) => {
430            let _ = std::fs::remove_file(candidate.as_path());
431            return Err(CoordinationFailure::Stop(reason));
432        }
433    };
434    if let Err(reason) = write_lock_record(transaction_directory, attempt, &identity) {
435        let _ = std::fs::remove_file(candidate.as_path());
436        return Err(CoordinationFailure::Stop(reason));
437    }
438    loop {
439        match std::fs::hard_link(candidate.as_path(), lock_path.as_path()) {
440            Ok(()) => break,
441            Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {
442                if let Err(failure) = wait_for_lock(real_index, timeout_ms, consequence) {
443                    // The lock was never taken, so nothing is journaled for it.
444                    let _ = std::fs::remove_file(candidate.as_path());
445                    let _ = std::fs::remove_file(transaction_directory.join(index_lock_record_filename(attempt)));
446                    return Err(failure);
447                }
448            }
449            Err(error) => {
450                debug(
451                    TAG,
452                    format!("the filesystem refused to link the lock candidate ({error}); creating the lock in place").as_str(),
453                );
454                let _ = std::fs::remove_file(candidate.as_path());
455                let record: PathBuf = transaction_directory.join(index_lock_record_filename(attempt));
456                if let Err(removal) = std::fs::remove_file(record.as_path()) {
457                    return Err(CoordinationFailure::Stop(format!("removing {} failed: {removal}", record.display())));
458                }
459                return Ok(None);
460            }
461        }
462    }
463    // The lock now has its own name; the candidate's name was only the way in.
464    if let Err(error) = std::fs::remove_file(candidate.as_path()) {
465        debug(TAG, format!("removing the lock candidate {} failed: {error}", candidate.display()).as_str());
466    }
467    return Ok(Some((lock_path, identity)));
468}
469
470/// What:
471///  Take the real index lock,
472///  waiting for a foreign holder under the classification rules,
473///  then
474///       record its identity and announce this process through the PID file.
475/// Why:
476///   `openLockWaiting` plus the identity and PID steps of the landing.
477///
478/// In TS you'd write (pseudocode):
479/// ```ts
480/// const handle = await openLockWaiting({ realIndexPath, timeoutMs });
481/// ```
482pub fn acquire_real_index_lock(
483    real_index: &Path,
484    timeout_ms: u64,
485    consequence: &str,
486) -> Result<RealIndexLock, CoordinationFailure> {
487    let (lock_path, identity): (PathBuf, LockIdentity) = create_identified_lock(real_index, timeout_ms, consequence)?;
488    let pid_path: PathBuf = real_pid_path(real_index);
489    // The PID file is unsynced: a torn or lost one completes or discards in the tearing table.
490    if let Err(error) = write_private_file_unsynced(pid_path.as_path(), format!("pid {}\n", std::process::id()).as_bytes()) {
491        let _ = std::fs::remove_file(lock_path.as_path());
492        return Err(CoordinationFailure::Stop(error.to_string()));
493    }
494    return Ok(RealIndexLock {
495        lock_path,
496        real_index: real_index.to_path_buf(),
497        pid_path,
498        identity,
499    });
500}
501
502/// What:
503///  Write `index-lock-<n>.json` for a lock just created and sync the file, not its directory.
504/// Why:
505///   Recovery can prove ownership of the lock before any landing record exists,
506///  so the bytes must be durable before the PID file announces the lock.
507///  The transaction directory's entry is made durable by the directory sync of `landing-<n>.json`, before the branch
508///  moves; a name lost earlier leaves the state of a kill between creating the lock and writing its record.
509///
510/// In TS you'd write (pseudocode):
511/// ```ts
512/// await writeJournalRecord({ directory: transactionDirectory, filename: indexLockRecordFilename(attempt), record, sync: 'file' });
513/// ```
514fn write_lock_record(transaction_directory: &Path, attempt: i64, identity: &LockIdentity) -> Result<(), String> {
515    let record: IndexLockRecord = IndexLockRecord {
516        attempt,
517        lock: identity.clone(),
518    };
519    return write_record(
520        transaction_directory,
521        index_lock_record_filename(attempt).as_str(),
522        encode_index_lock(&record).as_bytes(),
523        RecordSync::FileOnly,
524    );
525}
526
527/// What:
528///  Take the real index lock for a landing attempt:
529///  create it,
530///  journal its identity in `index-lock-<n>.json`,
531///  fix its mode,
532///  and only then announce this process through the PID file.
533/// Why:
534///   `acquireRealIndexLock`:
535///  the journal comes before the PID file,
536///  so a crash never leaves a PID file naming a lock that recovery cannot prove ours;
537///  an existing PID file is left alone,
538///  as the incumbent does.
539///  A failure after the lock exists removes the lock again.
540///
541/// In TS you'd write (pseudocode):
542/// ```ts
543/// await using lock = await acquireRealIndexLock({ realIndexPath, transactionDirectory, attempt, timeoutMs });
544/// ```
545pub fn acquire_landing_index_lock(
546    real_index: &Path,
547    transaction_directory: &Path,
548    attempt: i64,
549    timeout_ms: u64,
550    consequence: &str,
551) -> Result<RealIndexLock, CoordinationFailure> {
552    if let Some((lock_path, identity)) = create_journaled_lock(real_index, transaction_directory, attempt, timeout_ms, consequence)? {
553        let announced: Result<(), String> = announce_held_lock(real_index, lock_path.as_path());
554        if let Err(reason) = announced {
555            let _ = std::fs::remove_file(lock_path.as_path());
556            return Err(CoordinationFailure::Stop(reason));
557        }
558        return Ok(RealIndexLock {
559            lock_path,
560            real_index: real_index.to_path_buf(),
561            pid_path: real_pid_path(real_index),
562            identity,
563        });
564    }
565    let (lock_path, identity): (PathBuf, LockIdentity) = create_identified_lock(real_index, timeout_ms, consequence)?;
566    let announced: Result<(), String> = announce_landing_lock(real_index, transaction_directory, attempt, lock_path.as_path(), &identity);
567    if let Err(reason) = announced {
568        let _ = std::fs::remove_file(lock_path.as_path());
569        return Err(CoordinationFailure::Stop(reason));
570    }
571    return Ok(RealIndexLock {
572        lock_path,
573        real_index: real_index.to_path_buf(),
574        pid_path: real_pid_path(real_index),
575        identity,
576    });
577}
578
579/// What:
580///  The steps between creating a landing's lock and holding it:
581///  journal record,
582///  mode,
583///  PID file.
584/// Why:
585///   One failure path removes the lock,
586///  whichever step failed.
587///
588/// In TS you'd write (pseudocode):
589/// ```ts
590/// await writeJournalRecord(...); await protectPath({ path: lockPath, directory: false }); await writeOwnPidFile(pidPath);
591/// ```
592fn announce_landing_lock(
593    real_index: &Path,
594    transaction_directory: &Path,
595    attempt: i64,
596    lock_path: &Path,
597    identity: &LockIdentity,
598) -> Result<(), String> {
599    write_lock_record(transaction_directory, attempt, identity)?;
600    return announce_held_lock(real_index, lock_path);
601}
602
603/// What:
604///  The steps between holding a landing's lock and announcing it:
605///  mode,
606///  PID file.
607/// Why:
608///   The journal record is already durable, whether it was written before the lock was published or after it was
609///  created.
610///
611/// In TS you'd write (pseudocode):
612/// ```ts
613/// await protectPath({ path: lockPath, directory: false }); await writeOwnPidFile(pidPath);
614/// ```
615fn announce_held_lock(real_index: &Path, lock_path: &Path) -> Result<(), String> {
616    if let Err(error) = protect_path(lock_path, 0o600) {
617        return Err(format!("protecting {} failed: {error}", lock_path.display()));
618    }
619    let pid_path: PathBuf = real_pid_path(real_index);
620    // Unsynced, as in `acquire_real_index_lock`: a torn or lost PID file completes or discards in the tearing table.
621    match write_private_file_unsynced(pid_path.as_path(), format!("pid {}\n", std::process::id()).as_bytes()) {
622        Ok(()) => return Ok(()),
623        Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {
624            debug(TAG, format!("a PID file already exists beside the lock: {}", pid_path.display()).as_str());
625            return Ok(());
626        }
627        Err(error) => return Err(format!("writing {} failed: {error}", pid_path.display())),
628    }
629}
630
631/// Real-lock controls stay out of the release executable.
632#[cfg(test)]
633#[path = "real_index_lock_tests.rs"]
634mod tests;
635
636/// Controls for failures of the exclusive creation and for the lock created in place after a refused link.
637#[cfg(test)]
638#[path = "real_index_lock_fallback_tests.rs"]
639mod fallback_tests;