Skip to content

Commit 3e2c2b2

Browse files
authored
Merge pull request #2248 from adamretter/feature/corruption-tests-and-fixes
Fix several Journalling and Recovery issues for XML and Binary documents
2 parents f597662 + 8a3c923 commit 3e2c2b2

40 files changed

+3610
-846
lines changed

build/scripts/junit.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@
321321
<formatter type="plain"/>
322322
<formatter type="xml"/>
323323
<test name="org.exist.storage.AllStorageTests" todir="${junit.reports.dat}"/>
324+
<test name="org.exist.storage.RecoverBinaryTest" todir="${junit.reports.dat}"/>
325+
<test name="org.exist.storage.RecoverXmlTest" todir="${junit.reports.dat}"/>
324326
</junit>
325327
</target>
326328

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: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,13 +1651,24 @@ public BinaryDocument addBinaryResource(final Txn transaction, final DBBroker br
16511651
}
16521652

16531653
if (oldDoc != null) {
1654-
LOG.debug("removing old document " + oldDoc.getFileURI());
1654+
if (LOG.isDebugEnabled()) {
1655+
LOG.debug("removing old document db entry" + oldDoc.getFileURI());
1656+
}
1657+
16551658
if (!broker.preserveOnCopy(preserve)) {
16561659
updateModificationTime(blob);
16571660
}
1658-
broker.removeResource(transaction, oldDoc);
1661+
1662+
broker.removeResourceMetadata(transaction, oldDoc);
1663+
1664+
broker.getIndexController().setDocument(oldDoc, StreamListener.ReindexMode.REMOVE_BINARY);
1665+
broker.getIndexController().flush();
1666+
1667+
// NOTE(AR): the actual binary file on disk will be removed/overwritten in broker#storeBinaryResource below
1668+
//broker.removeResource(transaction, oldDoc);
16591669
}
16601670

1671+
// store the binary content (create/replace)
16611672
broker.storeBinaryResource(transaction, blob, is);
16621673
addDocument(transaction, broker, blob, oldDoc);
16631674

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ public String getInstanceId() {
5757
return pool.getId();
5858
}
5959

60+
@Override
61+
public String getStatus() {
62+
return pool.getStatus();
63+
}
64+
65+
@Override
66+
public void shutdown() {
67+
pool.shutdown();
68+
}
69+
6070
@Override
6171
public int getMaxBrokers() {
6272
return pool.getMax();

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
* $Id$
2525
*/
2626
public interface DatabaseMXBean extends PerInstanceMBean {
27-
27+
28+
String getStatus();
29+
30+
void shutdown();
31+
2832
int getMaxBrokers();
2933

3034
int getAvailableBrokers();
@@ -44,4 +48,4 @@ public interface DatabaseMXBean extends PerInstanceMBean {
4448
public long getUptime();
4549

4650
public String getExistHome();
47-
}
51+
}
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
@@ -174,6 +174,10 @@ private enum Event {
174174
.build()
175175
);
176176

177+
public String getStatus() {
178+
return status.getCurrentState().name();
179+
}
180+
177181
/**
178182
* The number of brokers for the database instance
179183
*/
@@ -362,7 +366,6 @@ private enum Event {
362366

363367
private StartupTriggersManager startupTriggersManager;
364368

365-
366369
/**
367370
* Creates and configures the database instance.
368371
*

src/org/exist/storage/BrokerPools.java

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

169-
LOG.debug("Configuring database instance '" + instanceName + "'...");
169+
if (LOG.isDebugEnabled()) {
170+
LOG.debug("Configuring database instance '" + instanceName + "'...");
171+
}
172+
170173
try {
171174
//Create the instance
172175
final BrokerPool instance = new BrokerPool(instanceName, minBrokers, maxBrokers, config, statusObserver);

0 commit comments

Comments
 (0)