Read and Write to a File While Keeping Lock

Read and write to a file while keeping lock

As said, you could use FLock. A simple example would be:

//Open the File Stream
$handle = fopen("file.txt","r+");

//Lock File, error if unable to lock
if(flock($handle, LOCK_EX)) {
$count = fread($handle, filesize("file.txt")); //Get Current Hit Count
$count = $count + 1; //Increment Hit Count by 1
ftruncate($handle, 0); //Truncate the file to 0
rewind($handle); //Set write pointer to beginning of file
fwrite($handle, $count); //Write the new Hit Count
flock($handle, LOCK_UN); //Unlock File
} else {
echo "Could not Lock File!";
}

//Close Stream
fclose($handle);

Lock the file while reading and writing

PHP has the flock function which will lock the file before writing to it, example,

$handle = fopen($file, 'r');
$contents = fread($handle, filesize($file));
fclose($handle);

$arr = json_decode($contents);

//Add stuff here to $arr and update counter $arr['counter']++

$handle = fopen($file, 'w');
if(flock($handle, LOCK_EX))
{
fwrite($handle, json_encode($arr));
flock($handle, LOCK_UN);
}
else
{
// couldn't lock the file
}
fclose($handle);

How do I write/read to a file that I've locked?

A simpler solution would be to use a FileOutputStream

FileOutputStream os = new FileOutputStream(path, true);
FileChannel channel = os.getChannel();

When you use

new FileWriter(os, true)

the true mean "append mode" however when you use

new PrintWriter(os,true)

the true means; "flush on newline" i.e. it will always overwrite

When you need to do is to only write from the end of the file. I suggest you use

channel.position(channel.size());

before you attempt to append.

Java FileLock for Reading and Writing

  1. Are you aware that locking the file won't keep other processes from touching it unless they also use locks?
  2. You have to lock via a writable channel. Get the lock via a RandomAccessFile in "rw" mode and then open your FileInputStream. Make sure to close both!

Java: How to hold off read/writing a file while it's locked

The standard way to use FileLock, is to open the file, e.g. via FileChannel.open, followed by tryLock. The presence of a lock does not prevent other processes from opening the file.

This can be demonstrated by the following program:

import java.io.IOException;
import java.nio.channels.*;
import java.nio.file.*;

class Locking {
public static void main(String[] args) throws IOException, InterruptedException {
if(args.length > 0) {
String me = String.format("%6s ", ProcessHandle.current());
Path p = Paths.get(args[0]);
try(FileChannel fc = FileChannel.open(p,
StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {

FileLock l = fc.tryLock();
if(l == null) System.out.println(me + "could not acquire lock");
else {
System.out.println(me + "got lock");
Thread.sleep(3000);
System.out.println(me + "releasing lock");
l.release();
}
}
}
else {
Path p = Files.createTempFile("lock", "test");
String[] command = {
Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
"-cp", System.getProperty("java.class.path"),
"Locking", p.toString()
};
ProcessBuilder b = new ProcessBuilder(command).inheritIO();
Process p1 = b.start(), p2 = b.start(), p3 = b.start();
p1.waitFor();
p2.waitFor();
p3.waitFor();
Files.delete(p);
}
}
}

which prints something alike

 12116 got lock
13948 could not acquire lock
13384 could not acquire lock
12116 releasing lock

which can be demonstrated online on tio.run

While this program works the same under Windows, this operating system supports opening files unshared, preventing other processes from opening. If a different process has opened the file in that way, we can’t even open it to probe the locking state.

This is not the way, Java opens the file, however, there’s a non-standard open option to replicate the behavior, com.sun.nio.file.ExtendedOpenOption.NOSHARE_WRITE. In recent JDKs, it’s in the jdk.unsupported module.

When we run the following extended test program under Windows

import java.io.IOException;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.HashSet;
import java.util.Set;

class LockingWindows {
public static void main(String[] args) throws IOException, InterruptedException {
if(args.length > 0) {
String me = String.format("%6s ", ProcessHandle.current());
Path p = Paths.get(args[0]);
Set<OpenOption> options
= Set.of(StandardOpenOption.WRITE, StandardOpenOption.APPEND);
if(Boolean.parseBoolean(args[1])) options = addExclusive(options);
try(FileChannel fc = FileChannel.open(p, options)) {
FileLock l = fc.tryLock();
if(l == null) System.out.println(me + "could not acquire lock");
else {
System.out.println(me + "got lock");
Thread.sleep(3000);
System.out.println(me + "releasing lock");
l.release();
}
}
}
else {
Path p = Files.createTempFile("lock", "test");
String[] command = {
Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
"-cp", System.getProperty("java.class.path"),
"LockingWindows", p.toString(), "false"
};
ProcessBuilder b = new ProcessBuilder(command).inheritIO();
for(int run = 0; run < 2; run++) {
Process p1 = b.start(), p2 = b.start(), p3 = b.start();
p1.waitFor();
p2.waitFor();
p3.waitFor();
if(run == 0) {
command[command.length - 1] = "true";
b.command(command);
System.out.println("\nNow with exclusive mode");
}
}
Files.delete(p);
}
}

private static Set<OpenOption> addExclusive(Set<OpenOption> options) {
OpenOption o;
try {
o = (OpenOption) Class.forName("com.sun.nio.file.ExtendedOpenOption")
.getField("NOSHARE_WRITE").get(null);
options = new HashSet<>(options);
options.add(o);
} catch(ReflectiveOperationException | ClassCastException ex) {
System.err.println("opening exclusive not supported");
}
return options;
}
}

we will get something like

  2356 got lock
6412 could not acquire lock
9824 could not acquire lock
2356 releasing lock

Now with exclusive mode
9160 got lock
Exception in thread "main" java.nio.file.FileSystemException: C:\Users\...\Temp\lock13936982436235244405test: The process cannot access the file because it is being used by another process
at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
at java.base/sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:121)
at java.base/java.nio.channels.FileChannel.open(FileChannel.java:298)
at LockingWindows.main(LockingWindows.java:148)
Exception in thread "main" java.nio.file.FileSystemException: C:\Users\...\Temp\lock13936982436235244405test: The process cannot access the file because it is being used by another process
at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
at java.base/sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:121)
at java.base/java.nio.channels.FileChannel.open(FileChannel.java:298)
at LockingWindows.main(LockingWindows.java:148)
9160 releasing lock

The similarity to the outcome of your test suggests that the Windows program you ran concurrently to your Java program did use such a mode.

For your Java programs, no such issue should arise, as long as you don’t use that mode. Only when you have to interact with another Windows program not using the collaborative locking, you have to deal with this.

File locking - read then write whilst locked

If you want to write to a file, you need to take exclusive file access, otherwise other programs can read partially written data (your writes aren't atomic). There are solutions to this, but they are quite complex.

A solution could be something like this:

static bool Read(FileStream fs, byte[] data, long position)
{
fs.Seek(position, SeekOrigin.Begin);

if (fs.ReadByte() == 0)
{
// Block of data not finished writing
return false;
}

fs.Read(data, 0, data.Length);
return true;
}

static bool Write(FileStream fs, byte[] data, long position)
{
try
{
fs.Lock(position, data.Length + 1);
fs.Seek(position, SeekOrigin.Begin);
fs.WriteByte(0);
fs.Write(data, 0, data.Length);
fs.Seek(position, SeekOrigin.Begin);
fs.WriteByte(1);
fs.Unlock(position, data.Length + 1);
return true;
}
catch (IOException)
{
return false;
}
}

static bool Append(FileStream fs, byte[] data)
{
return Write(fs, data, fs.Length);
}

where you always keep open the FileStream as

FileStream fs1 = new FileStream("Test.txt", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);

Before the data there is a "guard byte" that tells if the data is being written. If it's being written then reads on it will fail. The file is locked "where it's needed" using FileStream.Lock.

This clearly works better with binary fixed-length data. If you have variable length data (or you need to atomically update more "regions" of a file) then it becomes more complex. Normally you use DBs for this reason :-)

Protecting read/write access to/from a file from multiple threads

Here's the algorithm each thread should follow:

  1. Acquire the lock that protects the shared state of files.
  2. See if the file we are trying to access exists in the table.
  3. If it does not exist, create it.
  4. Check the entry for the file we are trying to access.
  5. If no other thread is accessing the file, jump to step 9.
  6. Release the lock that protects the shared state of files.
  7. Wait
  8. Go to step 1.
  9. Mark the file in use.
  10. Release the lock that protects the shared state of files.
  11. Read or write the file as appropriate.
  12. Acquire the lock that protects the shared state of files.
  13. Mark the file not in use.
  14. Release the lock that protects the shared state of files.

Note that if you use a condition variable to make the wait more efficient, then steps 6, 7 and 8 turn into waiting on the condition variable and then jumping to step 2. Also, you would need to broadcast the condition variable (notify all) before or after step 14. (Preferably before.)

How to lock a file, read content and overwrite (truncate) it

To solve it, added manually a truncate to 0 (beginning of file) and changed open option to a+

$fp = fopen($file, "a+");
if(flock($fp, LOCK_EX)) {
$content = fread($fp, $filesize);
echo $content; // this is not empty now
$job_queue = explode("\n", $content, LOCK_EX);
$next_job = array_shift($job_queue);
ftruncate($fp, 0);
fwrite($fp, implode("\n", $job_queue));
flock($fp, LOCK_UN);
} else {
echo '<br>Error: cannot lock job queue file';
}
fclose($fp);


Related Topics



Leave a reply



Submit