RESTinio
sendfile_operation_posix.ipp
Go to the documentation of this file.
1 /*
2  restinio
3 */
4 
5 /*!
6  sendfile routine.
7 */
8 
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 
25 //! A runner of sendfile operation
26 template < typename 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  {
58  return;
59  }
60  }
61 
62  /*!
63  * @note
64  * This method is noexcept since v.0.6.0.
65  */
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(
90  errno,
92  this->m_transfered_size );
93  }
94  else if( 0 == n )
95  {
96  this->m_after_sendfile_cb(
98  asio_ec::eof,
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  {
108  this->m_socket,
110  this->m_buffer.get(),
111  static_cast< std::size_t >( n ) },
113  this->m_executor,
115  }
116  catch( ... )
117  {
118  this->m_after_sendfile_cb(
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 
132  //! Helper method for making a lambda for async_write completion handler.
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 
165 //! A specialization for plain tcp-socket using
166 //! linux sendfile() (http://man7.org/linux/man-pages/man2/sendfile.2.html).
167 template <>
170 {
171  private:
172 
174  bool
176  {
177  bool result = true;
178 
180  {
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(
210  static_cast< size_t >(
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.
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 >(
248 
249  auto rc =
250  ::sendfile(
254  &n,
255  nullptr, // struct sf_hdtr *hdtr
256  0 );
257 
258  // Shift the number of bytes successfully sent.
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(
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.
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  {
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  {
338  }
339 
340  /*!
341  * @note
342  * This method is noexcept since v.0.6.0.
343  */
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  {
375  m_after_sendfile_cb(
376  asio_ns::error_code{
377  errno, asio_ns::error::get_system_category() },
378  m_transfered_size );
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 */
sendfile_operation_runner_t(const sendfile_operation_runner_t &)=delete
sendfile_operation_runner_t(sendfile_operation_runner_t &&)=delete
sendfile_operation_runner_t(const sendfile_operation_runner_t &)=delete
auto make_async_write_handler() noexcept
Helper method for making a lambda for async_write completion handler.
sendfile_operation_runner_t & operator=(sendfile_operation_runner_t &&)=delete
sendfile_operation_runner_t & operator=(sendfile_operation_runner_t &&)=delete
RESTINIO_NODISCARD char to_lower_case(unsigned char ch)
sendfile_operation_runner_t & operator=(const sendfile_operation_runner_t &)=delete
std::enable_if< std::is_same< Parameter_Container, query_string_params_t >::value||std::is_same< Parameter_Container, router::route_params_t >::value, optional_t< Value_Type > >::type opt_value(const Parameter_Container &params, string_view_t key)
Gets the value of a parameter specified by key wrapped in optional_t<Value_Type> if parameter exists ...
Definition: value_or.hpp:64
sendfile_operation_runner_t & operator=(const sendfile_operation_runner_t &)=delete