Skip to content
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
141 changes: 141 additions & 0 deletions src/java.base/share/classes/jdk/internal/event/SocketConnectEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2024, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package jdk.internal.event;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnixDomainSocketAddress;

/**
* A JFR event for when a socket connection is established. This event is mirrored in
* {@code jdk.jfr.events.SocketConnectEvent} where the metadata for the event is
* provided with annotations. Some of the methods are replaced by generated
* methods when jfr is enabled. Note that the order of the arguments of the
Copy link
Contributor

Choose a reason for hiding this comment

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

In passing, there is a mix of "JFR" and "jfr" through-out, I assume you want "JFR" everywhere.

* {@link #commit(long, long, String, String, int)} method must be the same as the
* order of the fields.
*/
public class SocketConnectEvent extends Event {

// THE ORDER OF THE FOLLOWING FIELDS IS IMPORTANT!
// The order must match the argument order of the generated commit method.
public String host;
public String address;
public int port;

/**
* Actually commit an event. The implementation is generated automatically.
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume "Actually" should be removed, this method commits an event.

* The order of the fields must be the same as the parameters in this method.
* {@code commit(..., String, String, int, long)}
*
* @param start timestamp of the start of the operation
* @param duration time in nanoseconds to complete the operation
* @param host remote host of the connection
* @param address remote address of the connection
* @param port remote port of the connection
*/
public static void commit(long start, long duration, String host, String address, int port) {
// Generated by JFR
}

/**
* Determine if an event should be emitted. The duration of the operation
* must exceed some threshold in order to commit the event. The implementation
* of this method is generated automatically if jfr is enabled.
*
* @param duration time in nanoseconds to complete the operation
* @return true if the event should be commited
*/
public static boolean shouldCommit(long duration) {
// Generated by JFR
return false;
}

/**
* Determine if this kind of event is enabled. The implementation
* of this method is generated automatically if jfr is enabled.
*
* @return true if event is enabled, false otherwise
*/
public static boolean enabled() {
// Generated by JFR
return false;
}

/**
* Fetch the current timestamp in nanoseconds. This method is used
* to determine the start and end of an operation. The implementation
* of this method is generated automatically if jfr is enabled.
*
* @return the current timestamp value
*/
public static long timestamp() {
// Generated by JFR
return 0L;
}

/**
* Helper method to offer the data needed to potentially commit an event.
* The duration of the operation is computed using the current
* timestamp and the given start time. If the duration is meets
* or exceeds the configured value (determined by calling the generated method
* {@link #shouldCommit(long)}), an event will be emitted by calling
* {@link #commit(long, long, String, String, int)}.
*
* @param start the start time
* @param remote the address of the remote socket
*/
public static void offer(long start, SocketAddress remote) {
long duration = timestamp() - start;
if (shouldCommit(duration)) {
if (remote instanceof InetSocketAddress isa) {
commit(start, duration, isa.getHostString(), isa.getAddress().getHostAddress(), isa.getPort());
} else if (remote instanceof UnixDomainSocketAddress udsa) {
String path = "[" + udsa.getPath().toString() + "]";
commit(start, duration, "Unix domain socket", path, 0);
}
}
}

/**
* Helper method to offer the data needed to potentially commit an event.
* The duration of the operation is computed using the current
* timestamp and the given start time. If the duration is meets
* or exceeds the configured value (determined by calling the generated method
* {@link #shouldCommit(long)}), an event will be emitted by calling
* {@code commit(long, long, String, String, int, long)}
*
* @param start timestamp of the start of the operation
* @param host remote host of the connection
* @param address remote address of the connection
* @param port remote port of the connection
*/
public static void offer(long start, String host, InetAddress address, int port) {
long duration = timestamp() - start;
if (shouldCommit(duration)) {
commit(start, duration, host, address.getHostAddress(), port);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright (c) 2024, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package jdk.internal.event;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnixDomainSocketAddress;

/**
* A JFR event for when a socket connection cannot be established. This event is mirrored
* in {@code jdk.jfr.events.SocketConnectFailedEvent} where the metadata for the event is
* provided with annotations. Some of the methods are replaced by generated
* methods when jfr is enabled. Note that the order of the arguments of the
* {@link #commit(long, long, String, String, int, String)} method must be the same as
* the order of the fields.
*/
public class SocketConnectFailedEvent extends Event {

// THE ORDER OF THE FOLLOWING FIELDS IS IMPORTANT!
// The order must match the argument order of the generated commit method.
public String host;
public String address;
public int port;
public String connectExceptionMessage;

/**
* Actually commit an event. The implementation is generated automatically.
* The order of the fields must be the same as the parameters in this method.
* {@code commit(..., String, String, int, long)}
*
* @param start timestamp of the start of the operation
* @param duration time in nanoseconds to complete the operation
* @param host remote host of the connection
* @param address remote address of the connection
* @param port remote port of the connection
* @param connectEx the connect exception message
*/
public static void commit(long start, long duration, String host, String address, int port, String connectEx) {
// Generated by JFR
}

/**
* Determine if an event should be emitted. The duration of the operation
* must exceed some threshold in order to commit the event. The implementation
* of this method is generated automatically if jfr is enabled.
*
* @param duration time in nanoseconds to complete the operation
* @return true if the event should be commited
*/
public static boolean shouldCommit(long duration) {
// Generated by JFR
return false;
}

/**
* Determine if this kind of event is enabled. The implementation
* of this method is generated automatically if jfr is enabled.
*
* @return true if event is enabled, false otherwise
*/
public static boolean enabled() {
// Generated by JFR
return false;
}

/**
* Fetch the current timestamp in nanoseconds. This method is used
* to determine the start and end of an operation. The implementation
* of this method is generated automatically if jfr is enabled.
*
* @return the current timestamp value
*/
public static long timestamp() {
// Generated by JFR
return 0L;
}

/**
* Helper method to offer the data needed to potentially commit an event.
* The duration of the operation is computed using the current
* timestamp and the given start time. If the duration is meets
* or exceeds the configured value (determined by calling the generated method
* {@link #shouldCommit(long)}), an event will be emitted by calling
* {@link #commit(long, long, String, String, int, String)}.
*
* @param start the start time
* @param remote the address of the remote socket
* @param connectEx the I/O exception thrown
*/
public static void offer(long start, SocketAddress remote, IOException connectEx) {
long duration = timestamp() - start;
if (shouldCommit(duration)) {
String msg = connectEx.toString();
Copy link
Member

Choose a reason for hiding this comment

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

Should the description for this field in the event be updated too?

if (remote instanceof InetSocketAddress isa) {
commit(start, duration, isa.getHostString(), isa.getAddress().getHostAddress(), isa.getPort(), msg);
} else if (remote instanceof UnixDomainSocketAddress udsa) {
String path = "[" + udsa.getPath().toString() + "]";
commit(start, duration, "Unix domain socket", path, 0, msg);
}
}
}

/**
* Helper method to offer the data needed to potentially commit an event.
* The duration of the operation is computed using the current
* timestamp and the given start time. If the duration is meets
* or exceeds the configured value (determined by calling the generated method
* {@link #shouldCommit(long)}), an event will be emitted by calling
* {@code commit(long, long, String, String, int, long)}
*
* @param start timestamp of the start of the operation
* @param host remote host of the connection
* @param address remote address of the connection
* @param port remote port of the connection
* @param connectEx the I/O exception thrown
*/
public static void offer(long start, String host, InetAddress address, int port, IOException connectEx) {
long duration = timestamp() - start;
if (shouldCommit(duration)) {
commit(start, duration, host, address.getHostAddress(), port, connectEx.toString());
}
}
}
26 changes: 23 additions & 3 deletions src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@

import jdk.internal.access.JavaIOFileDescriptorAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.event.SocketConnectEvent;
import jdk.internal.event.SocketConnectFailedEvent;
import jdk.internal.ref.CleanerFactory;
import sun.net.ConnectionResetException;
import sun.net.NetHooks;
Expand Down Expand Up @@ -563,6 +565,9 @@ protected void connect(SocketAddress remote, int millis) throws IOException {
address = InetAddress.getLocalHost();
int port = isa.getPort();

long connectStart = 0L;
IOException connectEx = null;

ReentrantLock connectLock = readLock;
try {
connectLock.lock();
Expand All @@ -571,6 +576,7 @@ protected void connect(SocketAddress remote, int millis) throws IOException {
FileDescriptor fd = beginConnect(address, port);
try {
configureNonBlockingIfNeeded(fd, millis > 0);
connectStart = SocketConnectEvent.timestamp();
int n = Net.connect(fd, address, port);
if (n > 0) {
// connection established
Expand Down Expand Up @@ -600,14 +606,28 @@ protected void connect(SocketAddress remote, int millis) throws IOException {
} catch (IOException ioe) {
close();
if (ioe instanceof SocketTimeoutException) {
throw ioe;
connectEx = ioe;
} else if (ioe instanceof InterruptedIOException) {
assert Thread.currentThread().isVirtual();
throw new SocketException("Closed by interrupt");
connectEx = new SocketException("Closed by interrupt");
} else {
throw SocketExceptions.of(ioe, isa);
connectEx = SocketExceptions.of(ioe, isa);
}
}

// record JFR event
if (connectStart != 0L) {
String hostname = isa.getHostString();
if (connectEx == null && SocketConnectEvent.enabled()) {
SocketConnectEvent.offer(connectStart, hostname , address, port);
} else if (connectEx != null && SocketConnectFailedEvent.enabled()) {
SocketConnectFailedEvent.offer(connectStart, hostname, address, port, connectEx);
}
}
Copy link
Contributor

@AlanBateman AlanBateman Oct 16, 2024

Choose a reason for hiding this comment

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

Would it be possible to update the JBS or PR description to indicate if the intent is to record an event when the connection cannot be established? I'm asking because the change will only record an event when a connection is successfully established ("connected" is always true here).

JFR will record exceptions already of course but I think for troubleshooting purposes, recording an event when "connect" hangs and eventually fails is very useful to have.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Capturing all calls even if they threw an exception does seem pretty useful. I'll update the JBS


if (connectEx != null) {
throw connectEx;
}
}

@Override
Expand Down
Loading