Skip to content

Comments

Support reading multiple bytes in escapeProxy#39728

Merged
thaJeztah merged 1 commit intomoby:masterfrom
bamarni:term-proxy-pty
Apr 16, 2020
Merged

Support reading multiple bytes in escapeProxy#39728
thaJeztah merged 1 commit intomoby:masterfrom
bamarni:term-proxy-pty

Conversation

@bamarni
Copy link
Contributor

@bamarni bamarni commented Aug 12, 2019

Currently, the escapeProxy works under the assumption that the
underlying reader will always return 1 byte at a time. Even though this
is usually true, it is not always the case, for example when using a pty
and writing multiple bytes to the master before flushing it.

In such cases the proxy reader doesn't work properly. For example with
an escape sequence being ctrl-p,ctrl-q, when the underlying reader
returns ctrl-p,ctrl-q at once, the escape sequence isn't detected.

A second issue occurs when there is a partial escape currently being
read, if on the next read the len of the passed buffer in argument is
not big enough, it will cause a panic. For example, when the escape sequence
is ctrl-p,ctrl-q, and the caller is passing a []byte of len 1 as argument to read.
And they press ctrl-p, then something else than ctrl-q.

This updates the reader to support this use-cases and adds unit tests.

I'd still like to test more cases to make sure it's working fine, but I'm first
opening the PR to see if it is an acceptable patch.

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Did a cursory review, and left some comments/suggestions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if it'd be clearer to return early here (possibly at the cost of some slight duplication)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I think it could be replaced with:

n = i + 1 - r.escapeKeyPos
if n < 0 {
    n = 0
}
return n, EscapeError{}

Let me know if that looks better and I'll try it out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes please! +1 to "return early"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be in a follow up PR is fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a huge fan of named output variables; it's easy to inadvertently mask them and introduce bugs; perhaps we can slightly refactor the code to not require them

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have to make variable declarations at the beginning of the function, this wouldn't look better IMO, I don't see an alternative with the current implementation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, 🤞 that we won't have to touch this again! so ok for named output variable here

@thaJeztah
Copy link
Member

Sorry for the delay; #39800 was merged, so you can rebase this PR 🤗

@bamarni
Copy link
Contributor Author

bamarni commented Sep 7, 2019

@thaJeztah : awesome 🎉 it's rebased now!

@bamarni
Copy link
Contributor Author

bamarni commented Sep 8, 2019

The build failed:

[2019-09-07T21:47:09.271Z] --- FAIL: TestLogBlocking (0.04s)
[2019-09-07T21:47:09.271Z]     cloudwatchlogs_test.go:313: Expected to be able to read from stream.messages but was unable to
[2019-09-07T21:47:09.271Z] time="2019-09-07T21:47:06Z" level=error msg=Error
[2019-09-07T21:47:09.271Z] time="2019-09-07T21:47:06Z" level=error msg="Failed to put log events" errorCode=InvalidSequenceTokenException logGroupName=groupName logStreamName=streamName message="use token token" origError="<nil>"
[2019-09-07T21:47:09.271Z] time="2019-09-07T21:47:06Z" level=error msg="Failed to put log events" errorCode=DataAlreadyAcceptedException logGroupName=groupName logStreamName=streamName message="use token token" origError="<nil>"
[2019-09-07T21:47:09.272Z] time="2019-09-07T21:47:06Z" level=info msg="Data already accepted, ignoring error" errorCode=DataAlreadyAcceptedException logGroupName=groupName logStreamName=streamName message="use token token"
[2019-09-07T21:47:09.272Z] FAIL

See https://ci.docker.com/public/blue/organizations/jenkins/moby/detail/PR-39728/4/pipeline

I'll try to re-run.

@bamarni
Copy link
Contributor Author

bamarni commented Sep 17, 2019

@thaJeztah : I've rebased since #39887 was merged and CI passed 🚀

@bamarni
Copy link
Contributor Author

bamarni commented Sep 26, 2019

@thaJeztah @runcom : I've rebased with the PR improving the tests and CI is now passing, is there anything else missing here?

@bamarni
Copy link
Contributor Author

bamarni commented Oct 23, 2019

@thaJeztah : is this PR still on your radar?

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, whoops, no it wasn't 😅

LGTM

@thaJeztah
Copy link
Member

ping @vdemeester PTAL, I think you're familiar with this area

@bamarni
Copy link
Contributor Author

bamarni commented Nov 4, 2019

@vdemeester any news on this one? 👼

@bamarni
Copy link
Contributor Author

bamarni commented Mar 11, 2020

Hey @thaJeztah @vdemeester, can you help with this one? It's been sitting here for a while.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the additional tests. helps understand the problem better.

@dims
Copy link
Contributor

dims commented Mar 12, 2020

LGTM

thanks @bamarni

@bamarni
Copy link
Contributor Author

bamarni commented Mar 17, 2020

@dims : so is it good to go, or should I apply your comment about early return here?

@dims
Copy link
Contributor

dims commented Mar 17, 2020

@bamarni if you can quickly make the change for the early return, that would be good. I am fine if you want to do it in a follow up PR as this has been stuck forever

@cpuguy83
Copy link
Member

If we are going to make this change, I'd like to simplify this down.
I feel like we can do this with a bufio.Reader and ReadByte.

I kinda started messing with this, it's not 100% yet, but PTAL:

func (r *escapeProxy) Read(p []byte) (nr int, err error) {
	if len(r.escapeKeys) == 0 {
		// This causes tests to fail b/c they are expecting an error in this case.
		// I'm not sure that's correct... but in any case this is a place holder to prevent test panics
		return r.r.Read(p)
	}

	for i := 0; i < len(p); i++ {
		b, err := r.r.ReadByte()
		if err != nil {
			return nr, err
		}
		p[nr] = b

		if b != r.escapeKeys[r.escapeKeyPos] {
			r.escapeKeyPos = 0
			nr++
			continue
		}

		r.escapeKeyPos++
		if r.escapeKeyPos == len(r.escapeKeys) {
			return nr, EscapeError{}
		}
	}

	return nr, nil
}

Currently, the escapeProxy works under the assumption that the
underlying reader will always return 1 byte at a time. Even though this
is usually true, it is not always the case, for example when using a pty
and writing multiple bytes to the master before flushing it.

In such cases the proxy reader doesn't work properly. For example with
an escape sequence being `ctrl-p,ctrl-q`, when the underlying reader
returns `ctrl-p,ctrl-q` at once, the escape sequence isn't detected.

This updates the reader to support this use-case and adds unit tests.

Signed-off-by: Bilal Amarni <[email protected]>
@bamarni
Copy link
Contributor Author

bamarni commented Mar 17, 2020

@dims : I've updated the code as suggested.

@cpuguy83 : I'll try to check this out. One notable difference is that it with your sample it will make multiple read syscalls instead of 1, is that not a concern?

@bamarni
Copy link
Contributor Author

bamarni commented Mar 17, 2020

@cpuguy83 : Ah I see, so the bufio.Reader would read 4096 by default and keep it in memory, then we'd iterate one byte at a time.

@bamarni
Copy link
Contributor Author

bamarni commented Mar 18, 2020

@cpuguy83 : I've looked at your snippet:

  • regarding your comment about the test failure because it expects an error, this is due to a difference between bytes.Reader and bufio.Reader, one will return io.EOF when you pass it a p of length 0, while the error wouldn't. See https://play.golang.org/p/YU4NAr0W52o

  • some other test failures are because the snippet doesn't handle the case where there were a previous call to the escape proxy reader which partially matched an escape sequence, at this point it returned no data. However, on a subsequent call, the proxy reader should not read and return data from the underlying reader straight away, rather, it should fill the p with the partial escape sequence previously matched.

It would require additional logic to fix these 2 (especially the 2nd point), and eventually I'm not sure that the resulting code would be necessarily simpler. Let me know what you think.

@bamarni
Copy link
Contributor Author

bamarni commented Mar 31, 2020

Hey @dims @cpuguy83, any news?

@dims
Copy link
Contributor

dims commented Mar 31, 2020

@bamarni waiting on @cpuguy83

@AkihiroSuda
Copy link
Member

@cpuguy83 PTAL

Copy link
Member

@cpuguy83 cpuguy83 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Should we move this to github.com/moby/term?

@dims
Copy link
Contributor

dims commented Apr 16, 2020

LGTM

+1 to move this to moby/term

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@thaJeztah thaJeztah merged commit 7d0e939 into moby:master Apr 16, 2020
@thaJeztah thaJeztah added this to the 20.03.0 milestone Apr 16, 2020
bamarni added a commit to bamarni/dcos-core-cli that referenced this pull request Apr 17, 2020
There used to be a bug in the Moby terminal escape proxy:

moby/moby#39728

Since it is now fixed upstream, we can remove the patched version from
the codebase.

Additionally, this narrows down the Moby dependency to moby/term
instead of the broad moby/moby package.
bamarni added a commit to bamarni/dcos-core-cli that referenced this pull request Apr 17, 2020
There used to be a bug in the Moby terminal escape proxy:

moby/moby#39728

Since it is now fixed upstream, we can remove the patched version from
the codebase.

Additionally, this narrows down the Moby dependency to moby/term
instead of the broad moby/moby package.
janisz pushed a commit to dcos/dcos-core-cli that referenced this pull request Apr 22, 2020
There used to be a bug in the Moby terminal escape proxy:

moby/moby#39728

Since it is now fixed upstream, we can remove the patched version from
the codebase.

Additionally, this narrows down the Moby dependency to moby/term
instead of the broad moby/moby package.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants