filesystem: move file object with stats, threading + flushing to AccessLogFile; add generic File object#5772
filesystem: move file object with stats, threading + flushing to AccessLogFile; add generic File object#5772mattklein123 merged 24 commits intoenvoyproxy:masterfrom greenhouse-org:filesystem-decorator
Conversation
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>
jmarantz
left a comment
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
Explicit type here as it's not obvious from context.
| 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_))); |
There was a problem hiding this comment.
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(); |
| // 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")) { |
There was a problem hiding this comment.
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(); |
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>
|
@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_))); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
OK can you add a TODO for that?
Thanks!
| 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_))); |
There was a problem hiding this comment.
OK can you add a TODO for that?
Thanks!
mattklein123
left a comment
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
+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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.?
| * Abstraction for a basic file on disk. | ||
| */ | ||
| class File { | ||
| class RawFile { |
There was a problem hiding this comment.
+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; |
There was a problem hiding this comment.
prefer using RawFilePtr = std::unique_ptr<RawFile>;
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>
|
@mattklein123 we've moved the old Filesystem::File into AccessLog::AccessLogFile and updated the Api object accordingly |
|
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
That stats_store was previously needed for the stats kept by the file-system. Do we not need that anymore?
There was a problem hiding this comment.
See also #5900 which eliminates that arg when the store is not needed.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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>
jmarantz
left a comment
There was a problem hiding this comment.
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()) {} |
| public: | ||
| ThreadLocalStorePerf() | ||
| : store_(options_, heap_alloc_), api_(Api::createApiForTest(store_, time_system_)) { | ||
| : store_(options_, heap_alloc_), api_(Api::createApiForTest(time_system_)) { |
| class CdsApiImplTest : public TestBase { | ||
| protected: | ||
| CdsApiImplTest() : request_(&cm_.async_client_), api_(Api::createApiForTest(store_)) {} | ||
| CdsApiImplTest() : request_(&cm_.async_client_), api_(Api::createApiForTest()) {} |
| class TestClusterManagerFactory : public ClusterManagerFactory { | ||
| public: | ||
| TestClusterManagerFactory() : api_(Api::createApiForTest(stats_)) { | ||
| TestClusterManagerFactory() : api_(Api::createApiForTest()) { |
test/common/upstream/eds_test.cc
Outdated
| class EdsTest : public TestBase { | ||
| protected: | ||
| EdsTest() : api_(Api::createApiForTest(stats_)) { resetCluster(); } | ||
| EdsTest() : api_(Api::createApiForTest()) { resetCluster(); } |
test/common/upstream/hds_test.cc
Outdated
| 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()), |
| class LogicalDnsClusterTest : public TestBase { | ||
| protected: | ||
| LogicalDnsClusterTest() : api_(Api::createApiForTest(stats_store_)) {} | ||
| LogicalDnsClusterTest() : api_(Api::createApiForTest()) {} |
| OriginalDstClusterTest() | ||
| : cleanup_timer_(new Event::MockTimer(&dispatcher_)), | ||
| api_(Api::createApiForTest(stats_store_)) {} | ||
| : cleanup_timer_(new Event::MockTimer(&dispatcher_)), api_(Api::createApiForTest()) {} |
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>
|
@jmarantz reverted the |
|
/retest |
|
🔨 rebuilding |
jmarantz
left a comment
There was a problem hiding this comment.
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}; |
There was a problem hiding this comment.
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.
| } | ||
|
|
||
| TEST_F(AccessLogManagerImplTest, flushToLogFilePeriodically) { | ||
| NiceMock<Event::MockTimer>* timer = new NiceMock<Event::MockTimer>(&dispatcher_); |
There was a problem hiding this comment.
consider using SimulatedTimeSystem rather than mocking timers. It might be a cleaner test. It didn't look like they were carried over from master.
There was a problem hiding this comment.
These tests were carried over from master, e.g.:
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>
|
@jmarantz @mattklein123 addressed feedback |
|
/retest |
|
🔨 rebuilding |
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>
|
|
||
| /** | ||
| * @return string a human-readable string describing the error code | ||
| * TODO(sesmith177) Use the IOError class after #5829 merges |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
we've got a follow-up queued up; we will fix this there
…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>
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::Fileobject (with stats, threading, etc.) is only used by theAccessLogManager, so move it into that interface. It is now completely cross-platform. ReplaceFilesystem::Filewith a basic file object that each platform (Windows, Linux, etc..) can use to provide its own implementationThis 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