LCOV - code coverage report
Current view: top level - corosio/native/detail/posix - posix_stream_file_service.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 91.1 % 158 144 14
Test Date: 2026-09-02 21:27:06 Functions: 100.0 % 16 16

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Michael Vandeberg
       3                 : //
       4                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6                 : //
       7                 : // Official repository: https://github.com/cppalliance/corosio
       8                 : //
       9                 : 
      10                 : #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_STREAM_FILE_SERVICE_HPP
      11                 : #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_STREAM_FILE_SERVICE_HPP
      12                 : 
      13                 : #include <boost/corosio/detail/platform.hpp>
      14                 : 
      15                 : #if BOOST_COROSIO_POSIX
      16                 : 
      17                 : #include <boost/corosio/native/detail/posix/posix_stream_file.hpp>
      18                 : #include <boost/corosio/native/detail/reactor/reactor_scheduler.hpp>
      19                 : #include <boost/corosio/detail/file_service.hpp>
      20                 : #include <boost/corosio/detail/thread_pool.hpp>
      21                 : 
      22                 : #include <mutex>
      23                 : #include <unordered_map>
      24                 : 
      25                 : namespace boost::corosio::detail {
      26                 : 
      27                 : /** Stream file service for POSIX backends.
      28                 : 
      29                 :     Owns all posix_stream_file instances. Thread lifecycle is
      30                 :     managed by the thread_pool service (shared with resolver).
      31                 : */
      32                 : class BOOST_COROSIO_DECL posix_stream_file_service final
      33                 :     : public file_service
      34                 : {
      35                 : public:
      36 HIT        1790 :     posix_stream_file_service(
      37                 :         capy::execution_context& ctx, scheduler& sched)
      38            3580 :         : sched_(&sched)
      39            1790 :         , pool_(ctx)
      40                 :     {
      41            1790 :     }
      42                 : 
      43            3580 :     ~posix_stream_file_service() override = default;
      44                 : 
      45                 :     posix_stream_file_service(posix_stream_file_service const&)            = delete;
      46                 :     posix_stream_file_service& operator=(posix_stream_file_service const&) = delete;
      47                 : 
      48             114 :     io_object::implementation* construct() override
      49                 :     {
      50             114 :         auto ptr   = std::make_shared<posix_stream_file>(*this);
      51             114 :         auto* impl = ptr.get();
      52                 : 
      53                 :         {
      54             114 :             std::lock_guard<std::mutex> lock(mutex_);
      55             114 :             file_list_.push_back(impl);
      56             114 :             file_ptrs_[impl] = std::move(ptr);
      57             114 :         }
      58                 : 
      59             114 :         return impl;
      60             114 :     }
      61                 : 
      62             112 :     void destroy(io_object::implementation* p) override
      63                 :     {
      64             112 :         auto& impl = static_cast<posix_stream_file&>(*p);
      65             112 :         impl.cancel();
      66             112 :         impl.close_file();
      67             112 :         destroy_impl(impl);
      68             112 :     }
      69                 : 
      70             197 :     void close(io_object::handle& h) override
      71                 :     {
      72             197 :         if (h.get())
      73                 :         {
      74             197 :             auto& impl = static_cast<posix_stream_file&>(*h.get());
      75             197 :             impl.cancel();
      76             197 :             impl.close_file();
      77                 :         }
      78             197 :     }
      79                 : 
      80              99 :     std::error_code open_file(
      81                 :         stream_file::implementation& impl,
      82                 :         std::filesystem::path const& path,
      83                 :         file_base::flags mode) override
      84                 :     {
      85                 :         // Unavailable in the unsafe tier: the file thread pool completes
      86                 :         // cross-thread, which the lockless scheduler cannot accept.
      87              99 :         if (sched_->scheduler_locking_disabled())
      88               2 :             return std::make_error_code(std::errc::operation_not_supported);
      89              97 :         return static_cast<posix_stream_file&>(impl).open_file(path, mode);
      90                 :     }
      91                 : 
      92            1790 :     void shutdown() override
      93                 :     {
      94            1790 :         std::lock_guard<std::mutex> lock(mutex_);
      95            1792 :         for (auto* impl = file_list_.pop_front(); impl != nullptr;
      96               2 :              impl       = file_list_.pop_front())
      97                 :         {
      98               2 :             impl->cancel();
      99               2 :             impl->close_file();
     100                 :         }
     101            1790 :         file_ptrs_.clear();
     102            1790 :     }
     103                 : 
     104             112 :     void destroy_impl(posix_stream_file& impl)
     105                 :     {
     106             112 :         std::lock_guard<std::mutex> lock(mutex_);
     107             112 :         file_list_.remove(&impl);
     108             112 :         file_ptrs_.erase(&impl);
     109             112 :     }
     110                 : 
     111              61 :     void post(scheduler_op* op)
     112                 :     {
     113              61 :         sched_->post(op);
     114              61 :     }
     115                 : 
     116                 :     void work_started() noexcept
     117                 :     {
     118                 :         sched_->work_started();
     119                 :     }
     120                 : 
     121                 :     void work_finished() noexcept
     122                 :     {
     123                 :         sched_->work_finished();
     124                 :     }
     125                 : 
     126                 :     /** Return the thread pool that runs this service's file work.
     127                 : 
     128                 :         The pool's service is created on first use, so this can fail
     129                 :         where a plain accessor could not. Its workers start later, on
     130                 :         the first post, and a thread the system refuses there is
     131                 :         reported by that post rather than thrown here.
     132                 : 
     133                 :         @throws std::bad_alloc If the service cannot be allocated.
     134                 : 
     135                 :         @return The context's shared blocking-I/O pool.
     136                 : 
     137                 :         @see thread_pool_ref::get
     138                 :     */
     139              61 :     thread_pool& pool()
     140                 :     {
     141              61 :         return pool_.get();
     142                 :     }
     143                 : 
     144                 : private:
     145                 :     scheduler* sched_;
     146                 :     thread_pool_ref pool_;
     147                 :     std::mutex mutex_;
     148                 :     intrusive_list<posix_stream_file> file_list_;
     149                 :     std::unordered_map<posix_stream_file*, std::shared_ptr<posix_stream_file>>
     150                 :         file_ptrs_;
     151                 : };
     152                 : 
     153                 : /** Get or create the stream file service for the given context. */
     154                 : inline posix_stream_file_service&
     155            1790 : get_stream_file_service(capy::execution_context& ctx, scheduler& sched)
     156                 : {
     157            1790 :     return ctx.make_service<posix_stream_file_service>(sched);
     158                 : }
     159                 : 
     160                 : // ---------------------------------------------------------------------------
     161                 : // posix_stream_file inline implementations (require complete service type)
     162                 : // ---------------------------------------------------------------------------
     163                 : 
     164                 : inline std::coroutine_handle<>
     165              40 : posix_stream_file::read_some(
     166                 :     std::coroutine_handle<> h,
     167                 :     capy::executor_ref ex,
     168                 :     buffer_param param,
     169                 :     std::stop_token token,
     170                 :     std::error_code* ec,
     171                 :     std::size_t* bytes_out)
     172                 : {
     173              40 :     auto& op = read_op_;
     174              40 :     op.reset();
     175              40 :     op.is_read = true;
     176                 : 
     177                 :     // Closed-object contract outranks the zero-length no-op.
     178              40 :     if (fd_ < 0)
     179                 :     {
     180               6 :         *ec        = make_error_code(std::errc::bad_file_descriptor);
     181               6 :         *bytes_out = 0;
     182               6 :         op.cont.h = h;
     183               6 :         return dispatch_coro(ex, op.cont);
     184                 :     }
     185                 : 
     186              34 :     capy::mutable_buffer bufs[max_buffers];
     187              34 :     op.iovec_count = static_cast<int>(param.copy_to(bufs, max_buffers));
     188                 : 
     189              34 :     if (op.iovec_count == 0)
     190                 :     {
     191               2 :         *ec        = {};
     192               2 :         *bytes_out = 0;
     193               2 :         op.cont.h = h;
     194               2 :         return dispatch_coro(ex, op.cont);
     195                 :     }
     196                 : 
     197              64 :     for (int i = 0; i < op.iovec_count; ++i)
     198                 :     {
     199              32 :         op.iovecs[i].iov_base = bufs[i].data();
     200              32 :         op.iovecs[i].iov_len  = bufs[i].size();
     201                 :     }
     202                 : 
     203              32 :     op.h         = h;
     204              32 :     op.ex        = ex;
     205              32 :     op.ec_out    = ec;
     206              32 :     op.bytes_out = bytes_out;
     207              32 :     op.start(token);
     208                 : 
     209              32 :     op.ex.on_work_started();
     210                 : 
     211              32 :     read_pool_op_.file_ = this;
     212              32 :     read_pool_op_.ref_  = this->shared_from_this();
     213              32 :     read_pool_op_.func_ = &posix_stream_file::do_read_work;
     214              32 :     if (auto pec = svc_.pool().post(&read_pool_op_))
     215                 :     {
     216                 :         // The pool is shutting down, or the system refused it a thread.
     217                 :         // Nothing of this read went cross-thread, so it answers here
     218                 :         // like the closed-descriptor and zero-length exits above rather
     219                 :         // than through a completion the scheduler has to carry back.
     220 MIS           0 :         read_pool_op_.ref_.reset();
     221               0 :         op.stop_cb.reset();
     222               0 :         op.ex.on_work_finished();
     223               0 :         *ec        = pec;
     224               0 :         *bytes_out = 0;
     225               0 :         op.cont.h  = h;
     226               0 :         return dispatch_coro(ex, op.cont);
     227                 :     }
     228 HIT          32 :     return std::noop_coroutine();
     229                 : }
     230                 : 
     231                 : inline void
     232              32 : posix_stream_file::do_read_work(pool_work_item* w) noexcept
     233                 : {
     234              32 :     auto* pw   = static_cast<pool_op*>(w);
     235              32 :     auto* self = pw->file_;
     236              32 :     auto& op   = self->read_op_;
     237                 : 
     238              32 :     if (!op.cancelled.load(std::memory_order_acquire))
     239                 :     {
     240                 :         ssize_t n;
     241                 :         do
     242                 :         {
     243              56 :             n = ::preadv(self->fd_, op.iovecs, op.iovec_count,
     244              28 :                          static_cast<off_t>(self->offset_));
     245                 :         }
     246              28 :         while (n < 0 && errno == EINTR);
     247                 : 
     248              28 :         if (n >= 0)
     249                 :         {
     250              21 :             op.errn              = 0;
     251              21 :             op.bytes_transferred = static_cast<std::size_t>(n);
     252              21 :             self->offset_ += static_cast<std::uint64_t>(n);
     253                 :         }
     254                 :         else
     255                 :         {
     256               7 :             op.errn              = errno;
     257               7 :             op.bytes_transferred = 0;
     258                 :         }
     259                 :     }
     260                 : 
     261              32 :     op.impl_ptr = std::move(pw->ref_);
     262              32 :     self->svc_.post(&op);
     263              32 : }
     264                 : 
     265                 : inline std::coroutine_handle<>
     266              37 : posix_stream_file::write_some(
     267                 :     std::coroutine_handle<> h,
     268                 :     capy::executor_ref ex,
     269                 :     buffer_param param,
     270                 :     std::stop_token token,
     271                 :     std::error_code* ec,
     272                 :     std::size_t* bytes_out)
     273                 : {
     274              37 :     auto& op = write_op_;
     275              37 :     op.reset();
     276              37 :     op.is_read = false;
     277                 : 
     278                 :     // Closed-object contract outranks the zero-length no-op.
     279              37 :     if (fd_ < 0)
     280                 :     {
     281               6 :         *ec        = make_error_code(std::errc::bad_file_descriptor);
     282               6 :         *bytes_out = 0;
     283               6 :         op.cont.h = h;
     284               6 :         return dispatch_coro(ex, op.cont);
     285                 :     }
     286                 : 
     287              31 :     capy::mutable_buffer bufs[max_buffers];
     288              31 :     op.iovec_count = static_cast<int>(param.copy_to(bufs, max_buffers));
     289                 : 
     290              31 :     if (op.iovec_count == 0)
     291                 :     {
     292               2 :         *ec        = {};
     293               2 :         *bytes_out = 0;
     294               2 :         op.cont.h = h;
     295               2 :         return dispatch_coro(ex, op.cont);
     296                 :     }
     297                 : 
     298              58 :     for (int i = 0; i < op.iovec_count; ++i)
     299                 :     {
     300              29 :         op.iovecs[i].iov_base = bufs[i].data();
     301              29 :         op.iovecs[i].iov_len  = bufs[i].size();
     302                 :     }
     303                 : 
     304              29 :     op.h         = h;
     305              29 :     op.ex        = ex;
     306              29 :     op.ec_out    = ec;
     307              29 :     op.bytes_out = bytes_out;
     308              29 :     op.start(token);
     309                 : 
     310              29 :     op.ex.on_work_started();
     311                 : 
     312              29 :     write_pool_op_.file_ = this;
     313              29 :     write_pool_op_.ref_  = this->shared_from_this();
     314              29 :     write_pool_op_.func_ = &posix_stream_file::do_write_work;
     315              29 :     if (auto pec = svc_.pool().post(&write_pool_op_))
     316                 :     {
     317                 :         // The pool is shutting down, or the system refused it a thread.
     318                 :         // Nothing of this write went cross-thread, so it answers here
     319                 :         // like the closed-descriptor and zero-length exits above rather
     320                 :         // than through a completion the scheduler has to carry back.
     321 MIS           0 :         write_pool_op_.ref_.reset();
     322               0 :         op.stop_cb.reset();
     323               0 :         op.ex.on_work_finished();
     324               0 :         *ec        = pec;
     325               0 :         *bytes_out = 0;
     326               0 :         op.cont.h  = h;
     327               0 :         return dispatch_coro(ex, op.cont);
     328                 :     }
     329 HIT          29 :     return std::noop_coroutine();
     330                 : }
     331                 : 
     332                 : inline void
     333              29 : posix_stream_file::do_write_work(pool_work_item* w) noexcept
     334                 : {
     335              29 :     auto* pw   = static_cast<pool_op*>(w);
     336              29 :     auto* self = pw->file_;
     337              29 :     auto& op   = self->write_op_;
     338                 : 
     339              29 :     if (!op.cancelled.load(std::memory_order_acquire))
     340                 :     {
     341                 :         ssize_t n;
     342                 :         do
     343                 :         {
     344              58 :             n = ::pwritev(self->fd_, op.iovecs, op.iovec_count,
     345              29 :                           static_cast<off_t>(self->offset_));
     346                 :         }
     347              29 :         while (n < 0 && errno == EINTR);
     348                 : 
     349              29 :         if (n >= 0)
     350                 :         {
     351              22 :             op.errn              = 0;
     352              22 :             op.bytes_transferred = static_cast<std::size_t>(n);
     353              22 :             self->offset_ += static_cast<std::uint64_t>(n);
     354                 :         }
     355                 :         else
     356                 :         {
     357               7 :             op.errn              = errno;
     358               7 :             op.bytes_transferred = 0;
     359                 :         }
     360                 :     }
     361                 : 
     362              29 :     op.impl_ptr = std::move(pw->ref_);
     363              29 :     self->svc_.post(&op);
     364              29 : }
     365                 : 
     366                 : } // namespace boost::corosio::detail
     367                 : 
     368                 : #endif // BOOST_COROSIO_POSIX
     369                 : 
     370                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_STREAM_FILE_SERVICE_HPP
        

Generated by: LCOV version 2.3