Skip to content

Commit fca4363

Browse files
authored
Merge pull request #2241 from adamretter/feature/corruption-tests-and-fixes-4.x.x
Fix several Journalling and Recovery issues for XML and Binary documents
2 parents cb5df6c + 4195f4b commit fca4363

40 files changed

+3482
-798
lines changed

build/scripts/junit.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@
311311
<formatter type="plain"/>
312312
<formatter type="xml"/>
313313
<test name="org.exist.storage.AllStorageTests" todir="${junit.reports.dat}"/>
314+
<test name="org.exist.storage.RecoverBinaryTest" todir="${junit.reports.dat}"/>
315+
<test name="org.exist.storage.RecoverXmlTest" todir="${junit.reports.dat}"/>
314316
</junit>
315317
</target>
316318

extensions/indexes/lucene/test/src/org/exist/indexing/lucene/ConcurrencyTest.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252

5353
public class ConcurrencyTest {
5454

55+
private static final long TIMEOUT_TERMINATION = 1000 * 60 * 3; // 3 minutes (in milliseconds)
56+
5557
@ClassRule
5658
public static final ExistXmldbEmbeddedServer existEmbeddedServer = new ExistXmldbEmbeddedServer();
5759

@@ -79,7 +81,7 @@ public void store() {
7981
try {
8082
storeRemoveDocs(name);
8183
} catch(final XMLDBException | IOException e) {
82-
e.printStackTrace();;
84+
e.printStackTrace();
8385
fail(e.getMessage());
8486
}
8587
};
@@ -89,7 +91,7 @@ public void store() {
8991
executor.shutdown();
9092
boolean terminated = false;
9193
try {
92-
terminated = executor.awaitTermination(60 * 60, TimeUnit.SECONDS);
94+
terminated = executor.awaitTermination(TIMEOUT_TERMINATION, TimeUnit.MILLISECONDS);
9395
} catch (final InterruptedException e) {
9496
//Nothing to do
9597
}
@@ -116,7 +118,7 @@ public void update() {
116118
executor.shutdown();
117119
boolean terminated = false;
118120
try {
119-
terminated = executor.awaitTermination(60 * 60, TimeUnit.SECONDS);
121+
terminated = executor.awaitTermination(TIMEOUT_TERMINATION, TimeUnit.MILLISECONDS);
120122
} catch (final InterruptedException e) {
121123
//Nothing to do
122124
}

src/org/exist/collections/MutableCollection.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,11 +1677,21 @@ public BinaryDocument addBinaryResource(final Txn transaction, final DBBroker br
16771677
}
16781678

16791679
if (oldDoc != null) {
1680-
LOG.debug("removing old document " + oldDoc.getFileURI());
1680+
if (LOG.isDebugEnabled()) {
1681+
LOG.debug("removing old document db entry" + oldDoc.getFileURI());
1682+
}
1683+
16811684
updateModificationTime(blob);
1682-
broker.removeResource(transaction, oldDoc);
1685+
broker.removeResourceMetadata(transaction, oldDoc);
1686+
1687+
broker.getIndexController().setDocument(oldDoc, StreamListener.ReindexMode.REMOVE_BINARY);
1688+
broker.getIndexController().flush();
1689+
1690+
// NOTE(AR): the actual binary file on disk will be removed/overwritten in broker#storeBinaryResource below
1691+
//broker.removeResource(transaction, oldDoc);
16831692
}
16841693

1694+
// store the binary content (create/replace)
16851695
broker.storeBinaryResource(transaction, blob, is);
16861696
addDocument(transaction, broker, blob, oldDoc);
16871697

src/org/exist/management/impl/Database.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ public String getInstanceId() {
4343
return pool.getId();
4444
}
4545

46+
@Override
47+
public String getStatus() {
48+
return pool.getStatus();
49+
}
50+
51+
@Override
52+
public void shutdown() {
53+
pool.shutdown();
54+
}
55+
4656
@Override
4757
public int getMaxBrokers() {
4858
return pool.getMax();

src/org/exist/management/impl/DatabaseMXBean.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
public interface DatabaseMXBean {
2727

2828
String getInstanceId();
29+
30+
String getStatus();
31+
32+
void shutdown();
2933

3034
int getMaxBrokers();
3135

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* eXist Open Source Native XML Database
3+
* Copyright (C) 2001-2018 The eXist Project
4+
* http://exist-db.org
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public License
8+
* as published by the Free Software Foundation; either version 2
9+
* of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public
17+
* License along with this library; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19+
*/
20+
21+
package org.exist.storage;
22+
23+
import org.apache.logging.log4j.LogManager;
24+
import org.apache.logging.log4j.Logger;
25+
import org.exist.storage.journal.AbstractLoggable;
26+
27+
import javax.annotation.Nullable;
28+
import java.nio.file.Path;
29+
import java.nio.file.Paths;
30+
31+
import static java.nio.charset.StandardCharsets.UTF_8;
32+
33+
/**
34+
* @author Adam Retter <[email protected]>
35+
*/
36+
public abstract class AbstractBinaryLoggable extends AbstractLoggable {
37+
private static final Logger LOG = LogManager.getLogger(AbstractBinaryLoggable.class);
38+
39+
public AbstractBinaryLoggable(final byte type, final long transactionId) {
40+
super(type, transactionId);
41+
}
42+
43+
/**
44+
* Get's the absolute path as a byte array.
45+
*
46+
* @param path the path to encode.
47+
*
48+
* @return the absolute path, UTF-8 encoded as bytes
49+
*/
50+
@Nullable protected static byte[] getPathData(@Nullable final Path path) {
51+
if (path == null) {
52+
return null;
53+
}
54+
return path.toAbsolutePath().toString().getBytes(UTF_8);
55+
}
56+
57+
/**
58+
* Get's the path from a byte array encoded by {@link #getPathData(Path)}.
59+
*
60+
* @param pathData the path data to decode.
61+
*
62+
* @return the path
63+
*/
64+
@Nullable protected static Path getPath(@Nullable final byte[] pathData) {
65+
if (pathData == null) {
66+
return null;
67+
}
68+
return Paths.get(new String(pathData, UTF_8));
69+
}
70+
71+
/**
72+
* Converts the first two bytes of an integer into
73+
* an unsigned short and stores the result into a short.
74+
*
75+
* @param i the integer
76+
*
77+
* @return the unsigned short stored in a short.
78+
*/
79+
protected static short asUnsignedShort(final int i) {
80+
return (short)(i & 0xFFFF);
81+
}
82+
83+
/**
84+
* Converts an unsigned short stored in a short back
85+
* into an integer. Inverse of {@link #asUnsignedShort(int)}.
86+
*
87+
* @param s the unsigned short as a short.
88+
*
89+
* @return the integer.
90+
*/
91+
protected static int asSignedInt(final short s) {
92+
return s & 0xFFFF;
93+
}
94+
95+
/**
96+
* Check that the length of a path does not need more storage than we have available (i.e. 2 bytes).
97+
*
98+
* @param loggableName The name of the loggable (for formatting error messages).
99+
* @param pathName The name of the path (for formatting error messages).
100+
* @param path The path to check the length of.
101+
*/
102+
protected static void checkPathLen(final String loggableName, final String pathName, @Nullable final byte path[]) {
103+
if (path == null) {
104+
return;
105+
}
106+
107+
final int len = path.length;
108+
if (len <= 0) {
109+
LOG.error(loggableName + ": " + pathName + " path has a zero length");
110+
} else if(len > 0xFFFF) {
111+
LOG.error(loggableName + ": " + pathName + " path needs more than 65,535 bytes. Path will be truncated: " + new String(path, UTF_8));
112+
}
113+
}
114+
}

src/org/exist/storage/BrokerPool.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ private enum Event {
171171
.build()
172172
);
173173

174+
public String getStatus() {
175+
return status.getCurrentState().name();
176+
}
177+
174178
/**
175179
* The number of brokers for the database instance
176180
*/
@@ -361,7 +365,6 @@ private enum Event {
361365

362366
private StartupTriggersManager startupTriggersManager;
363367

364-
365368
/**
366369
* Creates and configures the database instance.
367370
*

src/org/exist/storage/BrokerPools.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,10 @@ public static void configure(final String instanceName, final int minBrokers, fi
171171
return;
172172
}
173173

174-
LOG.debug("Configuring database instance '" + instanceName + "'...");
174+
if (LOG.isDebugEnabled()) {
175+
LOG.debug("Configuring database instance '" + instanceName + "'...");
176+
}
177+
175178
try {
176179
//Create the instance
177180
final BrokerPool instance = new BrokerPool(instanceName, minBrokers, maxBrokers, config, statusObserver);

0 commit comments

Comments
 (0)