RESTinio
sendfile_operation_posix.ipp
Go to the documentation of this file.
1 /*
2  restinio
3 */
4 
9 #if defined( RESTINIO_FREEBSD_TARGET ) || defined( RESTINIO_MACOS_TARGET )
10  #include <sys/uio.h>
11 #else
12  #include <sys/sendfile.h>
13 #endif
14 
15 namespace restinio
16 {
17 
18 namespace impl
19 {
20 
21 //
22 // sendfile_operation_runner_t
23 //
24 
26 template < typename Socket >
27 class sendfile_operation_runner_t final
28  : public sendfile_operation_runner_base_t< Socket >
29 {
30  public:
32 
37 
38  // Reuse construstors from base.
39  using base_type_t::base_type_t;
40 
41  virtual void
42  start() override
43  {
44 #if defined( RESTINIO_FREEBSD_TARGET ) || defined( RESTINIO_MACOS_TARGET )
45  auto const n = ::lseek( this->m_file_descriptor, this->m_next_write_offset, SEEK_SET );
46 #else
47  auto const n = ::lseek64( this->m_file_descriptor, this->m_next_write_offset, SEEK_SET );
48 #endif
49 
50  if( static_cast< off_t >( -1 ) != n )
51  {
52  this->init_next_write();
53  }
54  else
55  {
56  const asio_ns::error_code ec{ errno, asio_ns::error::get_system_category() };
57  this->m_after_sendfile_cb( ec, this->m_transfered_size );
58  return;
59  }
60  }
61 
66  void
67  init_next_write() noexcept
68  {
69  // A note about noexcept for that method.
70  // It seems that there is no exceptions thrown by the method itself.
71  // The only dangerous place is a call to m_after_sendfile_cb.
72  // But the main code behind m_after_sendfile_cb is going from
73  // connection_t class and that code is noexcept since v.0.6.0.
74  //
75  while( true )
76  {
77  auto const n = ::read(
78  this->m_file_descriptor,
79  this->m_buffer.get(),
80  std::min< file_size_t >(
81  this->m_remained_size, this->m_chunk_size ) );
82 
83  if( -1 == n )
84  {
85  if( errno == EINTR )
86  continue;
87 
88  this->m_after_sendfile_cb(
89  asio_ns::error_code{
90  errno,
91  asio_ns::error::get_system_category() },
92  this->m_transfered_size );
93  }
94  else if( 0 == n )
95  {
96  this->m_after_sendfile_cb(
97  asio_ns::error_code{
99  asio_ns::error::get_system_category() },
100  this->m_transfered_size );
101  }
102  else
103  {
104  // If asio_ns::async_write fails we'll call m_after_sendfile_cb.
105  try
106  {
107  asio_ns::async_write(
108  this->m_socket,
110  this->m_buffer.get(),
111  static_cast< std::size_t >( n ) },
112  asio_ns::bind_executor(
113  this->m_executor,
115  }
116  catch( ... )
117  {
118  this->m_after_sendfile_cb(
120  asio_convertible_error_t::async_write_call_failed ),
121  this->m_transfered_size );
122  }
123  }
124 
125  break;
126  }
127  }
128 
129  private:
130  std::unique_ptr< char[] > m_buffer{ new char [ this->m_chunk_size ] };
131 
133  auto
135  {
136  return [this, ctx = this->shared_from_this()]
137  // NOTE: this lambda is noexcept since v.0.6.0.
138  ( const asio_ns::error_code & ec, std::size_t written ) noexcept
139  {
140  if( !ec )
141  {
142  this->m_remained_size -= written;
143  this->m_transfered_size += written;
144  if( 0 == this->m_remained_size )
145  {
146  this->m_after_sendfile_cb(
147  ec,
148  this->m_transfered_size );
149  }
150  else
151  {
152  this->init_next_write();
153  }
154  }
155  else
156  {
157  this->m_after_sendfile_cb(
158  ec,
159  this->m_transfered_size );
160  }
161  };
162  }
163 };
164 
167 template <>
168 class sendfile_operation_runner_t< asio_ns::ip::tcp::socket > final
169  : public sendfile_operation_runner_base_t< asio_ns::ip::tcp::socket >
170 {
171  private:
172 
174  bool
176  {
177  bool result = true;
178 
179  if( !m_socket.native_non_blocking() )
180  {
181  asio_ns::error_code ec;
182  m_socket.native_non_blocking( true, ec );
183  if( ec )
184  {
185  // We assume that m_after_sendfile_cb doesn't throw;
187  result = false;
188  }
189  }
190 
191  return result;
192  }
193 
194 #if defined( RESTINIO_FREEBSD_TARGET )
196  auto
197  call_native_sendfile() noexcept
198  {
199  // FreeBSD sendfile signature:
200  // int sendfile(int fd, int s, off_t offset, size_t nbytes,
201  // struct sf_hdtr *hdtr, off_t *sbytes, int flags);
202  // https://www.freebsd.org/cgi/man.cgi?query=sendfile
203 
204  off_t n{ 0 };
205  auto rc =
206  ::sendfile(
208  m_socket.native_handle(),
210  static_cast< size_t >(
211  std::min< file_size_t >( m_remained_size, m_chunk_size ) ),
212  nullptr, // struct sf_hdtr *hdtr
213  &n, // sbytes
214  // Is 16 a reasonable constant here.
215 #if __FreeBSD__ >= 11
216  SF_FLAGS( 16, SF_NOCACHE )
217 #else
218  SF_MNOWAIT
219 #endif
220  );
221 
222  // Shift the number of bytes successfully sent.
223  m_next_write_offset += n;
224 
225  if( -1 == rc )
226  {
227  // It is still possible that some bytes had been sent.
228  m_remained_size -= static_cast< file_size_t >( n );
229  m_transfered_size += static_cast< file_size_t >( n );
230 
231  n = -1;
232  }
233 
234  return n;
235  }
236 #elif defined( RESTINIO_MACOS_TARGET )
238  auto
239  call_native_sendfile() noexcept
240  {
241  // macOS sendfile signature:
242  // in sendfile(int fd, int s, off_t offset,
243  // off_t *len, struct sf_hdtr *hdtr, int flags);
244 
245  off_t n =
246  static_cast< off_t >(
247  std::min< file_size_t >( m_remained_size, m_chunk_size ) );
248 
249  auto rc =
250  ::sendfile(
252  m_socket.native_handle(),
254  &n,
255  nullptr, // struct sf_hdtr *hdtr
256  0 );
257 
258  // Shift the number of bytes successfully sent.
259  m_next_write_offset += n;
260 
261  if( -1 == rc )
262  {
263  // It is still possible that some bytes had been sent.
264  m_remained_size -= static_cast< file_size_t >( n );
265  m_transfered_size += static_cast< file_size_t >( n );
266 
267  n = -1;
268  }
269 
270  return n;
271  }
272 #else
274  auto
276  {
277  return ::sendfile64(
278  m_socket.native_handle(),
281  std::min< file_size_t >( m_remained_size, m_chunk_size ) );
282  }
283 #endif
284 
286  bool
288  {
289  bool result = true;
290 
291  try
292  {
293  // We have to wait for the socket to become ready again.
294  m_socket.async_wait(
295  asio_ns::ip::tcp::socket::wait_write,
296  asio_ns::bind_executor(
297  m_executor,
298  [ this, ctx = this->shared_from_this() ]
299  // NOTE: this lambda is noexcept since v.0.6.0.
300  ( const asio_ns::error_code & ec ) noexcept {
301  if( ec || 0 == m_remained_size )
302  {
304  }
305  else
306  {
307  init_next_write();
308  }
309  } ) );
310  }
311  catch( ... )
312  {
315  asio_convertible_error_t::async_write_call_failed ),
317  result = false;
318  }
319 
320  return result;
321  }
322 
323  public:
325 
330 
331  // Reuse construstors from base.
332  using base_type_t::base_type_t;
333 
334  virtual void
335  start() override
336  {
337  init_next_write();
338  }
339 
344  void
345  init_next_write() noexcept
346  {
347  if( !try_turn_non_blocking_mode() )
348  return;
349 
350  while( true )
351  {
352  // Try the system call.
353  errno = 0;
354 
355  if( 0 == m_remained_size )
356  {
357  // We are done.
358  // Result of try_initiate_waiting_for_write_readiness can
359  // be ignored here.
360  (void)try_initiate_waiting_for_write_readiness();
361  break;
362  }
363 
364  const auto n = call_native_sendfile();
365 
366  if( -1 == n )
367  {
368  if( errno == EAGAIN || errno == EINTR )
369  {
370  if( !try_initiate_waiting_for_write_readiness() )
371  return;
372  }
373  else
374  {
376  asio_ns::error_code{
377  errno, asio_ns::error::get_system_category() },
379  }
380 
381  break;
382  }
383  else if( 0 == n )
384  {
385  // Result of try_initiate_waiting_for_write_readiness can
386  // be ignored here.
387  (void)try_initiate_waiting_for_write_readiness();
388  break;
389  }
390  else
391  {
392  m_remained_size -= static_cast< file_size_t >( n );
393  m_transfered_size += static_cast< file_size_t >( n );
394  }
395 
396  // Loop around to try calling sendfile again.
397  }
398  }
399 };
400 
401 } /* namespace impl */
402 
403 } /* namespace restinio */
404 
restinio::const_buffer
constexpr const_buffer_t const_buffer(const void *str, std::size_t size) noexcept
Definition: buffers.hpp:424
restinio::impl::sendfile_operation_runner_base_t::m_socket
Socket & m_socket
Definition: sendfile_operation.hpp:82
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
RESTINIO_NODISCARD
#define RESTINIO_NODISCARD
Definition: compiler_features.hpp:33
restinio::file_size_t
std::uint64_t file_size_t
Definition: sendfile_defs_default.hpp:23
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_file_descriptor
file_descriptor_t m_file_descriptor
Definition: sendfile_operation.hpp:72
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_base_t
A base runner of sendfile operation (keeps all the data).
Definition: sendfile_operation.hpp:50
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< 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 >::sendfile_operation_runner_t
sendfile_operation_runner_t(sendfile_operation_runner_t &&)=delete
restinio::asio_ec::eof
constexpr auto eof
Definition: asio_include.hpp:41
restinio::impl::sendfile_operation_runner_base_t::m_after_sendfile_cb
after_sendfile_cb_t m_after_sendfile_cb
Definition: sendfile_operation.hpp:83
SEEK_SET
#define SEEK_SET
Definition: zconf.h:498
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::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 >::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_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_transfered_size
file_size_t m_transfered_size
Definition: sendfile_operation.hpp:75
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< asio_ns::ip::tcp::socket >::sendfile_operation_runner_t
sendfile_operation_runner_t(const sendfile_operation_runner_t &)=delete
restinio
Definition: asio_include.hpp:21
restinio::impl::sendfile_operation_runner_t::sendfile_operation_runner_t
sendfile_operation_runner_t(sendfile_operation_runner_t &&)=delete
restinio::impl::sendfile_operation_runner_base_t::m_executor
default_asio_executor m_executor
Definition: sendfile_operation.hpp:81
restinio::impl::sendfile_operation_runner_t::start
virtual void start() override
Definition: sendfile_operation_posix.ipp:42
restinio::impl::sendfile_operation_runner_t
A runner of sendfile operation.
Definition: sendfile_operation_default.ipp:25
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_base_t::m_chunk_size
const file_size_t m_chunk_size
Definition: sendfile_operation.hpp:77
restinio::impl::sendfile_operation_runner_t::operator=
sendfile_operation_runner_t & operator=(const sendfile_operation_runner_t &)=delete
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 >::try_turn_non_blocking_mode
RESTINIO_NODISCARD bool try_turn_non_blocking_mode() noexcept
Definition: sendfile_operation_posix.ipp:175