Skip to content
Advertisement

The optimal way to lock and write to a file in Scala on Linux

I’m having a hard time finding the correct way to do any advanced file-system operations on Linux using Scala.

The one which I really can’t figure out if best described by the following pseudo-code:

with fd = open(path, append | create):
    with flock(fd, exclusive_lock):
        fd.write(string)

Basically open a file in append mode (create it if it’s non existent), get an exclusive lock to it and write to it (with the implicit unlock and close afterwards).

Is there an easy, clean and efficient way of doing this if I know my program will be ran on linux only ? (preferably without glancing offer the exceptions that should be handled).

Edit:

The answer I got is, as far as I’ve seen and tested is correct. However it’s quite verbose, so I’m marking it as ok but I’m leaving this snippet of code here, which is the one I ended up using (Not sure if it’s correct, but as far as I see it does everything that I need):

  val fc  = FileChannel.open(Paths.get(file_path), StandardOpenOption.CREATE, StandardOpenOption.APPEND)
  try {
    fc.lock()
    fc.write(ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)))
  } finally { fc.close() }

Advertisement

Answer

You can use FileChannel.lock and FileLock to get what you wanted:

import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import java.nio.charset.StandardCharsets
import java.nio.file.{Path, Paths, StandardOpenOption}

import scala.util.{Failure, Success, Try}

object ExclusiveFsWrite {
  def main(args: Array[String]): Unit = {
    val path = Paths.get("/tmp/file")
    val buffer = ByteBuffer.wrap("Some text data here".getBytes(StandardCharsets.UTF_8))

    val fc = getExclusiveFileChannel(path)
    try {
      fc.write(buffer)
    }
    finally {
      // channel close will also release a lock
      fc.close()
    }

    ()
  }

  private def getExclusiveFileChannel(path: Path): FileChannel = {
    // Append if exist or create new file (if does not exist)
    val fc = FileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.APPEND,
      StandardOpenOption.CREATE)
    if (fc.size > 0) {
      // set position to the end
      fc.position(fc.size - 1)
    }
    // get an exclusive lock
    Try(fc.lock()) match {
      case Success(lock) =>
        println("Is shared lock: " + lock.isShared)
        fc
      case Failure(ex) =>
        Try(fc.close())
        throw ex
    }
  }
}
Advertisement