FileLockSimulator.java

/*
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v2.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */
package ch.qos.logback.core.issue.lbcore258;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

/**
 * FileLockSimulator is a small application intended to simulate FileAppender in
 * prudent mode. In this mode, the application obtains an exclusive lock on the
 * file, writes to the file and then releases the lock.
 *
 * <pre>
 *   Usage:
 *   java  FileLockSimulator instanceName pathToLogFile delay
 * where
 * "instanceName" is the name given to the current instance of the application
 * "pathToLogFile" is the path to the log file
 * "delay" is the number of milliseconds of sleep observed every 128 writes
 * </pre>
 *
 * <b>This small application requires only the JDK to compile and to
 * execute.</b>
 *
 * <p>
 * FileLockSimulator should be launched as many times and from as many hosts as
 * there will be JVMs writing to a log file in prudent mode. Performance should
 * be quite good if "pathToLogFile" is on a local file system. On networked file
 * systems such as NFS, performance depends on the speed of the network and NFS
 * implementation. It has been observed that file locking over NFS is biased so
 * that the current owner of the lock is favored over other processes. Thus,
 * while one process hogs the lock for the log file, other processes starve
 * waiting for the lock to the point of appearing deadlocked.
 *
 */
public class FileLockSimulator {

    static String LINE_SEPARATOR = System.getProperty("line.separator");
    static final int DOT_FREQ = 128;
    static final int DOT_WITH_NEW_LINE_FREQ = DOT_FREQ * 80;

    static String instanceName;
    static int delay;
    static FileOutputStream fos;
    static FileChannel fileChannel;

    public static void main(String[] args) throws IOException, InterruptedException {

        String instanceName = args[0];
        System.out.println("Instance named as [" + instanceName + "]");

        String fileStr = args[1];
        System.out.println("Output target specified as [" + fileStr + "]");

        int delay = Integer.parseInt(args[2]);
        System.out.println("Sleep delay specified as [" + delay + "] milliseconds");

        fos = new FileOutputStream(fileStr, true);
        fileChannel = fos.getChannel();

        for (int i = 1;; i++) {
            printDotAndSleep(i);
            lockAndWrite(i);
        }
    }

    static void lockAndWrite(int i) throws InterruptedException, IOException {
        FileLock fileLock = null;
        try {
            fileLock = fileChannel.lock();
            long position = fileChannel.position();
            long size = fileChannel.size();
            if (size != position) {
                fileChannel.position(size);
            }
            String msg = "hello from" + instanceName + " " + i + LINE_SEPARATOR;
            fos.write(msg.getBytes());
        } finally {
            if (fileLock != null) {
                fileLock.release();
            }
        }
    }

    static void printDotAndSleep(int i) throws InterruptedException {
        if (i % DOT_FREQ == 0) {
            System.out.print(".");
            Thread.sleep(delay);
        }
        if (i % DOT_WITH_NEW_LINE_FREQ == 0)
            System.out.println("");
    }
}