cloudius-systems / osv

OSv, a new operating system for the cloud.
osv.io
Other
4.11k stars 604 forks source link

Cannot monitor file/directory changes in Java code - missing 'inotify_add_watch' (and maybe others) #435

Open lgoldstein opened 10 years ago

lgoldstein commented 10 years ago

I am using Java code similar to the Groovy script below in order to monitor file/directory changes (similar to tail -f). The code works fine on Windows and Linux and it used to work on OSV, but no longer. When I run it now I get:

java.io.IOException: No file descriptors available
    at sun.nio.fs.LinuxWatchService.<init>(LinuxWatchService.java:61)
    at sun.nio.fs.LinuxFileSystem.newWatchService(LinuxFileSystem.java:47)

The Groovy test script (which can easily be turned into a Java one...):

#!/usr/bin/env groovy

import java.nio.file.FileSystem
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds
import java.nio.file.WatchEvent
import java.nio.file.WatchKey
import java.nio.file.WatchService
import java.nio.file.WatchEvent.Kind
import java.util.concurrent.atomic.AtomicLong

import org.codehaus.groovy.tools.shell.ExitNotification

if (args.length <= 0) {
    dieWithMessage("Usage: FollowFile file")
}

try {
    monitorFileChanges(new File(args[0]).canonicalFile)
} catch(Throwable t) {
    System.err.println(t.getClass().getSimpleName() + ": " + t.getMessage())
    t.printStackTrace(System.err)
}
return

void monitorFileChanges(File targetFile) {
    String          absPath=targetFile.absolutePath
    FileSystem      fs=FileSystems.getDefault()
    Path            filePath=targetFile.toPath()
    Path            dirPath=filePath.getParent()
    AtomicLong      lastAccessedOffset=new AtomicLong(0L)
    println "Start following $absPath"

    WatchService    watchService=fs.newWatchService()
    try {
        WatchKey      registrationKey=dirPath.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY)
        try {
            while(true) {
                long    curOffset=lastAccessedOffset.get(), available=targetFile.length()
                if (curOffset < available) {
                    updateDisplayedContents(targetFile, curOffset, available - curOffset, System.out)
                    lastAccessedOffset.set(available)
                }

                WatchKey    key=watchService.take()
                if (key == null) {
                    continue
                }

                if (!key.isValid()) {
                    break
                }

                Collection<WatchEvent<?>>   events=key.pollEvents();
                if ((events == null) || events.isEmpty()) {
                    continue
                }

                for (WatchEvent<?> event : events) {
                    Kind<?> kind=event.kind()
                    if (!StandardWatchEventKinds.ENTRY_MODIFY.equals(kind)) {
                        continue
                    }

                    Path    modifiedPath=((WatchEvent<Path>) event).context()
                    File    modifiedFile=modifiedPath.toFile()
                    String  absModified=modifiedFile.absolutePath
                    if (!absPath.equals(absModified)) {
                        continue
                    }

                    break  // no need to examine the rest of the events
                }
            }
        } finally {
            registrationKey.cancel()
        }
    } finally {
        watchService.close()
    }
}

void updateDisplayedContents(File file, long curOffset, long displaySize, OutputStream outputStream) {
    RandomAccessFile    raf=new RandomAccessFile(file, "r")
    try {
        raf.seek(curOffset)

        byte[]  workBuf=new byte[4096]
        for (long remSize=displaySize; remSize > 0L; ) {
            int readLen=raf.read(workBuf)
            if (readLen <= 0) {
                break
            }
            outputStream.write(workBuf, 0, readLen)
            remSize -= readLen
        }
        outputStream.flush()
    } finally {
        raf.close()
    }

}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////

def dieWithMessage(String msg, int rc=1) {
    System.err.println(msg)
    throw new ExitNotification(rc)
}
nyh commented 10 years ago

On Mon, Aug 4, 2014 at 3:40 PM, Lyor Goldstein notifications@github.com wrote:

I am using Java code similar to the Groovy script below in order to monitor file/directory changes (similar to tail -f). The code works fine on Windows and Linux and it used to work on OSV, but no longer. When I run it now I get:

java.io.IOException: No file descriptors available at sun.nio.fs.LinuxWatchService.(LinuxWatchService.java:61) at sun.nio.fs.LinuxFileSystem.newWatchService(LinuxFileSystem.java:47)

OpenJDK's LinuxWatchService.c uses inotify_add_watch() and friends, which in OSv are stubbed (see libc/inotify.cc), so I don't understand how this used to work. Could it be that you tried this 4 months ago, before these stubs were added?

By the way, if you just need something similar to "tail -f" on a single file, inotify is an overkill.

lgoldstein commented 10 years ago

@nyh I don't understand how this used to work. I am pretty sure it worked (though I may be wrong). Regardless, it does not work now (so if you wish you can change the title and remove the Regression word).

By the way, if you just need something similar to "tail -f" on a single file, inotify is an overkill - there is no inotify in Java (which is what I am describing here...) + this bug has nothing to do with tail -f - it was just an example of where such a monitoring API is required.

Anyway, OSV is supposed to support the full Java 7 API, so it must also support this one...