"DOMException: A buffer overrun has been detected" but only if USB serial port left connected for some time?

1k Views Asked by At

I have a React application, where I read data from a microcontroller using a UART cable. The microcontroller is continuously printing a JSON string. The application parses this string and displays its values.

The JSON string coming from my microcontroller looks like this:

{"addr":"00xxxxxxxxxxxxxx","1":{"type":"a","value":40.7}}

I followed this blog post to help me with this, and am using a TransformStream with the same LineBreakTransformer that is used in the blog post so that I can parse a complete JSON string. I have a button that triggers this all on click.

Here is the issue I am facing: If I plug in the USB and quickly press the button, everything works fine. I get the prompt asking me to select the correct COM port, I receive data, and am able to parse it. However, if I plug in the USB, and leave it for a while before pressing the button, I get the COM port prompt, but then get these errors:

Uncaught (in promise) DOMException: A buffer overrun has been detected.
Uncaught (in promise) TypeError: Failed to execute 'pipeTo' on 'ReadableStream': Cannot pipe a locked stream

Also, after I refresh the screen when I get this error, and quickly press the button, it works successfully.

Why is this happening, and how can I resolve this?

Thank you.

Source code:

import React, { useState } from 'react'

class LineBreakTransformer {
  constructor() {
    this.chunks = ''
  }

  transform(chunk, controller) {
    this.chunks += chunk
    const lines = this.chunks.split('\r\n')
    this.chunks = lines.pop()
    lines.forEach((line) => controller.enqueue(line))
  }

  flush(controller) {
    controller.enqueue(this.chunks)
  }
}

const App = () => {
  const [macAddr, setMacAddr] = useState('')
  const [sensors, setSensors] = useState([])

  async function onButtonClick() {
    const port = await navigator.serial.requestPort()
    await port.open({ baudRate: 115200, bufferSize: 10000000 })
    while (port.readable) {
      // eslint-disable-next-line no-undef
      const textDecoder = new TextDecoderStream()
      port.readable.pipeTo(textDecoder.writable)
      const reader = textDecoder.readable.pipeThrough(new TransformStream(new LineBreakTransformer())).getReader()
      try {
        while (true) {
          const { value, done } = await reader.read()
          if (done) {
            reader.releaseLock()
            break
          }
          if (value) {
            const { addr, ...sensors } = JSON.parse(value)
            setMacAddr(addr)
            setSensors(sensors)
          }
        }
      } catch (error) {}
    }
  }
  return (
    <div>
      <h1>{`macAddr: ${macAddr}`}</h1>
      {Object.keys(sensors).map((sensor) => (
        <div key={sensor}>
          <div>{`Channel: ${sensor}`}</div>
          <div>{`Temp: ${sensors[sensor].value}`}</div>
        </div>
      ))}
      <button
        onClick={async () => {
          await onButtonClick()
        }}
      >
        CLick
      </button>
    </div>
  )
}

export default App
2

There are 2 best solutions below

0
sshakya On BEST ANSWER

Alright guys, I figured out what I was doing wrong. The issue had nothing to do with waiting. The issue was actually on JSON.parse().

Since I am reading streams, occasionally I don't get a full packet, and only get the remaining part of the JSON string being sent from the microcontroller, (e.g. only :1} instead of {"a":1}. It's just less likely for this to happen when I begin reading immediately after plugging in the device.

When I parse it, my function errors out because it is not valid JSON. Since I have it on a while loop, it then fails at port.readable.pipeTo(textDecoder.writable) because the reader is locked.

I fixed this by wrapping the parse() in a try catch block.

1
Reilly Grant On

The issue is that your device is sending data whether or not the host is ready to receive it. The UART in your computer (or in the cable if it's a USB serial converter) has a limited amount of buffer space for data. If that overflows you get the error you are seeing. This is a recoverable error. If you catch it and try again as long as port.readable is truthy accessing it will clear the error and give you a new ReadableStream you can use to continue reading. Just be aware that some of the initial data will be corrupted because of the overflow.