1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
|
/* An async handle to a file
(C) 2015-2017 Niall Douglas <http://www.nedproductions.biz/> (11 commits)
File Created: Dec 2015
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License in the accompanying file
Licence.txt or at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Distributed under the Boost Software License, Version 1.0.
(See accompanying file Licence.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef LLFIO_ASYNC_FILE_HANDLE_H
#define LLFIO_ASYNC_FILE_HANDLE_H
#include "file_handle.hpp"
#include "io_service.hpp"
//! \file async_file_handle.hpp Provides async_file_handle
LLFIO_V2_NAMESPACE_EXPORT_BEGIN
namespace detail
{
#if __cplusplus > 201700 && (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION > 7000 /* approx end of 2017 */)
template <class R, class Fn, class... Args> using is_invocable_r = std::is_invocable_r<R, Fn, Args...>;
#else
template <class R, class Fn, class... Args> using is_invocable_r = std::true_type;
#endif
}
/*! \class async_file_handle
\brief An asynchronous handle to an open something.
\note Unlike the others, `async_file_handle` defaults to `only_metadata` caching as that is the
only use case where using async i/o makes sense given the other options below.
<table>
<tr><th></th><th>Cost of opening</th><th>Cost of i/o</th><th>Concurrency and Atomicity</th><th>Other remarks</th></tr>
<tr><td>`file_handle`</td><td>Least</td><td>Syscall</td><td>POSIX guarantees (usually)</td><td>Least gotcha</td></tr>
<tr><td>`async_file_handle`</td><td>More</td><td>Most (syscall + malloc/free + reactor)</td><td>POSIX guarantees (usually)</td><td>Makes no sense to use with cached i/o as it's a very expensive way to call `memcpy()`</td></tr>
<tr><td>`mapped_file_handle`</td><td>Most</td><td>Least</td><td>None</td><td>Cannot be used with uncached i/o</td></tr>
</table>
\warning i/o initiated by this class MUST be on the same kernel thread as which
created the owning `io_service` which MUST also be the same kernel thread as which
runs the i/o service's `run()` function.
\snippet coroutines.cpp coroutines_example
*/
class LLFIO_DECL async_file_handle : public file_handle
{
friend class io_service;
public:
using dev_t = file_handle::dev_t;
using ino_t = file_handle::ino_t;
using path_view_type = file_handle::path_view_type;
using path_type = io_handle::path_type;
using extent_type = io_handle::extent_type;
using size_type = io_handle::size_type;
using mode = io_handle::mode;
using creation = io_handle::creation;
using caching = io_handle::caching;
using flag = io_handle::flag;
using buffer_type = io_handle::buffer_type;
using const_buffer_type = io_handle::const_buffer_type;
using buffers_type = io_handle::buffers_type;
using const_buffers_type = io_handle::const_buffers_type;
template <class T> using io_request = io_handle::io_request<T>;
template <class T> using io_result = io_handle::io_result<T>;
protected:
// Do NOT declare variables here, put them into file_handle to preserve up-conversion
public:
//! Default constructor
constexpr async_file_handle() {} // NOLINT
~async_file_handle() = default;
//! Construct a handle from a supplied native handle
constexpr async_file_handle(io_service *service, native_handle_type h, dev_t devid, ino_t inode, caching caching = caching::none, flag flags = flag::none)
: file_handle(std::move(h), devid, inode, caching, flags)
{
this->_service = service;
}
//! Implicit move construction of async_file_handle permitted
async_file_handle(async_file_handle &&o) noexcept = default;
//! No copy construction (use `clone()`)
async_file_handle(const async_file_handle &) = delete;
//! Explicit conversion from file_handle permitted
explicit constexpr async_file_handle(file_handle &&o) noexcept : file_handle(std::move(o)) {}
//! Explicit conversion from handle and io_handle permitted
explicit constexpr async_file_handle(handle &&o, io_service *service, dev_t devid, ino_t inode) noexcept : file_handle(std::move(o), devid, inode) { this->_service = service; }
//! Move assignment of async_file_handle permitted
async_file_handle &operator=(async_file_handle &&o) noexcept
{
this->~async_file_handle();
new(this) async_file_handle(std::move(o));
return *this;
}
//! No copy assignment
async_file_handle &operator=(const async_file_handle &) = delete;
//! Swap with another instance
LLFIO_MAKE_FREE_FUNCTION
void swap(async_file_handle &o) noexcept
{
async_file_handle temp(std::move(*this));
*this = std::move(o);
o = std::move(temp);
}
/*! Create an async file handle opening access to a file on path
using the given io_service.
\param service The `io_service` to use.
\param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute.
\param _path The path relative to base to open.
\param _mode How to open the file.
\param _creation How to create the file.
\param _caching How to ask the kernel to cache the file.
\param flags Any additional custom behaviours.
\errors Any of the values POSIX open() or CreateFile() can return.
*/
LLFIO_MAKE_FREE_FUNCTION
static LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result<async_file_handle> async_file(io_service &service, const path_handle &base, path_view_type _path, mode _mode = mode::read, creation _creation = creation::open_existing, caching _caching = caching::only_metadata, flag flags = flag::none) noexcept
{
// Open it overlapped, otherwise no difference.
OUTCOME_TRY(v, file_handle::file(std::move(base), _path, _mode, _creation, _caching, flags | flag::multiplexable));
async_file_handle ret(std::move(v));
ret._service = &service;
return {std::move(ret)};
}
/*! Create an async file handle creating a uniquely named file on a path.
The file is opened exclusively with `creation::only_if_not_exist` so it
will never collide with nor overwrite any existing file.
\errors Any of the values POSIX open() or CreateFile() can return.
*/
LLFIO_MAKE_FREE_FUNCTION
static inline result<async_file_handle> async_uniquely_named_file(io_service &service, const path_handle &dirpath, mode _mode = mode::write, caching _caching = caching::only_metadata, flag flags = flag::none) noexcept
{
try
{
for(;;)
{
auto randomname = utils::random_string(32);
randomname.append(".random");
result<async_file_handle> ret = async_file(service, dirpath, randomname, _mode, creation::only_if_not_exist, _caching, flags);
if(ret || (!ret && ret.error() != errc::file_exists))
{
return ret;
}
}
}
catch(...)
{
return error_from_exception();
}
}
/*! Create an async file handle creating the named file on some path which
the OS declares to be suitable for temporary files. Most OSs are
very lazy about flushing changes made to these temporary files.
Note the default flags are to have the newly created file deleted
on first handle close.
Note also that an empty name is equivalent to calling
`async_uniquely_named_file(path_discovery::storage_backed_temporary_files_directory())` and the creation
parameter is ignored.
\note If the temporary file you are creating is not going to have its
path sent to another process for usage, this is the WRONG function
to use. Use `temp_inode()` instead, it is far more secure.
\errors Any of the values POSIX open() or CreateFile() can return.
*/
LLFIO_MAKE_FREE_FUNCTION
static inline result<async_file_handle> async_temp_file(io_service &service, path_view_type name = path_view_type(), mode _mode = mode::write, creation _creation = creation::if_needed, caching _caching = caching::only_metadata, flag flags = flag::unlink_on_first_close) noexcept
{
auto &tempdirh = path_discovery::storage_backed_temporary_files_directory();
return name.empty() ? async_uniquely_named_file(service, tempdirh, _mode, _caching, flags) : async_file(service, tempdirh, name, _mode, _creation, _caching, flags);
}
/*! \em Securely create an async file handle creating a temporary anonymous inode in
the filesystem referred to by \em dirpath. The inode created has
no name nor accessible path on the filing system and ceases to
exist as soon as the last handle is closed, making it ideal for use as
a temporary file where other processes do not need to have access
to its contents via some path on the filing system (a classic use case
is for backing shared memory maps).
\errors Any of the values POSIX open() or CreateFile() can return.
*/
LLFIO_MAKE_FREE_FUNCTION
static LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result<async_file_handle> async_temp_inode(io_service &service, const path_handle &dir = path_discovery::storage_backed_temporary_files_directory(), mode _mode = mode::write, flag flags = flag::none) noexcept
{
// Open it overlapped, otherwise no difference.
OUTCOME_TRY(v, file_handle::temp_inode(dir, _mode, flags | flag::multiplexable));
async_file_handle ret(std::move(v));
ret._service = &service;
return {std::move(ret)};
}
LLFIO_MAKE_FREE_FUNCTION
LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), barrier_kind kind = barrier_kind::nowait_data_only, deadline d = deadline()) noexcept override;
/*! Reopen this handle to a different io_service (copy constructor is disabled to avoid accidental copying)
\errors Any of the values POSIX dup() or DuplicateHandle() can return.
*/
result<async_file_handle> reopen(io_service &service, mode mode_ = mode::unchanged, caching caching_ = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept
{
OUTCOME_TRY(v, file_handle::reopen(mode_, caching_, d));
async_file_handle ret(std::move(v));
ret._service = &service;
return {std::move(ret)};
}
LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result<file_handle> reopen(mode mode_ = mode::unchanged, caching caching_ = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept
{
OUTCOME_TRY(v, file_handle::reopen(mode_, caching_, d));
async_file_handle ret(std::move(v));
ret._service = _service;
return {static_cast<file_handle &&>(ret)};
}
#if DOXYGEN_SHOULD_SKIP_THIS
private:
#else
protected:
#endif
using shared_size_type = size_type;
enum class operation_t
{
read,
write,
fsync_sync,
dsync_sync,
fsync_async,
dsync_async
};
struct _erased_completion_handler;
#if defined(__clang__) && __clang_major__ >= 8
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdefaulted-function-deleted"
#endif
// Holds state for an i/o in progress. Will be subclassed with platform specific state and how to implement completion.
struct _erased_io_state_type
{
friend class io_service;
async_file_handle *parent;
operation_t operation;
bool must_deallocate_self;
size_t items;
shared_size_type items_to_go;
union result_storage {
io_result<buffers_type> read;
io_result<const_buffers_type> write;
constexpr result_storage()
: read(buffers_type())
{
}
~result_storage() { /* needed as io_result is move-only when configured with status code */}
} result;
constexpr _erased_io_state_type(async_file_handle *_parent, operation_t _operation, bool _must_deallocate_self, size_t _items)
: parent(_parent)
, operation(_operation)
, must_deallocate_self(_must_deallocate_self)
, items(_items)
, items_to_go(0)
{
}
LLFIO_HEADERS_ONLY_VIRTUAL_SPEC ~_erased_io_state_type()
{
// i/o still pending is very bad, this should never happen
assert(!items_to_go);
if(items_to_go != 0u)
{
LLFIO_LOG_FATAL(parent->native_handle().h, "FATAL: io_state destructed while i/o still in flight, the derived class should never allow this.");
abort();
}
}
//! Retrieves a pointer to the copy of the completion handler held inside the i/o state.
virtual _erased_completion_handler *erased_completion_handler() noexcept = 0;
/* Called when an i/o is completed by the system, figures out whether to call invoke_completion.
For Windows:
- errcode: GetLastError() code
- bytes_transferred: obvious
- internal_state: LPOVERLAPPED for this op
For POSIX AIO:
- errcode: errno code
- bytes_transferred: return from aio_return(), usually bytes transferred
- internal_state: address of pointer to struct aiocb in io_service's _aiocbsv
*/
virtual void _system_io_completion(long errcode, long bytes_transferred, void *internal_state) noexcept = 0;
protected:
_erased_io_state_type(_erased_io_state_type &&) = default;
_erased_io_state_type(const _erased_io_state_type &) = default;
_erased_io_state_type &operator=(_erased_io_state_type &&) = default;
_erased_io_state_type &operator=(const _erased_io_state_type &) = default;
};
#if defined(__clang__) && __clang_major__ >= 8
#pragma clang diagnostic pop
#endif
struct _io_state_deleter
{
template <class U> void operator()(U *_ptr) const
{
bool must_deallocate_self = _ptr->must_deallocate_self;
_ptr->~U();
if(must_deallocate_self)
{
auto *ptr = reinterpret_cast<char *>(_ptr);
::free(ptr); // NOLINT
}
}
};
public:
/*! Smart pointer to state of an i/o in progress. Destroying this before an i/o has completed
is <b>blocking</b> because the i/o must be cancelled before the destructor can safely exit.
*/
using io_state_ptr = std::unique_ptr<_erased_io_state_type, _io_state_deleter>;
#if DOXYGEN_SHOULD_SKIP_THIS
private:
#else
protected:
#endif
// Used to indirect copy and call of unknown completion handler
struct _erased_completion_handler
{
virtual ~_erased_completion_handler() = default;
// Returns my size including completion handler
virtual size_t bytes() const noexcept = 0;
// Moves me and handler to some new location
virtual void move(_erased_completion_handler *dest) = 0;
// Invokes my completion handler
virtual void operator()(_erased_io_state_type *state) = 0;
// Returns a pointer to the completion handler
virtual void *address() noexcept = 0;
protected:
_erased_completion_handler() = default;
_erased_completion_handler(_erased_completion_handler &&) = default;
_erased_completion_handler(const _erased_completion_handler &) = default;
_erased_completion_handler &operator=(_erased_completion_handler &&) = default;
_erased_completion_handler &operator=(const _erased_completion_handler &) = default;
};
template <class BuffersType, class IORoutine> result<io_state_ptr> LLFIO_HEADERS_ONLY_MEMFUNC_SPEC _begin_io(span<char> mem, operation_t operation, io_request<BuffersType> reqs, _erased_completion_handler &&completion, IORoutine &&ioroutine) noexcept;
LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result<io_state_ptr> _begin_io(span<char> mem, operation_t operation, io_request<const_buffers_type> reqs, _erased_completion_handler &&completion) noexcept;
public:
/*! \brief Schedule a barrier to occur asynchronously.
\note All the caveats and exclusions which apply to `barrier()` also apply here. Note that Microsoft Windows
does not support asynchronously executed barriers, and this call will fail on that operating system.
\return Either an io_state_ptr to the i/o in progress, or an error code.
\param reqs A scatter-gather and offset request for what range to barrier. May be ignored on some platforms
which always write barrier the entire file. Supplying a default initialised reqs write barriers the entire file.
\param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result<const_buffers_type> &&)`.
Note that buffers returned may not be buffers input, see documentation for `barrier()`.
\param kind Which kind of write reordering barrier to perform.
\param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry.
\errors As for `barrier()`, plus `ENOMEM`.
\mallocs If mem is not set, one calloc, one free. The allocation is unavoidable due to the need to store a type
erased completion handler of unknown type and state per buffers input.
*/
LLFIO_MAKE_FREE_FUNCTION
template <class CompletionRoutine> //
LLFIO_REQUIRES(detail::is_invocable_r<void, CompletionRoutine, async_file_handle *, io_result<const_buffers_type> &&>::value) //
result<io_state_ptr> async_barrier(io_request<const_buffers_type> reqs, CompletionRoutine &&completion, barrier_kind kind = barrier_kind::nowait_data_only, span<char> mem = {}) noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
struct completion_handler : _erased_completion_handler
{
CompletionRoutine completion;
explicit completion_handler(CompletionRoutine c)
: completion(std::move(c))
{
}
size_t bytes() const noexcept final { return sizeof(*this); }
void move(_erased_completion_handler *_dest) final
{
auto *dest = reinterpret_cast<void *>(_dest);
using msvc_workaround = std::decay_t<decltype(*this)>;
new(dest) msvc_workaround(std::move(*this));
}
void operator()(_erased_io_state_type *state) final { completion(state->parent, std::move(state->result.write)); }
void *address() noexcept final { return &completion; }
} ch{std::forward<CompletionRoutine>(completion)};
operation_t operation = operation_t::fsync_sync;
if(kind == barrier_kind::nowait_all)
{
operation = operation_t::fsync_async;
}
else if(kind == barrier_kind::wait_data_only)
{
operation = operation_t::dsync_sync;
}
else if(kind == barrier_kind::nowait_data_only)
{
operation = operation_t::dsync_async;
}
return _begin_io(mem, operation, reinterpret_cast<io_request<const_buffers_type> &>(reqs), std::move(ch));
}
/*! \brief Schedule a read to occur asynchronously.
Note that some OS kernels can only process a limited number async i/o
operations at a time. You should therefore check for the error `errc::resource_unavailable_try_again`
and gracefully reschedule the i/o for a later time. This temporary
failure may be returned immediately, or to the completion handler
and hence you ought to handle both situations.
\return Either an io_state_ptr to the i/o in progress, or an error code.
\param reqs A scatter-gather and offset request.
\param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result<buffers_type> &&)`.
Note that buffers returned may not be buffers input, see documentation for `read()`.
\param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry.
\errors As for `read()`, plus `ENOMEM`.
\mallocs If mem is not set, one calloc, one free. The allocation is unavoidable due to the need to store a type
erased completion handler of unknown type and state per buffers input.
*/
LLFIO_MAKE_FREE_FUNCTION
template <class CompletionRoutine> //
LLFIO_REQUIRES(detail::is_invocable_r<void, CompletionRoutine, async_file_handle *, io_result<buffers_type> &&>::value) //
result<io_state_ptr> async_read(io_request<buffers_type> reqs, CompletionRoutine &&completion, span<char> mem = {}) noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
struct completion_handler : _erased_completion_handler
{
CompletionRoutine completion;
explicit completion_handler(CompletionRoutine c)
: completion(std::move(c))
{
}
size_t bytes() const noexcept final { return sizeof(*this); }
void move(_erased_completion_handler *_dest) final
{
auto *dest = reinterpret_cast<void *>(_dest);
using msvc_workaround = std::decay_t<decltype(*this)>;
new(dest) msvc_workaround(std::move(*this));
}
void operator()(_erased_io_state_type *state) final { completion(state->parent, std::move(state->result.read)); }
void *address() noexcept final { return &completion; }
} ch{std::forward<CompletionRoutine>(completion)};
return _begin_io(mem, operation_t::read, io_request<const_buffers_type>({reinterpret_cast<const_buffer_type *>(reqs.buffers.data()), reqs.buffers.size()}, reqs.offset), std::move(ch));
}
/*! \brief Schedule a write to occur asynchronously.
Note that some OS kernels can only process a limited number async i/o
operations at a time. You should therefore check for the error `errc::resource_unavailable_try_again`
and gracefully reschedule the i/o for a later time. This temporary
failure may be returned immediately, or to the completion handler
and hence you ought to handle both situations.
\return Either an io_state_ptr to the i/o in progress, or an error code.
\param reqs A scatter-gather and offset request.
\param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result<const_buffers_type> &&)`.
Note that buffers returned may not be buffers input, see documentation for `write()`.
\param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry.
\errors As for `write()`, plus `ENOMEM`.
\mallocs If mem in not set, one calloc, one free. The allocation is unavoidable due to the need to store a type
erased completion handler of unknown type and state per buffers input.
*/
LLFIO_MAKE_FREE_FUNCTION
template <class CompletionRoutine> //
LLFIO_REQUIRES(detail::is_invocable_r<void, CompletionRoutine, async_file_handle *, io_result<const_buffers_type> &&>::value) //
result<io_state_ptr> async_write(io_request<const_buffers_type> reqs, CompletionRoutine &&completion, span<char> mem = {}) noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
struct completion_handler : _erased_completion_handler
{
CompletionRoutine completion;
explicit completion_handler(CompletionRoutine c)
: completion(std::move(c))
{
}
size_t bytes() const noexcept final { return sizeof(*this); }
void move(_erased_completion_handler *_dest) final
{
auto *dest = reinterpret_cast<void *>(_dest);
using msvc_workaround = std::decay_t<decltype(*this)>;
new(dest) msvc_workaround(std::move(*this));
}
void operator()(_erased_io_state_type *state) final { completion(state->parent, std::move(state->result.write)); }
void *address() noexcept final { return &completion; }
} ch{std::forward<CompletionRoutine>(completion)};
return _begin_io(mem, operation_t::write, reqs, std::move(ch));
}
using file_handle::read;
LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<buffers_type> read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept override;
using file_handle::write;
LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept override;
#if defined(LLFIO_ENABLE_COROUTINES) || defined(DOXYGEN_IS_IN_THE_HOUSE)
private:
template <class BuffersType> class awaitable_state
{
friend class async_file_handle;
optional<coroutine_handle<>> _suspended;
optional<io_result<BuffersType>> _result;
// Called on completion of the i/o
void operator()(async_file_handle * /*unused*/, io_result<BuffersType> &&result)
{
// store the result and resume the coroutine
_result = std::move(result);
if(_suspended)
{
_suspended->resume();
}
}
};
public:
//! Type sugar to tell `co_await` what to do
template <class BuffersType> class awaitable
{
friend class async_file_handle;
io_state_ptr _state;
awaitable_state<BuffersType> *_astate;
explicit awaitable(io_state_ptr state)
: _state(std::move(state))
, _astate(reinterpret_cast<awaitable_state<BuffersType> *>(_state->erased_completion_handler()->address()))
{
}
public:
//! Called by `co_await` to determine whether to suspend the coroutine.
bool await_ready() { return _astate->_result.has_value(); }
//! Called by `co_await` to suspend the coroutine.
void await_suspend(coroutine_handle<> co) { _astate->_suspended = co; }
//! Called by `co_await` after resuming the coroutine to return a value.
io_result<BuffersType> await_resume() { return std::move(*_astate->_result); }
};
public:
/*! \brief Schedule a read to occur asynchronously.
\return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine
until the operation has completed, resuming with the buffers read, which may
not be the buffers input. The size of each scatter-gather buffer is updated with the number
of bytes of that buffer transferred, and the pointer to the data may be \em completely
different to what was submitted (e.g. it may point into a memory map).
\param reqs A scatter-gather and offset request.
\errors As for read(), plus ENOMEM.
\mallocs One calloc, one free.
*/
LLFIO_MAKE_FREE_FUNCTION
result<awaitable<buffers_type>> co_read(io_request<buffers_type> reqs) noexcept
{
OUTCOME_TRY(r, async_read(reqs, awaitable_state<buffers_type>()));
return awaitable<buffers_type>(std::move(r));
}
//! \overload
LLFIO_MAKE_FREE_FUNCTION
result<awaitable<buffers_type>> co_read(extent_type offset, std::initializer_list<buffer_type> lst) noexcept
{
buffer_type *_reqs = reinterpret_cast<buffer_type *>(alloca(sizeof(buffer_type) * lst.size()));
memcpy(_reqs, lst.begin(), sizeof(buffer_type) * lst.size());
io_request<buffers_type> reqs(buffers_type(_reqs, lst.size()), offset);
return co_read(reqs);
}
/*! \brief Schedule a write to occur asynchronously
\return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine
until the operation has completed, resuming with the buffers written, which
may not be the buffers input. The size of each scatter-gather buffer is updated with
the number of bytes of that buffer transferred.
\param reqs A scatter-gather and offset request.
\errors As for write(), plus ENOMEM.
\mallocs One calloc, one free.
*/
LLFIO_MAKE_FREE_FUNCTION
result<awaitable<const_buffers_type>> co_write(io_request<const_buffers_type> reqs) noexcept
{
OUTCOME_TRY(r, async_write(reqs, awaitable_state<const_buffers_type>()));
return awaitable<const_buffers_type>(std::move(r));
}
//! \overload
LLFIO_MAKE_FREE_FUNCTION
result<awaitable<const_buffers_type>> co_write(extent_type offset, std::initializer_list<const_buffer_type> lst) noexcept
{
const_buffer_type *_reqs = reinterpret_cast<const_buffer_type *>(alloca(sizeof(const_buffer_type) * lst.size()));
memcpy(_reqs, lst.begin(), sizeof(const_buffer_type) * lst.size());
io_request<const_buffers_type> reqs(const_buffers_type(_reqs, lst.size()), offset);
return co_write(reqs);
}
#endif
};
//! \brief Constructor for `async_file_handle`
template <> struct construct<async_file_handle>
{
io_service &service;
const path_handle &base;
async_file_handle::path_view_type _path;
async_file_handle::mode _mode = async_file_handle::mode::read;
async_file_handle::creation _creation = async_file_handle::creation::open_existing;
async_file_handle::caching _caching = async_file_handle::caching::only_metadata;
async_file_handle::flag flags = async_file_handle::flag::none;
result<async_file_handle> operator()() const noexcept { return async_file_handle::async_file(service, base, _path, _mode, _creation, _caching, flags); }
};
// BEGIN make_free_functions.py
//! Swap with another instance
inline void swap(async_file_handle &self, async_file_handle &o) noexcept
{
return self.swap(std::forward<decltype(o)>(o));
}
/*! Create an async file handle opening access to a file on path
using the given io_service.
\param service The `io_service` to use.
\param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute.
\param _path The path relative to base to open.
\param _mode How to open the file.
\param _creation How to create the file.
\param _caching How to ask the kernel to cache the file.
\param flags Any additional custom behaviours.
\errors Any of the values POSIX open() or CreateFile() can return.
*/
inline result<async_file_handle> async_file(io_service &service, const path_handle &base, async_file_handle::path_view_type _path, async_file_handle::mode _mode = async_file_handle::mode::read, async_file_handle::creation _creation = async_file_handle::creation::open_existing,
async_file_handle::caching _caching = async_file_handle::caching::only_metadata, async_file_handle::flag flags = async_file_handle::flag::none) noexcept
{
return async_file_handle::async_file(std::forward<decltype(service)>(service), std::forward<decltype(base)>(base), std::forward<decltype(_path)>(_path), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching),
std::forward<decltype(flags)>(flags));
}
/*! Create an async file handle creating a randomly named file on a path.
The file is opened exclusively with `creation::only_if_not_exist` so it
will never collide with nor overwrite any existing file.
\errors Any of the values POSIX open() or CreateFile() can return.
*/
inline result<async_file_handle> async_uniquely_named_file(io_service &service, const path_handle &dirpath, async_file_handle::mode _mode = async_file_handle::mode::write, async_file_handle::caching _caching = async_file_handle::caching::only_metadata, async_file_handle::flag flags = async_file_handle::flag::none) noexcept
{
return async_file_handle::async_uniquely_named_file(std::forward<decltype(service)>(service), std::forward<decltype(dirpath)>(dirpath), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
}
/*! Create an async file handle creating the named file on some path which
the OS declares to be suitable for temporary files. Most OSs are
very lazy about flushing changes made to these temporary files.
Note the default flags are to have the newly created file deleted
on first handle close.
Note also that an empty name is equivalent to calling
`async_uniquely_named_file(path_discovery::storage_backed_temporary_files_directory())` and the creation
parameter is ignored.
\note If the temporary file you are creating is not going to have its
path sent to another process for usage, this is the WRONG function
to use. Use `temp_inode()` instead, it is far more secure.
\errors Any of the values POSIX open() or CreateFile() can return.
*/
inline result<async_file_handle> async_temp_file(io_service &service, async_file_handle::path_view_type name = async_file_handle::path_view_type(), async_file_handle::mode _mode = async_file_handle::mode::write, async_file_handle::creation _creation = async_file_handle::creation::if_needed,
async_file_handle::caching _caching = async_file_handle::caching::only_metadata, async_file_handle::flag flags = async_file_handle::flag::unlink_on_first_close) noexcept
{
return async_file_handle::async_temp_file(std::forward<decltype(service)>(service), std::forward<decltype(name)>(name), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(flags));
}
/*! \em Securely create an async file handle creating a temporary anonymous inode in
the filesystem referred to by \em dirpath. The inode created has
no name nor accessible path on the filing system and ceases to
exist as soon as the last handle is closed, making it ideal for use as
a temporary file where other processes do not need to have access
to its contents via some path on the filing system (a classic use case
is for backing shared memory maps).
\errors Any of the values POSIX open() or CreateFile() can return.
*/
inline result<async_file_handle> async_temp_inode(io_service &service, const path_handle &dir = path_discovery::storage_backed_temporary_files_directory(), async_file_handle::mode _mode = async_file_handle::mode::write, async_file_handle::flag flags = async_file_handle::flag::none) noexcept
{
return async_file_handle::async_temp_inode(std::forward<decltype(service)>(service), std::forward<decltype(dir)>(dir), std::forward<decltype(_mode)>(_mode), std::forward<decltype(flags)>(flags));
}
/*! \brief Schedule a read to occur asynchronously.
Note that some OS kernels can only process a limited number async i/o
operations at a time. You should therefore check for the error `errc::resource_unavailable_try_again`
and gracefully reschedule the i/o for a later time. This temporary
failure may be returned immediately, or to the completion handler
and hence you ought to handle both situations.
\return Either an io_state_ptr to the i/o in progress, or an error code.
\param self The object whose member function to call.
\param reqs A scatter-gather and offset request.
\param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result<buffers_type> &)`.
Note that buffers returned may not be buffers input, see documentation for `read()`.
\param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry.
\errors As for `read()`, plus `ENOMEM`.
\mallocs If mem is not set, one calloc, one free. The allocation is unavoidable due to the need to store a type
erased completion handler of unknown type and state per buffers input.
*/
template <class CompletionRoutine> inline result<async_file_handle::io_state_ptr> async_read(async_file_handle &self, async_file_handle::io_request<async_file_handle::buffers_type> reqs, CompletionRoutine &&completion, span<char> mem = {}) noexcept
{
return self.async_read(std::forward<decltype(reqs)>(reqs), std::forward<decltype(completion)>(completion), std::forward<decltype(mem)>(mem));
}
/*! \brief Schedule a write to occur asynchronously.
Note that some OS kernels can only process a limited number async i/o
operations at a time. You should therefore check for the error `errc::resource_unavailable_try_again`
and gracefully reschedule the i/o for a later time. This temporary
failure may be returned immediately, or to the completion handler
and hence you ought to handle both situations.
\return Either an io_state_ptr to the i/o in progress, or an error code.
\param self The object whose member function to call.
\param reqs A scatter-gather and offset request.
\param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result<const_buffers_type> &)`.
Note that buffers returned may not be buffers input, see documentation for `write()`.
\param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry.
\errors As for `write()`, plus `ENOMEM`.
\mallocs If mem in not set, one calloc, one free. The allocation is unavoidable due to the need to store a type
erased completion handler of unknown type and state per buffers input.
*/
template <class CompletionRoutine> inline result<async_file_handle::io_state_ptr> async_write(async_file_handle &self, async_file_handle::io_request<async_file_handle::const_buffers_type> reqs, CompletionRoutine &&completion, span<char> mem = {}) noexcept
{
return self.async_write(std::forward<decltype(reqs)>(reqs), std::forward<decltype(completion)>(completion), std::forward<decltype(mem)>(mem));
}
#if defined(LLFIO_ENABLE_COROUTINES) || defined(DOXYGEN_IS_IN_THE_HOUSE)
/*! \brief Schedule a read to occur asynchronously.
\return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine
until the operation has completed, resuming with the buffers read, which may
not be the buffers input. The size of each scatter-gather buffer is updated with the number
of bytes of that buffer transferred, and the pointer to the data may be \em completely
different to what was submitted (e.g. it may point into a memory map).
\param self The object whose member function to call.
\param reqs A scatter-gather and offset request.
\errors As for read(), plus ENOMEM.
\mallocs One calloc, one free.
*/
inline result<async_file_handle::awaitable<async_file_handle::buffers_type>> co_read(async_file_handle &self, async_file_handle::io_request<async_file_handle::buffers_type> reqs) noexcept
{
return self.co_read(std::forward<decltype(reqs)>(reqs));
}
//! \overload
inline result<async_file_handle::awaitable<async_file_handle::buffers_type>> co_read(async_file_handle &self, async_file_handle::extent_type offset, std::initializer_list<async_file_handle::buffer_type> lst) noexcept
{
return self.co_read(std::forward<decltype(offset)>(offset), std::forward<decltype(lst)>(lst));
}
/*! \brief Schedule a write to occur asynchronously
\return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine
until the operation has completed, resuming with the buffers written, which
may not be the buffers input. The size of each scatter-gather buffer is updated with
the number of bytes of that buffer transferred.
\param self The object whose member function to call.
\param reqs A scatter-gather and offset request.
\errors As for write(), plus ENOMEM.
\mallocs One calloc, one free.
*/
inline result<async_file_handle::awaitable<async_file_handle::const_buffers_type>> co_write(async_file_handle &self, async_file_handle::io_request<async_file_handle::const_buffers_type> reqs) noexcept
{
return self.co_write(std::forward<decltype(reqs)>(reqs));
}
//! \overload
inline result<async_file_handle::awaitable<async_file_handle::const_buffers_type>> co_write(async_file_handle &self, async_file_handle::extent_type offset, std::initializer_list<async_file_handle::const_buffer_type> lst) noexcept
{
return self.co_write(std::forward<decltype(offset)>(offset), std::forward<decltype(lst)>(lst));
}
#endif
// END make_free_functions.py
LLFIO_V2_NAMESPACE_END
#if LLFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
#define LLFIO_INCLUDED_BY_HEADER 1
#ifdef _WIN32
#include "detail/impl/windows/io_service.ipp"
#include "detail/impl/windows/async_file_handle.ipp"
#else
#include "detail/impl/posix/io_service.ipp"
#include "detail/impl/posix/async_file_handle.ipp"
#endif
#undef LLFIO_INCLUDED_BY_HEADER
#endif
#endif
|