RESTinio
sendfile_operation_win.ipp
Go to the documentation of this file.
1 /*
2  restinio
3 */
4 
5 /*!
6  sendfile routine.
7 */
8 
9 #if defined(RESTINIO_ASIO_HAS_WINDOWS_OVERLAPPED_PTR)
10 
11 #include <cstdio>
12 
13 #include <restinio/utils/impl/safe_uint_truncate.hpp>
14 
15 namespace restinio
16 {
17 
18 namespace impl
19 {
20 
21 namespace asio_details
22 {
23 
24 #if RESTINIO_ASIO_VERSION < 101300
25 
26 template<typename Socket >
27 decltype(auto)
28 executor_or_context_from_socket( Socket & socket )
29 {
30  return socket.get_executor().context();
31 }
32 
33 #else
34 
35 template<typename Socket >
36 decltype(auto)
37 executor_or_context_from_socket( Socket & socket )
38 {
39  return socket.get_executor();
40 }
41 
42 #endif
43 
44 } /* namespace asio_details */
45 
46 //
47 // sendfile_operation_runner_t
48 //
49 
50 //! A runner of sendfile operation.
51 template < typename Socket >
52 class sendfile_operation_runner_t final
53  : public sendfile_operation_runner_base_t< Socket >
54 {
55  public:
56  using base_type_t = sendfile_operation_runner_base_t< Socket >;
57 
58  sendfile_operation_runner_t( const sendfile_operation_runner_t & ) = delete;
59  sendfile_operation_runner_t( sendfile_operation_runner_t && ) = delete;
60  sendfile_operation_runner_t & operator = ( const sendfile_operation_runner_t & ) = delete;
61  sendfile_operation_runner_t & operator = ( sendfile_operation_runner_t && ) = delete;
62 
63  sendfile_operation_runner_t(
64  sendfile_t & sf,
65  default_asio_executor executor,
66  Socket & socket,
67  after_sendfile_cb_t after_sendfile_cb )
68  : base_type_t{ sf, std::move( executor), socket, std::move( after_sendfile_cb ) }
69  {
70  // We have passed sf.file_descriptor() to m_file_handle object.
71  // It means that file description will be closed automatically
72  // by m_file_handle.
73  // But sf still holds the same file_descriptor. Because of that
74  // we should tell sf to release this file_descriptor.
75  takeaway_file_descriptor(sf).release();
76  }
77 
78  virtual void
79  start() override
80  {
81  init_next_read_some_from_file();
82  }
83 
84  /*!
85  * @note
86  * This method is noexcept since v.0.6.0.
87  */
88  void
89  init_next_read_some_from_file() noexcept
90  {
91  const auto desired_size =
92  std::min< file_size_t >( this->m_remained_size, this->m_chunk_size );
93 
94  try
95  {
96  this->m_file_handle.async_read_some_at(
97  this->m_next_write_offset,
98  asio_ns::buffer(
99  this->m_buffer.get(),
100  static_cast< std::size_t >( desired_size ) ),
101  asio_ns::bind_executor(
102  this->m_executor,
103  make_async_read_some_at_handler() ) );
104  }
105  catch( ... )
106  {
107  this->m_after_sendfile_cb(
108  make_asio_compaible_error(
109  asio_convertible_error_t::async_read_some_at_call_failed ),
110  this->m_transfered_size );
111  }
112  }
113 
114  /*!
115  * @note
116  * This method is noexcept since v.0.6.0.
117  */
118  void
119  init_next_write( std::size_t len ) noexcept
120  {
121  try
122  {
123  asio_ns::async_write(
124  this->m_socket,
125  asio_ns::const_buffer{
126  this->m_buffer.get(),
127  static_cast< std::size_t >( len ) },
128  asio_ns::bind_executor(
129  this->m_executor,
130  make_async_write_handler() ) );
131  }
132  catch( ... )
133  {
134  this->m_after_sendfile_cb(
135  make_asio_compaible_error(
136  asio_convertible_error_t::async_write_call_failed ),
137  this->m_transfered_size );
138  }
139  }
140 
141  private:
142  std::unique_ptr< char[] > m_buffer{ new char [ this->m_chunk_size ] };
143  asio_ns::windows::random_access_handle m_file_handle{
144  asio_details::executor_or_context_from_socket(this->m_socket),
145  this->m_file_descriptor
146  };
147 
148  auto
149  make_async_read_some_at_handler() noexcept
150  {
151  return [this, ctx = this->shared_from_this()]
152  // NOTE: this lambda is noexcept since v.0.6.0.
153  ( const asio_ns::error_code & ec, std::size_t len ) noexcept
154  {
155  if( ec || 0 == this->m_remained_size )
156  {
157  this->m_after_sendfile_cb( ec, this->m_transfered_size );
158  }
159  if( !ec )
160  {
161  if( 0 != len )
162  init_next_write( len );
163  else
164  {
165  this->m_after_sendfile_cb(
166  make_error_code( asio_ec::eof ),
167  this->m_transfered_size );
168  }
169  }
170  else
171  {
172  this->m_after_sendfile_cb( ec, this->m_transfered_size );
173  }
174  };
175  }
176 
177  auto
178  make_async_write_handler() noexcept
179  {
180  return [ this, ctx = this->shared_from_this() ]
181  // NOTE: this lambda is noexcept since v.0.6.0.
182  ( const asio_ns::error_code & ec, std::size_t written ) noexcept
183  {
184  if( !ec )
185  {
186  this->m_remained_size -= written;
187  this->m_transfered_size += written;
188  this->m_next_write_offset += written;
189 
190  if( 0 == this->m_remained_size )
191  {
192  this->m_after_sendfile_cb( ec, this->m_transfered_size );
193  }
194  else
195  {
196  this->init_next_read_some_from_file();
197  }
198  }
199  else
200  {
201  this->m_after_sendfile_cb( ec, this->m_transfered_size );
202  }
203  };
204  }
205 };
206 
207 //! A runner of sendfile operation for raw socket.
208 template <>
209 class sendfile_operation_runner_t < asio_ns::ip::tcp::socket > final
210  : public sendfile_operation_runner_base_t< asio_ns::ip::tcp::socket >
211 {
212  auto
213  make_completion_handler() noexcept
214  {
215  return [this, ctx = shared_from_this() ]
216  // NOTE: this lambda is noexcept since v.0.6.0.
217  ( const asio_ns::error_code & ec, std::size_t written )
218  {
219  if( !ec )
220  {
221  m_remained_size -= written;
222  m_transfered_size += written;
223  m_next_write_offset += written;
224 
225  if( 0 == m_remained_size )
226  {
227  m_after_sendfile_cb( ec, m_transfered_size );
228  }
229  else
230  {
231  init_next_write();
232  }
233  }
234  else
235  {
236  m_after_sendfile_cb( ec, m_transfered_size );
237  }
238  };
239  }
240 
241  public:
242  using base_type_t = sendfile_operation_runner_base_t< asio_ns::ip::tcp::socket >;
243 
244  sendfile_operation_runner_t( const sendfile_operation_runner_t & ) = delete;
245  sendfile_operation_runner_t( sendfile_operation_runner_t && ) = delete;
246  sendfile_operation_runner_t & operator = ( const sendfile_operation_runner_t & ) = delete;
247  sendfile_operation_runner_t & operator = ( sendfile_operation_runner_t && ) = delete;
248 
249  sendfile_operation_runner_t(
250  sendfile_t & sf,
251  default_asio_executor executor,
252  asio_ns::ip::tcp::socket & socket,
253  after_sendfile_cb_t after_sendfile_cb )
254  : base_type_t{ sf, std::move( executor), socket, std::move( after_sendfile_cb ) }
255  {
256  // We have passed sf.file_descriptor() to m_file_handle object.
257  // It means that file description will be closed automatically
258  // by m_file_handle.
259  // But sf still holds the same file_descriptor. Because of that
260  // we should tell sf to release this file_descriptor.
261  takeaway_file_descriptor(sf).release();
262  }
263 
264  virtual void
265  start() override
266  {
267  init_next_write();
268  }
269 
270  /*!
271  * @note
272  * This method is noexcept since v.0.6.0.
273  */
274  void
275  init_next_write() noexcept
276  {
277  // In this function bind_executor is the main suspect
278  // for throwing an exception. Because of that the whole
279  // function's logic is wrapped by try-catch.
280  try
281  {
282  asio_ns::windows::overlapped_ptr overlapped{
283  asio_details::executor_or_context_from_socket( m_socket ),
284  asio_ns::bind_executor(
285  m_executor,
286  make_completion_handler() )
287  };
288 
289  // Set offset.
290  overlapped.get()->Offset =
291  static_cast< DWORD >( m_next_write_offset & 0xFFFFFFFFULL );
292  overlapped.get()->OffsetHigh =
293  static_cast< DWORD >( (m_next_write_offset>>32) & 0xFFFFFFFFULL );
294 
295  // Amount of data to transfer.
296  const auto desired_size =
297  std::min< file_size_t >( this->m_remained_size, this->m_chunk_size );
298 
299  // Initiate the TransmitFile operation.
300  BOOL ok =
301  ::TransmitFile(
302  m_socket.native_handle(),
303  m_file_handle.native_handle(),
304  static_cast< DWORD >( desired_size ),
305  0,
306  overlapped.get(),
307  nullptr,
308  0 );
309 
310  DWORD last_error = ::GetLastError();
311 
312  // Check if the operation completed immediately.
313  if( !ok && last_error != ERROR_IO_PENDING )
314  {
315  // The operation completed immediately, so a completion notification needs
316  // to be posted. When complete() is called, ownership of the OVERLAPPED-
317  // derived object passes to the io_context.
318  overlapped.complete( make_error_code( last_error ) , 0 );
319  }
320  else
321  {
322  // The operation was successfully initiated, so ownership of the
323  // OVERLAPPED-derived object has passed to the io_context.
324  overlapped.release();
325  }
326  }
327  catch( ... )
328  {
329  // Report that error as a failure of async_write.
330  this->m_after_sendfile_cb(
331  make_asio_compaible_error(
332  asio_convertible_error_t::async_write_call_failed ),
333  this->m_transfered_size );
334  }
335  }
336 
337  private:
338  std::unique_ptr< char[] > m_buffer{
339  std::make_unique< char[] >(
340  ::restinio::utils::impl::uint64_to_size_t(
341  m_chunk_size ) )
342  };
343  asio_ns::windows::random_access_handle m_file_handle{
344  asio_details::executor_or_context_from_socket(m_socket),
345  m_file_descriptor
346  };
347 };
348 
349 } /* namespace impl */
350 
351 } /* namespace restinio */
352 
353 #else // #if defined(RESTINIO_ASIO_HAS_WINDOWS_OVERLAPPED_PTR)
354 
356 
357 #endif // #if defined(RESTINIO_ASIO_HAS_WINDOWS_OVERLAPPED_PTR)