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
- Are you aware that locking the file won't keep other processes from touching it unless they also use locks?
- You have to lock via a writable channel. Get the lock via a
RandomAccessFile
in "rw" mode and then open yourFileInputStream
. 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:
- Acquire the lock that protects the shared state of files.
- See if the file we are trying to access exists in the table.
- If it does not exist, create it.
- Check the entry for the file we are trying to access.
- If no other thread is accessing the file, jump to step 9.
- Release the lock that protects the shared state of files.
- Wait
- Go to step 1.
- Mark the file in use.
- Release the lock that protects the shared state of files.
- Read or write the file as appropriate.
- Acquire the lock that protects the shared state of files.
- Mark the file not in use.
- 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
I18N with Gettext But Without the Locale Hassle
Dates Not Casting After Upgrading to Laravel 7
Prevent Browser Back Button Cache
How to Put a Translation System in PHP Website
Ssl Error Can Not Change to Tls
Extending Session Timeout in PHP via the .Htaccess
Sqlstate[Hy000] [2003] Can't Connect to MySQL Server on '127.0.0.1' (61) Error Laravel 4.1
Is There a Difference Between Instantiation with Parentheses or Without
Setting Variables on Constructor VS on the Class Definition
PHP Mail() Function Server and Localhost Not Working
Interpreting Return Value of Function Directly as an Array
Php: Split String on Comma, But Not When Between Braces or Quotes