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:
31 using base_type_t = sendfile_operation_runner_base_t< Socket >;
32
33 sendfile_operation_runner_t( const sendfile_operation_runner_t & ) = delete;
34 sendfile_operation_runner_t( sendfile_operation_runner_t && ) = delete;
35 sendfile_operation_runner_t & operator = ( const sendfile_operation_runner_t & ) = delete;
36 sendfile_operation_runner_t & operator = ( sendfile_operation_runner_t && ) = delete;
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{
98 asio_ec::eof,
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,
109 asio_ns::const_buffer{
110 this->m_buffer.get(),
111 static_cast< std::size_t >( n ) },
112 asio_ns::bind_executor(
113 this->m_executor,
114 make_async_write_handler() ) );
115 }
116 catch( ... )
117 {
118 this->m_after_sendfile_cb(
119 make_asio_compaible_error(
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
134 make_async_write_handler() noexcept
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
173 RESTINIO_NODISCARD
174 bool
175 try_turn_non_blocking_mode() noexcept
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;
186 m_after_sendfile_cb( ec, m_transfered_size );
187 result = false;
188 }
189 }
190
191 return result;
192 }
193
194#if defined( RESTINIO_FREEBSD_TARGET )
195 RESTINIO_NODISCARD
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(
207 m_file_descriptor,
208 m_socket.native_handle(),
209 m_next_write_offset,
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 )
237 RESTINIO_NODISCARD
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(
251 m_file_descriptor,
252 m_socket.native_handle(),
253 m_next_write_offset,
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
273 RESTINIO_NODISCARD
274 auto
275 call_native_sendfile() noexcept
276 {
277 return ::sendfile64(
278 m_socket.native_handle(),
279 m_file_descriptor,
280 &m_next_write_offset,
281 std::min< file_size_t >( m_remained_size, m_chunk_size ) );
282 }
283#endif
284
285 RESTINIO_NODISCARD
286 bool
287 try_initiate_waiting_for_write_readiness() noexcept
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 {
303 m_after_sendfile_cb( ec, m_transfered_size );
304 }
305 else
306 {
307 init_next_write();
308 }
309 } ) );
310 }
311 catch( ... )
312 {
313 m_after_sendfile_cb(
314 make_asio_compaible_error(
315 asio_convertible_error_t::async_write_call_failed ),
316 m_transfered_size );
317 result = false;
318 }
319
320 return result;
321 }
322
323 public:
324 using base_type_t = sendfile_operation_runner_base_t< asio_ns::ip::tcp::socket >;
325
326 sendfile_operation_runner_t( const sendfile_operation_runner_t & ) = delete;
327 sendfile_operation_runner_t( sendfile_operation_runner_t && ) = delete;
328 sendfile_operation_runner_t & operator = ( const sendfile_operation_runner_t & ) = delete;
329 sendfile_operation_runner_t & operator = ( sendfile_operation_runner_t && ) = delete;
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 {
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 */
404
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