Skip to content

Commit a4df929

Browse files
committed
[bugfix] Fix race-condition in JMX MBeanServer setup and handle multiple MBeanServers correctly
1 parent 1ccad2b commit a4df929

File tree

4 files changed

+171
-65
lines changed

4 files changed

+171
-65
lines changed

exist-core/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,8 @@
857857
<include>src/test/java/org/exist/http/RESTServiceTest.java</include>
858858
<include>src/test/java/org/exist/management/JmxRemoteTest.java</include>
859859
<include>src/main/java/org/exist/management/client/JMXServlet.java</include>
860+
<include>src/main/java/org/exist/management/client/JMXtoXML.java</include>
861+
<include>src/main/java/org/exist/management/impl/JMXAgent.java</include>
860862
<include>src/test/java/org/exist/xmldb/CreateCollectionsTest.java</include>
861863
<include>src/test/java/org/exist/xquery/XQueryFunctionsTest.java</include>
862864
<include>src/main/java/org/exist/xquery/functions/fn/FunBaseURI.java</include>
@@ -1073,6 +1075,8 @@
10731075
<exclude>src/test/java/org/exist/http/RESTServiceTest.java</exclude>
10741076
<exclude>src/test/java/org/exist/management/JmxRemoteTest.java</exclude>
10751077
<exclude>src/main/java/org/exist/management/client/JMXServlet.java</exclude>
1078+
<exclude>src/main/java/org/exist/management/client/JMXtoXML.java</exclude>
1079+
<exclude>src/main/java/org/exist/management/impl/JMXAgent.java</exclude>
10761080
<exclude>src/test/java/org/exist/xmldb/CreateCollectionsTest.java</exclude>
10771081
<exclude>src/test/java/org/exist/xquery/XQueryFunctionsTest.java</exclude>
10781082
<exclude>src/main/java/org/exist/xquery/functions/fn/transform/Options.java</exclude>

exist-core/src/main/java/org/exist/management/client/JMXServlet.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import org.exist.util.serializer.DOMSerializer;
7676
import org.w3c.dom.Element;
7777

78+
import static org.exist.util.StringUtil.notNullOrEmpty;
7879
import static org.exist.util.StringUtil.notNullOrEmptyOrWs;
7980

8081
/**
@@ -165,7 +166,7 @@ private void writeXmlData(HttpServletRequest request, HttpServletResponse respon
165166
} else {
166167
root = client.generateXMLReport(null, new String[]{"sanity"});
167168
}
168-
} else if (operation != null && operation.length() > 0) {
169+
} else if (notNullOrEmpty(operation)) {
169170
final String mbean = request.getParameter("mbean");
170171
if (mbean == null) {
171172
throw new ServletException("to call an operation, you also need to specify parameter 'mbean'");

exist-core/src/main/java/org/exist/management/client/JMXtoXML.java

Lines changed: 139 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
11
/*
2+
* Elemental
3+
* Copyright (C) 2024, Evolved Binary Ltd
4+
*
5+
6+
* https://www.evolvedbinary.com | https://www.elemental.xyz
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; version 2.1.
11+
*
12+
* This library is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
* Lesser General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public
18+
* License along with this library; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+
*
21+
* NOTE: Parts of this file contain code from 'The eXist-db Authors'.
22+
* The original license header is included below.
23+
*
24+
* =====================================================================
25+
*
226
* eXist-db Open Source Native XML Database
327
* Copyright (C) 2001 The eXist-db Authors
428
*
@@ -23,8 +47,8 @@
2347

2448
import java.io.IOException;
2549
import java.io.StringWriter;
26-
import java.lang.management.ManagementFactory;
2750

51+
import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple;
2852
import static java.lang.management.ManagementFactory.CLASS_LOADING_MXBEAN_NAME;
2953
import static java.lang.management.ManagementFactory.MEMORY_MXBEAN_NAME;
3054
import static java.lang.management.ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME;
@@ -34,6 +58,7 @@
3458
import java.net.MalformedURLException;
3559
import java.util.*;
3660
import java.util.concurrent.*;
61+
import javax.annotation.Nullable;
3762
import javax.management.*;
3863
import javax.management.openmbean.CompositeData;
3964
import javax.management.openmbean.CompositeType;
@@ -45,6 +70,7 @@
4570
import javax.xml.transform.OutputKeys;
4671
import javax.xml.transform.TransformerException;
4772

73+
import com.evolvedbinary.j8fu.tuple.Tuple2;
4874
import org.apache.logging.log4j.LogManager;
4975
import org.apache.logging.log4j.Logger;
5076
import org.exist.dom.QName;
@@ -137,17 +163,16 @@ private static void putCategory(final String categoryName, final String... objec
137163

138164
public static final int VERSION = 1;
139165

140-
private final MBeanServerConnection platformConnection = ManagementFactory.getPlatformMBeanServer();
141-
private MBeanServerConnection connection;
166+
private @Nullable List<MBeanServerConnection> connections;
142167
private JMXServiceURL url;
143168

144169
/**
145170
* Connect to the local JMX instance.
146171
*/
147172
public void connect() {
148173
final List<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);
149-
if (servers.size() > 0) {
150-
this.connection = servers.get(0);
174+
if (!servers.isEmpty()) {
175+
this.connections = new ArrayList<>(servers);
151176
}
152177
}
153178

@@ -166,7 +191,7 @@ public void connect(final String address, final int port) throws MalformedURLExc
166191
env.put(JMXConnector.CREDENTIALS, creds);
167192

168193
final JMXConnector jmxc = JMXConnectorFactory.connect(url, env);
169-
this.connection = jmxc.getMBeanServerConnection();
194+
this.connections = Collections.singletonList(jmxc.getMBeanServerConnection());
170195

171196
if (LOG.isDebugEnabled()) {
172197
LOG.debug("Connected to JMX server at {}", url.toString());
@@ -204,7 +229,24 @@ public long ping(final String instance, final long timeout) {
204229
final long start = System.currentTimeMillis();
205230
final ThreadFactory jmxPingFactory = new NamedThreadFactory(instance, "jmx.ping");
206231
final ExecutorService executorService = Executors.newSingleThreadExecutor(jmxPingFactory);
207-
final Future<Long> futurePing = executorService.submit(new Ping(instance, connection));
232+
233+
// find the connection and MBean name for the ping
234+
final ObjectName name;
235+
final MBeanServerConnection connection;
236+
try {
237+
name = SanityReport.getName(instance);
238+
final List<Tuple2<MBeanServerConnection, Set<ObjectName>>> matches = queryNames(name);
239+
if (matches.isEmpty()) {
240+
LOG.warn("Unable to locate MBean connection for ping destination");
241+
return SanityReport.PING_ERROR;
242+
}
243+
connection = matches.get(0)._1;
244+
} catch (final MalformedObjectNameException | IOException e) {
245+
LOG.warn("Unable to locate MBean connection for ping destination: " + e.getMessage(), e);
246+
return SanityReport.PING_ERROR;
247+
}
248+
249+
final Future<Long> futurePing = executorService.submit(new Ping(connection, name));
208250

209251
while (true) {
210252
try {
@@ -224,18 +266,17 @@ public long ping(final String instance, final long timeout) {
224266
}
225267

226268
private static class Ping implements Callable<Long> {
227-
private final String instance;
228269
private final MBeanServerConnection connection;
270+
private final ObjectName name;
229271

230-
public Ping(final String instance, final MBeanServerConnection connection) {
231-
this.instance = instance;
272+
public Ping(final MBeanServerConnection connection, final ObjectName name) {
232273
this.connection = connection;
274+
this.name = name;
233275
}
234276

235277
@Override
236278
public Long call() {
237279
try {
238-
final ObjectName name = SanityReport.getName(instance);
239280
return (Long) connection.invoke(name, "ping", new Object[]{Boolean.TRUE}, new String[]{boolean.class.getName()});
240281
} catch (final Exception e) {
241282
LOG.warn(e.getMessage(), e);
@@ -290,23 +331,27 @@ public Element generateXMLReport(final String errcode, final String categories[]
290331

291332
public String getDataDir() {
292333
try {
293-
final Object dir = connection.getAttribute(new ObjectName("org.exist.management.exist:type=DiskUsage"), "DataDirectory");
334+
final List<Tuple2<MBeanServerConnection, Object>> attributeValues = getAttribute(new ObjectName("org.exist.management.exist:type=DiskUsage"), "DataDirectory");
335+
if (attributeValues.isEmpty()) {
336+
return null;
337+
}
338+
final Object dir = attributeValues.get(0)._2;
294339
return dir == null ? null : dir.toString();
295-
} catch (final MBeanException | AttributeNotFoundException | InstanceNotFoundException | ReflectionException | IOException | MalformedObjectNameException e) {
340+
} catch (final MalformedObjectNameException | IOException e) {
296341
return null;
297342
}
298343
}
299344

300-
public Element invoke(final String objectName, final String operation, String[] args) throws InstanceNotFoundException, MalformedObjectNameException, MBeanException, IOException, ReflectionException, IntrospectionException {
301-
final ObjectName name = new ObjectName(objectName);
302-
MBeanServerConnection conn = connection;
303-
MBeanInfo info;
304-
try {
305-
info = conn.getMBeanInfo(name);
306-
} catch (InstanceNotFoundException e) {
307-
conn = platformConnection;
308-
info = conn.getMBeanInfo(name);
345+
public Element invoke(final String name, final String operation, final String[] args) throws InstanceNotFoundException, MalformedObjectNameException, MBeanException, IOException, ReflectionException, IntrospectionException {
346+
final ObjectName objectName = new ObjectName(name);
347+
final List<Tuple2<MBeanServerConnection, MBeanInfo>> mbeanInfos = getMBeanInfo(objectName);
348+
if (mbeanInfos.isEmpty()) {
349+
return null;
309350
}
351+
352+
final MBeanServerConnection conn = mbeanInfos.get(0)._1;
353+
final MBeanInfo info = mbeanInfos.get(0)._2;
354+
310355
final MBeanOperationInfo[] operations = info.getOperations();
311356
for (final MBeanOperationInfo op : operations) {
312357
if (operation.equals(op.getName())) {
@@ -318,7 +363,7 @@ public Element invoke(final String objectName, final String operation, String[]
318363
types[i] = type;
319364
params[i] = mapParameter(type, args[i]);
320365
}
321-
final Object result = conn.invoke(name, operation, params, types);
366+
final Object result = conn.invoke(objectName, operation, params, types);
322367

323368
final MemTreeBuilder builder = new MemTreeBuilder((Expression) null);
324369

@@ -350,46 +395,83 @@ public Element invoke(final String objectName, final String operation, String[]
350395
return null;
351396
}
352397

353-
private void queryMBeans(final MemTreeBuilder builder, final ObjectName query)
354-
throws IOException, InstanceNotFoundException, IntrospectionException, ReflectionException, NullPointerException {
398+
private List<Tuple2<MBeanServerConnection, Set<ObjectName>>> queryNames(final ObjectName nameQuery) throws IOException {
399+
final List<Tuple2<MBeanServerConnection, Set<ObjectName>>> matchedNames = new ArrayList<>();
400+
if (connections != null ) {
401+
for (final MBeanServerConnection connection : connections) {
402+
final Set<ObjectName> matches = connection.queryNames(nameQuery, null);
403+
if (!matches.isEmpty()) {
404+
matchedNames.add(Tuple(connection, matches));
405+
}
406+
}
407+
}
408+
return matchedNames;
409+
}
355410

356-
MBeanServerConnection conn = connection;
357-
Set<ObjectName> beans = conn.queryNames(query, null);
358-
359-
//if the query is not found in the eXist specific MBeans server, then attempt to query the platform for it
360-
if (beans.isEmpty()) {
361-
beans = platformConnection.queryNames(query, null);
362-
conn = platformConnection;
363-
} //TODO examine JUnit source code as alternative method
364-
365-
for (final ObjectName name : beans) {
366-
final MBeanInfo info = conn.getMBeanInfo(name);
367-
String className = info.getClassName().replace('$', '.');
368-
final int p = className.lastIndexOf('.');
369-
if (p > -1 && p + 1 < className.length()) {
370-
className = className.substring(p + 1);
411+
private List<Tuple2<MBeanServerConnection, Object>> getAttribute(final ObjectName objectName, final String attributeName) throws IOException {
412+
final List<Tuple2<MBeanServerConnection, Object>> attributeValues = new ArrayList<>();
413+
if (connections != null ) {
414+
for (final MBeanServerConnection connection : connections) {
415+
try {
416+
attributeValues.add(Tuple(connection, connection.getAttribute(objectName, attributeName)));
417+
} catch (final AttributeNotFoundException | MBeanException | InstanceNotFoundException | ReflectionException e) {
418+
// no-op
419+
}
371420
}
421+
}
422+
return attributeValues;
423+
}
372424

373-
final QName qname = new QName(className, JMX_NAMESPACE, JMX_PREFIX);
374-
builder.startElement(qname, null);
375-
builder.addAttribute(new QName("name", XMLConstants.NULL_NS_URI), name.toString());
376-
377-
final MBeanAttributeInfo[] beanAttribs = info.getAttributes();
378-
for (MBeanAttributeInfo beanAttrib : beanAttribs) {
379-
if (beanAttrib.isReadable()) {
380-
try {
381-
final QName attrQName = new QName(beanAttrib.getName(), JMX_NAMESPACE, JMX_PREFIX);
382-
final Object attrib = conn.getAttribute(name, beanAttrib.getName());
383-
384-
builder.startElement(attrQName, null);
385-
serializeObject(builder, attrib);
386-
builder.endElement();
387-
} catch (final Exception e) {
388-
LOG.debug("exception caught: {}", e.getMessage(), e);
425+
private List<Tuple2<MBeanServerConnection, MBeanInfo>> getMBeanInfo(final ObjectName objectName) throws IOException {
426+
final List<Tuple2<MBeanServerConnection, MBeanInfo>> mbeanInfos = new ArrayList<>();
427+
if (connections != null ) {
428+
for (final MBeanServerConnection connection : connections) {
429+
try {
430+
mbeanInfos.add(Tuple(connection, connection.getMBeanInfo(objectName)));
431+
} catch (final IntrospectionException | InstanceNotFoundException | ReflectionException e) {
432+
// no-op
433+
}
434+
}
435+
}
436+
return mbeanInfos;
437+
}
438+
439+
private void queryMBeans(final MemTreeBuilder builder, final ObjectName nameQuery)
440+
throws IOException, InstanceNotFoundException, IntrospectionException, ReflectionException, NullPointerException {
441+
442+
final List<Tuple2<MBeanServerConnection, Set<ObjectName>>> matchedNames = queryNames(nameQuery);
443+
444+
for (final Tuple2<MBeanServerConnection, Set<ObjectName>> matchedName : matchedNames) {
445+
final MBeanServerConnection connection = matchedName._1;
446+
for (final ObjectName name : matchedName._2) {
447+
final MBeanInfo info = connection.getMBeanInfo(name);
448+
String className = info.getClassName().replace('$', '.');
449+
final int p = className.lastIndexOf('.');
450+
if (p > -1 && p + 1 < className.length()) {
451+
className = className.substring(p + 1);
452+
}
453+
454+
final QName qname = new QName(className, JMX_NAMESPACE, JMX_PREFIX);
455+
builder.startElement(qname, null);
456+
builder.addAttribute(new QName("name", XMLConstants.NULL_NS_URI), name.toString());
457+
458+
final MBeanAttributeInfo[] beanAttribs = info.getAttributes();
459+
for (MBeanAttributeInfo beanAttrib : beanAttribs) {
460+
if (beanAttrib.isReadable()) {
461+
try {
462+
final QName attrQName = new QName(beanAttrib.getName(), JMX_NAMESPACE, JMX_PREFIX);
463+
final Object attrib = connection.getAttribute(name, beanAttrib.getName());
464+
465+
builder.startElement(attrQName, null);
466+
serializeObject(builder, attrib);
467+
builder.endElement();
468+
} catch (final Exception e) {
469+
LOG.debug("exception caught: {}", e.getMessage(), e);
470+
}
389471
}
390472
}
473+
builder.endElement();
391474
}
392-
builder.endElement();
393475
}
394476
}
395477

exist-core/src/main/java/org/exist/management/impl/JMXAgent.java

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
11
/*
2+
* Elemental
3+
* Copyright (C) 2024, Evolved Binary Ltd
4+
*
5+
6+
* https://www.evolvedbinary.com | https://www.elemental.xyz
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; version 2.1.
11+
*
12+
* This library is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
* Lesser General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public
18+
* License along with this library; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+
*
21+
* NOTE: Parts of this file contain code from 'The eXist-db Authors'.
22+
* The original license header is included below.
23+
*
24+
* =====================================================================
25+
*
226
* eXist-db Open Source Native XML Database
327
* Copyright (C) 2001 The eXist-db Authors
428
*
@@ -32,10 +56,10 @@
3256
import javax.management.InstanceNotFoundException;
3357
import javax.management.MBeanRegistrationException;
3458
import javax.management.MBeanServer;
35-
import javax.management.MBeanServerFactory;
3659
import javax.management.MalformedObjectNameException;
3760
import javax.management.NotCompliantMBeanException;
3861
import javax.management.ObjectName;
62+
import java.lang.management.ManagementFactory;
3963
import java.util.*;
4064

4165
/**
@@ -58,12 +82,7 @@ public JMXAgent() {
5882
LOG.debug("Creating the JMX MBeanServer.");
5983
}
6084

61-
final ArrayList<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);
62-
if (servers.size() > 0) {
63-
server = servers.get(0);
64-
} else {
65-
server = MBeanServerFactory.createMBeanServer();
66-
}
85+
this.server = ManagementFactory.getPlatformMBeanServer();
6786

6887
registerSystemMBeans();
6988
}

0 commit comments

Comments
 (0)