JEP 373: Reimplement the Legacy DatagramSocket API

OwnerDaniel Fuchs
TypeFeature
ScopeJDK
StatusClosed / Delivered
Release15
Componentcore-libs / java.net
Discussionnet dash dev at openjdk dot java dot net
EffortS
Reviewed byAlan Bateman, Brian Goetz, Chris Hegarty
Endorsed byBrian Goetz
Created2019/12/10 16:13
Updated2023/03/04 02:10
Issue8235674

Summary

Replace the underlying implementations of the java.net.DatagramSocket and java.net.MulticastSocket APIs with simpler and more modern implementations that are easy to maintain and debug. The new implementations will be easy to adapt to work with virtual threads, currently being explored in Project Loom. This is a follow-on to JEP 353, which already reimplemented the legacy Socket API.

Motivation

The code base of the java.net.DatagramSocket and java.net.MulticastSocket APIs, and their underlying implementations, is old and brittle:

In addition, in the context of virtual threads that park rather than block underlying kernel threads in system calls, the current implementation is not fit for purpose. As datagram-based transports gain traction again (e.g. QUIC), a simpler and more maintainable implementation is needed.

Description

Currently, the DatagramSocket and MulticastSocket classes delegate all socket calls to a java.net.DatagramSocketImpl implementation, for which different platform-specific concrete implementations exist: PlainDatagramSocketImpl on Unix platforms, and TwoStackPlainDatagramSocketImpl and DualPlainDatagramSocketImpl on Windows platforms. The abstract DatagramSocketImpl class, which dates back to JDK 1.1, is very under-specified and contains several obsolete methods that are an impediment to providing an implementation of this class based on NIO (see alternatives, discussed below).

Rather than provide a drop-in replacement for implementations of DatagramSocketImpl, similar to what was done in JEP 353 for SocketImpl, this JEP proposes to make DatagramSocket internally wrap another instance of DatagramSocket to which it delegates all calls directly. The wrapped instance is either a socket adapter created from a NIO DatagramChannel::socket (the new implementation), or else a clone of the legacy DatagramSocket class which then delegates to the legacy DatagramSocketImpl implementation (for the purpose of implementing a backward compatibility switch). If a DatagramSocketImplFactory is installed by an application, the old legacy implementation is selected. Otherwise, the new implementation is selected and used by default.

To reduce the risk of switching the implementation after more than twenty years, the legacy implementation will not be removed. A JDK-specific system property, jdk.net.usePlainDatagramSocketImpl, is introduced to configure the JDK to use the legacy implementation (see risks and assumptions, below). If set with no value or set to the value ”true" at startup, the legacy implementation is used. Otherwise, the new (NIO-based) implementation is used. In some future release we will remove the legacy implementation and the system property. At some point we may also deprecate and remove DatagramSocketImpl and DatagramSocketImplFactory.

The new implementation is enabled by default. It provides non-interruptible behavior for datagram and multicast sockets by directly using the platform-default implementation of the selector provider (sun.nio.ch.SelectorProviderImpl and sun.nio.ch.DatagramChannelImpl). Installing a custom selector provider will thus have no effect on DatagramSocket and MulticastSocket.

Alternatives

We investigated, prototyped, and discarded two alternative approaches.

Alternative 1

Create an implementation of DatagramSocketImpl that delegates all its calls to a wrapped DatagramChannel and sun.nio.ch.DatagramSocketAdaptor. Upgrade sun.nio.ch.DatagramSocketAdaptor to extend java.net.MulticastSocket.

This approach showed that it would be relatively easy to provide an implementation of DatagramSocketImpl based on DatagramChannel. Tests were passing, but it also highlighted several limitations:

Alternative 2

Create an implementation of DatagramSocketImpl in the sun.nio.ch package that invokes low-level sun.nio.ch.Net primitives. This allowed the implementation to directly access lower-level NIO primitives instead of relying on DatagramChannel. This was somewhat analogous to what was done for reimplementing Socket and ServerSocket in JEP 353.

Testing

The existing tests in the jdk/jdk repository will be used to test the new implementation. To ensure a smooth transition, the new implementation should pass the tier2 (jdk_net and jdk_nio) regression-test suite and the JCK for java_net/api. The jdk_net test group has accumulated many tests for networking corner case scenarios over the years. Some of the tests in this test group will be modified to run twice, the second time with -Djdk.net.usePlainDatagramSocketImpl to ensure that the old implementation does not decay during the time that the JDK includes both implementations. New tests will be added as required, to expand code coverage and increase confidence in the new implementation.

Every effort will be made to publicize the proposal and encourage developers that have code using DatagramSocket and MulticastSocket to test their code with the early-access builds that are published on jdk.java.net.

The microbenchmarks in the jdk/jdk repository include benchmarks for DatagramChannel. Similar benchmarks for datagram socket will be created if missing, or updated if they already exist, in a way that makes it easy to compare the old and new implementations.

Risks and Assumptions

The primary risk of this proposal is that there is existing code that depends upon unspecified behavior in corner cases where the old and new implementations behave differently. To minimize this risk, some preparatory work to clarify the specifications of DatagramSocket and MulticastSocket, and to minimize the behavioral differences between these classes and the DatagramChannel::socket adapter, has already been done in JDK 14 and JDK 15. Some small differences, listed below, might however persist. These differences might be observable in corner-case situations but should be transparent to the vast majority of API users. The differences we have identified so far are listed here; all but the first two can be mitigated by running with either -Djdk.net.usePlainDatagramSocketImpl or -Djdk.net.usePlainDatagramSocketImpl=true.

Other observable behavioral differences:

Aside from behavioral differences, the performance of the new implementation may differ compared to the old when running certain workloads. This JEP will endeavor to provide some performance benchmarks to gauge the difference.

Dependencies