Skip to content

Commit 11a14f9

Browse files
committed
Bug#36805638 (noclose) NdbRecordSmartValueHandlerImpl leaking memory
This patch contains some improvements to the testing and diagnostic facilities in Cluster/J. * A new test case, BlobInstanceTest, aiming to reproduce the reported problem. * Changes to AllTests.java - Display the Java version when running tests - New option --gc=<n> runs <n> iterations of GC after testing. - New option --hprof creates a Java heap dump on exit * Fix a bug that caused TestRunner to incorrectly report "No tests run" if all tests fail. * Changes to testClusterJ - Do not create default properties - Start Java with assertions enabled - If LOG_GC is set in the environment, start Java with GC logging enabled * Changes to Cluster/J internals - Change the level of some log messages from info to debug Change-Id: Ic8b6e84e2632701fce781d923a1954a9d035b24a
1 parent 632ca5c commit 11a14f9

File tree

7 files changed

+184
-14
lines changed

7 files changed

+184
-14
lines changed

storage/ndb/clusterj/clusterj-test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ SET(JAVA_SOURCES
4646
${CLUSTERJ_TESTSUITE_PREFIX}/BinaryPKTest.java
4747
${CLUSTERJ_TESTSUITE_PREFIX}/BinaryTypesTest.java
4848
${CLUSTERJ_TESTSUITE_PREFIX}/BitTypesTest.java
49+
${CLUSTERJ_TESTSUITE_PREFIX}/BlobInstanceTest.java
4950
${CLUSTERJ_TESTSUITE_PREFIX}/BlobTest.java
5051
${CLUSTERJ_TESTSUITE_PREFIX}/BulkDeleteTest.java
5152
${CLUSTERJ_TESTSUITE_PREFIX}/Bug17200163Test.java

storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/AllTests.java

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,13 @@ This program is designed to work with certain software (including
2525

2626
package testsuite.clusterj;
2727

28+
import com.sun.management.HotSpotDiagnosticMXBean;
2829
import java.io.File;
2930
import java.io.FileInputStream;
3031
import java.io.IOException;
3132
import java.lang.annotation.Annotation;
33+
import java.lang.management.ManagementFactory;
34+
import java.lang.Process;
3235
import java.util.ArrayList;
3336
import java.util.HashSet;
3437
import java.util.List;
@@ -55,6 +58,8 @@ private static void usage(String message) {
5558
System.out.println(" Options: ");
5659
System.out.println(" --print-cases / -l : List test cases");
5760
System.out.println(" --enable-debug-tests : Run extra debug-only tests");
61+
System.out.println(" --gc=<n> : Run <n> GC iterations after tests");
62+
System.out.println(" --hprof : Create heap dump at exit");
5863
System.out.println(" -n TestName [...] : Run named tests");
5964
System.exit(2);
6065
}
@@ -165,6 +170,13 @@ public static void printCases() throws IOException, ClassNotFoundException {
165170
}
166171
}
167172

173+
private static void tryGc(int n) throws InterruptedException {
174+
System.out.println("Awaiting GC " + n);
175+
for(int i = 0 ; i < n ; i++) {
176+
Thread.sleep(1000);
177+
System.gc();
178+
}
179+
}
168180

169181
/**
170182
* Usage: java -cp ... AllTests file.jar [-l] [--print-cases]
@@ -186,20 +198,28 @@ public static void main(String[] args) throws Exception {
186198

187199
boolean runNamedTests = false;
188200
boolean printTestList = false;
201+
int doGc = 0;
202+
boolean doHeapDump = false;
189203
HashSet<String> namedTests = new HashSet<String>();
190204

191205
for(int i = 1 ; i < args.length ; i++) {
192206
if (args[i].equals("-l") || args[i].equals("--print-cases"))
193-
printTestList = true;
207+
printTestList = true;
194208

195209
else if (args[i].equalsIgnoreCase("--enable-debug-tests"))
196210
enableDebugTests = true;
197211

212+
else if (args[i].equals("--hprof"))
213+
doHeapDump = true;
214+
215+
else if (args[i].startsWith("--gc="))
216+
doGc = Integer.parseInt(args[i].substring(5));
217+
198218
else if(runNamedTests)
199-
namedTests.add(args[i]);
219+
namedTests.add(args[i]);
200220

201221
else if (args[i].equals("-n"))
202-
runNamedTests = true;
222+
runNamedTests = true;
203223

204224
else
205225
usage("Unrecognized option");
@@ -215,11 +235,27 @@ else if (args[i].equals("-n"))
215235

216236
TestSuite suite = suite(namedTests);
217237

238+
System.out.println("Java " + System.getProperty("java.version"));
218239
System.out.println("Running " + (runNamedTests ? "named" : "all") +
219240
" tests in '" + jarFile + "'");
220241
System.out.println("Found " + suite.testCount() + " test classes.");
221242
TestResult res = junit.textui.TestRunner.run(suite);
222243
System.out.println("Finished running tests in '" + jarFile + "'");
223-
System.exit(res.wasSuccessful() ? 0 : 1);
244+
245+
int exitCode = res.wasSuccessful() ? 0 : 1;
246+
if(doGc > 0) {
247+
suite = null;
248+
res = null;
249+
tryGc(doGc);
250+
}
251+
252+
if(doHeapDump) {
253+
String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
254+
String file = "clusterj-test." + pid + ".hprof";
255+
System.out.println("Creating heap dump: " + file);
256+
ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class).dumpHeap(file, true);
257+
}
258+
259+
System.exit(exitCode);
224260
}
225261
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
2+
/*
3+
Copyright (c) 2010, 2024, Oracle and/or its affiliates.
4+
Use is subject to license terms.
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License, version 2.0,
8+
as published by the Free Software Foundation.
9+
10+
This program is designed to work with certain software (including
11+
but not limited to OpenSSL) that is licensed under separate terms,
12+
as designated in a particular file or component or in included license
13+
documentation. The authors of MySQL hereby grant you an additional
14+
permission to link the program and your derivative works with the
15+
separately licensed software that they have either included with
16+
the program or referenced in the documentation.
17+
18+
This program is distributed in the hope that it will be useful,
19+
but WITHOUT ANY WARRANTY; without even the implied warranty of
20+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21+
GNU General Public License, version 2.0, for more details.
22+
23+
You should have received a copy of the GNU General Public License
24+
along with this program; if not, write to the Free Software
25+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26+
*/
27+
28+
package testsuite.clusterj;
29+
30+
import java.io.IOException;
31+
import java.io.InputStream;
32+
33+
import java.util.ArrayList;
34+
import java.util.List;
35+
36+
import com.mysql.clusterj.Session;
37+
38+
import testsuite.clusterj.model.BlobTypes;
39+
40+
public class BlobInstanceTest extends AbstractClusterJModelTest {
41+
42+
@Override
43+
public void localSetUp() {
44+
createSessionFactory();
45+
addTearDownClasses(BlobTypes.class);
46+
}
47+
48+
static final int ITERATIONS = 500;
49+
50+
BlobTypes[] instances = null;
51+
52+
/* The purpose of this test is to induce leaking of DirectByteBuffers
53+
*/
54+
55+
public void test() {
56+
instances = new BlobTypes[ITERATIONS];
57+
create();
58+
save();
59+
update();
60+
failOnError();
61+
instances = null;
62+
}
63+
64+
private void create() {
65+
for(int i = 0 ; i < ITERATIONS ; i++)
66+
instances[i] = create(i);
67+
}
68+
69+
private void save() {
70+
for(int i = 0 ; i < ITERATIONS ; i++)
71+
instances[i] = save(instances[i]);
72+
}
73+
74+
private void update() {
75+
for(int i = 0 ; i < ITERATIONS ; i++)
76+
update(instances[i]);
77+
}
78+
79+
private BlobTypes create(int id) {
80+
final Session s = sessionFactory.getSession();
81+
try {
82+
BlobTypes b = s.newInstance(BlobTypes.class);
83+
b.setId(id);
84+
b.setId_null_none(id);
85+
b.setId_null_hash(id);
86+
b.setBlobbytes(getBlobBytes(4600 + id));
87+
return b;
88+
} finally {
89+
s.close();
90+
}
91+
}
92+
93+
private BlobTypes save(BlobTypes instance) {
94+
final Session s = sessionFactory.getSession();
95+
final int id = instance.getId();
96+
try {
97+
BlobTypes b = s.newInstance(BlobTypes.class);
98+
b.setId(id);
99+
b.setId_null_none(id+1000);
100+
b.setId_null_hash(id);
101+
b.setBlobbytes(instance.getBlobbytes());
102+
return b;
103+
} finally {
104+
s.close();
105+
}
106+
}
107+
108+
private void update(BlobTypes instance) {
109+
final Session s = sessionFactory.getSession();
110+
final int id = instance.getId();
111+
try {
112+
BlobTypes b = s.newInstance(BlobTypes.class);
113+
b.setId(id);
114+
b.setId_null_none(instance.getId_null_none());
115+
b.setId_null_hash(id+2000);
116+
b.setBlobbytes(instance.getBlobbytes());
117+
} finally {
118+
s.close();
119+
}
120+
}
121+
122+
123+
/** Create a new byte[] of the specified size containing a pattern
124+
* of bytes in which each byte is the unsigned value of the index
125+
* modulo 256. This pattern is easy to test.
126+
* @param size the length of the returned byte[]
127+
* @return the byte[] filled with the pattern
128+
*/
129+
protected byte[] getBlobBytes(int size) {
130+
byte[] result = new byte[size];
131+
for (int i = 0; i < size; ++i) {
132+
result[i] = (byte)((i % 256) - 128);
133+
}
134+
return result;
135+
}
136+
}

storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/FixedByteBufferPoolImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public FixedByteBufferPoolImpl(int bufferSize, String name) {
100100
this.bufferSize = bufferSize;
101101
this.name = name;
102102
this.pool = new ConcurrentLinkedQueue<ByteBuffer>();
103-
logger.info("FixedByteBufferPoolImpl<init> for " + name + " bufferSize " + bufferSize);
103+
logger.debug("FixedByteBufferPoolImpl<init> for " + name + " bufferSize " + bufferSize);
104104
}
105105

106106
/** Borrow a buffer from the pool. If none in the pool, create a new one. */

storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/Utility.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2460,7 +2460,7 @@ private static CharsetConverter addCollation(int collation) {
24602460
}
24612461
for (int peer: collations) {
24622462
// for each collation that shares the same charset name, set the charset converter
2463-
logger.info("Adding charset converter " + charsetName + " for collation " + peer);
2463+
logger.debug("Adding charset converter " + charsetName + " for collation " + peer);
24642464
charsetConverters[peer] = charsetConverter;
24652465
}
24662466
return charsetConverter;

storage/ndb/clusterj/clusterj-unit/src/main/java/junit/textui/TestRunner.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ static public TestResult run(Test test) {
5757
} else if(result.failures.size() > 0) {
5858
System.out.println(resultPrinter.toString());
5959
System.out.println("\nSome tests failed.");
60+
} else if(result.failures.size() + result.throwables.size() > 0) {
61+
System.out.println(resultPrinter.toString());
62+
System.out.println("\nAll tests failed.");
6063
} else {
6164
System.out.println("No tests run.");
6265
}

storage/ndb/test/ndbapi/testClusterJ.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,18 +124,10 @@ bool write_properties(const char *connStr, const char *mysqlStr) {
124124
if (!fp) return false;
125125
fprintf(fp,
126126
"com.mysql.clusterj.connectstring=%s\n"
127-
"com.mysql.clusterj.connect.retries=4\n"
128-
"com.mysql.clusterj.connect.delay=5\n"
129-
"com.mysql.clusterj.connect.verbose=1\n"
130-
"com.mysql.clusterj.connect.timeout.before=30\n"
131-
"com.mysql.clusterj.connect.timeout.after=20\n"
132127
"com.mysql.clusterj.jdbc.url=jdbc:mysql://%s/test\n"
133128
"com.mysql.clusterj.jdbc.driver=com.mysql.cj.jdbc.Driver\n"
134129
"com.mysql.clusterj.jdbc.username=root\n"
135130
"com.mysql.clusterj.jdbc.password=\n"
136-
"com.mysql.clusterj.username=\n"
137-
"com.mysql.clusterj.password=\n"
138-
"com.mysql.clusterj.database=test\n"
139131
"com.mysql.clusterj.max.transactions=1024\n",
140132
connStr, mysqlStr);
141133
fclose(fp);
@@ -201,6 +193,8 @@ int run_tests(int argc, char **argv) {
201193
args.add("-Djava.library.path=", ndbClientDir.c_str());
202194
args.add("-Dclusterj.properties=", paths.propsFile().c_str());
203195
args.add("-Duser.timezone=GMT-3");
196+
args.add("-ea");
197+
if (getenv("LOG_GC")) args.add("-Xlog:gc=trace:file=clusterj-%p-gc.log");
204198
args.add2("-cp", classpath.c_str());
205199
args.add("testsuite.clusterj.AllTests");
206200
args.add(clusterjTestJar.c_str());

0 commit comments

Comments
 (0)