Skip to content

8320687: sun.jvmstat.monitor.MonitoredHost.getMonitoredHost() throws unexpected exceptions when invoked concurrently #3593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2004, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -50,7 +50,7 @@
* @see HostListener
*/
public abstract class MonitoredHost {
private static Map<HostIdentifier, MonitoredHost> monitoredHosts =
private static final Map<HostIdentifier, MonitoredHost> monitoredHosts =
new HashMap<HostIdentifier, MonitoredHost>();

/*
Expand Down Expand Up @@ -133,13 +133,6 @@ public static MonitoredHost getMonitoredHost(VmIdentifier vmid)
return getMonitoredHost(hostId);
}


/*
* Load the MonitoredHostServices
*/
private static ServiceLoader<MonitoredHostService> monitoredHostServiceLoader =
ServiceLoader.load(MonitoredHostService.class, MonitoredHostService.class.getClassLoader());

/**
* Factory method to construct a MonitoredHost instance to manage the
* connection to the host indicated by {@code hostId}.
Expand Down Expand Up @@ -167,9 +160,12 @@ public static MonitoredHost getMonitoredHost(HostIdentifier hostId)

hostId = resolveHostId(hostId);

for (MonitoredHostService mhs : monitoredHostServiceLoader) {
ServiceLoader<MonitoredHostService> services = ServiceLoader.load(
MonitoredHostService.class, MonitoredHostService.class.getClassLoader());
for (MonitoredHostService mhs : services) {
if (mhs.getScheme().equals(hostId.getScheme())) {
mh = mhs.getMonitoredHost(hostId);
break;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import sun.jvmstat.monitor.MonitoredHost;
import sun.jvmstat.monitor.VmIdentifier;

/*
* @test
* @bug 8320687
* @summary verify that sun.jvmstat.monitor.MonitoredHost.getMonitoredHost() doesn't
* unexpectedly throw an exception when invoked by concurrent threads
*
* @run main/othervm ConcurrentGetMonitoredHost
*/
public class ConcurrentGetMonitoredHost {

/*
* Launches multiple concurrent threads and invokes MonitoredHost.getMonitoredHost()
* in each of these threads and expects the call to return successfully without any
* exceptions.
*/
public static void main(final String[] args) throws Exception {
final String pidStr = "12345";
final VmIdentifier vmid = new VmIdentifier(pidStr);
final int numTasks = 100;
final List<Task> tasks = new ArrayList<>();
for (int i = 0; i < numTasks; i++) {
tasks.add(new Task(vmid));
}
System.out.println("Submitting " + numTasks + " concurrent tasks to" +
" get MonitoredHost for " + vmid);
try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you've already mentioned, the original implementation uses try-with-resources to close the ExecutorService but ExecutorService only implements AutoClosable since JDK 19. Alternatively, we could put the new ExecutorService's close() handling into a finallly block:

        boolean terminated = isTerminated();
        if (!terminated) {
            shutdown();
            boolean interrupted = false;
            while (!terminated) {
                try {
                    terminated = awaitTermination(1L, TimeUnit.DAYS);
                } catch (InterruptedException e) {
                    if (!interrupted) {
                        shutdownNow();
                        interrupted = true;
                    }
                }
            }
            if (interrupted) {
                Thread.currentThread().interrupt();
            }
        }

But that seems to be overkill, taking into account that in the body of the try/catch we already wait for the completion of all ExecutorService tasks. So I leave it up to you if you want the additional safe guard.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. The test exits immediately in any case, so cleanup doesn't matter.

final ExecutorService executor = Executors.newCachedThreadPool();
// wait for all tasks to complete
final List<Future<MonitoredHost>> results = executor.invokeAll(tasks);
// verify each one successfully completed and each of
// the returned MonitoredHost is not null
for (final Future<MonitoredHost> result : results) {
final MonitoredHost mh = result.get();
if (mh == null) {
throw new AssertionError("MonitoredHost.getMonitoredHost() returned" +
" null for vmid " + vmid);
}
}
} catch (Exception e) {
}
System.out.println("All " + numTasks + " completed successfully");
}

// a task which just calls MonitoredHost.getMonitoredHost(VmIdentifier) and
// returns the resultant MonitoredHost
private static final class Task implements Callable<MonitoredHost> {
private final VmIdentifier vmid;

private Task(final VmIdentifier vmid) {
this.vmid = Objects.requireNonNull(vmid);
}

@Override
public MonitoredHost call() throws Exception {
return MonitoredHost.getMonitoredHost(this.vmid);
}
}
}