diff options
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/Server.java')
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/Server.java | 142 |
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 { |