Skip to content

utilities: Implemented an ostream that writes to a user provided buffer #13797

Merged
mattklein123 merged 7 commits intoenvoyproxy:masterfrom
KBaichoo:ostream-memory-buff
Nov 23, 2020
Merged

utilities: Implemented an ostream that writes to a user provided buffer #13797
mattklein123 merged 7 commits intoenvoyproxy:masterfrom
KBaichoo:ostream-memory-buff

Conversation

@KBaichoo
Copy link
Contributor

Implemented an ostream that writes to a user provided buffer and truncates if we surpass the buffer size.

Signed-off-by: Kevin Baichoo kbaichoo@google.com

For an explanation of how to fill out the fields, please see the relevant section
in PULL_REQUESTS.md

Commit Message: Implemented an ostream that writes to a user provided buffer and truncates if we surpass the buffer size.
Additional Description: Will be useful for Fatal Actions (see PR #13676 ), and other situations where we want a fixed-sized ostream.
Risk Level: low
Testing: unit tests
Docs Changes: NA
Release Notes: NA
Platform Specific Features: NA

…cates if we surpass the buffer size.

Signed-off-by: Kevin Baichoo <kbaichoo@google.com>
@KBaichoo
Copy link
Contributor Author

/assign @akonradi

// Can write to buffer
{
std::string data = "123";
std::string buffer(3, 'x');
Copy link
Contributor

Choose a reason for hiding this comment

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

This and the others could be std::array; that saves you a little on runtime since the contents of the buffer before streaming don't actually matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right the contents don't matter.

With std::array I'd actually have to loop when calling << which is an antipattern when using ostreams.

What I'll do is just declare a string, and resize() instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand the std::array issue. I was suggesting declaring std::array<uint8, 3> buffer and using std::array::data() and size() below to access the raw bytes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now use std::array<char, N> for the buffer, thanks for clarifying

/**
* std::ostream class that serializes writes into the provided buffer.
*/
class OutputBufferStream : public virtual FixedSizeStreamBuffer, public std::ostream {
Copy link
Contributor

Choose a reason for hiding this comment

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

This class should probably have a way to provide the number of bytes that were written into the buffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Signed-off-by: Kevin Baichoo <kbaichoo@google.com>
// Can write to buffer
{
std::string data = "123";
std::string buffer(3, 'x');
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand the std::array issue. I was suggesting declaring std::array<uint8, 3> buffer and using std::array::data() and size() below to access the raw bytes.

Signed-off-by: Kevin Baichoo <kbaichoo@google.com>
akonradi
akonradi previously approved these changes Oct 28, 2020
Copy link
Contributor

@akonradi akonradi left a comment

Choose a reason for hiding this comment

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

Thanks!

@KBaichoo
Copy link
Contributor Author

PTAL @envoyproxy/senior-maintainers

@ggreenway
Copy link
Member

ggreenway commented Oct 28, 2020

We mostly haven't used the iostream model in envoy. What's the motivation/justification for introducing it here?

Or stated a different way: why not have a class with a write() function instead?

@KBaichoo
Copy link
Contributor Author

The motivation for using iostream here is due to the ScopedTrackedObject interface.

One of the use cases for the OutputBufferStream would be as the ostream that the ScopedTrackedObject dumps it state on without allocating additional memory. This is necessary as the ScopedTrackedObject is dumped on a crash and we want to be async-signal-safe which means one thing we shouldn't do is allocate memory.

@antoniovicente
Copy link
Contributor

The motivation for using iostream here is due to the ScopedTrackedObject interface.

One of the use cases for the OutputBufferStream would be as the ostream that the ScopedTrackedObject dumps it state on without allocating additional memory. This is necessary as the ScopedTrackedObject is dumped on a crash and we want to be async-signal-safe which means one thing we shouldn't do is allocate memory.

We need a way to capture the output generated by dumpState. The other alternative is to replace use of ostream by dumpState with something else.

@KBaichoo
Copy link
Contributor Author

The other alternative is to replace use of ostream by dumpState with something else.

The benefit of using ostream is that objects implement it in order to be serializable, and the serialization is decoupled from the storage medium (it could be a fixed sized buffer as in this case, it could be a file, etc.)

@ggreenway
Copy link
Member

The other alternative is to replace use of ostream by dumpState with something else.

The benefit of using ostream is that objects implement it in order to be serializable, and the serialization is decoupled from the storage medium (it could be a fixed sized buffer as in this case, it could be a file, etc.)

Sounds like a reasonable justification to me.

@KBaichoo
Copy link
Contributor Author

KBaichoo commented Nov 2, 2020

/retest

@repokitteh-read-only
Copy link

Retrying Azure Pipelines:
Retried failed jobs in: envoy-presubmit

🐱

Caused by: a #13797 (comment) was created by @KBaichoo.

see: more, trace.

@KBaichoo
Copy link
Contributor Author

friendly ping @ggreenway since the PR #13676 which this will be useful for has merged

@ggreenway
Copy link
Member

Sorry, I didn't know you were waiting on me for this, and somehow it never got assigned to anyone. I've been pretty busy. @antoniovicente can you take a look?

@ggreenway
Copy link
Member

Oh, I see what happened: you assigned it to a non-maintainer, so it was never seen in the view for "unassigned PRs" to get assigned to a maintainer.

Copy link
Contributor

@antoniovicente antoniovicente left a comment

Choose a reason for hiding this comment

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

Very nice. I expected this to be more complicated to implement. Still some regrets about this not being something provided out of the box by C++ standard libraries, since it does provide all the tools needed to do this.


TEST(OutputBufferStream, CanWriteToBuffer) {
constexpr char data[] = "123";
std::array<char, 3> buffer;
Copy link
Contributor

Choose a reason for hiding this comment

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

I forget if ASAN is able to find issues with data members like this allocated in the stack. Consider instead:

std::unique_ptr<char[]> buffer(new char[3]);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ASAN doesn't complain about it.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you were to do something like:

OutputBufferStream ostream{buffer.data(), buffer.size()+1};
ostream << "lots of bytes";

Would ASAN detect the problem?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes ASAN detected that.

We're saying the buffer is bigger than it is (i.e. buffer.size() + 1) and trying to write to memory we don't have in that case.

Copy link
Contributor

Choose a reason for hiding this comment

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

Great. std::array works for me.

}

OutputBufferStream::OutputBufferStream(char* data, size_t size)
: FixedSizeStreamBuffer{data, size}, std::ostream{static_cast<std::streambuf*>(this)} {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the call to setp need to happen before a pointer is given to the ostream constructor?

This seems to work:
/**
* std::ostream class that serializes writes into the provided buffer.
*/
class OutputBufferStream : private std::streambuf, public std::ostream {
public:
OutputBufferStream(char* data, size_t size);
...
}

OutputBufferStream::OutputBufferStream(char* data, size_t size)
: std::ostream(this) {
setp(data, data + size);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The call to setp doesn't need to occur first since streambuf are more focused on managing some storage (buffer, file, ...) and ostreams are more focused on serializing objects. ostream defers the actual writing / storage management to the streambuf.

I organized it like this to be consistent with the InputConstMemoryStream. If you want I can flatten it (removing FixedSizeStreamBuffer)

Copy link
Contributor

Choose a reason for hiding this comment

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

I would consider using the name MutableMemoryStreamBuffer

I don't feel strongly about flattening it. Flattening it seems slightly cleaner but both approaches are fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Signed-off-by: Kevin Baichoo <kbaichoo@google.com>
Signed-off-by: Kevin Baichoo <kbaichoo@google.com>
Copy link
Contributor Author

@KBaichoo KBaichoo left a comment

Choose a reason for hiding this comment

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

Thanks for the review!


TEST(OutputBufferStream, CanWriteToBuffer) {
constexpr char data[] = "123";
std::array<char, 3> buffer;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ASAN doesn't complain about it.

}

OutputBufferStream::OutputBufferStream(char* data, size_t size)
: FixedSizeStreamBuffer{data, size}, std::ostream{static_cast<std::streambuf*>(this)} {}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The call to setp doesn't need to occur first since streambuf are more focused on managing some storage (buffer, file, ...) and ostreams are more focused on serializing objects. ostream defers the actual writing / storage management to the streambuf.

I organized it like this to be consistent with the InputConstMemoryStream. If you want I can flatten it (removing FixedSizeStreamBuffer)

}

OutputBufferStream::OutputBufferStream(char* data, size_t size)
: FixedSizeStreamBuffer{data, size}, std::ostream{static_cast<std::streambuf*>(this)} {}
Copy link
Contributor

Choose a reason for hiding this comment

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

I would consider using the name MutableMemoryStreamBuffer

I don't feel strongly about flattening it. Flattening it seems slightly cleaner but both approaches are fine.


TEST(OutputBufferStream, CanWriteToBuffer) {
constexpr char data[] = "123";
std::array<char, 3> buffer;
Copy link
Contributor

Choose a reason for hiding this comment

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

If you were to do something like:

OutputBufferStream ostream{buffer.data(), buffer.size()+1};
ostream << "lots of bytes";

Would ASAN detect the problem?

EXPECT_EQ(ostream.contents(), "12");
}

TEST(OutputBufferStream, ReturnsNumberOfBytesWrittenIntoBuffer) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This test seems very similar to OutputBufferStream.CanWriteToBuffer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Folded into that test and the overflow test.

Signed-off-by: Kevin Baichoo <kbaichoo@google.com>
Copy link
Contributor Author

@KBaichoo KBaichoo left a comment

Choose a reason for hiding this comment

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

Thanks for the review.

EXPECT_EQ(ostream.contents(), "12");
}

TEST(OutputBufferStream, ReturnsNumberOfBytesWrittenIntoBuffer) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Folded into that test and the overflow test.

}

OutputBufferStream::OutputBufferStream(char* data, size_t size)
: FixedSizeStreamBuffer{data, size}, std::ostream{static_cast<std::streambuf*>(this)} {}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


TEST(OutputBufferStream, CanWriteToBuffer) {
constexpr char data[] = "123";
std::array<char, 3> buffer;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes ASAN detected that.

We're saying the buffer is bigger than it is (i.e. buffer.size() + 1) and trying to write to memory we don't have in that case.


TEST(OutputBufferStream, CanWriteToBuffer) {
constexpr char data[] = "123";
std::array<char, 3> buffer;
Copy link
Contributor

Choose a reason for hiding this comment

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

Great. std::array works for me.

@antoniovicente
Copy link
Contributor

Could you merge master to see if that fixes the coverage error?

2020-11-20T22:29:10.8563882Z Per-extension coverage failed:
2020-11-20T22:29:10.8565398Z Code coverage for source/common/event is lower than limit of 93.5 (92.9)

Signed-off-by: Kevin Baichoo <kbaichoo@google.com>
@antoniovicente
Copy link
Contributor

cc @asraa

Thoughts on what may be up with linux_x64 fuzz_coverage? Run seems to still be pending after more than 5 hours.

@asraa
Copy link
Contributor

asraa commented Nov 23, 2020

I'm going to guess it's an AZP issue. The build aborted midway
##[error]We stopped hearing from agent i-0c54ce55437fb840e.
time="2020-11-23T15:01:24Z" level=error msg="error waiting for container: unexpected EOF"

I'll re-run and keep the tab open.

@mattklein123 mattklein123 merged commit c41850c into envoyproxy:master Nov 23, 2020
qqustc pushed a commit to qqustc/envoy that referenced this pull request Nov 24, 2020
…er (envoyproxy#13797)

Signed-off-by: Kevin Baichoo <kbaichoo@google.com>
Signed-off-by: Qin Qin <qqin@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants