Im currently working on a application to manage a remote minecraft server and the first thing Im working on is observing the messages in the minecraft console. Im doing this by observing the latest.log textfile in which the minecraft server copies its output. The log messages are added each in a newline, so the basic workflow for monitoring is:
start -> read all existing lines of latest.log -> watch for file change notifications on latest.log ->
read newly added line(s) -> wait for next file change notification
Ive implemented the following class to do this:
public class McServerService {
private String directory;
private List<String> currentLog;
private Thread logObserverThread;
private PropertyChangeSupport pcsupport;
public McServerService (String directory) {
this.currentLog = new ArrayList<String>();
this.directory = directory;
this.pcsupport = new PropertyChangeSupport(this);
}
public void startWatching () {
this.logObserverThread = new Thread(new LogObserverThreadImpl(this.directory));
this.logObserverThread.start();
}
public void addNewLogLine (String newLogLine) {
this.pcsupport.firePropertyChange("currentLog", this.currentLog, newLogLine);
this.currentLog.add(newLogLine);
System.out.println("addNewLogLine: " + newLogLine);
}
public void addPropertyChangeListener (PropertyChangeListener pcl) {
this.pcsupport.addPropertyChangeListener(pcl);
}
public void removePropertyChangeListener (PropertyChangeListener pcl) {
this.pcsupport.removePropertyChangeListener(pcl);
}
private class LogObserverThreadImpl implements Runnable {
BufferedReader br;
WatchService watchService;
private LogObserverThreadImpl (String directory) {
try {
this.br = new BufferedReader(new java.io.FileReader(directory + "\\" + "latest.log"));
String nextLine = this.br.readLine();
while (nextLine != null) {
McServerService.this.currentLog.add(nextLine);
System.out.println("init: " + nextLine);
this.br.mark(2048);
nextLine = this.br.readLine();
System.out.println("init: " + nextLine);
}
this.br.reset();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
public void run() {
Path path = Paths.get(directory);
try {
System.out.println("entered try");
this.watchService = FileSystems.getDefault().newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY);
WatchKey key;
while ((key = this.watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
if (event.context().toString().equals("latest.log")) {
String line = this.br.readLine();
/*
* if (line.equalsIgnoreCase("")) { line = this.br.readLine(); }
*/
McServerService.this.addNewLogLine(line);
System.out.println("thread: " + line);
}
}
key.reset();
}
System.out.println("after while");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
The existing latest.log textfile is read as exspected with all its lines and then ends on null, but when adding the two lines
gadhsjlhgadsjlkh
jlhkadshljhads
and saving the file after each line, the output looks like this:
init: null //"last" line of existing file
entered try
//now adding the two lines
pclimlp:
addNewLogLine:
thread:
pclimlp: gadhsjlhgadsjlkh
addNewLogLine: gadhsjlhgadsjlkh
thread: gadhsjlhgadsjlkh
The problem is solved by uncommenting the equals check for "" in:
while ((key = this.watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
if (event.context().toString().equals("latest.log")) {
String line = this.br.readLine();
/*
* if (line.equalsIgnoreCase("")) { line = this.br.readLine(); }
*/
McServerService.this.addNewLogLine(line);
System.out.println("thread: " + line);
}
}
key.reset();
}
But why is the BufferedReder reading empty lines there in the first place? Id understand null if the BufferedReader isnt updated yet, but why empty lines? Also if I manually save the file again (without adding more lines or anything), the BufferedReader will read the line it hasnt read before fine and is not skipping it. Can someone explain what happens there to cause this behaviour?
BufferedReade.readLine() reads until either the next linefeed or the end of file.
So when reading this file:
[......]
[21:34:31] [Server thread/INFO]: [removed] left the game
BufferedReader gives the following output:
[......]
init: [21:34:31] [Server thread/INFO]: [removed] left the game
init: null
"init: " was only added for debug purposes.
When appending
xx
ghasjdkhajs
So the end of the file looks like this:
[21:34:31] [Server thread/INFO]: [removed] left the gamexx
ghasjdkhajs
This leads to the following output:
pclimlp: xx
addNewLogLine: xx
thread: xx
So BufferedReader was at the end of the file which was directly after
left the game
So when adding
xx\r\nghasjdkhajs
the BufferedReader reads until the next linefeed. If there wasnt the "xx" added there would simply be nothing before the next linefeed, so the BufferedReader would return "" like in the question.
I'm using Java WatchEvent to monitor an external process that creates an external file.
Java create multiple events until the file is eventually completed while the external process is creating the file:
Event kind:ENTRY_CREATE. File affected: fileArticoli (16).zip.
Event kind:ENTRY_MODIFY. File affected: fileArticoli (16).zip.
Event kind:ENTRY_MODIFY. File affected: fileArticoli (16).zip.
Event kind:ENTRY_MODIFY. File affected: fileArticoli (16).zip.
I need to be awaken at the end of the process when the file creation is done.
I'm exploring Java RX
PublishSubject<WatchEvent> fsEvents = PublishSubject.create();
fsEvents.subscribe(this);
...
fsEvents.onNext(event);
I'm searching for a JavaRx function similar to debounce that triggers when no new events have been triggered for a period of time es. 2000ms.
Is there such a function in java RX?
Here is a minimal example
#Component
public class FileWatcherComponent implements Action1<WatchEvent> {
Logger logger = Logger.getLogger("Upload Ordini");
#Value("${app.ecommerce.dirOrdini}")
String orderDir;
WatchService watchService;
Path path;
#PostConstruct
public void init(){
new Thread(()->{
PublishSubject<WatchEvent> fsEvents = PublishSubject.create();
fsEvents.throttleWithTimeout(2, TimeUnit.SECONDS).subscribe(this);
if (StringUtils.isNotEmpty(orderDir)){
try {
watchService = FileSystems.getDefault().newWatchService();
path = Paths.get(orderDir);
path.register(watchService,ENTRY_MODIFY);
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
fsEvents.onNext(event);
}
key.reset();
}
} catch (Exception e) {
logger.config(e.getMessage());
e.printStackTrace();
}
}
}).start();
}
#Override
public void call(WatchEvent event) {
logger.config( "File affected: " + event.context() + ".");
//Process the file
}
}
Eventually in order avoid to process the same file multiple times I used a different approach. Each file can be processed after 2000 ms. The class tries to process all file in a map (mFiles) every 500ms.
Hope the some may take advantage of this solution.
The file is placed in the map by filewatcher and remove after processing.
#Component
public class FileWatcherComponent{
public static final long DELAY = 2000L;
#Value("${app.ecommerce.dirOrdini}")
String orderDir;
WatchService watchService;
Path path;
Map<String, Long> mFiles = new HashMap<>();
#PostConstruct
public void init(){
new Thread(()->{
if (StringUtils.isNotEmpty(orderDir)){
try {
watchService = FileSystems.getDefault().newWatchService();
path = Paths.get(orderDir);
path.register(watchService,ENTRY_MODIFY);
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
mFiles.put(event.context()+"", (new Date()).getTime());
}
key.reset();
}
} catch (Exception e) {}
}
}).start();
}
#Scheduled(fixedRate = 500)
public void processFiles(){
long startfrom = (new Date()).getTime() - DELAY;
for (String fname : mFiles.keySet()) {
if (mFiles.get(fname) < startfrom){
new Thread(()->{
try {
processFile(fname);
} catch (Exception e){
logger.config(e.getMessage());
e.printStackTrace();
}
mFiles.remove(fname);
}).start();
}
}
}
public void processFile(String fileName) throws Exception{
//TODO process file
}
}
This solution is based on Spingboot for class init and scheduling.
I have a Python app and a Java app running simultaneously.
I want Java to start the Python process and then communicate using the normal STDIN/STDOUT streams with Python.
I have started the process correctly and have two threads to handle the two I/O streams.
OUTPUT THREAD:
class output2 extends Thread {
Process process;
OutputStream stdin;
BufferedWriter writer;
Scanner in = new Scanner(System.in);
output2(Process p) {
try {
process = p;
stdin = process.getOutputStream();
writer = new BufferedWriter(new OutputStreamWriter(stdin));
} catch (Exception e) {
System.out.println("ERROR output2(): " + e);
}
}
#Override
public void run() {
System.out.println("Starting OUTPUT THREAD");
try {
while (true) {
String input = in.nextLine();
writer.write(input);
writer.flush();
}
} catch (Exception e) {
System.out.println("ERROR output2_run(): " + e);
}
System.out.println("Ending OUTPUT THREAD");
}
}
INPUT THREAD :
class input2 extends Thread {
Process process;
InputStream stdout;
BufferedReader reader;
input2(Process p) {
try {
process = p;
stdout = process.getInputStream();
reader = new BufferedReader(new InputStreamReader(stdout));
} catch (Exception e) {
System.out.println("ERROR input2(): " + e);
}
}
#Override
public void run() {
System.out.println("Started INPUT THREAD");
try {
while (true) {
System.out.println(Thread.currentThread().getName() + " is executing");
if (reader.readLine() != null) {
System.out.println("Stdout: " + reader.readLine());
}
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " stopped executing");
}
} catch (Exception e) {
System.out.println("ERROR input2_run(): " + e);
}
System.out.println("Ending INPUT THREAD");
}
}
MAIN :
public class My_Java {
public static void main(String args[]) {
File file = new File("C:\\Location\\");
try {
Process process = Runtime.getRuntime().exec("C:\\Python27\\python.exe chat_from_file.py", null, file);
input2 input = new input2(process);
output2 output = new output2(process);
input.setName("INPUT THREAD");
output.setName("OUTPUT THREAD");
input.start();
output.start();
} catch (Exception e) {
System.out.println("ERROR main(): " + e);
}
}
}
This doesn't seem to give any response at all.
It starts both threads, says INPUT THREAD is executing but nothing after that.
Where am I going wrong?
First of all, after calling if (reader.readLine() != null) { in your input class, you effectively have read the line and the next call will return null.
Use ready to check for non-blocking read possibility. Don't read upfront.
However, I'm pretty sure that you process exists abnormally, with something like python: can't open file 'chat_from_file.py': [Errno 2] No such file or directory or, throws a stacktrace and exits.
Use getErrorStream to check what the process is outputting if an error exists. This will put you on the correct path to solve your issue.
Also, just in case, make sure there's actually something to be read. Make sure your Python application is outputting enough data for buffers to be flushed (or flushing its writes).
And don't forget to join and exit cleanly and correctly. Good luck.
I want my application to check if another version of itself is already running.
For example, demo.jar started, user clicks to run it again, but the second instance realizes "oh wait, there is already a demo.jar running." and quits with a message.
Enforce one instance of a program running with a ServerSocket Lock
Java Code. Put this into a file called Main.java:
import java.net.*;
import java.io.*;
public class Main{
public static void main(String args[]){
ServerSocket socket = null;
try {
socket = new ServerSocket(34567);
System.out.println("Doing hard work for 100 seconds");
try{ Thread.sleep(100000); } catch(Exception e){ }
socket.close();
}
catch (IOException ex) {
System.out.println("App already running, exiting...");
}
finally {
if (socket != null)
try{ socket.close(); } catch(Exception e){}
}
}
}
Compile and run it
javac Main.java
java Main
Test it in a normal case:
Run the program. You have 100 seconds to run the program again in another terminal, it will fall through saying its already running. Then wait 100 seconds, it should allow you to run it in the 2nd terminal.
Test it after force halting the program with a kill -9
Start the program in terminal 1.
kill -9 that process from another terminal within 100 seconds.
Run the program again, it is allowed to run.
Conclusion:
The socket occupation is cleaned up by the operating system when your program is no longer operating. So you can be sure that the program will not run twice.
Drawbacks
If some sneaky person, or some naughty process were to bind all of the ports, or just your port, then your program will not run because it thinks its already running.
What you are looking for can probably best be accomplished with a lock file. By lock file I simply mean a file that will have a predefined location and whose existence is your mutex.
Test if that file exists when your program starts, if it does, exit immediately. Create a file in a known location. If your program exits normally, delete the lock file.
Probably best is if you can also populate the file with a pid (process id) so that you can detect abnormal exits that didn't delete the file but this get OS specific.
Simple yet powerful tested solution.
static File file;
static FileChannel fileChannel;
static FileLock lock;
static boolean running = false;
#SuppressWarnings("resource")
public static boolean checkIfAlreadyRunning() throws IOException {
file = new File(FilePath.FILEPATH + "az-client.lock");
if (!file.exists()) {
file.createNewFile();
running = true;
} else {
file.delete();
}
fileChannel = new RandomAccessFile(file, "rw").getChannel();
lock = fileChannel.tryLock();
if (lock == null) {
fileChannel.close();
return true;
}
ShutdownHook shutdownHook = new ShutdownHook();
Runtime.getRuntime().addShutdownHook(shutdownHook);
return running;
}
public static void unlockFile() {
try {
if (lock != null)
lock.release();
fileChannel.close();
file.delete();
running = false;
} catch (IOException e) {
e.printStackTrace();
}
}
static class ShutdownHook extends Thread {
public void run() {
unlockFile();
}
}
Put these methods in some Util class and before launching your main class just check that if already exists then show some dialog to user otherwise launch an application. It works even if you abnormally shutdown java process or what ever you do. It is robust and efficient, no need to set up DataGram listeners or whatever...
If you use a Mutex, logically that Mutex would need to be accessible from any JVM which was running a copy of "the program". In C programming, this might be accomplished via shared memory, but Java doesn't have such a thing by default.
With that understanding, there are plenty of ways to implement what you want. You could open a server socket on a designated port (the operating system assures that only one process is the recipient of the server socket, and subsequent opens fail).
You could use a "lock file" but it is a bit complicated, as the file you would need to use would really be a directory (and it becomes heavily dependent on whether directory creation is atomic for your file system, even though most directory creations are). If a sysadmin decides to run you via NFS, then things get even harder (if not impossible).
You can also do a number of nifty tricks with JVMs and debugging / JMI, provided you can somehow assure youself that all relevant JVMs are launched with the same configurations (in time, an impossible task).
Other people have used the exec facility to run the equivalent of a process listing, but it is a bit tricky due to the possibility of race condition (two processes simultaneously check, and fail to see each other).
In the end, the server socket route is probably the most stable, as it is guaranteed to only bind to one process by the TCP/IP stack (and is mediated by the operating system). That said, you will have to flush the socket of incoming messages, and it opens up the possibility of other security issues.
If your application is running on Windows, you can call CreateMutex through JNI.
jboolean ret = FALSE;
HANDLE hMutex = CreateMutex(NULL, FALSE, mutexName);
ret = TRUE;
if(WAIT_TIMEOUT == WaitForSingleObject(hMutex, 10))
{
ret = FALSE;
}
else if(GetLastError() != 0)
{
ret = FALSE;
}
This returns true if nobody else is using this mutex, false otherwise.
You could provide "myApplication" as a mutex name or "Global\MyApplication" if you want your mutex to be shared by all Windows sessions.
Edit: It's not as complicated as it looks :) and I find it clean.
The strategy of this code is to keep the PID around from the last run in the registry, if that PID is found running on the system, don't start. If you finish, reset.
The preferences are stored on Windows Registry in HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs
import java.io.*;
import java.util.prefs.Preferences;
public class JavaApplication3 {
public static void main(String[] args){
if(isRunning()){
System.out.println("Two instances of this program cannot " +
"be running at the same time. Exiting now");
}
else{
onStart();
epicHeavyWorkGoesHere();
onFinish();
}
}
public static void epicHeavyWorkGoesHere(){
try {
Thread.sleep(15000);
} catch (InterruptedException ex) {}
}
public static void onStart(){
Preferences prefs = Preferences.systemRoot().node("JavaApplication3");
prefs.put("RUNNINGPID", getCurrentPID());
}
public static void onFinish(){
Preferences prefs = Preferences.systemRoot().node("JavaApplication3");
prefs.put("RUNNINGPID", "");
}
public static boolean isRunning(){
Preferences prefs = Preferences.systemRoot().node("JavaApplication3");
if (prefs.get("RUNNINGPID", null) == null || prefs.get("RUNNINGPID", null).equals(""))
return false;
if (isProcessIdRunningOnWindows(Integer.parseInt(prefs.get("RUNNINGPID", null))))
return true;
return false;
}
public static String getCurrentPID(){
//This function should work with Windows, Linux and Mac but you'll have to
//test to make sure. If not then get a suitable getCurrentPID function replacement.
try{
java.lang.management.RuntimeMXBean runtime = java.lang.management.ManagementFactory.getRuntimeMXBean();
java.lang.reflect.Field jvm = runtime.getClass().getDeclaredField("jvm");
jvm.setAccessible(true);
sun.management.VMManagement mgmt = (sun.management.VMManagement) jvm.get(runtime);
java.lang.reflect.Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId");
pid_method.setAccessible(true);
return pid_method.invoke(mgmt) + "";
}
catch(Exception e){
throw new RuntimeException("Cannot get the current PID");
}
}
public static boolean isProcessIdRunningOnWindows(int pid){
//This Function only works for windows, if you want it to work on linux
//or mac, you will have to go find a replacement method that
//takes the processID as a parameter and spits out a true/false
//if it is running on the operating system.
try {
Runtime runtime = Runtime.getRuntime();
String cmds[] = {"cmd", "/c", "tasklist /FI \"PID eq " + pid + "\""};
Process proc = runtime.exec(cmds);
InputStream inputstream = proc.getInputStream();
InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
String line;
while ((line = bufferedreader.readLine()) != null) {
if (line.contains(" " + pid + " ")){
return true;
}
}
return false;
}
catch (Exception ex) {
throw new RuntimeException("Cannot run the tasklist command to query if a pid is running or not");
}
}
}
If the program is hung and the pid remains in the task list this will be blocked. You could add an additional registry key that will store the last successful run time, and if the run time becomes too great, the stored PID is killed, and the program re-run.
Following solution work in two deadly scenerio too.
1> Even your launched exe scheduled as javaw.exe in task manager.
2> You can install your application at two location and from launching both location it also works.
String tempDir = System.getProperty("java.io.tmpdir");// dependent to OS find any tem dir.
String filePath = tempDir + "lockReserverd.txt";
try {
final File file = new File(filePath);
if(file.exists())
return false;
final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
final FileLock fileLock = randomAccessFile.getChannel().tryLock();
if (fileLock != null) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
fileLock.release();
randomAccessFile.close();
file.delete();
} catch (Exception e) {
//log.error("Unable to remove lock file: " + lockFile, e);
}
}
});
return true;
}
} catch (Exception e) {
//log.Error("Unable to create and/or lock file");
}
return false
or
This will work if your application.exe is listed in task manager
"tasklist /FI \"IMAGENAME eq "+MyApplication+".exe
Contrary to several other answers, the most reliable method is to create a ServerSocket on a fixed port known only to you, way up in the paint cards. It will automatically be released when your application exits, unlike any lock file, and its prior existence via a BindException is a pretty infallible sign that another instance is already running.
Here is one method that uses an automatically named lock file in the user's home directory.
The name is based on where the jar is being ran from.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
public class SingleInstance {
#SuppressWarnings("resource")
public static boolean isAlreadyRunning() {
File file;
FileChannel fileChannel;
File userDir = new File(System.getProperty("user.home"));
file = new File(userDir, myLockName());
if (!file.exists()) {
try {
file.createNewFile();
file.deleteOnExit();
} catch (IOException e) {
throw new RuntimeException("Unable to create Single Instance lock file!", e);
}
}
try {
fileChannel = new RandomAccessFile(file, "rw").getChannel();
} catch (FileNotFoundException e) {
throw new RuntimeException("Single Instance lock file vanished!", e);
}
try {
if (fileChannel.tryLock() != null) {
return false;
}
} catch (Exception e) {
}
try {
fileChannel.close();
} catch (IOException e1) {
}
return true;
}
private static String myLockName() {
return "." + SingleInstance.class.getProtectionDomain().getCodeSource().getLocation().getPath()
.replaceAll("[^a-zA-Z0-9_]", "_");
}
}
This is also a good solution if your Appication can schedued in task manager with a unique name
"tasklist /FI \"IMAGENAME eq "+MyApplication+".exe
Check PID and file lock technique
We can write the process id of the process that created the lock file into the file. When we encounter an existing lock file, we do not just quit, but we check if the process with that id is still alive. If not, then create a new application instance. I think MongoDB use this technique.
static File file;
static FileChannel fileChannel;
static FileLock lock;
static boolean running = false;
static String currentPID = null;
static String lockFilePID = null;
public static final String USER_DIR = System.getProperty("user.dir");
public static final String LOCK_FILE = "az-client.lock";
public static boolean checkInstance() {
try {
file = new File(USER_DIR + File.separator + LOCK_FILE);
currentPID = Integer.toString(getCurrentPID());
if (!file.exists()) {
file.createNewFile();
writePID(currentPID);
lockFile();
addShudDownHook();
running = true;
return running;
} else {
if (isFileLocked()) {
syso("App already running");
System.exit(0);
} else {
lockFilePID = getPIDFromLockFile();
if (isProcessIdRunningOnWindows(Integer.parseInt(lockFilePID))) {
lockFile();
addShudDownHook();
running = true;
return running;
} else {
file.delete();
file.createNewFile();
writePID(currentPID);
lockFile();
addShudDownHook();
running = true;
return running;
}
}
}
} catch (Exception e) {
syso(e + "App already running");
System.exit(0);
}
return running;
}
/**
*
* #return
* #throws IOException
*/
#SuppressWarnings("resource")
private static boolean isFileLocked() throws IOException {
fileChannel = new RandomAccessFile(file, "rw").getChannel();
lock = fileChannel.tryLock();
if (lock == null) {
fileChannel.close();
fileChannel = null;
return true;
} else {
lock.release();
fileChannel.close();
fileChannel = null;
}
return false;
}
public static int getCurrentPID() {
// This function should work with Windows, Linux and Mac but you'll have
// to
// test to make sure. If not then get a suitable getCurrentPID function
// replacement.
try {
java.lang.management.RuntimeMXBean runtime = java.lang.management.ManagementFactory.getRuntimeMXBean();
java.lang.reflect.Field jvm = runtime.getClass().getDeclaredField("jvm");
jvm.setAccessible(true);
sun.management.VMManagement mgmt = (sun.management.VMManagement) jvm.get(runtime);
java.lang.reflect.Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId");
pid_method.setAccessible(true);
return (int) pid_method.invoke(mgmt);
} catch (Exception e) {
throw new RuntimeException("Cannot get the current PID");
}
}
public static boolean isProcessIdRunningOnWindows(int pid) {
// This Function only works for windows, if you want it to work on linux
// or mac, you will have to go find a replacement method that
// takes the processID as a parameter and spits out a true/false
// if it is running on the operating system.
try {
Runtime runtime = Runtime.getRuntime();
String cmds[] = { "cmd", "/c", "tasklist /FI \"PID eq " + pid + "\"" };
Process proc = runtime.exec(cmds);
InputStream inputstream = proc.getInputStream();
InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);
String line;
while ((line = bufferedreader.readLine()) != null) {
if (line.contains(" " + pid + " ")) {
return true;
}
}
return false;
} catch (Exception ex) {
throw new RuntimeException("Cannot run the tasklist command to query if a pid is running or not");
}
}
/**
* This method write PID to Lock file
*
* #param pid
* #throws Exception
*/
private static void writePID(String pid) throws Exception {
try {
// To Do write PID to LockFile
} catch (Exception e) {
syso(e);
throw e;
}
}
/**
* This method return PID from Lock File
*
* #return
* #throws Exception
*/
private static String getPIDFromLockFile() throws Exception {
try {
return //To Do getPID from File
} catch (Exception e) {
syso(e);
throw e;
}
}
private static void addShudDownHook() {
try {
ShutdownHook shutdownHook = new ShutdownHook();
Runtime.getRuntime().addShutdownHook(shutdownHook);
} catch (Exception e) {
LogWriter.logger.error(e);
}
}
private static void unlockFile() {
try {
if (lock != null) {
lock.release();
}
fileChannel.close();
file.delete();
running = false;
} catch (IOException e) {
syso(e);
}
}
private static void lockFile() {
try {
fileChannel = new RandomAccessFile(file, "rw").getChannel();
lock = fileChannel.tryLock();
if (lock == null) {
fileChannel.close();
fileChannel = null;
}
} catch (IOException e) {
syso(e);
}
}
static class ShutdownHook extends Thread {
public void run() {
unlockFile();
}
}
I created a file and add some contents into it. Then, I want to delete it with java api. Before this operation, the write out stream is closed, but still failed, so could someone help me to resolve it?
Code snippets:
private static void _saveLogFile(String logContent, String urlPathName) throws Exception {
try {
StringBuilder sb = new StringBuilder("");
sb.append(logContent + "\r\n");
String a = sb.toString();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(urlPathName, true)));
bw.write(a);
bw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private static void _deleteLogFile(String urlPathName) throws Exception {
File file = new File(urlPathName);
if (!file.exists()) {
throw new IllegalArgumentException("Delete: no such file or directory: " + urlPathName);
}
if (file.isDirectory()) {
String[] files = file.list();
if (files.length > 0) {
throw new IllegalArgumentException("Delete: directory is not empty: " + urlPathName);
}
}
boolean success = file.delete();
if (!success) {
throw new IllegalArgumentException("Delete:deletion failed.");
}
}
Your code is correct, but only prone to resource leaking. As long as bw.write(a) doesn't throw an exception, bw.close() will succeed. You should rather do the close in finally block to ensure that it will take place regardless of exceptions.
BufferedWriter bw = null;
try {
bw = new BufferedWriter(...);
bw.write(...);
} finally {
if (bw != null) try { bw.close(); } catch (IOException logOrIgnore) {}
}
Back to the actual problem, the symptoms suggests that something else is still holding the file open. Are you able to delete it from inside the platform's shell (Windows Explorer, etc) while the program is still running? For Windows, there are several tools to check if the file is still locked and if so, by what process.
Process Explorer
OpenedFilesView
WhoLockMe
Here's an SSCCE. Just copy'n'paste'n'run it unchanged. It works fine at my machine. Please run it at yours and alter where necessary so that it matches the actual coding at a minimum which still reproduces/exhibits your problem.
package mypackage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class Test {
public static void main(String args[]) throws Exception {
File file = new File("/test.txt");
for (int i = 0; i < 1000; i++) {
write("line" + i, file); // Write "many" lines.
}
System.out.println("File exist before delete? " + file.exists());
System.out.println("File deleted? " + file.delete());
System.out.println("File exist after delete? " + file.exists());
}
public static void write(String line, File file) throws IOException {
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true)));
writer.write(line);
} finally {
if (writer != null) try {
writer.close();
} catch (IOException e) {
System.err.println("Close failed!");
e.printStackTrace();
}
}
}
}
Output (as expected):
File exist before delete? true
File deleted? true
File exist after delete? false
Using JDK 1.6.0_21 on Windows XP.
Finally, fix it. These snippets are right definitely. The cause is that another thread opens the generated log file and does not close this stream. So could not delete the generated file.
It is a bug of my team player.
Thanks.