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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Improvements

- Handle `RejectedExecutionException` everywhere ([#4747](https://github.com/getsentry/sentry-java/pull/4747))
- Mark `SentryEnvelope` as not internal ([#4748](https://github.com/getsentry/sentry-java/pull/4748))

## 8.22.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import io.sentry.DateUtils;
import io.sentry.EventProcessor;
import io.sentry.Hint;
import io.sentry.IpAddressUtils;
import io.sentry.NoOpLogger;
import io.sentry.SentryAttributeType;
import io.sentry.SentryBaseEvent;
import io.sentry.SentryEvent;
import io.sentry.SentryLevel;
import io.sentry.SentryLogEvent;
import io.sentry.SentryLogEventAttributeValue;
import io.sentry.SentryReplayEvent;
import io.sentry.*;
import io.sentry.android.core.internal.util.AndroidThreadChecker;
import io.sentry.android.core.performance.AppStartMetrics;
import io.sentry.android.core.performance.TimeSpan;
Expand All @@ -37,6 +26,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
Expand All @@ -47,7 +37,7 @@ final class DefaultAndroidEventProcessor implements EventProcessor {

private final @NotNull BuildInfoProvider buildInfoProvider;
private final @NotNull SentryAndroidOptions options;
private final @NotNull Future<DeviceInfoUtil> deviceInfoUtil;
private final @Nullable Future<DeviceInfoUtil> deviceInfoUtil;
private final @NotNull LazyEvaluator<String> deviceFamily =
new LazyEvaluator<>(() -> ContextUtils.getFamily(NoOpLogger.getInstance()));

Expand All @@ -65,9 +55,16 @@ public DefaultAndroidEventProcessor(
// don't ref. to method reference, theres a bug on it
// noinspection Convert2MethodRef
// some device info performs disk I/O, but it's result is cached, let's pre-cache it
@Nullable Future<DeviceInfoUtil> deviceInfoUtil;
final @NotNull ExecutorService executorService = Executors.newSingleThreadExecutor();
this.deviceInfoUtil =
executorService.submit(() -> DeviceInfoUtil.getInstance(this.context, options));
try {
deviceInfoUtil =
executorService.submit(() -> DeviceInfoUtil.getInstance(this.context, options));
} catch (RejectedExecutionException e) {
deviceInfoUtil = null;
options.getLogger().log(SentryLevel.WARNING, "Device info caching task rejected.", e);
}
this.deviceInfoUtil = deviceInfoUtil;
executorService.shutdown();
}

Expand Down Expand Up @@ -181,25 +178,34 @@ private void setDevice(
final boolean errorEvent,
final boolean applyScopeData) {
if (event.getContexts().getDevice() == null) {
try {
event
.getContexts()
.setDevice(deviceInfoUtil.get().collectDeviceInformation(errorEvent, applyScopeData));
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info", e);
if (deviceInfoUtil != null) {
try {
event
.getContexts()
.setDevice(deviceInfoUtil.get().collectDeviceInformation(errorEvent, applyScopeData));
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info", e);
}
} else {
options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info");
}
mergeOS(event);
}
}

private void mergeOS(final @NotNull SentryBaseEvent event) {
final OperatingSystem currentOS = event.getContexts().getOperatingSystem();
try {
final OperatingSystem androidOS = deviceInfoUtil.get().getOperatingSystem();
// make Android OS the main OS using the 'os' key
event.getContexts().setOperatingSystem(androidOS);
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve os system", e);

if (deviceInfoUtil != null) {
try {
final OperatingSystem androidOS = deviceInfoUtil.get().getOperatingSystem();
// make Android OS the main OS using the 'os' key
event.getContexts().setOperatingSystem(androidOS);
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve os system", e);
}
} else {
options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info");
}

if (currentOS != null) {
Expand Down Expand Up @@ -284,10 +290,14 @@ private void setPackageInfo(final @NotNull SentryBaseEvent event, final @NotNull
setDist(event, versionCode);

@Nullable DeviceInfoUtil deviceInfoUtil = null;
try {
deviceInfoUtil = this.deviceInfoUtil.get();
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info", e);
if (this.deviceInfoUtil != null) {
try {
deviceInfoUtil = this.deviceInfoUtil.get();
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info", e);
}
} else {
options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info");
}

ContextUtils.setAppPackageInfo(packageInfo, buildInfoProvider, deviceInfoUtil, app);
Expand Down Expand Up @@ -331,16 +341,20 @@ private void setAppExtras(final @NotNull App app, final @NotNull Hint hint) {
}

private void setSideLoadedInfo(final @NotNull SentryBaseEvent event) {
try {
final ContextUtils.SideLoadedInfo sideLoadedInfo = deviceInfoUtil.get().getSideLoadedInfo();
if (sideLoadedInfo != null) {
final @NotNull Map<String, String> tags = sideLoadedInfo.asTags();
for (Map.Entry<String, String> entry : tags.entrySet()) {
event.setTag(entry.getKey(), entry.getValue());
if (deviceInfoUtil != null) {
try {
final ContextUtils.SideLoadedInfo sideLoadedInfo = deviceInfoUtil.get().getSideLoadedInfo();
if (sideLoadedInfo != null) {
final @NotNull Map<String, String> tags = sideLoadedInfo.asTags();
for (Map.Entry<String, String> entry : tags.entrySet()) {
event.setTag(entry.getKey(), entry.getValue());
}
}
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Error getting side loaded info.", e);
}
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Error getting side loaded info.", e);
} else {
options.getLogger().log(SentryLevel.ERROR, "Failed to retrieve device info");
}
}

Expand Down
20 changes: 14 additions & 6 deletions sentry/src/main/java/io/sentry/Scopes.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@
import io.sentry.hints.SessionStartHint;
import io.sentry.logger.ILoggerApi;
import io.sentry.logger.LoggerApi;
import io.sentry.protocol.Feedback;
import io.sentry.protocol.SentryId;
import io.sentry.protocol.SentryTransaction;
import io.sentry.protocol.User;
import io.sentry.protocol.*;
import io.sentry.transport.RateLimiter;
import io.sentry.util.HintUtils;
import io.sentry.util.Objects;
import io.sentry.util.SpanUtils;
import io.sentry.util.TracingUtils;
import java.io.Closeable;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -449,8 +447,18 @@ public void close(final boolean isRestarting) {
getOptions().getConnectionStatusProvider().close();
final @NotNull ISentryExecutorService executorService = getOptions().getExecutorService();
if (isRestarting) {
executorService.submit(
() -> executorService.close(getOptions().getShutdownTimeoutMillis()));
try {
executorService.submit(
() -> executorService.close(getOptions().getShutdownTimeoutMillis()));
} catch (RejectedExecutionException e) {
getOptions()
.getLogger()
.log(
SentryLevel.WARNING,
"Failed to submit executor service shutdown task during restart. Shutting down synchronously.",
e);
executorService.close(getOptions().getShutdownTimeoutMillis());
}
} else {
executorService.close(getOptions().getShutdownTimeoutMillis());
}
Expand Down
48 changes: 29 additions & 19 deletions sentry/src/main/java/io/sentry/SentryExecutorService.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ public SentryExecutorService() {
}

@Override
public @NotNull Future<?> submit(final @NotNull Runnable runnable) {
public @NotNull Future<?> submit(final @NotNull Runnable runnable)
throws RejectedExecutionException {
if (executorService.getQueue().size() < MAX_QUEUE_SIZE) {
return executorService.submit(runnable);
}
// TODO: maybe RejectedExecutionException?
if (options != null) {
options
.getLogger()
Expand All @@ -68,11 +68,11 @@ public SentryExecutorService() {
}

@Override
public @NotNull <T> Future<T> submit(final @NotNull Callable<T> callable) {
public @NotNull <T> Future<T> submit(final @NotNull Callable<T> callable)
throws RejectedExecutionException {
if (executorService.getQueue().size() < MAX_QUEUE_SIZE) {
return executorService.submit(callable);
}
// TODO: maybe RejectedExecutionException?
if (options != null) {
options
.getLogger()
Expand All @@ -82,11 +82,11 @@ public SentryExecutorService() {
}

@Override
public @NotNull Future<?> schedule(final @NotNull Runnable runnable, final long delayMillis) {
public @NotNull Future<?> schedule(final @NotNull Runnable runnable, final long delayMillis)
throws RejectedExecutionException {
if (executorService.getQueue().size() < MAX_QUEUE_SIZE) {
return executorService.schedule(runnable, delayMillis, TimeUnit.MILLISECONDS);
}
// TODO: maybe RejectedExecutionException?
if (options != null) {
options
.getLogger()
Expand Down Expand Up @@ -122,20 +122,30 @@ public boolean isClosed() {
@SuppressWarnings({"FutureReturnValueIgnored"})
@Override
public void prewarm() {
executorService.submit(
() -> {
try {
// schedule a bunch of dummy runnables in the future that will never execute to trigger
// queue growth and then purge the queue
for (int i = 0; i < INITIAL_QUEUE_SIZE; i++) {
final Future<?> future = executorService.schedule(dummyRunnable, 365L, TimeUnit.DAYS);
future.cancel(true);
try {
executorService.submit(
() -> {
try {
// schedule a bunch of dummy runnables in the future that will never execute to
// trigger
// queue growth and then purge the queue
for (int i = 0; i < INITIAL_QUEUE_SIZE; i++) {
final Future<?> future =
executorService.schedule(dummyRunnable, 365L, TimeUnit.DAYS);
future.cancel(true);
}
executorService.purge();
} catch (RejectedExecutionException ignored) {
// ignore
}
executorService.purge();
} catch (RejectedExecutionException ignored) {
// ignore
}
});
});
} catch (RejectedExecutionException e) {
if (options != null) {
options
.getLogger()
.log(SentryLevel.WARNING, "Prewarm task rejected from " + executorService, e);
}
}
}

private static final class SentryExecutorServiceThreadFactory implements ThreadFactory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.sentry.SentryOptions;
import io.sentry.util.AutoClosableReentrantLock;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -79,7 +80,13 @@ private void reschedule(final int delay) {
final @NotNull ISentryExecutorService executorService = sentryOptions.getExecutorService();
if (!executorService.isClosed()) {
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
latestScheduledRun = executorService.schedule(this, delay);
try {
latestScheduledRun = executorService.schedule(this, delay);
} catch (RejectedExecutionException e) {
sentryOptions
.getLogger()
.log(SentryLevel.WARNING, "Backpressure monitor reschedule task rejected", e);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -76,7 +77,14 @@ private void maybeSchedule(boolean forceSchedule, boolean immediately) {
|| latestScheduledFlush.isCancelled()) {
hasScheduled = true;
final int flushAfterMs = immediately ? 0 : FLUSH_AFTER_MS;
scheduledFlush = executorService.schedule(new BatchRunnable(), flushAfterMs);
try {
scheduledFlush = executorService.schedule(new BatchRunnable(), flushAfterMs);
} catch (RejectedExecutionException e) {
hasScheduled = false;
options
.getLogger()
.log(SentryLevel.WARNING, "Logs batch processor flush task rejected", e);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.concurrent.CancellationException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
Expand Down Expand Up @@ -65,7 +66,14 @@ public QueuedThreadPoolExecutor(
public Future<?> submit(final @NotNull Runnable task) {
if (isSchedulingAllowed()) {
unfinishedTasksCount.increment();
return super.submit(task);
try {
return super.submit(task);
} catch (RejectedExecutionException e) {
unfinishedTasksCount.decrement();
lastRejectTimestamp = dateProvider.now();
logger.log(SentryLevel.WARNING, "Submit rejected by thread pool executor", e);
return new CancelledFuture<>();
}
} else {
lastRejectTimestamp = dateProvider.now();
// if the thread pool is full, we don't cache it
Expand Down
Loading