-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Add async_files library to support filesystem buffering and filesystem caching #20332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
b021036
Add async_files library to support filesystem buffering and filesyste…
ravenblackx 9c19d71
Redo async_files so a handle is associated with a file and manager pr…
ravenblackx df818e8
Undo coverage weakening - new version has better coverage
ravenblackx 7c329ac
Replace mockable PosixFileOperations with Api::OsSysCalls
ravenblackx 21649cc
Rename thread-local variables
ravenblackx 3628850
Add proto config for instantiating AsyncFileManager singletons
ravenblackx 6bfd14c
Make manager instantiation take place through a singleton
ravenblackx 7fb7e4a
Merge branch 'main' of https://github.com/envoyproxy/envoy into async…
ravenblackx f9c9870
Fix to work with OsSysCalls and SingletonManager
ravenblackx bb42739
Remove the mocks, mock treatment can now be done by mocking OsSysCall…
ravenblackx 26a6f84
Remove outdated dependency and unused result_ field
ravenblackx ca42468
s/thread/callback/ comment correction
ravenblackx 6722df2
Update api coverage constraint to its new improved values
ravenblackx 14b103e
Skip test on Windows, remove debug output
ravenblackx 51da0ea
Use Buffer::InstancePtr, remove redundant Envoy::, other nits
ravenblackx b1006c9
Deflake tests by using std::promise instead of absl::Barrier
ravenblackx 7326ad8
Fix outdated comment on duplicate
ravenblackx 45e126b
Add ENVOY_BUG to unrecognized error codes
ravenblackx fef75a7
Make AsyncFileManagerFactory validate that config within a single man…
ravenblackx 0b7f16e
Fix format, update README
ravenblackx 511451f
Replace panics with thrown exceptions
ravenblackx f1fa626
Make enqueue actions return a StatusOr<CancelFunction> (WIP)
ravenblackx 3207fde
Merge branch 'main' of https://github.com/envoyproxy/envoy into async…
ravenblackx f4dae8a
Finish up EXPECT_OK and returning StatusOr<CancelFunction>
ravenblackx b0d72d6
[trivial] Remove underscore from unused go package name
ravenblackx 4ef5ca4
[trivial] Re-add underscore in unused go package name
ravenblackx 5df0c67
Add extension to extensions_build_config etc., and mark the api work_…
ravenblackx 87a1592
Merge branch 'main' of https://github.com/envoyproxy/envoy into async…
ravenblackx 155f3e9
Add async_files to api/BUILD v3_protos
ravenblackx 107f2fb
Change library rule to extension rule
ravenblackx 31b89ec
Add doc link in common_messages.rst
ravenblackx 3369af0
Add extension-category to async_file_manager.proto
ravenblackx a2ff010
Move extension-category inside the message so it works
ravenblackx 7e3ab26
Add #extension tag to proto
ravenblackx e17908a
Merge branch 'main' of https://github.com/envoyproxy/envoy into async…
ravenblackx 42e139d
Add skip_on_windows and add to WINDOWS_SKIP_TARGETS
ravenblackx 700ebe9
Merge branch 'main' of https://github.com/envoyproxy/envoy into async…
ravenblackx 2fe97f3
De-extension the async_files library
ravenblackx d09c87f
Undo bzl autoformat, flatten list, make switches stricter, lock less …
ravenblackx 94eec25
Add PANIC_DUE_TO_CORRUPT_ENUM, and lose the 100% coverage
ravenblackx 6e6aa04
Use envoy_package not envoy_extension_package
ravenblackx fd45a56
Back to extension_package!
ravenblackx e61dd51
Remove extension_package, autofix was not a good fix!
ravenblackx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. | ||
|
|
||
| load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") | ||
|
|
||
| licenses(["notice"]) # Apache 2 | ||
|
|
||
| api_proto_package( | ||
| deps = [ | ||
| "@com_github_cncf_udpa//udpa/annotations:pkg", | ||
| "@com_github_cncf_udpa//xds/annotations/v3:pkg", | ||
| ], | ||
| ) |
41 changes: 41 additions & 0 deletions
41
api/envoy/extensions/common/async_files/v3/async_file_manager.proto
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| package envoy.extensions.common.async_files.v3; | ||
|
|
||
| import "xds/annotations/v3/status.proto"; | ||
|
|
||
| import "udpa/annotations/status.proto"; | ||
| import "validate/validate.proto"; | ||
|
|
||
| option java_package = "io.envoyproxy.envoy.extensions.common.async_files.v3"; | ||
| option java_outer_classname = "AsyncFileManagerProto"; | ||
| option java_multiple_files = true; | ||
| option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/common/async_files/v3;async_filesv3"; | ||
| option (udpa.annotations.file_status).package_version_status = ACTIVE; | ||
| option (xds.annotations.v3.file_status).work_in_progress = true; | ||
|
|
||
| // [#protodoc-title: AsyncFileManager configuration] | ||
|
|
||
| // Configuration to instantiate or select a singleton `AsyncFileManager`. | ||
| message AsyncFileManagerConfig { | ||
| message ThreadPool { | ||
| // The number of threads to use. If unset or zero, will default to the number | ||
| // of concurrent threads the hardware supports. This default is subject to | ||
| // change if performance analysis suggests it. | ||
| uint32 thread_count = 1; | ||
| } | ||
|
|
||
| // An optional identifier for the manager. An empty string is a valid identifier | ||
| // for a common, default `AsyncFileManager`. | ||
| // | ||
| // Reusing the same id with different configurations in the same envoy instance | ||
| // is an error. | ||
| string id = 1; | ||
|
|
||
| oneof manager_type { | ||
| option (validate.required) = true; | ||
|
|
||
| // Configuration for a thread-pool based async file manager. | ||
| ThreadPool thread_pool = 2; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| load( | ||
| "//bazel:envoy_build_system.bzl", | ||
| "envoy_cc_library", | ||
| "envoy_extension_package", | ||
| ) | ||
|
|
||
| licenses(["notice"]) # Apache 2 | ||
|
|
||
| envoy_extension_package() | ||
|
|
||
| envoy_cc_library( | ||
| name = "async_files_base", | ||
| srcs = [ | ||
| "async_file_action.cc", | ||
| "async_file_context_base.cc", | ||
| ], | ||
| hdrs = [ | ||
| "async_file_action.h", | ||
| "async_file_context_base.h", | ||
| "async_file_handle.h", | ||
| "async_file_manager.h", | ||
| ], | ||
| deps = [ | ||
| ":status_after_file_error", | ||
| "//source/common/buffer:buffer_lib", | ||
| "//source/common/common:utility_lib", | ||
| "@com_google_absl//absl/base", | ||
| "@com_google_absl//absl/status:statusor", | ||
| ], | ||
| ) | ||
|
|
||
| envoy_cc_library( | ||
| name = "async_files_thread_pool", | ||
| srcs = [ | ||
| "async_file_context_thread_pool.cc", | ||
| "async_file_manager_thread_pool.cc", | ||
| ], | ||
| hdrs = [ | ||
| "async_file_context_thread_pool.h", | ||
| "async_file_manager_thread_pool.h", | ||
| ], | ||
| deps = [ | ||
| ":async_files_base", | ||
| ":status_after_file_error", | ||
| "//source/common/api:os_sys_calls_lib", | ||
| "//source/common/buffer:buffer_lib", | ||
| "@com_google_absl//absl/base", | ||
| "@com_google_absl//absl/status:statusor", | ||
| "@envoy_api//envoy/extensions/common/async_files/v3:pkg_cc_proto", | ||
| ], | ||
| ) | ||
|
|
||
| envoy_cc_library( | ||
| name = "async_files", | ||
| srcs = [ | ||
| "async_file_manager.cc", | ||
| "async_file_manager_factory.cc", | ||
| ], | ||
| hdrs = [ | ||
| "async_file_manager_factory.h", | ||
| ], | ||
| deps = [ | ||
| ":async_files_thread_pool", | ||
| "//source/common/api:os_sys_calls_lib", | ||
| "//source/common/buffer:buffer_lib", | ||
| "//source/common/protobuf:utility_lib", | ||
| "@com_google_absl//absl/base", | ||
| "@com_google_absl//absl/status:statusor", | ||
| "@envoy_api//envoy/extensions/common/async_files/v3:pkg_cc_proto", | ||
| ], | ||
| ) | ||
|
|
||
| envoy_cc_library( | ||
| name = "status_after_file_error", | ||
| srcs = ["status_after_file_error.cc"], | ||
| hdrs = ["status_after_file_error.h"], | ||
| deps = [ | ||
| "//envoy/api:os_sys_calls_interface", | ||
| "//source/common/common:assert_lib", | ||
| "//source/common/common:utility_lib", | ||
| "@com_google_absl//absl/status", | ||
| ], | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # AsyncFileManager | ||
|
|
||
| An `AsyncFileManager` should be a singleton or similarly long-lived scope. It represents a | ||
| thread pool for performing file operations asynchronously. | ||
|
|
||
| `AsyncFileManager` can create `AsyncFileHandle`s via `createAnonymousFile` or `openExistingFile`, | ||
| can postpone queuing file actions using `whenReady`, and can delete files via `unlink`. | ||
|
|
||
| # AsyncFileHandle | ||
|
|
||
| An `AsyncFileHandle` represents a context in which asynchronous file operations can be performed. It is associated with at most one file at a time. | ||
|
|
||
| Each action on an AsyncFileHandle is effectively an "enqueue" action, in that it places the action in the manager's execution queue, it does not immediately perform the requested action. Actions on an `AsyncFileHandle` can be *chained*, by enqueuing another action during the callback from a previous action, e.g. | ||
|
|
||
| ``` | ||
| manager->createAnonymousFile("/tmp", [](absl::StatusOr<AsyncFileHandle> opened) { | ||
| if (!opened.ok()) { | ||
| std::cout << "oh no, an error: " << opened.status() << std::endl; | ||
| return; | ||
| } | ||
| auto handle = opened.value(); | ||
| handle->write(someBuffer, 0, [handle](absl::StatusOr<size_t> written) { | ||
| if (!written.ok()) { | ||
| std::cout << "oh no, an error: " << written.status() << std::endl; | ||
| return; | ||
| } | ||
| std::cout << "wrote " << written.value() << " bytes" << std::endl; | ||
| handle->close([](absl::Status closed) { | ||
| if (!closed.ok()) { | ||
| std::cout << "oh no, an error: " << closed << std::endl; | ||
| } | ||
| }).IgnoreError(); // A returned error only occurs if the file handle was closed. | ||
| }).IgnoreError(); // A returned error only occurs if the file handle was closed. | ||
| }); | ||
| ``` | ||
|
|
||
| Will open an unnamed file, write 5 bytes, and close it. (This is just for explanatory purposes, in practice you would most likely want the callbacks to call something on `this` rather than nesting lambdas!) | ||
|
|
||
| Chaining actions, as opposed to enqueuing, passing the result to a main thread, and from there enqueuing again, will not yield the thread in a thread-pool based implementation. An advantage of this is that, for example, if 5 workers all wanted to write a 100kb file at the same moment, with unchained requests in a one-thread threadpool the sequence would most likely resemble | ||
|
|
||
| ``` | ||
| OPEN-OPEN-OPEN-OPEN-OPEN-WRITE-WRITE-WRITE-WRITE-WRITE-CLOSE-CLOSE-CLOSE-CLOSE-CLOSE | ||
| ``` | ||
|
|
||
| Versus with appropriately chained requests in a one-thread threadpool the sequence would be guaranteed to be | ||
|
|
||
| ``` | ||
| OPEN-WRITE-CLOSE-OPEN-WRITE-CLOSE-OPEN-WRITE-CLOSE-OPEN-WRITE-CLOSE-OPEN-WRITE-CLOSE | ||
| ``` | ||
|
|
||
| Expand this concept to 100+ files all asking to be written at once and you can immediately see the advantages of chaining; not having the resource issues of many files open at the same time, more localized access, etc. | ||
|
|
||
| ## cancellation | ||
|
|
||
| Each action function returns a cancellation function which can be called to remove an action from the queue and prevent the callback from being called. If the execution is already in progress, it may be undone (e.g. a file open operation will close the file if it is opening when cancel is called). The cancel function will block if the callback is already in progress when cancel is called, until the callback completes. This should not be a long block, as callbacks should be short (see callbacks below). | ||
|
|
||
| As such, a client should ensure that the cleanup order is consistent - if a callback captures a file handle, the client should clean up that file handle (if present) *after* calling cancel, in case the file was opened during the call to cancel. | ||
|
|
||
| ## callbacks | ||
|
|
||
| The callbacks passed to `AsyncFileHandle` and `AsyncFileManager` are scheduled in a thread or thread pool belonging to the AsyncFileManager - therefore they should be doing minimal work, not blocking (for more than a trivial data-guard lock), and return promptly. If any significant work or blocking is required, the result of the previous action should be passed from the callback to another thread (via some dispatcher or other queuing mechanism) so the manager's thread can continue performing file operations for other clients. | ||
|
|
||
| ## Possible actions | ||
|
|
||
| See `async_file_handle.h` for the actions that can currently be queued on an `AsyncFileHandle`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| #include "source/extensions/common/async_files/async_file_action.h" | ||
|
|
||
| #include <thread> | ||
|
|
||
| namespace Envoy { | ||
| namespace Extensions { | ||
| namespace Common { | ||
| namespace AsyncFiles { | ||
|
|
||
| void AsyncFileAction::cancel() { | ||
| auto previousState = state_.exchange(State::Cancelled); | ||
| if (previousState == State::InCallback) { | ||
| // A gentle spin-lock. This situation should be rare, and callbacks are | ||
| // supposed to be quick, so we don't need a real lock here. | ||
| while (state_.load() != State::Done) { | ||
| std::this_thread::yield(); | ||
| } | ||
| } else if (previousState == State::Done) { | ||
| state_.store(State::Done); | ||
| } | ||
| } | ||
|
|
||
| } // namespace AsyncFiles | ||
| } // namespace Common | ||
| } // namespace Extensions | ||
| } // namespace Envoy |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| #pragma once | ||
|
|
||
| #include <atomic> | ||
| #include <functional> | ||
|
|
||
| #include "envoy/common/pure.h" | ||
|
|
||
| #include "source/common/common/assert.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Extensions { | ||
| namespace Common { | ||
| namespace AsyncFiles { | ||
|
|
||
| // A CancelFunction attempts to stop an action in flight. | ||
| // * If the action already occurred, the CancelFunction does nothing. | ||
| // * If the action is already calling the callback, CancelFunction blocks until the callback | ||
| // completes. | ||
| // * If the action is already executing, CancelFunction causes the removal of any resource-consuming | ||
| // return value (e.g. file handles), and prevents the callback. | ||
| // * If the action is still just queued, CancelFunction prevents its execution. | ||
| using CancelFunction = std::function<void()>; | ||
|
|
||
| // Actions to be passed to asyncFileManager->enqueue. | ||
| class AsyncFileAction { | ||
| public: | ||
| virtual ~AsyncFileAction() = default; | ||
|
|
||
| // Cancel the action, as much as possible. | ||
| // | ||
| // If the action has not been started, it will become a no-op. | ||
| // | ||
| // If the action has started, onCancelledBeforeCallback will be called, | ||
| // and the callback will not. | ||
| // | ||
| // If the callback is already being called, cancel will block until the | ||
| // callback has completed. | ||
| // | ||
| // If the action is already complete, cancel does nothing. | ||
| void cancel(); | ||
|
|
||
| // Performs the action represented by this instance, and calls the callback | ||
| // on completion or on error. | ||
| virtual void execute() PURE; | ||
|
|
||
| protected: | ||
| enum class State { Queued, Executing, InCallback, Done, Cancelled }; | ||
| std::atomic<State> state_{State::Queued}; | ||
| }; | ||
|
|
||
| // All concrete AsyncFileActions are a subclass of AsyncFileActionWithResult. | ||
| // The template allows for different on_complete callback signatures appropriate | ||
| // to each specific action. | ||
| // | ||
| // on_complete callbacks run in the AsyncFileManager's thread pool, and therefore: | ||
| // 1. Should avoid using variables that may be out of scope by the time the callback is called. | ||
| // 2. May need to lock-guard variables that can be changed in other threads. | ||
| // 3. Must not block significantly or do significant work - if anything time-consuming is required | ||
| // the result should be passed to another thread for handling. | ||
| template <typename T> class AsyncFileActionWithResult : public AsyncFileAction { | ||
| public: | ||
| explicit AsyncFileActionWithResult(std::function<void(T)> on_complete) | ||
| : on_complete_(on_complete) {} | ||
|
|
||
| void execute() final { | ||
| State expected = State::Queued; | ||
| if (!state_.compare_exchange_strong(expected, State::Executing)) { | ||
| ASSERT(expected == State::Cancelled); | ||
| return; | ||
| } | ||
| expected = State::Executing; | ||
| T result = executeImpl(); | ||
| if (!state_.compare_exchange_strong(expected, State::InCallback)) { | ||
| ASSERT(expected == State::Cancelled); | ||
| onCancelledBeforeCallback(std::move(result)); | ||
| return; | ||
| } | ||
| on_complete_(std::move(result)); | ||
| state_.store(State::Done); | ||
| } | ||
|
|
||
| protected: | ||
| // Performs any action to undo side-effects of the execution if the callback | ||
| // has not yet been called (e.g. closing a file that was just opened). | ||
| // Not necessary for things that don't make persistent resources, | ||
| // e.g. cancelling a write does not have to undo the write. | ||
| virtual void onCancelledBeforeCallback(T){}; | ||
|
|
||
| // Implementation of the actual action. | ||
| virtual T executeImpl() PURE; | ||
|
|
||
| private: | ||
| std::function<void(T)> on_complete_; | ||
| }; | ||
|
|
||
| } // namespace AsyncFiles | ||
| } // namespace Common | ||
| } // namespace Extensions | ||
| } // namespace Envoy | ||
27 changes: 27 additions & 0 deletions
27
source/extensions/common/async_files/async_file_context_base.cc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| #include "source/extensions/common/async_files/async_file_context_base.h" | ||
|
|
||
| #include <functional> | ||
| #include <memory> | ||
| #include <utility> | ||
|
|
||
| #include "source/extensions/common/async_files/async_file_action.h" | ||
| #include "source/extensions/common/async_files/async_file_manager.h" | ||
|
|
||
| #include "absl/base/thread_annotations.h" | ||
| #include "absl/status/statusor.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Extensions { | ||
| namespace Common { | ||
| namespace AsyncFiles { | ||
|
|
||
| AsyncFileContextBase::AsyncFileContextBase(AsyncFileManager& manager) : manager_(manager) {} | ||
|
|
||
| std::function<void()> AsyncFileContextBase::enqueue(std::shared_ptr<AsyncFileAction> action) { | ||
| return manager_.enqueue(std::move(action)); | ||
| } | ||
|
|
||
| } // namespace AsyncFiles | ||
| } // namespace Common | ||
| } // namespace Extensions | ||
| } // namespace Envoy |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.