LCOV - code coverage report
Current view: top level - corosio/native/detail/posix - posix_random_access_file_service.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 96.6 % 174 168 6
Test Date: 2026-10-17 04:01:32 Functions: 100.0 % 20 20

           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_RANDOM_ACCESS_FILE_SERVICE_HPP
      11                 : #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RANDOM_ACCESS_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_random_access_file.hpp>
      18                 : #include <boost/corosio/native/detail/reactor/reactor_scheduler.hpp>
      19                 : #include <boost/corosio/detail/random_access_file_service.hpp>
      20                 : #include <boost/corosio/detail/object_pool.hpp>
      21                 : #include <boost/corosio/detail/object_ref.hpp>
      22                 : #include <boost/corosio/detail/thread_pool.hpp>
      23                 : 
      24                 : #include <limits>
      25                 : #include <vector>
      26                 : 
      27                 : namespace boost::corosio::detail {
      28                 : 
      29                 : /** Random-access file service for POSIX backends. */
      30                 : class BOOST_COROSIO_DECL posix_random_access_file_service final
      31                 :     : public random_access_file_service
      32                 : {
      33                 :     friend class posix_random_access_file;
      34                 : 
      35                 : public:
      36 HIT         189 :     explicit posix_random_access_file_service(capy::execution_context& ctx)
      37             567 :         : sched_(&get_scheduler(ctx))
      38             189 :         , pool_(ctx)
      39                 :     {
      40             189 :     }
      41                 : 
      42             378 :     ~posix_random_access_file_service() override = default;
      43                 : 
      44                 :     posix_random_access_file_service(posix_random_access_file_service const&) =
      45                 :         delete;
      46                 :     posix_random_access_file_service&
      47                 :     operator=(posix_random_access_file_service const&) = delete;
      48                 : 
      49             244 :     io_object::implementation* construct() override
      50                 :     {
      51             244 :         return object_pool_.acquire(*this);
      52                 :     }
      53                 : 
      54             244 :     void destroy(io_object::implementation* p) override
      55                 :     {
      56             244 :         auto& impl = static_cast<posix_random_access_file&>(*p);
      57             244 :         impl.cancel();
      58             244 :         impl.close_file();
      59             244 :         release(&impl);
      60             244 :     }
      61                 : 
      62             455 :     void close(io_object::handle& h) override
      63                 :     {
      64             455 :         if (h.get())
      65                 :         {
      66             455 :             auto& impl = static_cast<posix_random_access_file&>(*h.get());
      67             455 :             impl.cancel();
      68             455 :             impl.close_file();
      69                 :         }
      70             455 :     }
      71                 : 
      72             224 :     std::error_code open_file(
      73                 :         random_access_file::implementation& impl,
      74                 :         std::filesystem::path const& path,
      75                 :         file_base::flags mode) override
      76                 :     {
      77             224 :         if (pool_unavailable())
      78 MIS           0 :             return std::make_error_code(std::errc::operation_not_supported);
      79 HIT         224 :         return static_cast<posix_random_access_file&>(impl).open_file(
      80             224 :             path, mode);
      81                 :     }
      82                 : 
      83                 :     /** Check whether file I/O is unavailable on this context.
      84                 : 
      85                 :         The file thread pool completes cross-thread, which the
      86                 :         lockless scheduler of the unsafe tier cannot accept.
      87                 :     */
      88             233 :     bool pool_unavailable() const noexcept
      89                 :     {
      90             233 :         return sched_->scheduler_locking_disabled();
      91                 :     }
      92                 : 
      93             189 :     void shutdown() override
      94                 :     {
      95                 :         // See uring_socket_service_base::shutdown(): snapshot under an
      96                 :         // acquired reference, cancel+close without the pool lock held;
      97                 :         // shutdown() sets shutting-down and takes the snapshot in one
      98                 :         // critical section, so a close that drops the last ref deletes
      99                 :         // rather than recycles.
     100             189 :         std::vector<posix_random_access_file*> live;
     101             189 :         object_pool_.shutdown(
     102             189 :             [&](posix_random_access_file* f)
     103                 :             {
     104               6 :                 acquire(f);
     105               6 :                 live.push_back(f);
     106               6 :             });
     107             195 :         for (auto* f : live)
     108                 :         {
     109               6 :             f->cancel();
     110               6 :             f->close_file();
     111               6 :             release(f);
     112                 :         }
     113             189 :     }
     114                 : 
     115             505 :     void post(scheduler_op* op)
     116                 :     {
     117             505 :         sched_->post(op);
     118             505 :     }
     119                 : 
     120             515 :     void work_started() noexcept
     121                 :     {
     122             515 :         sched_->work_started();
     123             515 :     }
     124                 : 
     125             515 :     void work_finished() noexcept
     126                 :     {
     127             515 :         sched_->work_finished();
     128             515 :     }
     129                 : 
     130                 :     /** Return the thread pool that runs this service's file work.
     131                 : 
     132                 :         The pool's service is created on first use, so this can fail
     133                 :         where a plain accessor could not. Its workers start later, on
     134                 :         the first post, and a thread the system refuses there is
     135                 :         reported by that post rather than thrown here.
     136                 : 
     137                 :         @throws std::bad_alloc If the service cannot be allocated.
     138                 : 
     139                 :         @return The context's shared blocking-I/O pool.
     140                 : 
     141                 :         @see thread_pool_ref::get
     142                 :     */
     143             515 :     thread_pool& pool()
     144                 :     {
     145             515 :         return pool_.get();
     146                 :     }
     147                 : 
     148                 : private:
     149                 :     scheduler* sched_;
     150                 :     thread_pool_ref pool_;
     151                 :     object_pool<posix_random_access_file> object_pool_;
     152                 : };
     153                 : 
     154                 : // ---------------------------------------------------------------------------
     155                 : // posix_random_access_file inline implementations (require complete service)
     156                 : // ---------------------------------------------------------------------------
     157                 : 
     158                 : inline std::coroutine_handle<>
     159             391 : posix_random_access_file::read_some_at(
     160                 :     std::uint64_t offset,
     161                 :     capy::continuation& cont,
     162                 :     capy::executor_ref ex,
     163                 :     buffer_param param,
     164                 :     std::stop_token token,
     165                 :     std::error_code* ec,
     166                 :     std::size_t* bytes_out)
     167                 : {
     168                 :     // Closed-object contract outranks the zero-length no-op.
     169             391 :     if (fd_ < 0)
     170                 :     {
     171               4 :         *ec        = make_error_code(std::errc::bad_file_descriptor);
     172               4 :         *bytes_out = 0;
     173               4 :         return cont.h;
     174                 :     }
     175                 : 
     176             387 :     capy::mutable_buffer bufs[max_buffers];
     177             387 :     auto count = param.copy_to(bufs, max_buffers);
     178                 : 
     179             387 :     if (count == 0)
     180                 :     {
     181               2 :         *ec        = {};
     182               2 :         *bytes_out = 0;
     183               2 :         return cont.h;
     184                 :     }
     185                 : 
     186             385 :     auto* op    = acquire_op();
     187             385 :     op->is_read = true;
     188             385 :     op->offset  = offset;
     189             385 :     op->fd      = fd_;
     190                 : 
     191             385 :     op->iovec_count = static_cast<int>(count);
     192             770 :     for (int i = 0; i < op->iovec_count; ++i)
     193                 :     {
     194             385 :         op->iovecs[i].iov_base = bufs[i].data();
     195             385 :         op->iovecs[i].iov_len  = bufs[i].size();
     196                 :     }
     197                 : 
     198             385 :     op->cont        = &cont;
     199             385 :     op->ex          = ex;
     200             385 :     op->ec_out      = ec;
     201             385 :     op->bytes_out   = bytes_out;
     202             385 :     op->file_       = this;
     203             385 :     op->object_ref_ = detail::object_ref(this);
     204             385 :     op->start(token);
     205                 : 
     206             385 :     svc_.work_started();
     207                 : 
     208                 :     {
     209             385 :         std::lock_guard<std::mutex> lock(ops_mutex_);
     210             385 :         outstanding_ops_.push_back(op);
     211             385 :     }
     212                 : 
     213             385 :     static_cast<pool_work_item*>(op)->func_    = &raf_op::do_work;
     214             385 :     static_cast<pool_work_item*>(op)->destroy_ = &raf_op::destroy_work;
     215             385 :     if (auto pec = svc_.pool().post(static_cast<pool_work_item*>(op)))
     216                 :     {
     217                 :         // The pool is shutting down, or the system refused it a thread.
     218                 :         // Nothing of this read went cross-thread, so it answers here
     219                 :         // like the closed-descriptor and zero-length exits above rather
     220                 :         // than through a completion the scheduler has to carry back.
     221                 :         // The op never reached the queue: it unlinks, unwinds the
     222                 :         // work count and recycles, and the coroutine resumes below.
     223               2 :         op->discard();
     224               2 :         *ec        = pec;
     225               2 :         *bytes_out = 0;
     226               2 :         return cont.h;
     227                 :     }
     228             383 :     return std::noop_coroutine();
     229                 : }
     230                 : 
     231                 : inline std::coroutine_handle<>
     232             134 : posix_random_access_file::write_some_at(
     233                 :     std::uint64_t offset,
     234                 :     capy::continuation& cont,
     235                 :     capy::executor_ref ex,
     236                 :     buffer_param param,
     237                 :     std::stop_token token,
     238                 :     std::error_code* ec,
     239                 :     std::size_t* bytes_out)
     240                 : {
     241                 :     // Closed-object contract outranks the zero-length no-op.
     242             134 :     if (fd_ < 0)
     243                 :     {
     244               2 :         *ec        = make_error_code(std::errc::bad_file_descriptor);
     245               2 :         *bytes_out = 0;
     246               2 :         return cont.h;
     247                 :     }
     248                 : 
     249             132 :     capy::mutable_buffer bufs[max_buffers];
     250             132 :     auto count = param.copy_to(bufs, max_buffers);
     251                 : 
     252             132 :     if (count == 0)
     253                 :     {
     254               2 :         *ec        = {};
     255               2 :         *bytes_out = 0;
     256               2 :         return cont.h;
     257                 :     }
     258                 : 
     259             130 :     auto* op    = acquire_op();
     260             130 :     op->is_read = false;
     261             130 :     op->offset  = offset;
     262             130 :     op->fd      = fd_;
     263                 : 
     264             130 :     op->iovec_count = static_cast<int>(count);
     265             260 :     for (int i = 0; i < op->iovec_count; ++i)
     266                 :     {
     267             130 :         op->iovecs[i].iov_base = bufs[i].data();
     268             130 :         op->iovecs[i].iov_len  = bufs[i].size();
     269                 :     }
     270                 : 
     271             130 :     op->cont        = &cont;
     272             130 :     op->ex          = ex;
     273             130 :     op->ec_out      = ec;
     274             130 :     op->bytes_out   = bytes_out;
     275             130 :     op->file_       = this;
     276             130 :     op->object_ref_ = detail::object_ref(this);
     277             130 :     op->start(token);
     278                 : 
     279             130 :     svc_.work_started();
     280                 : 
     281                 :     {
     282             130 :         std::lock_guard<std::mutex> lock(ops_mutex_);
     283             130 :         outstanding_ops_.push_back(op);
     284             130 :     }
     285                 : 
     286             130 :     static_cast<pool_work_item*>(op)->func_    = &raf_op::do_work;
     287             130 :     static_cast<pool_work_item*>(op)->destroy_ = &raf_op::destroy_work;
     288             130 :     if (auto pec = svc_.pool().post(static_cast<pool_work_item*>(op)))
     289                 :     {
     290                 :         // The pool is shutting down, or the system refused it a thread.
     291                 :         // Nothing of this write went cross-thread, so it answers here
     292                 :         // like the closed-descriptor and zero-length exits above rather
     293                 :         // than through a completion the scheduler has to carry back.
     294                 :         // The op never reached the queue: it unlinks, unwinds the
     295                 :         // work count and recycles, and the coroutine resumes below.
     296               2 :         op->discard();
     297               2 :         *ec        = pec;
     298               2 :         *bytes_out = 0;
     299               2 :         return cont.h;
     300                 :     }
     301             128 :     return std::noop_coroutine();
     302                 : }
     303                 : 
     304                 : // -- raf_op thread-pool work function --
     305                 : 
     306                 : inline void
     307             505 : posix_random_access_file::raf_op::do_work(pool_work_item* w) noexcept
     308                 : {
     309             505 :     auto* op   = static_cast<raf_op*>(w);
     310             505 :     auto* self = op->file_;
     311                 : 
     312             505 :     if (op->cancelled.load(std::memory_order_acquire))
     313                 :     {
     314              68 :         op->errn              = ECANCELED;
     315              68 :         op->bytes_transferred = 0;
     316                 :     }
     317             437 :     else if (
     318             874 :         op->offset >
     319             437 :         static_cast<std::uint64_t>(std::numeric_limits<file_off_t>::max()))
     320                 :     {
     321               2 :         op->errn              = EOVERFLOW;
     322               2 :         op->bytes_transferred = 0;
     323                 :     }
     324             435 :     else if (!self->gate_.enter(op->fd))
     325                 :     {
     326                 :         // Closed or replaced since this was queued.
     327 MIS           0 :         op->errn              = ECANCELED;
     328               0 :         op->bytes_transferred = 0;
     329                 :     }
     330 HIT         435 :     else if (op->cancelled.load(std::memory_order_acquire))
     331                 :     {
     332                 :         // Closed and reopened onto the same number since the check
     333                 :         // above: the gate admits the number, but not this op.
     334 MIS           0 :         self->gate_.leave();
     335               0 :         op->errn              = ECANCELED;
     336               0 :         op->bytes_transferred = 0;
     337                 :     }
     338                 :     else
     339                 :     {
     340                 :         ssize_t n;
     341 HIT         435 :         if (op->is_read)
     342                 :         {
     343                 :             do
     344                 :             {
     345             323 :                 n = file_preadv(
     346             323 :                     op->fd, op->iovecs, op->iovec_count,
     347             323 :                     static_cast<file_off_t>(op->offset));
     348                 :             }
     349             323 :             while (n < 0 && errno == EINTR);
     350                 :         }
     351                 :         else
     352                 :         {
     353                 :             do
     354                 :             {
     355             112 :                 n = file_pwritev(
     356             112 :                     op->fd, op->iovecs, op->iovec_count,
     357             112 :                     static_cast<file_off_t>(op->offset));
     358                 :             }
     359             112 :             while (n < 0 && errno == EINTR);
     360                 :         }
     361                 : 
     362             435 :         if (n >= 0)
     363                 :         {
     364             421 :             op->errn              = 0;
     365             421 :             op->bytes_transferred = static_cast<std::size_t>(n);
     366                 :         }
     367                 :         else
     368                 :         {
     369              14 :             op->errn              = errno;
     370              14 :             op->bytes_transferred = 0;
     371                 :         }
     372             435 :         self->gate_.leave();
     373                 :     }
     374                 : 
     375             505 :     self->svc_.post(static_cast<scheduler_op*>(op));
     376             505 : }
     377                 : 
     378                 : inline std::error_code
     379               9 : posix_random_access_file::assign(native_handle_type handle) noexcept
     380                 : {
     381                 :     // The public assign() guarantees the object is closed.
     382               9 :     if (svc_.pool_unavailable())
     383               2 :         return std::make_error_code(std::errc::operation_not_supported);
     384               7 :     if (auto ec = validate_file_fd(handle))
     385               4 :         return ec;
     386                 : 
     387               3 :     fd_ = handle;
     388               3 :     gate_.open(handle);
     389               3 :     return {};
     390                 : }
     391                 : 
     392                 : inline void
     393             244 : posix_random_access_file::retire() noexcept
     394                 : {
     395             244 :     svc_.object_pool_.recycle(this);
     396             244 : }
     397                 : 
     398                 : inline void
     399             515 : posix_random_access_file::release_work(
     400                 :     posix_random_access_file_service& svc) noexcept
     401                 : {
     402             515 :     svc.work_finished();
     403             515 : }
     404                 : 
     405                 : } // namespace boost::corosio::detail
     406                 : 
     407                 : #endif // BOOST_COROSIO_POSIX
     408                 : 
     409                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_RANDOM_ACCESS_FILE_SERVICE_HPP
        

Generated by: LCOV version 2.3