Skip to content

Commit

Permalink
Tests and (minor) fixes for opening the database multiple times.
Browse files Browse the repository at this point in the history
  • Loading branch information
broneill committed Aug 21, 2024
1 parent 978f449 commit a0e9363
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 20 deletions.
43 changes: 24 additions & 19 deletions src/main/java/org/cojen/tupl/core/LockedFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,30 +45,35 @@ final class LockedFile implements Closeable {
} catch (IOException e) {
}

RandomAccessFile raf;
RandomAccessFile raf = null;
FileLock lock;

try {
raf = new RandomAccessFile(file, readOnly ? "r" : "rw");
lock = raf.getChannel().tryLock(8, Long.MAX_VALUE - 8, readOnly);
if (lock == null) {
String message = "Database is open and locked by another process";
try {
message = message + ": " + raf.readLong();
} catch (EOFException e) {
// Ignore.
try {
raf = new RandomAccessFile(file, readOnly ? "r" : "rw");
lock = raf.getChannel().tryLock(8, Long.MAX_VALUE - 8, readOnly);
if (lock == null) {
String message = "Database is open and locked by another process";
try {
message = message + ": " + raf.readLong();
} catch (EOFException e) {
// Ignore.
}
throw new DatabaseException(message);
}
throw new DatabaseException(message);
}
} catch (FileNotFoundException e) {
if (readOnly) {
raf = null;
lock = null;
} else {
throw e;
} catch (FileNotFoundException e) {
if (readOnly) {
raf = null;
lock = null;
} else {
throw e;
}
} catch (OverlappingFileLockException e) {
throw new DatabaseException("Database is already open in the current process");
}
} catch (OverlappingFileLockException e) {
throw new DatabaseException("Database is already open in the current process");
} catch (Throwable e) {
Utils.closeQuietly(raf);
throw e;
}

mRaf = raf;
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/org/cojen/tupl/io/PosixFileIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
import com.sun.jna.Pointer;
import com.sun.jna.Structure;

import org.cojen.tupl.WriteFailureException;

import org.cojen.tupl.unsafe.DirectAccess;

import org.cojen.tupl.util.LocalPool;
Expand Down Expand Up @@ -161,14 +163,27 @@ protected void doWrite(long pos, byte[] buf, int offset, int length) throws IOEx
bb.position(0);
bb.put(buf, offset, length);
pwriteFd(fd(), ref.mPointer, length, pos);
} catch (IOException ex) {
writeFailure(ex);
} finally {
e.release();
}
}

@Override
protected void doWrite(long pos, long ptr, int length) throws IOException {
pwriteFd(fd(), ptr, length, pos);
try {
pwriteFd(fd(), ptr, length, pos);
} catch (IOException ex) {
writeFailure(ex);
}
}

private void writeFailure(IOException ex) throws IOException {
if (isReadOnly()) {
throw new WriteFailureException("File is read only", ex);
}
throw ex;
}

@Override
Expand Down
179 changes: 179 additions & 0 deletions src/test/java/org/cojen/tupl/core/OpenTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* Copyright (C) 2024 Cojen.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package org.cojen.tupl.core;

import java.io.File;

import org.junit.*;
import static org.junit.Assert.*;

import org.cojen.tupl.*;

import static org.cojen.tupl.TestUtils.*;

/**
*
*
* @author Brian S. O'Neill
*/
public class OpenTest {
public static void main(String[] args) throws Exception {
if (args.length == 0) {
org.junit.runner.JUnitCore.main(OpenTest.class.getName());
return;
}

boolean readOnly = false;

if (args.length > 1 && args[1].equals("r")) {
readOnly = true;
}

var config = new DatabaseConfig().baseFilePath(args[0]).readOnly(readOnly);

Database db = Database.open(config);
System.out.println(readOnly ? "r" : "w");

for (int i=0; i<Integer.MAX_VALUE; i++) {
Thread.sleep(9999999999999L);
}

System.out.println(db);
}

@After
public void teardown() throws Exception {
deleteTempDatabases(getClass());
}

@Test
public void openTwiceSameProcess() throws Exception {
var config = new DatabaseConfig();
Database db = newTempDatabase(getClass(), config);

try {
Database.open(config);
fail();
} catch (DatabaseException e) {
assertTrue(e.getMessage().contains("open in the current process"));
}

config.readOnly(true);

try {
Database.open(config);
fail();
} catch (DatabaseException e) {
assertTrue(e.getMessage().contains("open in the current process"));
}
}

@Test
public void openTwiceDifferentProcess() throws Exception {
var config = new DatabaseConfig();
Database db = newTempDatabase(getClass(), config);
File baseFile = baseFileForTempDatabase(getClass(), db);
db.close();

Process p = new ProcessBuilder("java", getClass().getName(), baseFile.getPath()).start();

int c = p.getInputStream().read();
assertEquals('w', c);

try {
reopenTempDatabase(getClass(), db, config);
fail();
} catch (DatabaseException e) {
assertTrue(e.getMessage().contains("open and locked by another process"));
assertTrue(e.getMessage().contains("" + p.pid()));
} finally {
p.destroyForcibly().waitFor();
}
}

@Test
public void openReadOnly() throws Exception {
var config = new DatabaseConfig();
Database db = newTempDatabase(getClass(), config);
Index ix = db.openIndex("test");
ix.store(null, "hello".getBytes(), "world".getBytes());
db.checkpoint();
db.close();

config.readOnly(true);
db = Database.open(config);
try {
ix = db.findIndex("test");
assertNotNull(ix);
fastAssertArrayEquals("world".getBytes(), ix.load(null, "hello".getBytes()));

int i = 0;
try {
for (; i<1_000_000; i++) {
byte[] key = ("key-" + i).getBytes();
ix.store(null, key, key);
}
fail();
} catch (DatabaseFullException e) {
assertTrue(e.getMessage().contains("read only"));
assertTrue(i > 0);
}
} finally {
db.close();
}
}

@Test
public void openReadOnlyDifferentProcess() throws Exception {
var config = new DatabaseConfig();
Database db = newTempDatabase(getClass(), config);
Index ix = db.openIndex("test");
ix.store(null, "hello".getBytes(), "world".getBytes());
File baseFile = baseFileForTempDatabase(getClass(), db);
db.checkpoint();
db.close();

Process p = new ProcessBuilder
("java", getClass().getName(), baseFile.getPath(), "r").start();

try {
int c = p.getInputStream().read();
assertEquals('r', c);

config.readOnly(true);
db = reopenTempDatabase(getClass(), db, config);
ix = db.findIndex("test");
assertNotNull(ix);
fastAssertArrayEquals("world".getBytes(), ix.load(null, "hello".getBytes()));

int i = 0;
try {
for (; i<1_000_000; i++) {
byte[] key = ("key-" + i).getBytes();
ix.store(null, key, key);
}
fail();
} catch (DatabaseFullException e) {
assertTrue(e.getMessage().contains("read only"));
assertTrue(i > 0);
}
} finally {
p.destroyForcibly().waitFor();
}
}
}

0 comments on commit a0e9363

Please sign in to comment.