Skip to content
This repository has been archived by the owner on Mar 11, 2020. It is now read-only.

channel: truncate twrite messages based on msize #30

Merged
merged 1 commit into from
Nov 16, 2016

Conversation

stevvooe
Copy link
Contributor

While there are a few problems around handling of msize, the easiest to
address and, arguably, the most problematic is that of Twrite. We now
truncate Twrite.Data to the correct length if it will overflow the msize
limit negotiated on the session.

Other problems with Twrite/Rread are documented in TODOs here, along
with possible solutions.

Signed-off-by: Stephen J Day [email protected]

@codecov-io
Copy link

codecov-io commented Oct 25, 2016

Current coverage is 15.65% (diff: 0.00%)

Merging #30 into master will decrease coverage by 0.30%

@@             master        #30   diff @@
==========================================
  Files            13         13          
  Lines          1128       1150    +22   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
  Hits            180        180          
- Misses          900        922    +22   
  Partials         48         48          

Powered by Codecov. Last update f717cf6...7725b53

@simonferquel
Copy link
Contributor

For the Write operation, LGTM.
However it does not fix the problem for reading large values (we know by experimentation that there are sloppy servers out there)

@stevvooe
Copy link
Contributor Author

@simonferquel let's take it one fix at a time.

I placed some todos in there for the read side fixes. Feel free to take those on but it would be good to get a proper bug report characterizing the problem messages.

@stevvooe
Copy link
Contributor Author

From the io.Writer documentation:

Write writes len(p) bytes from p to the underlying data stream. It returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early. Write must return a non-nil error if it returns n < len(p). Write must not modify the slice data, even temporarily.

Basically, if we truncate, we need to pass through an error. We can still allow the write to occur. I am not sure if we want to rely on the good behavior of writers to ensure this is done correctly.

}

type overflowErr struct {
size int //
Copy link
Contributor

Choose a reason for hiding this comment

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

missing comment?

@justincormack
Copy link
Contributor

The io package defines ErrShortWrite (also ErrShortBuffer) see https://golang.org/pkg/io/#pkg-variables for this case.

@stevvooe
Copy link
Contributor Author

The io package defines ErrShortWrite (also ErrShortBuffer) see https://golang.org/pkg/io/#pkg-variables for this case.

@justincormack Do you think it is sufficient to return ErrShortWrite then have the caller adjust the buffer size to the returned n (bytes written)?

@stevvooe
Copy link
Contributor Author

@justincormack I've updated the PR accordingly.

@simonferquel
Copy link
Contributor

I think ErrShortWrite is a good option. We can also provide a io.Writer wrapper that handles the chunking according to ErrShortWrite (so the caller won't have to compute the correct offset for the next chunk).

Reads can be fixed in the same fashion, except that io.Reader does not specify that a short read should return a non-nil err. However, we have to make sure we return a correct io.EOF error at some point such that io.ReadFull works correctly even if len(buf) > [length of the actual 9p file - initial offset] with the provided io.Reader wrapper.

@stevvooe @justincormack does it make sense to you ?

@simonferquel
Copy link
Contributor

io.Reader / io.Writer wrappers could be created by functions like:

CreateReaderWriter(session *Session, ctx context.Context, fid Fid, offset int64) *io.ReaderWriter

@stevvooe
Copy link
Contributor Author

However, we have to make sure we return a correct io.EOF error at some point such that io.ReadFull works correctly even if len(buf) > [length of the actual 9p file - initial offset] with the provided io.Reader wrapper.

This is a basic requirement of an io.Reader.

CreateReaderWriter(session *Session, ctx context.Context, fid Fid, offset int64) *io.ReaderWriter

Typically, I would avoid creating a duplex stream, unless you're trying to implement net.Conn.

Also, never use pointers to interfaces.

Have you tested this? Can we merge?

// The truncation is reflected in the return message (Rwrite) by
// the server, so we don't need a return value or error condition
// to communicate it.
msg.Data = msg.Data[:len(msg.Data)-overflow]
Copy link
Contributor

Choose a reason for hiding this comment

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

msg seems to be a copy of fcall.Message (I launched test from #31) on this branch with debugging.
adding fcall.Message = msg fixes the issue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be addressed.

@stevvooe
Copy link
Contributor Author

@simonferquel This PR should now handle the covered issues, from client and server.

Copy link
Contributor

@simonferquel simonferquel left a comment

Choose a reason for hiding this comment

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

Almost good to merge to me. Will begin work on datakit client today


// first, craft the shape of the response message
resp := newFcall(fcall.Tag, MessageRread{})
overflow := uint32(ch.msgmsize(resp)) + msg.Count - uint32(ch.msize)
Copy link
Contributor

Choose a reason for hiding this comment

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

We might want to compute empty MessageRRead fcall size once at channel initialization to avoid allocating a new fcall each time we do a read transaction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's take that optimization in a separate PR, with a benchmark.

There is an across the board improvement if we add a field cache to the codec.

func TestWriteOverflow(t *testing.T) {
const (
msize = 512
overflowMSize = msize * 3 / 2
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like to cover the case where len(p) fits in msize, but len(p) + [size of envelope] don't to the regression suite. That would avoid some potential naïve (and wrong) optimizations.

func TestTreadRewrite(t *testing.T) {
const (
msize = 256
overflowMSize = msize + 1
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as with writes : I'd like to cover the case where len(p) fits in msize, but len(p) + [size of envelope] don't to the regression suite. That would avoid some potential naïve (and wrong) optimizations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, I've clean up these tests. You're welcome to refactoring this one according to the others and create another PR for this one.

simonferquel added a commit to simonferquel/datakit that referenced this pull request Nov 15, 2016
simonferquel added a commit to simonferquel/datakit that referenced this pull request Nov 15, 2016
@stevvooe
Copy link
Contributor Author

Ok, so the tests have been beefed up quite a bit. If there is anything else needed here, please submit a PR.

Let's get this one merged.

While there are a few problems around handling of msize, the easiest to
address and, arguably, the most problematic is that of Twrite. We now
truncate Twrite.Data to the correct length if it will overflow the msize
limit negotiated on the session. ErrShortWrite is returned by the
`Session.Write` method if written data is truncated.

In addition, we now reject incoming messages from `ReadFcall` that
overflow the msize. Such messages are probably terminal in practice, but
can be detected with the `Overflow` function.

Tread is also handled accordingly, such that the Count field will be
rewritten such that the response doesn't overflow the msize.

Signed-off-by: Stephen J Day <[email protected]>
@stevvooe stevvooe merged commit c74282f into docker-archive:master Nov 16, 2016
@stevvooe stevvooe deleted the truncate-twrite-msize branch November 16, 2016 01:05
djs55 pushed a commit to djs55/datakit that referenced this pull request Mar 16, 2017
simonferquel added a commit to simonferquel/datakit that referenced this pull request Jun 16, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants