-
Notifications
You must be signed in to change notification settings - Fork 1.6k
rpc: sink_impl: batch sending and deletion of snd_buf:s #2980
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
Changes from all commits
3574a31
9908c27
06c671c
ccd480d
dc616e8
1fb710f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,6 +40,8 @@ | |
| #include <seastar/core/queue.hh> | ||
| #include <seastar/core/weak_ptr.hh> | ||
| #include <seastar/core/scheduling.hh> | ||
| #include <seastar/core/deleter.hh> | ||
| #include <seastar/core/semaphore.hh> | ||
| #include <seastar/util/backtrace.hh> | ||
| #include <seastar/util/log.hh> | ||
|
|
||
|
|
@@ -228,6 +230,14 @@ public: | |
| void operator()(const socket_address& addr, log_level level, std::string_view str) const; | ||
| }; | ||
|
|
||
| namespace internal { | ||
| template<typename Serializer, typename... Out> | ||
| class sink_impl; | ||
|
|
||
| template<typename Serializer, typename... In> | ||
| class source_impl; | ||
| } | ||
|
|
||
| class connection { | ||
| protected: | ||
| struct socket_and_buffers { | ||
|
|
@@ -378,9 +388,9 @@ public: | |
| future<typename FrameType::return_type> read_frame_compressed(socket_address info, std::unique_ptr<compressor>& compressor, input_stream<char>& in); | ||
| friend class client; | ||
| template<typename Serializer, typename... Out> | ||
| friend class sink_impl; | ||
| friend class internal::sink_impl; | ||
| template<typename Serializer, typename... In> | ||
| friend class source_impl; | ||
| friend class internal::source_impl; | ||
|
|
||
| void suspend_for_testing(promise<>& p) { | ||
| _outgoing_queue_ready.get(); | ||
|
|
@@ -391,28 +401,90 @@ public: | |
| } | ||
| }; | ||
|
|
||
| struct deferred_snd_buf { | ||
| promise<> pr; | ||
| snd_buf data; | ||
| namespace internal { | ||
|
|
||
| template <typename T> | ||
| requires std::is_base_of_v<bi::slist_base_hook<>, T> | ||
| class batched_queue { | ||
| using list_type = boost::intrusive::slist<T, boost::intrusive::cache_last<true>, boost::intrusive::constant_time_size<false>>; | ||
|
|
||
| std::function<future<>(T*)> _process_func; | ||
| shard_id _processing_shard; | ||
| list_type _queue; | ||
| list_type _cur_batch; | ||
| future<> _process_fut = make_ready_future(); | ||
|
|
||
| public: | ||
| batched_queue(std::function<future<>(T*)> process_func, shard_id processing_shard) | ||
| : _process_func(std::move(process_func)) | ||
| , _processing_shard(processing_shard) | ||
| {} | ||
|
|
||
| ~batched_queue() { | ||
| assert(_process_fut.available()); | ||
| } | ||
|
|
||
| future<> stop() noexcept { | ||
| return std::exchange(_process_fut, make_ready_future()); | ||
| } | ||
|
|
||
| void enqueue(T* buf) noexcept { | ||
| _queue.push_back(*buf); | ||
| if (_process_fut.available()) { | ||
| _process_fut = process_loop(); | ||
| } | ||
| } | ||
|
|
||
| future<> process_loop() { | ||
| return seastar::do_until([this] { return _queue.empty(); }, [this] { | ||
| _cur_batch = std::exchange(_queue, list_type()); | ||
| return smp::submit_to(_processing_shard, [this] { | ||
| return seastar::do_until([this] { return _cur_batch.empty(); }, [this] { | ||
| auto* buf = &_cur_batch.front(); | ||
| _cur_batch.pop_front(); | ||
| return _process_func(buf); | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| // Safely delete the original allocation buffer on the local shard | ||
| // When deleted after it was sent on the remote shard, we queue | ||
| // up the buffer pointers to be destroyed and deleted as a batch | ||
| // back on the local shard. | ||
| class snd_buf_deleter_impl final : public deleter::impl { | ||
| snd_buf* _obj_ptr; | ||
| batched_queue<snd_buf>& _delete_queue; | ||
|
|
||
| public: | ||
| snd_buf_deleter_impl(snd_buf* obj_ptr, batched_queue<snd_buf>& delete_queue) | ||
| : impl(deleter()) | ||
| , _obj_ptr(obj_ptr) | ||
| , _delete_queue(delete_queue) | ||
| {} | ||
|
|
||
| virtual ~snd_buf_deleter_impl() override { | ||
| _delete_queue.enqueue(_obj_ptr); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This may throw, no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it might. That's one of the reasons I prefer the intrusive list approach. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @avikivity what do you think regarding using intrusive lists for the queues vs. a vector? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's okay. intrusive lists are expensive because each item will take a cache miss and stall the machine. With a vector, each miss brings in multiple items and can initiate a prefetch for even more, so the stall is better amortized. Still, we can allow the list here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An alternative is boost::static_vector (or std::inplace_vector). It does not allocate. The downside is that if the capacity is exhausted, the connection shard has to stop processing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Here the vector contains pointers to the snd_buf:s so the random access to memory happens anyway. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @avikivity after playing around a bit with the idea of a static vector / array, it will require another semaphore and managing the units throughout the snd_buf lifecycle which adds another complication. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Used boost::intrusive::slist in 1fb710f There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The hardware will prefetch pointers from the array automatically. You cannot prefetch across a linked list because you don't know what address to prefetch. You have to wait until you have data in the next node. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ok |
||
| } | ||
| }; | ||
|
|
||
| // send data Out... | ||
| template<typename Serializer, typename... Out> | ||
| class sink_impl : public sink<Out...>::impl { | ||
| // Used on the shard *this lives on. | ||
| alignas (cache_line_size) uint64_t _next_seq_num = 1; | ||
|
|
||
| // Used on the shard the _conn lives on. | ||
| struct alignas (cache_line_size) { | ||
| uint64_t last_seq_num = 0; | ||
| std::map<uint64_t, deferred_snd_buf> out_of_order_bufs; | ||
| } _remote_state; | ||
| batched_queue<snd_buf> _send_queue; | ||
| batched_queue<snd_buf> _delete_queue; | ||
|
|
||
| public: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The patch asserts that, not "makes sure" :) |
||
| sink_impl(xshard_connection_ptr con) : sink<Out...>::impl(std::move(con)) { this->_con->get()->_sink_closed = false; } | ||
| sink_impl(xshard_connection_ptr con); | ||
| future<> operator()(const Out&... args) override; | ||
| future<> close() override; | ||
| future<> flush() override; | ||
| future<> close() noexcept override; | ||
| future<> flush() noexcept override; | ||
| ~sink_impl() override; | ||
|
|
||
| private: | ||
| // Runs on connection shard | ||
| future<> send_buffer(snd_buf* buf); | ||
| }; | ||
|
|
||
| // receive data In... | ||
|
|
@@ -423,6 +495,8 @@ public: | |
| future<std::optional<std::tuple<In...>>> operator()() override; | ||
| }; | ||
|
|
||
| } // namespace internal | ||
|
|
||
| class client : public rpc::connection, public weakly_referencable<client> { | ||
| socket _socket; | ||
| id_type _message_id = 1; | ||
|
|
@@ -562,7 +636,7 @@ public: | |
| } | ||
| xshard_connection_ptr s = make_lw_shared(make_foreign(static_pointer_cast<rpc::connection>(c))); | ||
| this->register_stream(c->get_connection_id(), s); | ||
| return sink<Out...>(make_shared<sink_impl<Serializer, Out...>>(std::move(s))); | ||
| return sink<Out...>(make_shared<internal::sink_impl<Serializer, Out...>>(std::move(s))); | ||
| }).handle_exception([c] (std::exception_ptr eptr) { | ||
| // If await_connection fails we need to stop the client | ||
| // before destroying it. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this depends on do_until (in process_loop) atomically checking the termination condition and making the future ready if needed. Which does hold.