Probleme beim Zugriff auf eine selbst gelockte Datei

Ich verwende zum Locken java.nio.channels.FileLock.

Siehe auch

http://vafer.org/blog/20060806175846/

Mein zum Testen und hier Posten aus verschiedenen Klassen zusammenkopierter Code

package fileLock;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.nio.channels.FileChannel;
import java.nio.channels.OverlappingFileLockException;

/*
 * Fürs Locking siehe auch:
 * http://vafer.org/blog/20060806175846/
 * https://examples.javacodegeeks.com/core-java/nio/channels/filelock-channels/java-nio-channels-filelock-example/
 */

public class CanNotReadLockedFile {

    private java.nio.channels.FileLock lock;
    private RandomAccessFile file;

    private String filename;
    private String rights;

    public CanNotReadLockedFile() {
        filename = "c:/temp/testdatei_zum_locken.txt";
        rights = "rw";
    }

    public void run() {
        if (lock()) {
            say("lock gained");
            runLocked();
            unlock();
        }
    }

    private void runLocked() {
        try {
            tryToRunLocked();
        }
        catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    private void tryToRunLocked() throws IOException {
        LineNumberReader reader = openReader();

        String line;
        while (null != (line = reader.readLine())) {
            say("Eingelesene Zeile: '" + line + "' in Zeile " + reader.getLineNumber());
        }

        reader.close();
    }

    /* --- DATEI LESEN --- */

    private LineNumberReader openReader() {
        try {
            return tryToOpenReader();
        }
        catch (FileNotFoundException e) {
            throw new RuntimeException("Die Eingabedatei '" + filename
                    + "' wurde nicht gefunden. (" + e.getMessage() + ")");
        }
        catch (UnsupportedEncodingException e) {
            throw new RuntimeException(
                    "Bei der Erzeugung des " + "InputStreamReaders für die Eingabedatei '"
                            + filename + "' wurde eine nicht unterstützte Kodierung verwendet. ("
                            + e.getMessage() + ")");
        }
    }

    private LineNumberReader tryToOpenReader()
            throws FileNotFoundException, UnsupportedEncodingException {
        InputStream inputStream = new FileInputStream(filename);
        InputStreamReader reader = new InputStreamReader(inputStream, "ISO-8859-1");
        return new LineNumberReader(reader);
    }

    /* --- LOCKING --- */

    boolean lock() {
        if (null != lock) {
            return false;
        }

        say("Versuche Lock auf " + filename + " zu erhalten ...");
        boolean success = lockInternal();

        if (success) {
            say("Lock auf " + filename + " erhalten.");
            say("Lock is shared: " + lock.isShared());
        }
        else {
            say("Kein Lock erhalten, lock = " + lock);
            lock = null;
        }

        return success;
    }

    private boolean lockInternal() {
        file = openRandomAccessFile();
        FileChannel channel = file.getChannel();

        say("Versuch den exclusiven Lock zu erhalten...");
        tryToLock(channel);

        if (null == lock) {
            say("Lock kann nicht erhalten werden. lock == null.");
            closeFile();
            return false;
        }

        return true;
    }

    private RandomAccessFile openRandomAccessFile() {
        try {
            return new RandomAccessFile(filename, rights);
        }
        catch (FileNotFoundException exception) {
            throw new RuntimeException(exception);
        }
    }


    private void closeFile() {
        try {
            file.close();
        }
        catch (IOException exception) {
            throw new RuntimeException(exception);
        }
        file = null;
    }

    private boolean tryToLock(FileChannel channel) {
        try {
            lock = channel.tryLock(); // gets an exclusive lock
        }
        catch (IOException exception) {
            say("Lock kann nicht erhalten werden. IOException.");
            return false;
        }
        catch (OverlappingFileLockException exception) {
            say("Lock kann nicht erhalten werden. OverlappingFileLockException.");
            return false;
        }

        return true;
    }

    private boolean unlock() {
        if (null == lock) {
            return false;
        }

        say("Versuche Lock auf " + filename + " zu lösen ...");

        boolean success = tryToUnlock();

        if (success) {
            say("Lock auf " + filename + " gelöst.");
        }

        lock = null;
        closeFile();

        return success;
    }

    private boolean tryToUnlock() {
        try {
            lock.release();
        }
        catch (IOException exception) {
            say("Lösen war nicht erfolgreich: " + exception.getMessage());
            exception.printStackTrace();
            return false;
        }

        return true;
    }


    private void say(String message) {
        System.out.println(message);
    }

    public static void main(String[] args) {
        new CanNotReadLockedFile().run();
    }

}

läuft auf den Fehler Der Prozess kann nicht auf die Datei zugreifen, da ein anderer Prozess einen Teil der Datei gesperrt hat, vermutlich weil ich einen eigenen Reader auf der Datei geöffnet habe und nicht über das RandomAccessFile auf die Datei zugreife.

Er erzeugt folgende Ausgabe:

Versuche Lock auf c:/temp/testdatei_zum_locken.txt zu erhalten ...
Versuch den exclusiven Lock zu erhalten...
Lock auf c:/temp/testdatei_zum_locken.txt erhalten.
Lock is shared: false
lock gained
java.io.IOException: Der Prozess kann nicht auf die Datei zugreifen, da ein anderer Prozess einen Teil der Datei gesperrt hat
    at java.io.FileInputStream.readBytes(Native Method)
    at java.io.FileInputStream.read(FileInputStream.java:255)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.readLine(BufferedReader.java:324)
    at java.io.LineNumberReader.readLine(LineNumberReader.java:201)
    at fileLock.CanNotReadLockedFile.tryToRunLocked(CanNotReadLockedFile.java:54)
    at fileLock.CanNotReadLockedFile.runLocked(CanNotReadLockedFile.java:43)
    at fileLock.CanNotReadLockedFile.run(CanNotReadLockedFile.java:36)
    at fileLock.CanNotReadLockedFile.main(CanNotReadLockedFile.java:198)
Versuche Lock auf c:/temp/testdatei_zum_locken.txt zu lösen ...
Lock auf c:/temp/testdatei_zum_locken.txt gelöst.

Muss ich mich jetzt mit dem RandomAccessFile auseinandersetzen, oder gibt es einen anderen, eleganten Weg? Momentan hab ich es so gelöst, dass ich nicht diese Datei selbst locke, sondern eine andere extra nur zum Locken angelegte Datei. Ich weiß aber nicht, ob das so richtig elegant ist.

Disclaimer: Ich habe noch nie mit FileLock gearbeitet, und nur wenig mit FileChannel, und mir ist absolut nicht klar, was du erreichen willst. (Nur, dass niemand auf die Datei zugreifen kann, während du mit ihr arbeitest?)

Zusätzlich sind die Methoden zwar “schön”, in dem Sinne, dass sie sehr genau loggen und Fehlermeldungen ausgeben, aber … der Kern dessen, was dort gemacht werden soll, geht etwas unter.

Jedenfalls könnte sowas wie

private LineNumberReader tryToOpenReader()
        throws FileNotFoundException, UnsupportedEncodingException {
    
    InputStream inputStream = Channels.newInputStream(file.getChannel());        
    InputStreamReader reader = new InputStreamReader(inputStream, "ISO-8859-1");
    return new LineNumberReader(reader);
}

etwa sein, was du suchst. Beim Lösen des Locks kommt dann natürlich eine Exception, weil der LineNumberReader (und damit alles, worauf der arbeitet - bis runter zum Channel) ja händisch geschlossen wird. Aber vielleicht ist das ja gerade das, was du eigentlich willst:

FileChannel fileChannel = create(); 
fileChannel.lock();
LineNumberReader reader = new LineNumberReader(inputStreamFor(fileChannel));
readFrom(reader);
fileChannel.unlock();
reader.close(); // Macht auch den FileChannel zu

Das ist aber nur wild ins Blaue geraten. Ob das so Sinn macht, müßte man sich nochmal durch den Kopf gehen lassen.

(BTW: Das Erstellen des FileChannels könnte über RandomAccessFile laufen - muss aber nicht, das geht auch direkt…)

1 „Gefällt mir“

Nicht „niemand“, ein anderer könnte einfach lesen / schreiben. Ich selbst will nicht drauf zugreifen können aus meinen Prozessen. :slight_smile:

Es geht darum, dass mehrere Prozesse auf mehreren Rechnern gleichzeitig an einer großen Aufgabe arbeiten. Koordiniert wird das über eine Datei, in der jede Teilaufgabe enthalten ist und drei Zustände haben kann: unbearbeitet, in Bearbeitung oder fertig.

Damit nicht mehrere Prozesse gleichzeitig an der Datei ändern oder gar die gleiche Aufgabe bearbeiten, benutze ich eine Semaphore. Wäre das ein einziges Programm auf einem Rechner, das mehrere Threads startet, wäre das wohl etwas für synchronized, das nutzt aber nichts für verschiedene Prozesse.

Es funktioniert jetzt, indem ich als Semaphore eine andere extra dafür erzeugte Datei verwende. (Und noch eine systemweite Datei für das Anlegen einer solchen Lockdatei, aber das ist eher ein technisches Detail).

Ich hatte mich daran gestört, dass ich „meine“ Datei, wenn ich den Lock auf sie habe, nicht bearbeiten kann. Das könnte ich aber sicher, würde ich das RandomAccessFile verwenden. Da aber der ganze Code schon fertig ist und die kleine, zusätzliche Lockdatei niemanden stört, lasse ich es jetzt so.

Außerdem hatte mich der andere Umgang mit Encoding im RandomAccessFile davon abgehalten, es könnte gut sein, dass ich sonst nach langem Basteln doch zur jetzigen Lösung zurückkehren müsste.

Zur grundlegenden Aufgabe: Eine solche Verwaltung vieler Teilaufgaben über eine zentrale Textdatei verwende ich schon seit vielen Jahren in einem anderen Projekt (noch in Perl geschrieben), das eine Arbeit, die von einem einzelnen Prozess bearbeitet 72 Tage dauern würde, auf eine halbe Woche reduziert.

In noch einem anderen Projekt verwende ich für eine ähnliche Aufgabe eine Tabelle in einer MySql-Datenbank. Da hier aber keine verwendet wird, wollte ich nicht nur für eine einzige Tabelle eine Datenbank anlegen und so kam mir mein altes Perl-Vorgehen in den Sinn. Und das funktioniert nach Tests mit der Javaversion ebenfalls hervorragend.

Ich glaube, das Erstellen eines dedizierten “lock-files” (wenn es da ist, wird gerade etwas gemacht) ist nich unüblich. So macht Eclipse das AFAIK auch mit seinem Workspace. Aber wenn man den oben angedeuteten Ablauf “sauber” implementiert, sollte das doch eigentlich reichen bzw. schon funktionieren…?!