Skip to content

Attach add-on sometimes reorders output #1893

@markspeters

Description

@markspeters

The attach add-on sometimes reorders data despite the frames being received in-order over the socket.

Details

  • Browser and browser version: Chrome 71.0.3578.98
  • OS version: OSX 10.13.4
  • xterm.js version: 3.10.1 (not positive, but don't think the plugin has changed since)

Steps to reproduce

  1. Attach the terminal to a websocket.
  2. Send the websocket two consecutive messages where the first's .data will be a Blob and the second an ArrayBuffer or string.

Expected: The data from these two messages to be displayed in order.
Actual: They're sometimes reordered.

My hack fix

The issue appears to be that FileReader's readAsArrayBuffer as asynchronous, so subsequent messages can be received/decoded/displayed while the FileReader is still loading.

I'm able to work around this by attaching an ID to each displayable message as they come in, and using that in displayData to check whether there are outstanding messages (and buffering them if there are). I'm patching the JS, not the TS but hopefully the idea comes across:

var nextId = 1;
var lastDisplayedId = 0;
var msgsToWrite = {};
var myTextDecoder;
addonTerminal.__getMessage = function (ev) {
	var str;
	if (typeof ev.data === 'object') {
		var msgId = nextId++;
		if (!myTextDecoder) {
			myTextDecoder = new TextDecoder();
		}
		if (ev.data instanceof ArrayBuffer) {
			str = myTextDecoder.decode(ev.data);
			displayData(msgId, str);
		}
		else {
			var fileReader_1 = new FileReader();
			fileReader_1.addEventListener('load', function () {
				str = myTextDecoder.decode(fileReader_1.result);
				displayData(msgId, str);
			});
			fileReader_1.readAsArrayBuffer(ev.data);
		}
	}
	else if (typeof ev.data === 'string') {
		displayData(nextId++, ev.data);
	}
	else {
		throw Error("Cannot handle \"" + typeof ev.data + "\" websocket message.");
	}
};
function displayData(msgId, str, data) {
	msgsToWrite[msgId] = (str || data);
	for (var i = lastDisplayedId + 1; typeof msgsToWrite[i] !== 'undefined'; i++) {
		if (buffered) {
			addonTerminal.__pushToBuffer(msgsToWrite[i]);
		}
		else {
			addonTerminal.write(msgsToWrite[i]);
		}
		delete msgsToWrite[i];
		lastDisplayedId = i;
	}
}

This seems to be working great, the only concern I have is recoverability if the filereader fails to decode a chunk--with this change it'll buffer subsequent data till the end of time.

Thanks for your work on this fantastic software.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions