feat(adb): support delayed ack

This commit is contained in:
Simon Chan 2024-02-01 23:04:21 +08:00
parent 59d78dae20
commit ac6dc1e57c
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
9 changed files with 267 additions and 61 deletions

View file

@ -49,7 +49,6 @@ export class AdbDaemonSocketController
return this.#readable;
}
#writePromise: PromiseResolver<void> | undefined;
#writableController!: WritableStreamDefaultController;
readonly writable: WritableStream<Consumable<Uint8Array>>;
@ -65,6 +64,23 @@ export class AdbDaemonSocketController
return this.#socket;
}
#availableWriteBytesChanged: PromiseResolver<void> | undefined;
/**
* When delayed ack is disabled, can be `Infinity` if the socket is ready to write.
* Exactly one packet can be written no matter how large it is. Or `-1` if the socket
* is waiting for ack.
*
* When delayed ack is enabled, a non-negative finite number indicates the number of
* bytes that can be written to the socket before receiving an ack.
*/
#availableWriteBytes = 0;
/**
* Gets the number of bytes that can be written to the socket without blocking.
*/
public get availableWriteBytes() {
return this.#availableWriteBytes;
}
constructor(options: AdbDaemonSocketConstructionOptions) {
this.#dispatcher = options.dispatcher;
this.localId = options.localId;
@ -88,17 +104,30 @@ export class AdbDaemonSocketController
start < size;
start = end, end += chunkSize
) {
this.#writePromise = new PromiseResolver();
const chunk = data.subarray(start, end);
const length = chunk.byteLength;
while (this.#availableWriteBytes < length) {
// Only one lock is required because Web Streams API guarantees
// that `write` is not reentrant.
this.#availableWriteBytesChanged =
new PromiseResolver();
await raceSignal(
() => this.#availableWriteBytesChanged!.promise,
controller.signal,
);
}
if (this.#availableWriteBytes === Infinity) {
this.#availableWriteBytes = -1;
} else {
this.#availableWriteBytes -= length;
}
await this.#dispatcher.sendPacket(
AdbCommand.Write,
this.localId,
this.remoteId,
data.subarray(start, end),
);
// Wait for ack packet
await raceSignal(
() => this.#writePromise!.promise,
controller.signal,
chunk,
);
}
},
@ -124,8 +153,9 @@ export class AdbDaemonSocketController
}
}
ack() {
this.#writePromise?.resolve();
public ack(bytes: number) {
this.#availableWriteBytes += bytes;
this.#availableWriteBytesChanged?.resolve();
}
async close(): Promise<void> {
@ -134,6 +164,8 @@ export class AdbDaemonSocketController
}
this.#closed = true;
this.#availableWriteBytesChanged?.reject(new Error("Socket closed"));
try {
this.#writableController.error(new Error("Socket closed"));
} catch {