Commit 5da632a9 authored by Reilly Grant's avatar Reilly Grant Committed by Commit Bot

[serial] Handle fatal read and write errors correctly

This change adds special handling for read and write errors for which
recovery is impossible (i.e. disconnection). In this case the readable
and writeable attribute are not initialized with new streams on demand.

Bug: 1013763
Change-Id: Ibd03cb684cb34cf9a8b22848f7b9ebc50f4d6621
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1884357Reviewed-by: default avatarMatt Reynolds <mattreynolds@chromium.org>
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#710088}
parent 02a2a337
...@@ -35,6 +35,18 @@ const char kDeviceLostError[] = "The device has been lost."; ...@@ -35,6 +35,18 @@ const char kDeviceLostError[] = "The device has been lost.";
const char kSystemError[] = "An unknown system error has occurred."; const char kSystemError[] = "An unknown system error has occurred.";
const int kMaxBufferSize = 16 * 1024 * 1024; /* 16 MiB */ const int kMaxBufferSize = 16 * 1024 * 1024; /* 16 MiB */
bool SendErrorIsFatal(SerialSendError error) {
switch (error) {
case SerialSendError::NONE:
NOTREACHED();
return false;
case SerialSendError::SYSTEM_ERROR:
return false;
case SerialSendError::DISCONNECTED:
return true;
}
}
DOMException* DOMExceptionFromSendError(SerialSendError error) { DOMException* DOMExceptionFromSendError(SerialSendError error) {
switch (error) { switch (error) {
case SerialSendError::NONE: case SerialSendError::NONE:
...@@ -49,6 +61,24 @@ DOMException* DOMExceptionFromSendError(SerialSendError error) { ...@@ -49,6 +61,24 @@ DOMException* DOMExceptionFromSendError(SerialSendError error) {
} }
} }
bool ReceiveErrorIsFatal(SerialReceiveError error) {
switch (error) {
case SerialReceiveError::NONE:
NOTREACHED();
return false;
case SerialReceiveError::BREAK:
case SerialReceiveError::FRAME_ERROR:
case SerialReceiveError::OVERRUN:
case SerialReceiveError::BUFFER_OVERFLOW:
case SerialReceiveError::PARITY_ERROR:
case SerialReceiveError::SYSTEM_ERROR:
return false;
case SerialReceiveError::DISCONNECTED:
case SerialReceiveError::DEVICE_LOST:
return true;
}
}
DOMException* DOMExceptionFromReceiveError(SerialReceiveError error) { DOMException* DOMExceptionFromReceiveError(SerialReceiveError error) {
switch (error) { switch (error) {
case SerialReceiveError::NONE: case SerialReceiveError::NONE:
...@@ -252,7 +282,7 @@ ReadableStream* SerialPort::readable(ScriptState* script_state, ...@@ -252,7 +282,7 @@ ReadableStream* SerialPort::readable(ScriptState* script_state,
if (readable_) if (readable_)
return readable_; return readable_;
if (!port_ || open_resolver_ || closing_) if (!port_ || open_resolver_ || closing_ || read_fatal_)
return nullptr; return nullptr;
mojo::ScopedDataPipeConsumerHandle readable_pipe; mojo::ScopedDataPipeConsumerHandle readable_pipe;
...@@ -273,7 +303,7 @@ WritableStream* SerialPort::writable(ScriptState* script_state, ...@@ -273,7 +303,7 @@ WritableStream* SerialPort::writable(ScriptState* script_state,
if (writable_) if (writable_)
return writable_; return writable_;
if (!port_ || open_resolver_ || closing_) if (!port_ || open_resolver_ || closing_ || write_fatal_)
return nullptr; return nullptr;
mojo::ScopedDataPipeProducerHandle writable_pipe; mojo::ScopedDataPipeProducerHandle writable_pipe;
...@@ -432,15 +462,17 @@ void SerialPort::Dispose() { ...@@ -432,15 +462,17 @@ void SerialPort::Dispose() {
} }
void SerialPort::OnReadError(device::mojom::blink::SerialReceiveError error) { void SerialPort::OnReadError(device::mojom::blink::SerialReceiveError error) {
if (underlying_source_) { if (ReceiveErrorIsFatal(error))
read_fatal_ = true;
if (underlying_source_)
underlying_source_->SignalErrorOnClose(DOMExceptionFromReceiveError(error)); underlying_source_->SignalErrorOnClose(DOMExceptionFromReceiveError(error));
}
} }
void SerialPort::OnSendError(device::mojom::blink::SerialSendError error) { void SerialPort::OnSendError(device::mojom::blink::SerialSendError error) {
if (underlying_sink_) { if (SendErrorIsFatal(error))
write_fatal_ = true;
if (underlying_sink_)
underlying_sink_->SignalErrorOnClose(DOMExceptionFromSendError(error)); underlying_sink_->SignalErrorOnClose(DOMExceptionFromSendError(error));
}
} }
bool SerialPort::CreateDataPipe(mojo::ScopedDataPipeProducerHandle* producer, bool SerialPort::CreateDataPipe(mojo::ScopedDataPipeProducerHandle* producer,
...@@ -461,6 +493,8 @@ bool SerialPort::CreateDataPipe(mojo::ScopedDataPipeProducerHandle* producer, ...@@ -461,6 +493,8 @@ bool SerialPort::CreateDataPipe(mojo::ScopedDataPipeProducerHandle* producer,
void SerialPort::OnConnectionError() { void SerialPort::OnConnectionError() {
closing_ = false; closing_ = false;
read_fatal_ = false;
write_fatal_ = false;
port_.reset(); port_.reset();
client_receiver_.reset(); client_receiver_.reset();
...@@ -589,6 +623,8 @@ void SerialPort::OnSetSignals(ScriptPromiseResolver* resolver, bool success) { ...@@ -589,6 +623,8 @@ void SerialPort::OnSetSignals(ScriptPromiseResolver* resolver, bool success) {
void SerialPort::OnClose() { void SerialPort::OnClose() {
DCHECK(close_resolver_); DCHECK(close_resolver_);
closing_ = false; closing_ = false;
read_fatal_ = false;
write_fatal_ = false;
port_.reset(); port_.reset();
client_receiver_.reset(); client_receiver_.reset();
......
...@@ -96,6 +96,11 @@ class SerialPort final : public ScriptWrappable, ...@@ -96,6 +96,11 @@ class SerialPort final : public ScriptWrappable,
Member<WritableStream> writable_; Member<WritableStream> writable_;
Member<SerialPortUnderlyingSink> underlying_sink_; Member<SerialPortUnderlyingSink> underlying_sink_;
// Indicates that the read or write streams have encountered a fatal error and
// should not be reopened.
bool read_fatal_ = false;
bool write_fatal_ = false;
// Indicates that the port is being closed and so the streams should not be // Indicates that the port is being closed and so the streams should not be
// reopened on demand. // reopened on demand.
bool closing_ = false; bool closing_ = false;
......
...@@ -102,6 +102,7 @@ void SerialPortUnderlyingSink::SignalErrorOnClose(DOMException* exception) { ...@@ -102,6 +102,7 @@ void SerialPortUnderlyingSink::SignalErrorOnClose(DOMException* exception) {
if (pending_write_) { if (pending_write_) {
pending_write_->Reject(exception); pending_write_->Reject(exception);
pending_write_ = nullptr; pending_write_ = nullptr;
serial_port_->UnderlyingSinkClosed();
} }
} }
......
...@@ -155,12 +155,34 @@ class FakeSerialPort { ...@@ -155,12 +155,34 @@ class FakeSerialPort {
return result; return result;
} }
simulateParityError() { simulateReadError(error) {
this.writer_.close(); this.writer_.close();
this.writer_.releaseLock(); this.writer_.releaseLock();
this.writer_ = undefined; this.writer_ = undefined;
this.writable_ = undefined; this.writable_ = undefined;
this.client_.onReadError(device.mojom.SerialReceiveError.PARITY_ERROR); this.client_.onReadError(error);
}
simulateParityError() {
this.simulateReadError(device.mojom.SerialReceiveError.PARITY_ERROR);
}
simulateDisconnectOnRead() {
this.simulateReadError(device.mojom.SerialReceiveError.DISCONNECTED);
}
simulateWriteError(error) {
this.readable_.cancel();
this.readable_ = undefined;
this.client_.onSendError(error);
}
simulateSystemErrorOnWrite() {
this.simulateWriteError(device.mojom.SerialSendError.SYSTEM_ERROR);
}
simulateDisconnectOnWrite() {
this.simulateWriteError(device.mojom.SerialSendError.DISCONNECTED);
} }
simulateInputSignals(signals) { simulateInputSignals(signals) {
...@@ -171,17 +193,30 @@ class FakeSerialPort { ...@@ -171,17 +193,30 @@ class FakeSerialPort {
return this.outputSignals_; return this.outputSignals_;
} }
waitForErrorCleared() { waitForReadErrorCleared() {
if (this.writable_) if (this.writable_)
return Promise.resolve(); return Promise.resolve();
if (!this.errorClearedPromise_) { if (!this.readErrorClearedPromise_) {
this.errorClearedPromise_ = new Promise((resolve) => { this.readErrorClearedPromise_ = new Promise((resolve) => {
this.errorCleared_ = resolve; this.readErrorCleared_ = resolve;
}); });
} }
return this.errorClearedPromise_; return this.readErrorClearedPromise_;
}
waitForWriteErrorCleared() {
if (this.readable_)
return Promise.resolve();
if (!this.writeErrorClearedPromise_) {
this.writeErrorClearedPromise_ = new Promise((resolve) => {
this.writeErrorCleared_ = resolve;
});
}
return this.writeErrorClearedPromise_;
} }
async open(options, in_stream, out_stream, client) { async open(options, in_stream, out_stream, client) {
...@@ -195,13 +230,23 @@ class FakeSerialPort { ...@@ -195,13 +230,23 @@ class FakeSerialPort {
return { success: true }; return { success: true };
} }
async clearSendError(in_stream) {} async clearSendError(in_stream) {
this.readable_ = new ReadableStream(new DataPipeSource(in_stream));
if (this.writeErrorCleared_) {
this.writeErrorCleared_();
this.writeErrorCleared_ = undefined;
this.writeErrorClearedPromise_ = undefined;
}
}
async clearReadError(out_stream) { async clearReadError(out_stream) {
this.writable_ = new WritableStream(new DataPipeSink(out_stream)); this.writable_ = new WritableStream(new DataPipeSink(out_stream));
this.writer_ = this.writable_.getWriter(); this.writer_ = this.writable_.getWriter();
if (this.errorCleared_) if (this.readErrorCleared_) {
this.errorCleared_(); this.readErrorCleared_();
this.readErrorCleared_ = undefined;
this.readErrorClearedPromise_ = undefined;
}
} }
async flush() { async flush() {
......
...@@ -96,7 +96,7 @@ serial_test(async (t, fake) => { ...@@ -96,7 +96,7 @@ serial_test(async (t, fake) => {
assert_true(readable instanceof ReadableStream); assert_true(readable instanceof ReadableStream);
reader = port.readable.getReader(); reader = port.readable.getReader();
await fakePort.waitForErrorCleared(); await fakePort.waitForReadErrorCleared();
fakePort.write(data); fakePort.write(data);
({ value, done } = await reader.read()); ({ value, done } = await reader.read());
...@@ -108,6 +108,26 @@ serial_test(async (t, fake) => { ...@@ -108,6 +108,26 @@ serial_test(async (t, fake) => {
assert_equals(port.readable, null); assert_equals(port.readable, null);
}, 'Parity error closes readable and replaces it with a new stream'); }, 'Parity error closes readable and replaces it with a new stream');
serial_test(async (t, fake) => {
const { port, fakePort } = await getFakeSerialPort(fake);
// Select a buffer size smaller than the amount of data transferred.
await port.open({ baudrate: 9600, buffersize: 64 });
const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
fakePort.write(data);
fakePort.simulateDisconnectOnRead();
const reader = port.readable.getReader();
const { value, done } = await reader.read();
assert_false(done);
compareArrays(data, value);
await promise_rejects(t, 'NetworkError', reader.read());
assert_equals(port.readable, null);
await port.close();
}, 'Disconnect error closes readable and sets it to null');
serial_test(async (t, fake) => { serial_test(async (t, fake) => {
const { port, fakePort } = await getFakeSerialPort(fake); const { port, fakePort } = await getFakeSerialPort(fake);
// Select a buffer size larger than the amount of data transferred. // Select a buffer size larger than the amount of data transferred.
......
...@@ -76,6 +76,48 @@ serial_test(async (t, fake) => { ...@@ -76,6 +76,48 @@ serial_test(async (t, fake) => {
await port.close(); await port.close();
}, 'Can read a large amount of data'); }, 'Can read a large amount of data');
serial_test(async (t, fake) => {
const { port, fakePort } = await getFakeSerialPort(fake);
await port.open({ baudrate: 9600 });
const writable = port.writable;
assert_true(writable instanceof WritableStream);
let writer = writable.getWriter();
fakePort.simulateSystemErrorOnWrite();
const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
await promise_rejects(t, 'UnknownError', writer.write(data));
assert_true(port.writable instanceof WritableStream);
assert_not_equals(port.writable, writable);
writer = port.writable.getWriter();
let writePromise = writer.write(data);
writer.close();
await fakePort.waitForWriteErrorCleared();
let { value, done } = await fakePort.read();
await writePromise;
compareArrays(value, data);
await port.close();
assert_equals(port.writable, null);
}, 'System error closes writable and replaces it with a new stream');
serial_test(async (t, fake) => {
const { port, fakePort } = await getFakeSerialPort(fake);
await port.open({ baudrate: 9600 });
assert_true(port.writable instanceof WritableStream);
const writer = port.writable.getWriter();
fakePort.simulateDisconnectOnWrite();
const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
await promise_rejects(t, 'NetworkError', writer.write(data));
assert_equals(port.writable, null);
await port.close();
}, 'Disconnect error closes writable and sets it to null');
serial_test(async (t, fake) => { serial_test(async (t, fake) => {
const { port, fakePort } = await getFakeSerialPort(fake); const { port, fakePort } = await getFakeSerialPort(fake);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment