Redesign error model: typed transport exceptions + operation result types (breaking)#101
Open
steviec wants to merge 23 commits into
Open
Redesign error model: typed transport exceptions + operation result types (breaking)#101steviec wants to merge 23 commits into
steviec wants to merge 23 commits into
Conversation
Reserve exceptions for transport/connection/auth failures (typed, rooted at IOException) and model expected operation outcomes (install/uninstall/push/pull/root) as result types. Direct-to-adbd only; host-server smart-socket errors are out of scope. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Major version bump — backward compatibility is not a design constraint. Reframe the IOException base as a semantic choice (recoverable socket faults, consistent with okio surface) rather than a compat hack; tighten to @throws(AdbException::class) with a no-bare-IOException-leaks guarantee; carry exitCode in CommandResult.Failure instead of discarding it; add Open Questions for result granularity / uninstall shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per the canonical adb client (client/adb_install.cpp does strncmp("Success")
and treats failures as opaque text; INSTALL_FAILED_* live in frameworks/base,
not adbd):
- install reasons stay a raw string (mirror adb, don't import a framework enum)
- uninstall returns AdbShellResponse directly (it is a shell command; don't
obfuscate adbd's stdout/stderr/exitCode)
- root/unroot get a RootResult (adbd service text line, no exit code)
- three per-shape result types, not a generic Result<T>
Removes the Open Questions section; decisions now folded into the spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
install/uninstall (and all named high-level ops) are API peers: each returns its own domain ...Result. uninstall gets UninstallResult, whose Failure carries adbd's real exitCode + output so the wrapper hides nothing. Adds the two-axis consistency rule (transport exceptions universal; outcome results operation-specific, peers sharing a type). shell/openShell stay the generic primitive (AdbShellResponse). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
13 TDD tasks: AdbException hierarchy, result types, nextMessage socket-fault fix, handshake/open exception mapping, AdbSyncFailException, and Dadb push/pull/install/uninstall/root result migration. Unit-tested via in-memory okio buffers and a FakeDadb fixture; emulator suite covers A_OPEN refusal and the install paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the single IOException catch in AdbStreamImpl.nextMessage into two handlers: AdbStreamClosed (peer A_CLSE) remains a clean EOF returning null, while any other IOException (socket EOF/RST) now throws AdbConnectionClosedException so callers can distinguish a dropped transport from a normal end-of-stream. Also updated testLargeRemoteWrite to append a proper A_CLSE packet after the WRTE, matching real device behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace bare IOExceptions in AdbSyncStream.send/recv with a typed AdbSyncFailException so callers can distinguish FAIL (operation failure, transport healthy) from protocol/connection errors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
open() only translated AdbStreamClosed (A_OPEN→A_CLSE). A raw socket fault (EOF/RST) during the OPEN read fell through to the Throwable catch and leaked a bare IOException, violating the "no bare IOException leaks" contract and the @throws(AdbException::class) signal on the public open(). Map a raw IOException here to AdbConnectionClosedException (per design spec §4), pass already-typed AdbExceptions through without re-wrapping, and tighten the internal annotation to @throws(AdbException::class). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
open_invalidService_throwsStreamOpenException was in the abstract DadbTest, so it also ran under AdbServerTest (the adb-server-backed impl), where a refused service still surfaces a generic IOException — that path is concern #2, out of scope. Moved the test to DadbTestImpl (direct connection), where the AdbStreamOpenException mapping applies. Full suite green on an emulator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reduce call-site verbosity for callers who don't want an exhaustive `when`. Mirrors Kotlin stdlib Result's onSuccess/onFailure/getOrThrow. orThrow throws AdbOperationFailedException, deliberately NOT an AdbException: surfacing an operation outcome as an exception is a caller choice, and a transport-level `catch (AdbException)` (reconnect-on-death) must not swallow it — the two axes stay distinct even when opting in. Helpers are per-type because the Failure payloads differ (uninstall carries an exitCode; orThrow propagates it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
dadbfunnels nearly every failure through barejava.io.IOException, so a caller cannot tell apart "the connection is dead — reconnect" (socket reset, handshake/auth failed) from "the connection is fine, but the operation failed" (apk rejected, remote file missing, non-zero exit). Today the only way to distinguish them is string-matching exception messages.The approach
Make the error model adbd-aware, grounded in the ADB wire protocol (
packages/modules/adb/protocol.txt). One principle:Two orthogonal axes:
AdbException : IOExceptionhierarchy (one type per documented adbd failure point). No bareIOExceptionleaks.sync:FAIL,shell,v2exit code) become values.Breaking change → 2.0.0.
Types referenced below
AdbException : IOException:AdbConnectException,AdbAuthException,AdbStreamOpenException,AdbConnectionClosedException,AdbProtocolExceptionsealed { Success | Failure(…) }:InstallResult,UninstallResult(Failure(reason, exitCode)),SyncResult,RootResultAdbStream,AdbShellResponse(holdsexitCode),AdbShellStream,AdbSyncStream,AutoCloseableHow error handling changes
Before / after — every public method
open(dest)AdbStreamThrows:
IOExceptionAdbStreamThrows:
AdbExceptionshell(cmd)AdbShellResponseThrows:
IOExceptionAdbShellResponseThrows:
AdbExceptionopenShell(cmd)AdbShellStreamThrows:
IOExceptionAdbShellStreamThrows:
AdbExceptionpush(...)UnitThrows:
IOException(any error)SyncResultThrows:
AdbException(transport error)pull(...)UnitThrows:
IOException(any error)SyncResultThrows:
AdbException(transport error)openSync()AdbSyncStreamThrows:
IOExceptionAdbSyncStreamThrows:
AdbExceptioninstall(...)UnitThrows:
IOException(any error)InstallResultThrows:
AdbException(transport error)installMultiple(...)UnitThrows:
IOException(any error)InstallResultThrows:
AdbException(transport error)uninstall(pkg)UnitThrows:
IOException(any error)UninstallResultThrows:
AdbException(transport error)execCmd(...)AdbStreamThrows:
IOExceptionThrows:
UnsupportedOperationExceptionAdbStreamThrows:
AdbExceptionThrows:
UnsupportedOperationExceptionabbExec(...)AdbStreamThrows:
IOExceptionThrows:
UnsupportedOperationExceptionAdbStreamThrows:
AdbExceptionThrows:
UnsupportedOperationExceptionroot()UnitThrows:
IOException(any error)RootResultThrows:
AdbException(transport error)unroot()UnitThrows:
IOException(any error)RootResultThrows:
AdbException(transport error)tcpForward(...)AutoCloseableThrows:
InterruptedExceptionAutoCloseableThrows:
InterruptedExceptionBehavioral change
A connection dropped mid-stream now throws
AdbConnectionClosedExceptioninstead of presenting as a clean end-of-stream. (A peerA_CLSE— normal stream termination — still reads as clean EOF.)Also fixed in passing:
pmInstallpreviously ignored thepm installresult; aninstallMultiplefinalize-error message interpolated the stream object instead of the response.Test plan
PIXEL_6_API_31): 77 tests, 0 failed, 5 skipped — incl.open()→AdbStreamOpenExceptionand the pm-install paths.nextMessageclean-close vs socket-fault, sync FAIL/desync, and the result paths via aFakeDadbfixture).Known follow-ups (non-blocking)
installMultiplefailure branches and the auth-key-rejection branch (open()→AdbStreamOpenExceptionis emulator-only — not deterministically unit-testable becauseopen()uses a random localId).🤖 Generated with Claude Code