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
rendered nor used as reference by other samples.
* Add `BasePreloadManager.Listener` to propagate preload events to apps.
* Allow changing SNTP client timeout
([#1540](https://github.com/androidx/media/issues/1540)).
* Allow changing SNTP client timeout and retry alternative addresses on
timeout ([#1540](https://github.com/androidx/media/issues/1540)).
* Remove `MediaCodecAdapter.Configuration.flags` as the field was always
zero.
* Transformer:

View File

@ -15,6 +15,8 @@
*/
package androidx.media3.exoplayer.util;
import static androidx.media3.common.util.Assertions.checkNotNull;
import android.os.SystemClock;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
@ -27,6 +29,7 @@ import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
@ -44,7 +47,7 @@ public final class SntpClient {
public static final String DEFAULT_NTP_HOST = "time.android.com";
/** 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)}. */
public interface InitializationCallback {
@ -60,6 +63,7 @@ public final class SntpClient {
void onInitializationFailed(IOException error);
}
private static final int MAX_RETRY_COUNT = 10;
private static final int ORIGINATE_TIME_OFFSET = 24;
private static final int RECEIVE_TIME_OFFSET = 32;
private static final int TRANSMIT_TIME_OFFSET = 40;
@ -191,56 +195,78 @@ public final class SntpClient {
}
private static long loadNtpTimeOffsetMs() throws IOException {
InetAddress address = InetAddress.getByName(getNtpHost());
try (DatagramSocket socket = new DatagramSocket()) {
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
// is in bits 3-5 of the first byte.
buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
int retryCount = 0;
SocketTimeoutException timeoutException = null;
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.
long requestTime = System.currentTimeMillis();
long requestTicks = SystemClock.elapsedRealtime();
writeTimestamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
// Set mode = 3 (client) and version = 3. Mode is in low 3 bits of the first byte and
// Version is in bits 3-5 of the first byte.
buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
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.
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
final long responseTicks = SystemClock.elapsedRealtime();
final long responseTime = requestTime + (responseTicks - requestTicks);
socket.send(request);
// Extract the results.
final byte leap = (byte) ((buffer[0] >> 6) & 0x3);
final byte mode = (byte) (buffer[0] & 0x7);
final int stratum = (int) (buffer[1] & 0xff);
final long originateTime = readTimestamp(buffer, ORIGINATE_TIME_OFFSET);
final long receiveTime = readTimestamp(buffer, RECEIVE_TIME_OFFSET);
final long transmitTime = readTimestamp(buffer, TRANSMIT_TIME_OFFSET);
// Read the response.
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
try {
socket.receive(response);
} catch (SocketTimeoutException e) {
// Store the timeout exception and try the next address if we have not reached retry limit
if (timeoutException == null) {
timeoutException = e;
} else {
timeoutException.addSuppressed(e);
}
if (retryCount++ < MAX_RETRY_COUNT) {
continue;
} else {
break;
}
}
// Check server reply validity according to RFC.
checkValidServerReply(leap, mode, stratum, transmitTime);
final long responseTicks = SystemClock.elapsedRealtime();
final long responseTime = requestTime + (responseTicks - requestTicks);
// 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;
// Extract the results.
final byte leap = (byte) ((buffer[0] >> 6) & 0x3);
final byte mode = (byte) (buffer[0] & 0x7);
final int stratum = (int) (buffer[1] & 0xff);
final long originateTime = readTimestamp(buffer, ORIGINATE_TIME_OFFSET);
final long receiveTime = readTimestamp(buffer, RECEIVE_TIME_OFFSET);
final long transmitTime = readTimestamp(buffer, TRANSMIT_TIME_OFFSET);
// 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;
// Check server reply validity according to RFC.
checkValidServerReply(leap, mode, stratum, transmitTime);
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);
}
}