mirror of
https://github.com/webbukkit/dynmap.git
synced 2024-11-25 03:35:18 +01:00
Merge pull request #184 from mikeprimm/master
Add file access read/write locking to avoid conflicting tile updates, dirty reads by internal web server
This commit is contained in:
commit
c97b373fa5
@ -27,6 +27,7 @@ import org.dynmap.debug.Debug;
|
||||
import org.dynmap.kzedmap.KzedMap;
|
||||
import org.dynmap.kzedmap.KzedMap.KzedBufferedImage;
|
||||
import org.dynmap.MapChunkCache;
|
||||
import org.dynmap.utils.FileLockManager;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class FlatMap extends MapType {
|
||||
@ -236,6 +237,7 @@ public class FlatMap extends MapType {
|
||||
}
|
||||
}
|
||||
/* Test to see if we're unchanged from older tile */
|
||||
FileLockManager.getWriteLock(outputFile);
|
||||
TileHashManager hashman = MapManager.mapman.hashman;
|
||||
long crc = hashman.calculateTileHash(argb_buf);
|
||||
boolean tile_update = false;
|
||||
@ -257,12 +259,13 @@ public class FlatMap extends MapType {
|
||||
Debug.debug("skipping image " + outputFile.getPath() + " - hash match");
|
||||
}
|
||||
KzedMap.freeBufferedImage(im);
|
||||
FileLockManager.releaseWriteLock(outputFile);
|
||||
MapManager.mapman.updateStatistics(tile, null, true, tile_update, !rendered);
|
||||
|
||||
/* If day too, handle it */
|
||||
if(night_and_day) {
|
||||
File dayfile = new File(outputFile.getParent(), tile.getDayFilename());
|
||||
|
||||
FileLockManager.getWriteLock(dayfile);
|
||||
crc = hashman.calculateTileHash(argb_buf_day);
|
||||
if((!dayfile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), "day", t.x, t.y))) {
|
||||
Debug.debug("saving image " + dayfile.getPath());
|
||||
@ -282,6 +285,7 @@ public class FlatMap extends MapType {
|
||||
tile_update = false;
|
||||
}
|
||||
KzedMap.freeBufferedImage(im_day);
|
||||
FileLockManager.releaseWriteLock(dayfile);
|
||||
MapManager.mapman.updateStatistics(tile, "day", true, tile_update, !rendered);
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import org.dynmap.TileHashManager;
|
||||
import org.dynmap.debug.Debug;
|
||||
import org.dynmap.MapChunkCache;
|
||||
import org.dynmap.kzedmap.KzedMap.KzedBufferedImage;
|
||||
import org.dynmap.utils.FileLockManager;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class DefaultTileRenderer implements MapTileRenderer {
|
||||
@ -228,6 +229,7 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
int oy = (mtile.py == zmtile.getTileY())?0:KzedMap.tileHeight/2;
|
||||
|
||||
/* Test to see if we're unchanged from older tile */
|
||||
FileLockManager.getWriteLock(fname);
|
||||
TileHashManager hashman = MapManager.mapman.hashman;
|
||||
long crc = hashman.calculateTileHash(img.argb_buf);
|
||||
boolean updated_fname = false;
|
||||
@ -245,6 +247,7 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
updated_fname = true;
|
||||
}
|
||||
KzedMap.freeBufferedImage(img);
|
||||
FileLockManager.releaseWriteLock(fname);
|
||||
MapManager.mapman.updateStatistics(mtile, null, true, updated_fname, !rendered);
|
||||
|
||||
mtile.file = fname;
|
||||
@ -252,6 +255,7 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
boolean updated_dfname = false;
|
||||
File dfname = new File(fname.getParent(), mtile.getDayFilename());
|
||||
if(img_day != null) {
|
||||
FileLockManager.getWriteLock(dfname);
|
||||
crc = hashman.calculateTileHash(img.argb_buf);
|
||||
if((!dfname.exists()) || (crc != hashman.getImageHashCode(mtile.getKey(), "day", mtile.px, mtile.py))) {
|
||||
Debug.debug("saving image " + dfname.getPath());
|
||||
@ -267,12 +271,14 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
updated_dfname = true;
|
||||
}
|
||||
KzedMap.freeBufferedImage(img_day);
|
||||
FileLockManager.releaseWriteLock(dfname);
|
||||
MapManager.mapman.updateStatistics(mtile, "day", true, updated_dfname, !rendered);
|
||||
}
|
||||
|
||||
// Since we've already got the new tile, and we're on an async thread, just
|
||||
// make the zoomed tile here
|
||||
boolean ztile_updated = false;
|
||||
FileLockManager.getWriteLock(zoomFile);
|
||||
if(updated_fname || (!zoomFile.exists())) {
|
||||
saveZoomedTile(zmtile, zoomFile, zimg, ox, oy);
|
||||
MapManager.mapman.pushUpdate(zmtile.getWorld(),
|
||||
@ -280,11 +286,13 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
ztile_updated = true;
|
||||
}
|
||||
KzedMap.freeBufferedImage(zimg);
|
||||
FileLockManager.releaseWriteLock(zoomFile);
|
||||
MapManager.mapman.updateStatistics(zmtile, null, true, ztile_updated, !rendered);
|
||||
|
||||
if(zimg_day != null) {
|
||||
File zoomFile_day = new File(zoomFile.getParent(), zmtile.getDayFilename());
|
||||
ztile_updated = false;
|
||||
FileLockManager.getWriteLock(zoomFile_day);
|
||||
if(updated_dfname || (!zoomFile_day.exists())) {
|
||||
saveZoomedTile(zmtile, zoomFile_day, zimg_day, ox, oy);
|
||||
MapManager.mapman.pushUpdate(zmtile.getWorld(),
|
||||
@ -292,6 +300,7 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
ztile_updated = true;
|
||||
}
|
||||
KzedMap.freeBufferedImage(zimg_day);
|
||||
FileLockManager.releaseWriteLock(zoomFile_day);
|
||||
MapManager.mapman.updateStatistics(zmtile, "day", true, ztile_updated, !rendered);
|
||||
}
|
||||
}
|
||||
|
105
src/main/java/org/dynmap/utils/FileLockManager.java
Normal file
105
src/main/java/org/dynmap/utils/FileLockManager.java
Normal file
@ -0,0 +1,105 @@
|
||||
package org.dynmap.utils;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.dynmap.Log;
|
||||
/**
|
||||
* Implements soft-locks for prevent concurrency issues with file updates
|
||||
*/
|
||||
public class FileLockManager {
|
||||
private static Object lock = new Object();
|
||||
private static HashMap<String, Integer> filelocks = new HashMap<String, Integer>();
|
||||
private static final Integer WRITELOCK = new Integer(-1);
|
||||
/**
|
||||
* Get write lock on file - exclusive lock, no other writers or readers
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public static void getWriteLock(File f) {
|
||||
String fn = f.getPath();
|
||||
synchronized(lock) {
|
||||
boolean got_lock = false;
|
||||
while(!got_lock) {
|
||||
Integer lockcnt = filelocks.get(fn); /* Get lock count */
|
||||
if(lockcnt != null) { /* If any locks, can't get write lock */
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException ix) {
|
||||
Log.severe("getWriteLock(" + fn + ") interrupted");
|
||||
}
|
||||
}
|
||||
else {
|
||||
filelocks.put(fn, WRITELOCK);
|
||||
got_lock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
//Log.info("getWriteLock(" + f + ")");
|
||||
}
|
||||
/**
|
||||
* Release write lock
|
||||
*/
|
||||
public static void releaseWriteLock(File f) {
|
||||
String fn = f.getPath();
|
||||
synchronized(lock) {
|
||||
Integer lockcnt = filelocks.get(fn); /* Get lock count */
|
||||
if(lockcnt == null)
|
||||
Log.severe("releaseWriteLock(" + fn + ") on unlocked file");
|
||||
else if(lockcnt.equals(WRITELOCK)) {
|
||||
filelocks.remove(fn); /* Remove lock */
|
||||
lock.notifyAll(); /* Wake up folks waiting for locks */
|
||||
}
|
||||
else
|
||||
Log.severe("releaseWriteLock(" + fn + ") on read-locked file");
|
||||
}
|
||||
//Log.info("releaseWriteLock(" + f + ")");
|
||||
}
|
||||
/**
|
||||
* Get read lock on file - multiple readers allowed, blocks writers
|
||||
*/
|
||||
public static void getReadLock(File f) {
|
||||
String fn = f.getPath();
|
||||
synchronized(lock) {
|
||||
boolean got_lock = false;
|
||||
while(!got_lock) {
|
||||
Integer lockcnt = filelocks.get(fn); /* Get lock count */
|
||||
if(lockcnt == null) {
|
||||
filelocks.put(fn, Integer.valueOf(1)); /* First lock */
|
||||
got_lock = true;
|
||||
}
|
||||
else if(!lockcnt.equals(WRITELOCK)) { /* Other read locks */
|
||||
filelocks.put(fn, Integer.valueOf(lockcnt+1));
|
||||
got_lock = true;
|
||||
}
|
||||
else { /* Write lock in place */
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException ix) {
|
||||
Log.severe("getReadLock(" + fn + ") interrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//Log.info("getReadLock(" + f + ")");
|
||||
}
|
||||
/**
|
||||
* Release read lock
|
||||
*/
|
||||
public static void releaseReadLock(File f) {
|
||||
String fn = f.getPath();
|
||||
synchronized(lock) {
|
||||
Integer lockcnt = filelocks.get(fn); /* Get lock count */
|
||||
if(lockcnt == null)
|
||||
Log.severe("releaseReadLock(" + fn + ") on unlocked file");
|
||||
else if(lockcnt.equals(WRITELOCK))
|
||||
Log.severe("releaseReadLock(" + fn + ") on write-locked file");
|
||||
else if(lockcnt > 1) {
|
||||
filelocks.put(fn, Integer.valueOf(lockcnt-1));
|
||||
}
|
||||
else {
|
||||
filelocks.remove(fn); /* Remove lock */
|
||||
lock.notifyAll(); /* Wake up folks waiting for locks */
|
||||
}
|
||||
}
|
||||
//Log.info("releaseReadLock(" + f + ")");
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import org.dynmap.web.HttpHandler;
|
||||
import org.dynmap.web.HttpRequest;
|
||||
import org.dynmap.web.HttpResponse;
|
||||
import org.dynmap.web.HttpStatus;
|
||||
import org.dynmap.utils.FileLockManager;
|
||||
|
||||
public abstract class FileHandler implements HttpHandler {
|
||||
protected static final Logger log = Logger.getLogger("Minecraft");
|
||||
@ -43,6 +44,10 @@ public abstract class FileHandler implements HttpHandler {
|
||||
|
||||
protected abstract InputStream getFileInput(String path, HttpRequest request, HttpResponse response);
|
||||
|
||||
protected void closeFileInput(String path, InputStream in) throws IOException {
|
||||
in.close();
|
||||
}
|
||||
|
||||
protected String getExtension(String path) {
|
||||
int dotindex = path.lastIndexOf('.');
|
||||
if (dotindex > 0)
|
||||
@ -113,7 +118,7 @@ public abstract class FileHandler implements HttpHandler {
|
||||
} finally {
|
||||
freeReadBuffer(readBuffer);
|
||||
}
|
||||
fileInput.close();
|
||||
closeFileInput(path, fileInput);
|
||||
} catch (Exception e) {
|
||||
if (fileInput != null) {
|
||||
try { fileInput.close(); } catch (IOException ex) { }
|
||||
|
@ -3,8 +3,10 @@ package org.dynmap.web.handlers;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.dynmap.utils.FileLockManager;
|
||||
import org.dynmap.web.HttpField;
|
||||
import org.dynmap.web.HttpRequest;
|
||||
import org.dynmap.web.HttpResponse;
|
||||
@ -20,6 +22,7 @@ public class FilesystemHandler extends FileHandler {
|
||||
@Override
|
||||
protected InputStream getFileInput(String path, HttpRequest request, HttpResponse response) {
|
||||
File file = new File(root, path);
|
||||
FileLockManager.getReadLock(file);
|
||||
if (file.getAbsolutePath().startsWith(root.getAbsolutePath()) && file.isFile()) {
|
||||
FileInputStream result;
|
||||
try {
|
||||
@ -30,6 +33,13 @@ public class FilesystemHandler extends FileHandler {
|
||||
response.fields.put(HttpField.ContentLength, Long.toString(file.length()));
|
||||
return result;
|
||||
}
|
||||
FileLockManager.releaseReadLock(file);
|
||||
return null;
|
||||
}
|
||||
protected void closeFileInput(String path, InputStream in) throws IOException {
|
||||
super.closeFileInput(path, in);
|
||||
File file = new File(root, path);
|
||||
FileLockManager.releaseReadLock(file);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user