Skip to content
Merged
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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@
* Added "disableSdkBehaviorSettingsUpdates()" init config method for disabling SDK behavior settings requests.
* Improved request queue handling with a built-in backoff mechanism which is enabled by default. When the server takes 10 seconds or longer to answer while few requests are queued, the SDK pauses sending for 60 seconds.
* Added "disableBackoffMechanism()" init config method to disable the backoff behavior.
* Added support for server requested SDK log gathering. When the server asks for it, the SDK uploads its own internal log lines, even if logging is disabled.
* Added support for server requested SDK log gathering. When the server asks for it, the SDK uploads its own internal log lines, even if logging is disabled. While consent is required, a line is uploaded only while the consent of the feature it belongs to is given.
* Added support for the SDK connection test. When the server asks for it, the SDK checks that it can reach every endpoint it depends on and reports the results.

* Mitigated an issue where saving the SDK storage while remote config values were changing could empty the storage file.
* Mitigated an issue where a storage file cut short by a stopped process made initialization fail.
* Mitigated an issue where a request recorded while the SDK was finishing a send could wait until the next request or session update before it was sent.

* Updated JSON library version from "20250517" to "20260814".

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public class DefaultNetworking implements Networking {
private Tasks tasks;
private volatile boolean shutdown;
private volatile boolean backedOff;

private final Object recheckLock = new Object();
//a check asked for while a task ran, which a drain that found the queue empty would otherwise lose:
//null for none, else whether every check asked for came from the log gathering's own transport work
private Boolean pendingRecheck = null;
private boolean backendMode;
private ScheduledExecutorService backoffScheduler;
IStorageForRequestQueue storageForRequestQueue;
Expand Down Expand Up @@ -56,23 +61,93 @@ public boolean isSending() {
}

/**
* Starts draining the request queue unless the SDK stopped, a request is being handled, there is
* no device ID yet or the backoff mechanism holds the queue back. A drain started from a thread
* marked as doing the log gathering's own transport work, which queued or sent a batch of
* gathered log lines, keeps that mark until it read the request to send.
* Starts draining the request queue unless the SDK stopped, there is no device ID yet or the
* backoff mechanism holds the queue back. While a request is being handled the check is kept
* for when the drain ends without handing a request over. A drain started from a thread marked
* as doing the log gathering's own transport work, which queued or sent a batch of gathered log
* lines, keeps that mark until it read the request to send.
*
* @param config configuration of the running SDK
* @return whether a request is being handled
*/
@Override
public boolean check(InternalConfig config) {
L.d("[Networking] [check] state: shutdown [" + shutdown + "], backed off [" + backedOff + "], tasks running [" + tasks.isRunning() + "], net running [" + tasks.isRunning() + "], device id [" + config.getDeviceId() + "]");
if (!shutdown && !backedOff && !tasks.isRunning() && config.getDeviceId() != null) {
tasks.run(submit(config, L.isOwnTransportWork()));
if (!shutdown && !backedOff && config.getDeviceId() != null) {
boolean ownTransportWork = L.isOwnTransportWork();
boolean drainNow;
//decided under the lock the end of a drain takes, so a check kept for that end is never kept too late
synchronized (recheckLock) {
drainNow = !tasks.isRunning();
if (!drainNow) {
pendingRecheck = pendingRecheck == null ? ownTransportWork : pendingRecheck && ownTransportWork;
}
}
if (drainNow) {
drain(config, ownTransportWork);
}
}
return tasks.isRunning();
}

/**
* Starts a queue drain. When it ends without handing a request over for sending, failing
* included, it checks again if a check was asked for meanwhile, as a request stored just after the drain read the
* queue would otherwise wait for the next check. A drain that hands a request over is followed
* by the check of that send, so one request is handled at a time.
*
* @param config configuration of the running SDK
* @param followsOwnTransportWork whether the log gathering's own transport work started the drain
*/
private void drain(@Nonnull InternalConfig config, boolean followsOwnTransportWork) {
synchronized (recheckLock) {
pendingRecheck = null;
}
final Tasks.Task<Boolean> drain = submit(config, followsOwnTransportWork);
tasks.run(new Tasks.Task<Boolean>(drain.id) {
/**
* Runs the drain, a failure counting as a drain that handed nothing over.
*/
@Override
public Boolean call() {
try {
return drain.call();
} catch (Exception e) {
L.e("[Networking] drain, failed to handle the next request, [" + e + "]");
return false;
}
}
}, handedOver -> {
if (!Boolean.TRUE.equals(handedOver)) {
recheckIfRequested(config);
}
});
}

/**
* Runs a check kept while a task ran, marked as the log gathering's own transport work when
* every thread that asked for it was.
*
* @param config configuration of the running SDK
*/
private void recheckIfRequested(@Nonnull InternalConfig config) {
Boolean followsOwnTransportWork;
synchronized (recheckLock) {
followsOwnTransportWork = pendingRecheck;
pendingRecheck = null;
}
if (followsOwnTransportWork == null) {
return;
}

L.setOwnTransportWork(followsOwnTransportWork);
try {
check(config);
} finally {
L.setOwnTransportWork(false);
}
}

/**
* Sends the oldest queued request, unless networking is off or the backoff mechanism holds the
* queue back. Queued requests older than the request drop age are dropped first, unsent. Once
Expand Down Expand Up @@ -122,7 +197,7 @@ public Boolean call() throws Exception {
* Hands a queued request to the transport once its owner says it is ready, or removes it when
* its owner says it never will be. While the request carries a batch of gathered log lines, the
* thread is marked as doing their transport work, also while the send completes, so nothing
* logged about the upload is gathered.
* logged about the upload is gathered, and once it is sent the logger may queue its next batch.
*
* @param config configuration of the running SDK
* @param request the oldest queued request
Expand Down Expand Up @@ -152,6 +227,9 @@ private boolean prepareAndSend(final InternalConfig config, final Request reques
L.d("[Networking] Request " + request.storageId() + " sent?: " + result + ", response time [" + responseTimeMs.get() + "] ms");
if (result) {
storageForRequestQueue.removeRequest(request);
if (uploadsGatheredLogs) {
L.onLogBatchSent();
}
if (!backOffIfTheServerIsSlow(config, request, responseTimeMs.get())) {
check(config);
}
Expand Down Expand Up @@ -234,6 +312,9 @@ private boolean dropWithoutSending(@Nonnull InternalConfig config, @Nonnull Requ
L.e("[Networking] dropWithoutSending, failed to remove request [" + request.storageId() + "], the queue drain stops here");
return false;
}
if (request.params.has(Log.keySdkLogs)) {
L.onLogBatchSent();
}

Class<? extends ModuleBase> requestOwner = request.owner();
SDKCore core = SDKCore.instance;
Expand Down
Loading