microg/GmsCore · error · IllegalArgumentException

Timeout must be positive

Error message

Timeout must be positive

What it means

Tasks.await(task, timeout, unit) requires a strictly positive timeout and throws IllegalArgumentException for timeout <= 0. A non-positive timeout cannot ever elapse usefully, so the call is rejected before waiting.

Source

Thrown at play-services-tasks/src/main/java/com/google/android/gms/tasks/Tasks.java:50

import java.util.concurrent.atomic.AtomicReference;

/**
 * {@link Task} utility methods.
 */
@PublicApi
public final class Tasks {

    /**
     * Blocks until the specified Task is complete.
     *
     * @return the Task's result
     * @throws ExecutionException   if the Task fails
     * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete
     * @throws TimeoutException     if the specified timeout is reached before the Task completes
     */
    public static <TResult> TResult await(Task<TResult> task, long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException {
        if (task == null) throw new IllegalArgumentException("Task must not be null");
        if (timeout <= 0) throw new IllegalArgumentException("Timeout must be positive");
        if (unit == null) throw new IllegalArgumentException("TimeUnit must not be null");
        if (task.isComplete()) return handleCompletedTask(task);
        CountDownLatch latch = new CountDownLatch(1);
        task.addOnCompleteListener(Runnable::run, completedTask -> latch.countDown());
        if (latch.await(timeout, unit)) {
            return handleCompletedTask(task);
        }
        throw new TimeoutException("Timed out waiting for Task");
    }

    /**
     * Blocks until the specified Task is complete.
     *
     * @return the Task's result
     * @throws ExecutionException   if the Task fails
     * @throws InterruptedException if an interrupt occurs while waiting for the Task to complete
     */
    public static <TResult> TResult await(Task<TResult> task) throws ExecutionException, InterruptedException {

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Pass a positive timeout value, e.g. Tasks.await(task, 5, TimeUnit.SECONDS)
  2. If waiting indefinitely, use the no-timeout Tasks.await(task) overload (off the main thread)
  3. Clamp computed remaining time: Math.max(1, deadline - now) and skip await when already expired
  4. Audit unit conversions so the numeric value matches the TimeUnit

Example fix

// before
long remaining = deadline - System.currentTimeMillis(); // can be <= 0
T r = Tasks.await(task, remaining, TimeUnit.MILLISECONDS);
// after
long remaining = deadline - System.currentTimeMillis();
if (remaining > 0) {
    T r = Tasks.await(task, remaining, TimeUnit.MILLISECONDS);
} else {
    throw new TimeoutException();
}
Defensive patterns

Strategy: validation

Validate before calling

if (timeout <= 0) throw new IllegalArgumentException("timeout must be > 0, got " + timeout);

Try / catch

try {
    T r = Tasks.await(task, timeout, TimeUnit.SECONDS);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("Timeout must be positive")) { /* fix timeout computation */ }
}

Prevention

When it happens

Trigger: Calling Tasks.await(task, 0, unit) or Tasks.await(task, -1, unit) — hardcoding 0, computing a deadline that already passed (now - deadline), or misunitizing (thinking the value is milliseconds when it is seconds).

Common situations: Computed remaining-time expressions that went negative; refactors that changed timeout units; tests passing 0 to expect immediate completion.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/d15c83ae7e86843d. Report an issue: GitHub.