aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/invalid/lena/scrcpy/Server.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/Server.java')
-rw-r--r--app/src/main/java/invalid/lena/scrcpy/Server.java142
1 files changed, 54 insertions, 88 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/Server.java b/app/src/main/java/invalid/lena/scrcpy/Server.java
index 6ae752f..284dd6c 100644
--- a/app/src/main/java/invalid/lena/scrcpy/Server.java
+++ b/app/src/main/java/invalid/lena/scrcpy/Server.java
@@ -7,12 +7,11 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.net.ConnectException;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
import io.github.muntashirakon.adb.AdbStream;
@@ -38,21 +37,8 @@ public final class Server {
private static final String ASSET_JAR = "scrcpy-server.jar";
private static final String ASSET_VERSION = "scrcpy-server.version";
private static final int FILE_MODE = 0100644; // regular file, 0644
- // Server forks a CleanUp helper before opening its abstract sockets;
- // on slow emulators that takes several seconds, so the budget needs
- // to be generous. The deadline is per stream: in practice only the
- // first open waits (the server is still starting) and the other two
- // dial instantly, so a healthy bring-up stays well below the e2e
- // test deadline (test-rig/e2e.sh's E2E_DEADLINE, default 60 s).
- private static final long OPEN_DEADLINE_MS = 20_000;
- private static final int OPEN_BACKOFF_MS = 100;
- // Per-attempt timeout for adb.openAbstract. libadb-android's
- // AdbConnection.open() blocks on a naked stream.wait() with no
- // loop or timeout - a missed notification (response arrives before
- // we park) wedges the call forever. Wrap each attempt with a
- // timeout + interrupt so the loop can move on.
- private static final long OPEN_ATTEMPT_TIMEOUT_MS = 800;
-
+ private static final long LISTENER_DEADLINE_MS = 20_000;
+ private static final long LISTENER_RETRY_MS = 100;
public static final class Streams {
public final AdbStream videoAds, audioAds, controlAds;
public final InputStream videoIn, audioIn, controlIn;
@@ -84,6 +70,7 @@ public final class Server {
}
public Streams bringUp() throws Exception {
+ serverEof = false;
String version = readVersion();
long pushed = push();
Log.i("push %s bytes=%d", REMOTE_PATH, pushed);
@@ -93,24 +80,40 @@ public final class Server {
Log.i("spawn server ver=%s scid=%s", version, scid);
Log.i("cmdline: %s", cmd);
- shell = adb.openShell(cmd);
- shellPump = new Thread(() -> pump(shell.openInputStream()), "server-stdout");
- shellPump.setDaemon(true);
- shellPump.start();
+ AdbStream va = null, aa = null, ca = null;
+ boolean committed = false;
+ try {
+ shell = adb.openShell(cmd);
+ AdbStream shellRef = shell;
+ shellPump = new Thread(() -> pump(shellRef.openInputStream()), "server-stdout");
+ shellPump.setDaemon(true);
+ shellPump.start();
- AdbStream va = openAbstract(scid);
- AdbStream aa = openAbstract(scid);
- AdbStream ca = openAbstract(scid);
+ // These accepts are ordered. If one times out, the whole ADB
+ // connection is discarded by Session; retrying an individual
+ // open could shift video/audio/control onto the wrong sockets.
+ va = openAbstract(scid);
+ aa = openAbstract(scid);
+ ca = openAbstract(scid);
- InputStream vi = va.openInputStream();
- InputStream ai = aa.openInputStream();
- InputStream ci = ca.openInputStream();
- OutputStream co = ca.openOutputStream();
+ InputStream vi = va.openInputStream();
+ InputStream ai = aa.openInputStream();
+ InputStream ci = ca.openInputStream();
+ OutputStream co = ca.openOutputStream();
- String name = readDeviceMeta(vi);
- Log.i("device name=%s", name);
- streams = new Streams(va, aa, ca, vi, ai, ci, co, name, scid, version);
- return streams;
+ String name = readDeviceMeta(vi);
+ Log.i("device name=%s", name);
+ streams = new Streams(va, aa, ca, vi, ai, ci, co, name, scid, version);
+ committed = true;
+ return streams;
+ } finally {
+ if (!committed) {
+ closeQuietly(va);
+ closeQuietly(aa);
+ closeQuietly(ca);
+ closeShell();
+ }
+ }
}
// Idempotent. Closes the three media/control streams first (lets
@@ -127,6 +130,10 @@ public final class Server {
closeQuietly(streams.controlAds);
streams = null;
}
+ closeShell();
+ }
+
+ private void closeShell() {
Thread t = shellPump;
AdbStream s = shell;
shell = null;
@@ -198,67 +205,26 @@ public final class Server {
private AdbStream openAbstract(String scid) throws Exception {
String name = "scrcpy_" + scid;
- long deadline = System.currentTimeMillis() + OPEN_DEADLINE_MS;
- Throwable last = null;
- for (int attempt = 1; System.currentTimeMillis() < deadline; attempt++) {
- // If the shell stream closed (server died early - usually with
- // an error printed to stderr that our pump captured), bail
- // immediately. Otherwise we'd spin out the deadline on a
- // dead target.
+ long deadline = System.currentTimeMillis() + LISTENER_DEADLINE_MS;
+ ConnectException last = null;
+ while (System.currentTimeMillis() < deadline) {
if (serverEof) {
- throw new IOException("server exited before opening "
- + name + " (see 'server:' logs)",
- last instanceof Exception ? (Exception) last : null);
+ throw new IOException("server exited before opening " + name, last);
}
try {
- AdbStream s = openAbstractOnce(name, OPEN_ATTEMPT_TIMEOUT_MS);
- Log.i("openAbstract %s ok (attempt %d)", name, attempt);
- return s;
- } catch (Throwable t) {
- last = t;
- Thread.sleep(OPEN_BACKOFF_MS);
- }
- }
- throw new IOException("openAbstract " + name + " failed after "
- + (OPEN_DEADLINE_MS / 1000) + " s",
- last instanceof Exception ? (Exception) last : null);
- }
-
- // Wraps a single adb.openAbstract call with a hard timeout. The
- // upstream call can wedge forever on a missed notify in its naked
- // stream.wait(); we run it on a daemon thread and join with timeout,
- // interrupt on overrun, and let the caller retry. If the orphaned
- // call succeeds after we gave up, the stream must be closed:
- // keeping it would silently consume one of the scrcpy server's
- // three accepts and shift every later dial off by one.
- private AdbStream openAbstractOnce(String name, long timeoutMs) throws Exception {
- AtomicReference<AdbStream> result = new AtomicReference<>();
- AtomicReference<Throwable> err = new AtomicReference<>();
- AtomicBoolean abandoned = new AtomicBoolean();
- Thread t = new Thread(() -> {
- try {
- AdbStream s = adb.openAbstract(name);
- result.set(s);
- if (abandoned.get()) closeQuietly(s);
- } catch (Throwable ex) {
- err.set(ex);
+ AdbStream stream = adb.openAbstract(name);
+ Log.i("openAbstract %s ok", name);
+ return stream;
+ } catch (ConnectException e) {
+ // A rejected OPEN consumed no server accept. Retry while the
+ // server creates its listener; timeout failures remain fatal
+ // because their acceptance state is ambiguous.
+ last = e;
+ Thread.sleep(LISTENER_RETRY_MS);
}
- }, "openAbstract-" + name);
- t.setDaemon(true);
- t.start();
- t.join(timeoutMs);
- if (t.isAlive()) {
- abandoned.set(true);
- t.interrupt();
- // One side of the publish/abandon race closes the stream;
- // closeQuietly tolerates both doing it.
- closeQuietly(result.get());
- throw new IOException("openAbstract " + name + " timed out");
}
- Throwable ex = err.get();
- if (ex instanceof Exception) throw (Exception) ex;
- if (ex != null) throw new RuntimeException(ex);
- return result.get();
+ throw new IOException("server did not open " + name + " within "
+ + LISTENER_DEADLINE_MS + " ms", last);
}
private static String readDeviceMeta(InputStream in) throws IOException {