Skip to content

filesystem: move file object with stats, threading + flushing to AccessLogFile; add generic File object#5772

Merged
mattklein123 merged 24 commits intoenvoyproxy:masterfrom
greenhouse-org:filesystem-decorator
Feb 21, 2019
Merged

filesystem: move file object with stats, threading + flushing to AccessLogFile; add generic File object#5772
mattklein123 merged 24 commits intoenvoyproxy:masterfrom
greenhouse-org:filesystem-decorator

Conversation

@sesmith177
Copy link
Member

@sesmith177 sesmith177 commented Jan 30, 2019

Description:
The RawFile + RawInstance classes will be used to hold platform-specific filesystem code. The File/Instance classes will be used to add stats collecting and periodic flushing -- they will be platform agnostic.

The old Filesystem::File object (with stats, threading, etc.) is only used by the AccessLogManager, so move it into that interface. It is now completely cross-platform. Replace Filesystem::File with a basic file object that each platform (Windows, Linux, etc..) can use to provide its own implementation

This is step 2/3 in adding Windows filesystem support as outlined here: #5470 (comment).

Risk Level:
Low

Testing:
bazel build //source/... && bazel test //test/...

Docs Changes:
N/A
Release Notes:
N/A

The RawFile + RawInstance classes will be used to hold platform-specific
filesystem code. The File/Instance classes will be used to add stats
collecting and periodic flushing -- they will be platform agnostic.

This is step 2/3 in adding Windows filesystem support.

Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sophie Wigmore <swigmore@pivotal.io>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Copy link
Contributor

@jmarantz jmarantz left a comment

Choose a reason for hiding this comment

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

Nice -- just a few nits. Apologies for the delay.

os_sys_calls_.open(path_, O_RDWR | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
fd_ = result.rc_;
if (-1 == fd_) {
const auto result = raw_file_->open();
Copy link
Contributor

Choose a reason for hiding this comment

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

Explicit type here as it's not obvious from context.

https://google.github.io/styleguide/cppguide.html#auto

os_sys_calls_.close(fd_);
const auto result = raw_file_->close();
ASSERT(result.rc_, fmt::format("unable to close file '{}': {}", raw_file_->path(),
strerror(result.errno_)));
Copy link
Contributor

@jmarantz jmarantz Feb 5, 2019

Choose a reason for hiding this comment

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

IMO we should be abstracting this need to do strerror() in the RawFile interface.

RawFileImpl::RawFileImpl(const std::string& path) : fd_(-1), path_(path) {}

RawFileImpl::~RawFileImpl() {
const auto result = close();
Copy link
Contributor

Choose a reason for hiding this comment

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

explicit type please.

// TODO(htuch): Optimize this as a hash lookup if we grow any further.
if (absl::StartsWith(canonical_path.rc_, "/dev") ||
absl::StartsWith(canonical_path.rc_, "/sys") ||
absl::StartsWith(canonical_path.rc_, "/proc")) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think someone else has an outstanding PR that adds to this list. Can you merge master?

::unlink(new_file_path.c_str());

RawFilePtr file = raw_instance_.createRawFile(new_file_path);
const auto result = file->open();
Copy link
Contributor

Choose a reason for hiding this comment

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

explicit type

sesmith177 and others added 2 commits February 5, 2019 14:58
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Yael Harel <yharel@pivotal.io>
Signed-off-by: Sam Smith <sesmith177@gmail.com>

Signed-off-by: Yael Harel <yharel@pivotal.io>
@sesmith177
Copy link
Member Author

@jmarantz thanks, we've addressed your feedback

const Api::SysCallBoolResult result = raw_file_->close();
ASSERT(result.rc_, fmt::format("unable to close file '{}': {}", raw_file_->path(),
strerror(result.errno_)));
raw_file_->errorToString(result.errno_)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Better, but I feel like even the errno_ could be abstracted here. Can we have errorString() be a method on Api::SysCallBoolResult, or something like that?

E.g. I was envisioning on Windows or some other hypothetical OS that an error might have a different representation than an 'int' for errors. It might be an enum or some other abstract type. WDYT?

We could also do this on a follow-up.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think for now we can hold off on addressing this, since our implementation of RawFile on Windows will also use errno + strerror as well.

This will definitely come up when we start PRing socket changes upstream (e.g. an implementation of #5829). In Windows sockets, errors are not reported in errno and are not displayed using strerror. With the full context, I think will be able to come up with the correct abstraction

Copy link
Contributor

Choose a reason for hiding this comment

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

OK can you add a TODO for that?

Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

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

we can add the TODO

jmarantz
jmarantz previously approved these changes Feb 5, 2019
const Api::SysCallBoolResult result = raw_file_->close();
ASSERT(result.rc_, fmt::format("unable to close file '{}': {}", raw_file_->path(),
strerror(result.errno_)));
raw_file_->errorToString(result.errno_)));
Copy link
Contributor

Choose a reason for hiding this comment

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

OK can you add a TODO for that?

Thanks!

@jmarantz jmarantz mentioned this pull request Feb 5, 2019
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Yael Harel <yharel@pivotal.io>
jmarantz
jmarantz previously approved these changes Feb 5, 2019
Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Thanks a ton for continuing to slog through this. I have some interface questions to start out with.

/wait

* Abstraction for a basic file on disk.
*/
class File {
class RawFile {
Copy link
Member

Choose a reason for hiding this comment

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

From an interface perspective, I'm not really sure what the difference is between a "raw" file and a "file." Can you at minimum add more comments and maybe think of a more descriptive name for File below?

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 for more comments. From my perspective a RawFile is an OS abstraction, and a File (maybe a better name would be good) adds Envoy semantics like stats and auto-flush.

RawFile needs to be re-implemented for different physical file layers, but the stats & auto-flush functionality can be shared across multiple RawFile impls.

Copy link
Member Author

Choose a reason for hiding this comment

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

As far as I can tell, the only use of the File class is by the AccessLogManager, so maybe AccessLogFile or something like that might be better?

Copy link
Member

Choose a reason for hiding this comment

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

If we leave it here I would probably do something like ThreadSafeFile or ThreadSafeAutoFlushFile or something. One other option is to actually completely move this file type and its creation into the access log manager interface, and then the filesystem interface just knows how to make "raw" or basic files, and then we can just call it File. WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think moving it into the access log manager interface makes sense -- we'll give it a look


typedef std::unique_ptr<RawFile> RawFilePtr;

class RawInstance {
Copy link
Member

Choose a reason for hiding this comment

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

It seems a little strange to me that we have a "raw" filesystem instance and then a "normal" filesystem instance. Why not just have a single filesystem that can create either "raw" or "regular" files? (Final names TBD).

Copy link
Member Author

Choose a reason for hiding this comment

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

The issue is that creating the "regular" file requires a Dispatcher, a lock, stats, and threading. In the final version of this (with Windows support), we want to be able to inject the platform specific filesystem code from here: https://github.com/envoyproxy/envoy/blob/f9107b26ccca409a13716b7b094bd87fec9765fb/source/exe/win32/platform_impl.h, similarly to how we inject the platform specific threading code.

To get around this, the "raw" filesystem instance doesn't know how to create "regular" files -- it is injected to the "normal" instance that does.

If this too strange, then maybe just the Api object will be able to create "regular" files (since they are not platform specific). This would end up reverting a bit of #5692, which wouldn't be a problem, except for the small bit of churn

Copy link
Member

Choose a reason for hiding this comment

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

See my comment above. Should we just have "regular" files be part of the access log manager interface since this is the interface the reopens them, etc.?

@mattklein123 mattklein123 self-assigned this Feb 6, 2019
* Abstraction for a basic file on disk.
*/
class File {
class RawFile {
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 for more comments. From my perspective a RawFile is an OS abstraction, and a File (maybe a better name would be good) adds Envoy semantics like stats and auto-flush.

RawFile needs to be re-implemented for different physical file layers, but the stats & auto-flush functionality can be shared across multiple RawFile impls.

virtual std::string errorToString(int error) PURE;
};

typedef std::unique_ptr<RawFile> RawFilePtr;
Copy link
Contributor

Choose a reason for hiding this comment

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

prefer using RawFilePtr = std::unique_ptr<RawFile>;

Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sam Smith <sesmith177@gmail.com>

wip

Signed-off-by: Sam Smith <sesmith177@gmail.com>

wip

Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Arjun Sreedharan <asreedharan@pivotal.io>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
@sesmith177 sesmith177 requested a review from lizan as a code owner February 7, 2019 19:06
@sesmith177
Copy link
Member Author

@mattklein123 we've moved the old Filesystem::File into AccessLog::AccessLogFile and updated the Api object accordingly

@mattklein123
Copy link
Member

From an interface perspective this looks awesome. +1. Looks like needs a master merge. @jmarantz do you mind taking a pass over the reworked code?

/wait


/**
* @return string a human-readable string describing the error code
* TODO(sesmith177) Abstract this method so it isn't dependant on integer error codes
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you explicitly reference IOError in pending #5829 , ( https://github.com/envoyproxy/envoy/pull/5829/files#diff-8a502ef7d52f5c01c9dc7240386e9fb4R20 )

I think that's what we'll want to use for this. Not sure if this will merge first but we should definitely converge them.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, I'll update the TODO

class TestImpl : public TimeSystemProvider, public Impl {
public:
TestImpl(std::chrono::milliseconds file_flush_interval_msec,
Thread::ThreadFactory& thread_factory, Stats::Store& stats_store)
Copy link
Contributor

Choose a reason for hiding this comment

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

That stats_store was previously needed for the stats kept by the file-system. Do we not need that anymore?

Copy link
Contributor

Choose a reason for hiding this comment

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

See also #5900 which eliminates that arg when the store is not needed.

Copy link
Member Author

Choose a reason for hiding this comment

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

The API doesn't need the stats_store anymore since it was only ever used by the Filesystem::File object, which is now in AccessLog::AccessLogFile

Copy link
Contributor

Choose a reason for hiding this comment

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

I see...that's now in AccessLogManager. Don't we want the API to provide the AccessLogManager though, and thus still need access to the StatsStore?

Copy link
Contributor

Choose a reason for hiding this comment

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

Answering myself: the AccessLogManager was previously existing and is owned by the server, which has access to the StatsStore, so it's not needed for that.

I also have to admit that I was planning (but did not document) to more broadly require access to StatsStore especially at initialization time, as part of my long-ongoing #4980 . So if you remove StatsStore from a bunch of call-sites in this PR I'm going to need to re-add it later. I'm OK with that, but I guess I'd say at the risk of speculative generality that providing access to the StatsStore is a reasonable core competency for the API object :)

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree that access to the StatsStore is reasonable functionality for the API object. While it feels a little odd to pass in an object to the API that won't be used, I think it's probably OK if we expect it to be used in the future

Copy link
Member Author

Choose a reason for hiding this comment

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

We added back the StatsStore argument to the Api constructor -- it's just ignored for now

Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Arjun Sreedharan <asreedharan@pivotal.io>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Copy link
Contributor

@jmarantz jmarantz left a comment

Choose a reason for hiding this comment

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

I started enumerating some eliminations of the stats_ arg to revert, but then realized you could just as easily go through this process yourself :)

Just search in your Files tab in the PR all the instances to createApiForTest and revert to prior state.

Thanks :)

class DiskBackedLoaderImplTest : public TestBase {
protected:
DiskBackedLoaderImplTest() : api_(Api::createApiForTest(store)) {}
DiskBackedLoaderImplTest() : api_(Api::createApiForTest()) {}
Copy link
Contributor

Choose a reason for hiding this comment

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

revert

public:
ThreadLocalStorePerf()
: store_(options_, heap_alloc_), api_(Api::createApiForTest(store_, time_system_)) {
: store_(options_, heap_alloc_), api_(Api::createApiForTest(time_system_)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

revert

class CdsApiImplTest : public TestBase {
protected:
CdsApiImplTest() : request_(&cm_.async_client_), api_(Api::createApiForTest(store_)) {}
CdsApiImplTest() : request_(&cm_.async_client_), api_(Api::createApiForTest()) {}
Copy link
Contributor

Choose a reason for hiding this comment

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

revert

class TestClusterManagerFactory : public ClusterManagerFactory {
public:
TestClusterManagerFactory() : api_(Api::createApiForTest(stats_)) {
TestClusterManagerFactory() : api_(Api::createApiForTest()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

revert

class EdsTest : public TestBase {
protected:
EdsTest() : api_(Api::createApiForTest(stats_)) { resetCluster(); }
EdsTest() : api_(Api::createApiForTest()) { resetCluster(); }
Copy link
Contributor

Choose a reason for hiding this comment

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

revert

HdsTest()
: retry_timer_(new Event::MockTimer()), server_response_timer_(new Event::MockTimer()),
async_client_(new Grpc::MockAsyncClient()), api_(Api::createApiForTest(stats_store_)),
async_client_(new Grpc::MockAsyncClient()), api_(Api::createApiForTest()),
Copy link
Contributor

Choose a reason for hiding this comment

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

revert

class LogicalDnsClusterTest : public TestBase {
protected:
LogicalDnsClusterTest() : api_(Api::createApiForTest(stats_store_)) {}
LogicalDnsClusterTest() : api_(Api::createApiForTest()) {}
Copy link
Contributor

Choose a reason for hiding this comment

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

revert

OriginalDstClusterTest()
: cleanup_timer_(new Event::MockTimer(&dispatcher_)),
api_(Api::createApiForTest(stats_store_)) {}
: cleanup_timer_(new Event::MockTimer(&dispatcher_)), api_(Api::createApiForTest()) {}
Copy link
Contributor

Choose a reason for hiding this comment

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

revert

sesmith177 and others added 2 commits February 13, 2019 09:38
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Yael Harel <yharel@pivotal.io>
Signed-off-by: Yael Harel <yharel@pivotal.io>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
@sesmith177
Copy link
Member Author

@jmarantz reverted the createApiForTest changes

@sesmith177
Copy link
Member Author

/retest

@repokitteh-read-only
Copy link

🔨 rebuilding ci/circleci: tsan (failed build)

🐱

Caused by: a #5772 (comment) was created by @sesmith177.

see: more, trace.

jmarantz
jmarantz previously approved these changes Feb 15, 2019
Copy link
Contributor

@jmarantz jmarantz 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 doing this; I'm good to go modulo the changes I suggested, but this definitely requires senior maintainer review.


Api::SysCallBoolResult FileImpl::close() {
if (!isOpen()) {
return {true, 0};
Copy link
Contributor

Choose a reason for hiding this comment

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

The doc in the interface is ambiguous, but this allows sloppy closes. WDYT of considering it an ASSERT() failure to close a non-open file, and in production to at least return false.

You'd have to change the destructor to close only if open of course.

Note the underlying system call at least returns an error if you close a file that isn't open. I couldn't tell on a quick scan if this would be incompatible with current usage in Envoy though. In which case a comment as to why you are going down this sloppy path is needed.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

}

TEST_F(AccessLogManagerImplTest, flushToLogFilePeriodically) {
NiceMock<Event::MockTimer>* timer = new NiceMock<Event::MockTimer>(&dispatcher_);
Copy link
Contributor

Choose a reason for hiding this comment

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

consider using SimulatedTimeSystem rather than mocking timers. It might be a cleaner test. It didn't look like they were carried over from master.

Copy link
Member Author

Choose a reason for hiding this comment

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

These tests were carried over from master, e.g.:

TEST_F(FileSystemImplTest, flushToLogFilePeriodically) {

Yael Harel and others added 2 commits February 21, 2019 08:49
Signed-off-by: Yael Harel <yharel@pivotal.io>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Yael Harel <yharel@pivotal.io>

Signed-off-by: Sam Smith <sesmith177@gmail.com>
@sesmith177
Copy link
Member Author

@jmarantz @mattklein123 addressed feedback

@sesmith177
Copy link
Member Author

/retest

@repokitteh-read-only
Copy link

🔨 rebuilding ci/circleci: compile_time_options (failed build)
🔨 rebuilding ci/circleci: release (failed build)

🐱

Caused by: a #5772 (comment) was created by @sesmith177.

see: more, trace.

Yael Harel added 2 commits February 21, 2019 13:39
Signed-off-by: Yael Harel <yharel@pivotal.io>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Yael Harel <yharel@pivotal.io>
Signed-off-by: Sam Smith <sesmith177@gmail.com>
Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Awesome!


/**
* @return string a human-readable string describing the error code
* TODO(sesmith177) Use the IOError class after #5829 merges
Copy link
Member

Choose a reason for hiding this comment

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

I'm going to merge this soon, so up to you if you want to merge that in or just do a quick follow up here.

Copy link
Member Author

Choose a reason for hiding this comment

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

we've got a follow-up queued up; we will fix this there

@mattklein123 mattklein123 merged commit 8aad39c into envoyproxy:master Feb 21, 2019
fredlas pushed a commit to fredlas/envoy that referenced this pull request Mar 5, 2019
…ssLogFile; add generic File object (envoyproxy#5772)

The RawFile + RawInstance classes will be used to hold platform-specific
filesystem code. The File/Instance classes will be used to add stats
collecting and periodic flushing -- they will be platform agnostic.

This is step 2/3 in adding Windows filesystem support.

Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Sophie Wigmore <swigmore@pivotal.io>
Signed-off-by: Fred Douglas <fredlas@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.

3 participants