Retry alternative addresses on timeout in SNTP client

Changed the default timeout for SNTP requests to 1 second.

Issue: androidx/media#1540
PiperOrigin-RevId: 652897579
This commit is contained in:
rohks 2024-07-16 10:34:59 -07:00 committed by Copybara-Service
parent 7f304092ae
commit ded66debc3
2 changed files with 68 additions and 42 deletions

View File

@ -24,8 +24,8 @@
* `MediaCodecVideoRenderer` avoids decoding samples that are neither * `MediaCodecVideoRenderer` avoids decoding samples that are neither
rendered nor used as reference by other samples. rendered nor used as reference by other samples.
* Add `BasePreloadManager.Listener` to propagate preload events to apps. * Add `BasePreloadManager.Listener` to propagate preload events to apps.
* Allow changing SNTP client timeout * Allow changing SNTP client timeout and retry alternative addresses on
([#1540](https://github.com/androidx/media/issues/1540)). timeout ([#1540](https://github.com/androidx/media/issues/1540)).
* Remove `MediaCodecAdapter.Configuration.flags` as the field was always * Remove `MediaCodecAdapter.Configuration.flags` as the field was always
zero. zero.
* Transformer: * Transformer:

View File

@ -15,6 +15,8 @@
*/ */
package androidx.media3.exoplayer.util; package androidx.media3.exoplayer.util;
import static androidx.media3.common.util.Assertions.checkNotNull;
import android.os.SystemClock; import android.os.SystemClock;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -27,6 +29,7 @@ import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.Arrays; import java.util.Arrays;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
@ -44,7 +47,7 @@ public final class SntpClient {
public static final String DEFAULT_NTP_HOST = "time.android.com"; public static final String DEFAULT_NTP_HOST = "time.android.com";
/** The default maximum time, in milliseconds, to wait for the SNTP request to complete. */ /** The default maximum time, in milliseconds, to wait for the SNTP request to complete. */
public static final int DEFAULT_TIMEOUT_MS = 5_000; public static final int DEFAULT_TIMEOUT_MS = 1_000;
/** Callback for calls to {@link #initialize(Loader, InitializationCallback)}. */ /** Callback for calls to {@link #initialize(Loader, InitializationCallback)}. */
public interface InitializationCallback { public interface InitializationCallback {
@ -60,6 +63,7 @@ public final class SntpClient {
void onInitializationFailed(IOException error); void onInitializationFailed(IOException error);
} }
private static final int MAX_RETRY_COUNT = 10;
private static final int ORIGINATE_TIME_OFFSET = 24; private static final int ORIGINATE_TIME_OFFSET = 24;
private static final int RECEIVE_TIME_OFFSET = 32; private static final int RECEIVE_TIME_OFFSET = 32;
private static final int TRANSMIT_TIME_OFFSET = 40; private static final int TRANSMIT_TIME_OFFSET = 40;
@ -191,56 +195,78 @@ public final class SntpClient {
} }
private static long loadNtpTimeOffsetMs() throws IOException { private static long loadNtpTimeOffsetMs() throws IOException {
InetAddress address = InetAddress.getByName(getNtpHost());
try (DatagramSocket socket = new DatagramSocket()) { try (DatagramSocket socket = new DatagramSocket()) {
socket.setSoTimeout(getTimeoutMs()); socket.setSoTimeout(getTimeoutMs());
byte[] buffer = new byte[NTP_PACKET_SIZE];
DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);
// Set mode = 3 (client) and version = 3. Mode is in low 3 bits of the first byte and Version int retryCount = 0;
// is in bits 3-5 of the first byte. SocketTimeoutException timeoutException = null;
buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3); InetAddress[] addresses = InetAddress.getAllByName(getNtpHost());
for (InetAddress address : addresses) {
byte[] buffer = new byte[NTP_PACKET_SIZE];
DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);
// Get current time and write it to the request packet. // Set mode = 3 (client) and version = 3. Mode is in low 3 bits of the first byte and
long requestTime = System.currentTimeMillis(); // Version is in bits 3-5 of the first byte.
long requestTicks = SystemClock.elapsedRealtime(); buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
writeTimestamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
socket.send(request); // Get current time and write it to the request packet.
long requestTime = System.currentTimeMillis();
long requestTicks = SystemClock.elapsedRealtime();
writeTimestamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
// Read the response. socket.send(request);
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
final long responseTicks = SystemClock.elapsedRealtime();
final long responseTime = requestTime + (responseTicks - requestTicks);
// Extract the results. // Read the response.
final byte leap = (byte) ((buffer[0] >> 6) & 0x3); DatagramPacket response = new DatagramPacket(buffer, buffer.length);
final byte mode = (byte) (buffer[0] & 0x7); try {
final int stratum = (int) (buffer[1] & 0xff); socket.receive(response);
final long originateTime = readTimestamp(buffer, ORIGINATE_TIME_OFFSET); } catch (SocketTimeoutException e) {
final long receiveTime = readTimestamp(buffer, RECEIVE_TIME_OFFSET); // Store the timeout exception and try the next address if we have not reached retry limit
final long transmitTime = readTimestamp(buffer, TRANSMIT_TIME_OFFSET); if (timeoutException == null) {
timeoutException = e;
} else {
timeoutException.addSuppressed(e);
}
if (retryCount++ < MAX_RETRY_COUNT) {
continue;
} else {
break;
}
}
// Check server reply validity according to RFC. final long responseTicks = SystemClock.elapsedRealtime();
checkValidServerReply(leap, mode, stratum, transmitTime); final long responseTime = requestTime + (responseTicks - requestTicks);
// receiveTime = originateTime + transit + skew // Extract the results.
// responseTime = transmitTime + transit - skew final byte leap = (byte) ((buffer[0] >> 6) & 0x3);
// clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2 final byte mode = (byte) (buffer[0] & 0x7);
// = ((originateTime + transit + skew - originateTime) + final int stratum = (int) (buffer[1] & 0xff);
// (transmitTime - (transmitTime + transit - skew)))/2 final long originateTime = readTimestamp(buffer, ORIGINATE_TIME_OFFSET);
// = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2 final long receiveTime = readTimestamp(buffer, RECEIVE_TIME_OFFSET);
// = (transit + skew - transit + skew)/2 final long transmitTime = readTimestamp(buffer, TRANSMIT_TIME_OFFSET);
// = (2 * skew)/2 = skew
long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2;
// Save our results using the times on this side of the network latency (i.e. response rather // Check server reply validity according to RFC.
// than request time) checkValidServerReply(leap, mode, stratum, transmitTime);
long ntpTime = responseTime + clockOffset;
long ntpTimeReference = responseTicks;
return ntpTime - ntpTimeReference; // receiveTime = originateTime + transit + skew
// responseTime = transmitTime + transit - skew
// clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
// = ((originateTime + transit + skew - originateTime) +
// (transmitTime - (transmitTime + transit - skew)))/2
// = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
// = (transit + skew - transit + skew)/2
// = (2 * skew)/2 = skew
long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2;
// Save our results using the times on this side of the network latency (i.e. response
// rather than request time)
long ntpTime = responseTime + clockOffset;
long ntpTimeReference = responseTicks;
return ntpTime - ntpTimeReference;
}
// If no response is received from any of the addresses, throw an exception.
throw checkNotNull(timeoutException);
} }
} }