sendfile_operation_posix.ipp
Go to the documentation of this file.
/*
restinio
*/
#if defined( RESTINIO_FREEBSD_TARGET ) || defined( RESTINIO_MACOS_TARGET )
#include <sys/uio.h>
#else
#include <sys/sendfile.h>
#endif
namespace restinio
{
namespace impl
{
//
// sendfile_operation_runner_t
//
template < typename Socket >
class sendfile_operation_runner_t final
: public sendfile_operation_runner_base_t< Socket >
{
public:
using base_type_t = sendfile_operation_runner_base_t< Socket >;
sendfile_operation_runner_t( const sendfile_operation_runner_t & ) = delete;
sendfile_operation_runner_t( sendfile_operation_runner_t && ) = delete;
sendfile_operation_runner_t & operator = ( const sendfile_operation_runner_t & ) = delete;
sendfile_operation_runner_t & operator = ( sendfile_operation_runner_t && ) = delete;
// Reuse construstors from base.
using base_type_t::base_type_t;
virtual void
start() override
{
#if defined( RESTINIO_FREEBSD_TARGET ) || defined( RESTINIO_MACOS_TARGET )
auto const n = ::lseek( this->m_file_descriptor, this->m_next_write_offset, SEEK_SET );
#else
auto const n = ::lseek64( this->m_file_descriptor, this->m_next_write_offset, SEEK_SET );
#endif
if( static_cast< off_t >( -1 ) != n )
{
this->init_next_write();
}
else
{
const asio_ns::error_code ec{ errno, asio_ns::error::get_system_category() };
this->m_after_sendfile_cb( ec, this->m_transfered_size );
return;
}
}
void
init_next_write() noexcept
{
// A note about noexcept for that method.
// It seems that there is no exceptions thrown by the method itself.
// The only dangerous place is a call to m_after_sendfile_cb.
// But the main code behind m_after_sendfile_cb is going from
// connection_t class and that code is noexcept since v.0.6.0.
//
while( true )
{
auto const n = ::read(
this->m_file_descriptor,
this->m_buffer.get(),
std::min< file_size_t >(
this->m_remained_size, this->m_chunk_size ) );
if( -1 == n )
{
if( errno == EINTR )
continue;
this->m_after_sendfile_cb(
asio_ns::error_code{
errno,
asio_ns::error::get_system_category() },
this->m_transfered_size );
}
else if( 0 == n )
{
this->m_after_sendfile_cb(
asio_ns::error_code{
asio_ec::eof,
asio_ns::error::get_system_category() },
this->m_transfered_size );
}
else
{
// If asio_ns::async_write fails we'll call m_after_sendfile_cb.
try
{
asio_ns::async_write(
this->m_socket,
asio_ns::const_buffer{
this->m_buffer.get(),
static_cast< std::size_t >( n ) },
asio_ns::bind_executor(
this->m_executor,
make_async_write_handler() ) );
}
catch( ... )
{
this->m_after_sendfile_cb(
make_asio_compaible_error(
asio_convertible_error_t::async_write_call_failed ),
this->m_transfered_size );
}
}
break;
}
}
private:
std::unique_ptr< char[] > m_buffer{ new char [ this->m_chunk_size ] };
auto
make_async_write_handler() noexcept
{
return [this, ctx = this->shared_from_this()]
// NOTE: this lambda is noexcept since v.0.6.0.
( const asio_ns::error_code & ec, std::size_t written ) noexcept
{
if( !ec )
{
this->m_remained_size -= written;
this->m_transfered_size += written;
if( 0 == this->m_remained_size )
{
this->m_after_sendfile_cb(
ec,
this->m_transfered_size );
}
else
{
this->init_next_write();
}
}
else
{
this->m_after_sendfile_cb(
ec,
this->m_transfered_size );
}
};
}
};
template <>
class sendfile_operation_runner_t< asio_ns::ip::tcp::socket > final
: public sendfile_operation_runner_base_t< asio_ns::ip::tcp::socket >
{
private:
RESTINIO_NODISCARD
bool
try_turn_non_blocking_mode() noexcept
{
bool result = true;
if( !m_socket.native_non_blocking() )
{
asio_ns::error_code ec;
m_socket.native_non_blocking( true, ec );
if( ec )
{
// We assume that m_after_sendfile_cb doesn't throw;
m_after_sendfile_cb( ec, m_transfered_size );
result = false;
}
}
return result;
}
#if defined( RESTINIO_FREEBSD_TARGET )
RESTINIO_NODISCARD
auto
call_native_sendfile() noexcept
{
// FreeBSD sendfile signature:
// int sendfile(int fd, int s, off_t offset, size_t nbytes,
// struct sf_hdtr *hdtr, off_t *sbytes, int flags);
// https://www.freebsd.org/cgi/man.cgi?query=sendfile
off_t n{ 0 };
auto rc =
::sendfile(
m_file_descriptor,
m_socket.native_handle(),
m_next_write_offset,
static_cast< size_t >(
std::min< file_size_t >( m_remained_size, m_chunk_size ) ),
nullptr, // struct sf_hdtr *hdtr
&n, // sbytes
// Is 16 a reasonable constant here.
#if __FreeBSD__ >= 11
SF_FLAGS( 16, SF_NOCACHE )
#else
SF_MNOWAIT
#endif
);
// Shift the number of bytes successfully sent.
m_next_write_offset += n;
if( -1 == rc )
{
// It is still possible that some bytes had been sent.
m_remained_size -= static_cast< file_size_t >( n );
m_transfered_size += static_cast< file_size_t >( n );
n = -1;
}
return n;
}
#elif defined( RESTINIO_MACOS_TARGET )
RESTINIO_NODISCARD
auto
call_native_sendfile() noexcept
{
// macOS sendfile signature:
// in sendfile(int fd, int s, off_t offset,
// off_t *len, struct sf_hdtr *hdtr, int flags);
off_t n =
static_cast< off_t >(
std::min< file_size_t >( m_remained_size, m_chunk_size ) );
auto rc =
::sendfile(
m_file_descriptor,
m_socket.native_handle(),
m_next_write_offset,
&n,
nullptr, // struct sf_hdtr *hdtr
0 );
// Shift the number of bytes successfully sent.
m_next_write_offset += n;
if( -1 == rc )
{
// It is still possible that some bytes had been sent.
m_remained_size -= static_cast< file_size_t >( n );
m_transfered_size += static_cast< file_size_t >( n );
n = -1;
}
return n;
}
#else
RESTINIO_NODISCARD
auto
call_native_sendfile() noexcept
{
return ::sendfile64(
m_socket.native_handle(),
m_file_descriptor,
&m_next_write_offset,
std::min< file_size_t >( m_remained_size, m_chunk_size ) );
}
#endif
RESTINIO_NODISCARD
bool
try_initiate_waiting_for_write_readiness() noexcept
{
bool result = true;
try
{
// We have to wait for the socket to become ready again.
m_socket.async_wait(
asio_ns::ip::tcp::socket::wait_write,
asio_ns::bind_executor(
m_executor,
[ this, ctx = this->shared_from_this() ]
// NOTE: this lambda is noexcept since v.0.6.0.
( const asio_ns::error_code & ec ) noexcept {
if( ec || 0 == m_remained_size )
{
m_after_sendfile_cb( ec, m_transfered_size );
}
else
{
init_next_write();
}
} ) );
}
catch( ... )
{
m_after_sendfile_cb(
make_asio_compaible_error(
asio_convertible_error_t::async_write_call_failed ),
m_transfered_size );
result = false;
}
return result;
}
public:
using base_type_t = sendfile_operation_runner_base_t< asio_ns::ip::tcp::socket >;
sendfile_operation_runner_t( const sendfile_operation_runner_t & ) = delete;
sendfile_operation_runner_t( sendfile_operation_runner_t && ) = delete;
sendfile_operation_runner_t & operator = ( const sendfile_operation_runner_t & ) = delete;
sendfile_operation_runner_t & operator = ( sendfile_operation_runner_t && ) = delete;
// Reuse construstors from base.
using base_type_t::base_type_t;
virtual void
start() override
{
init_next_write();
}
void
init_next_write() noexcept
{
if( !try_turn_non_blocking_mode() )
return;
while( true )
{
// Try the system call.
errno = 0;
if( 0 == m_remained_size )
{
// We are done.
// Result of try_initiate_waiting_for_write_readiness can
// be ignored here.
(void)try_initiate_waiting_for_write_readiness();
break;
}
const auto n = call_native_sendfile();
if( -1 == n )
{
if( errno == EAGAIN || errno == EINTR )
{
if( !try_initiate_waiting_for_write_readiness() )
return;
}
else
{
m_after_sendfile_cb(
asio_ns::error_code{
errno, asio_ns::error::get_system_category() },
m_transfered_size );
}
break;
}
else if( 0 == n )
{
// Result of try_initiate_waiting_for_write_readiness can
// be ignored here.
(void)try_initiate_waiting_for_write_readiness();
break;
}
else
{
m_remained_size -= static_cast< file_size_t >( n );
m_transfered_size += static_cast< file_size_t >( n );
}
// Loop around to try calling sendfile again.
}
}
};
} /* namespace impl */
} /* namespace restinio */
restinio::impl::sendfile_operation_runner_base_t
A base runner of sendfile operation (keeps all the data).
Definition: sendfile_operation.hpp:50
restinio::impl::sendfile_operation_runner_base_t::m_after_sendfile_cb
after_sendfile_cb_t m_after_sendfile_cb
Definition: sendfile_operation.hpp:83
restinio::impl::sendfile_operation_runner_base_t::m_chunk_size
const file_size_t m_chunk_size
Definition: sendfile_operation.hpp:77
restinio::impl::sendfile_operation_runner_base_t::m_remained_size
file_size_t m_remained_size
Definition: sendfile_operation.hpp:74
restinio::impl::sendfile_operation_runner_base_t::m_next_write_offset
file_offset_t m_next_write_offset
Definition: sendfile_operation.hpp:73
restinio::impl::sendfile_operation_runner_base_t::m_file_descriptor
file_descriptor_t m_file_descriptor
Definition: sendfile_operation.hpp:72
restinio::impl::sendfile_operation_runner_base_t::m_executor
default_asio_executor m_executor
Definition: sendfile_operation.hpp:81
restinio::impl::sendfile_operation_runner_base_t::m_socket
Socket & m_socket
Definition: sendfile_operation.hpp:82
restinio::impl::sendfile_operation_runner_base_t::m_transfered_size
file_size_t m_transfered_size
Definition: sendfile_operation.hpp:75
restinio::impl::sendfile_operation_runner_t< asio_ns::ip::tcp::socket >::try_turn_non_blocking_mode
RESTINIO_NODISCARD bool try_turn_non_blocking_mode() noexcept
Definition: sendfile_operation_posix.ipp:175
restinio::impl::sendfile_operation_runner_t< asio_ns::ip::tcp::socket >::sendfile_operation_runner_t
sendfile_operation_runner_t(const sendfile_operation_runner_t &)=delete
restinio::impl::sendfile_operation_runner_t< asio_ns::ip::tcp::socket >::init_next_write
void init_next_write() noexcept
Definition: sendfile_operation_posix.ipp:345
restinio::impl::sendfile_operation_runner_t< asio_ns::ip::tcp::socket >::call_native_sendfile
RESTINIO_NODISCARD auto call_native_sendfile() noexcept
Definition: sendfile_operation_posix.ipp:275
restinio::impl::sendfile_operation_runner_t< asio_ns::ip::tcp::socket >::try_initiate_waiting_for_write_readiness
RESTINIO_NODISCARD bool try_initiate_waiting_for_write_readiness() noexcept
Definition: sendfile_operation_posix.ipp:287
restinio::impl::sendfile_operation_runner_t< asio_ns::ip::tcp::socket >::start
virtual void start() override
Definition: sendfile_operation_posix.ipp:335
restinio::impl::sendfile_operation_runner_t< asio_ns::ip::tcp::socket >::sendfile_operation_runner_t
sendfile_operation_runner_t(sendfile_operation_runner_t &&)=delete
restinio::impl::sendfile_operation_runner_t
A runner of sendfile operation.
Definition: sendfile_operation_default.ipp:25
restinio::impl::sendfile_operation_runner_t::m_buffer
std::unique_ptr< char[] > m_buffer
Definition: sendfile_operation_default.ipp:108
restinio::impl::sendfile_operation_runner_t::make_async_write_handler
auto make_async_write_handler() noexcept
Helper method for making a lambda for async_write completion handler.
Definition: sendfile_operation_default.ipp:112
restinio::impl::sendfile_operation_runner_t::start
virtual void start() override
Definition: sendfile_operation_posix.ipp:42
restinio::impl::sendfile_operation_runner_t::sendfile_operation_runner_t
sendfile_operation_runner_t(const sendfile_operation_runner_t &)=delete
restinio::impl::sendfile_operation_runner_t::operator=
sendfile_operation_runner_t & operator=(const sendfile_operation_runner_t &)=delete
restinio::impl::sendfile_operation_runner_t::init_next_write
void init_next_write() noexcept
Definition: sendfile_operation_default.ipp:65
restinio::impl::sendfile_operation_runner_t::sendfile_operation_runner_t
sendfile_operation_runner_t(sendfile_operation_runner_t &&)=delete
RESTINIO_NODISCARD
#define RESTINIO_NODISCARD
Definition: compiler_features.hpp:33
restinio::asio_ec::eof
constexpr auto eof
Definition: asio_include.hpp:41
restinio
Definition: asio_include.hpp:21
restinio::sendfile
sendfile_t sendfile(file_descriptor_holder_t fd, file_meta_t meta, file_size_t chunk_size=sendfile_default_chunk_size) noexcept
Definition: sendfile.hpp:468
restinio::file_size_t
std::uint64_t file_size_t
Definition: sendfile_defs_default.hpp:23
restinio::asio_convertible_error_t::async_write_call_failed
@ async_write_call_failed
A call to async_write failed. The corresponding write operation wasn't done.
restinio::const_buffer
constexpr const_buffer_t const_buffer(const void *str, std::size_t size) noexcept
Definition: buffers.hpp:424
restinio::make_asio_compaible_error
asio_ns::error_code make_asio_compaible_error(asio_convertible_error_t err) noexcept
Make restinio error_code compatible with asio_ns::error_code.
Definition: asio_include.hpp:210
SEEK_SET
#define SEEK_SET
Definition: zconf.h:498