0

I'm trying to lock one file when there's one process writing it. When another process tries reading the file, it needs to make sure that no process is writing on it. The idea is that when the write process dies before unlocking the file, and another read process can detect this and deletes this semi-finished file.

To do that, I built such a FileLock structure:

type FileLock struct {
    filePath string
    f        *os.File
}

func (l *FileLock) Lock() error {
    if l.f == nil {
        f, err := os.OpenFile(l.filePath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0200)
        if err != nil {
            return err
        }
        l.f = f
    }
    err := syscall.Flock(int(l.f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
    if err != nil {
        return fmt.Errorf("cannot flock file %s - %s", l.filePath, err)
    }
    return nil
}

func (l *FileLock) Unlock() error {
    defer l.f.Close()
    return syscall.Flock(int(l.f.Fd()), syscall.LOCK_UN)
}

Before writing to this localfile, I lock it. And unlock when the write is finished:

func downloadFile(response *http.Response, filePath string) error {
    output, _ := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0200)
    localFileLock := &FileLock{filePath: filePath, f: output}
    // Lock the file before writing.
    if err := localFileLock.Lock(); err != nil {
        return err
    }
    if _, err := io.Copy(output, response.Body); err != nil {
        return fmt.Errorf("problem saving %s to %s: %s", response.Request.URL, filePath, err)
    }
    // Unlock the file after writing.
    if err := localFileLock.Unlock(); err != nil {
        return err
    }
    return nil

}

But for another process, how to check if that file is locked?

Thanks!

IsaIkari
  • 1,002
  • 16
  • 31
  • 1
    Couldn't you just attempt to lock it in non-blocking mode, then immediately unlock it afterwards? – Andrew Sun Aug 26 '21 at 18:37
  • @AndrewSun Sorry I didn't make it clear. The idea is that when the write process dies before unlocking the file, and another read process detect this and delete this semi-finished file. – IsaIkari Aug 26 '21 at 18:48
  • 1
    I meant that you could check if it's locked by attempting to lock it, and if you get the lock then you unlock it and return false; otherwise you return true (because the file is locked) – Andrew Sun Aug 26 '21 at 18:59
  • 2
    dont lock the fle, write the results to a temporary location, once you have finished generating the results perform atomic rename. though, avoid crossing partitions https://stackoverflow.com/questions/50740902/move-a-file-to-a-different-drive-with-go –  Aug 26 '21 at 19:03
  • 1
    Related: [flock(): is it possible to merely check if the file is already locked, without actually acquiring the lock if not?](https://stackoverflow.com/questions/29067893/flock-is-it-possible-to-merely-check-if-the-file-is-already-locked-without-a) – rustyx Aug 26 '21 at 19:19

1 Answers1

0

when the write process dies before unlocking the file, and another read process can detect this and deletes this semi-finished file.

A process's file descriptors are closed when the process terminates, so if a process terminates with a file locked, its locks are released.¹

So the way to detect and delete an unfinished file is to actually lock it, then delete it with the file descriptor still open and the lock still held.

If you attempt to delete the file without actually acquiring the lock, the deletion may race: another process may have already deleted the original file and recreated and fully written a file with the same name.


¹ man 2 flock:

the lock is released either by an explicit LOCK_UN operation on any of these duplicate file descriptors, or when all such file descriptors have been closed.

bcmills
  • 4,391
  • 24
  • 34