join 1.0
lightweight network framework library
Loading...
Searching...
No Matches
async_socket.hpp
Go to the documentation of this file.
1
25#ifndef JOIN_CORE_ASYNC_SOCKET_HPP
26#define JOIN_CORE_ASYNC_SOCKET_HPP
27
28// libjoin.
30#include <join/proactor.hpp>
31#include <join/backoff.hpp>
32#include <join/memory.hpp>
33#include <join/socket.hpp>
34#include <join/utils.hpp>
35
36// C++.
37#include <system_error>
38#include <utility>
39#include <atomic>
40#include <array>
41#include <new>
42
43// C.
44#include <sys/socket.h>
45#include <poll.h>
46
47namespace join
48{
52 template <class Protocol, class ProactorType, size_t OpCount>
53 class BasicAsyncSocket : public CompletionHandler
54 {
55 public:
56 using Socket = typename Protocol::Socket;
57 using Endpoint = typename Protocol::Endpoint;
59 using WaitHandler = typename AsyncWait::Wait;
60
62 static constexpr size_t _opCount = OpCount;
63
64 static_assert (OpCount > 0, "a socket needs at least one operation slot");
65
68
70
71 protected:
76 explicit BasicAsyncSocket (ProactorType& proactor = ProactorThread::proactor ())
77 : _proactor (&proactor)
78 {
79 }
80
86 explicit BasicAsyncSocket (Socket&& sock, ProactorType& proactor = ProactorThread::proactor ())
87 : _proactor (&proactor)
88 , _socket (std::move (sock))
89 {
90 }
91
96 BasicAsyncSocket (const BasicAsyncSocket& other) = delete;
97
103 : _proactor (other._proactor)
104 {
105 other.suspendAll ();
106
107 _allocator = std::move (other._allocator);
108
109 for (size_t i = 0; i < other._ops.size (); ++i)
110 {
111 _ops[i].store (other._ops[i].exchange (nullptr, std::memory_order_acq_rel), std::memory_order_release);
112 }
113
114 _socket = std::move (other._socket);
115 }
116
117 public:
124
131 {
132 close ();
133
134 _proactor = other._proactor;
135
136 other.suspendAll ();
137
138 _allocator = std::move (other._allocator);
139
140 for (size_t i = 0; i < other._ops.size (); ++i)
141 {
142 _ops[i].store (other._ops[i].exchange (nullptr, std::memory_order_acq_rel), std::memory_order_release);
143 }
144
145 _socket = std::move (other._socket);
146
147 return *this;
148 }
149
154 {
155 close ();
156 }
157
163 int open (const Protocol& protocol = Protocol ()) noexcept
164 {
165 return _socket.open (protocol);
166 }
167
171 void close () noexcept
172 {
173 bool pending = false;
174 typename ProactorType::InvokeHandler fn = [this, &pending] () {
175 cancelAll ();
176 pending = pendingAny ();
177 };
178 Backoff backoff;
179
180 do
181 {
182 if (_proactor->isRunning ())
183 {
184 _proactor->invoke (&fn);
185 }
186 else
187 {
188 fn ();
189 }
190
191 backoff ();
192 }
193 while (!_proactor->isProactorThread () && pending);
194
195 _socket.close ();
196 }
197
205 template <size_t Count, size_t Size>
207 {
208 return _proactor->registerBufferRing (group, arena);
209 }
210
216 int unregisterBufferRing (uint16_t group)
217 {
218 return _proactor->unregisterBufferRing (group);
219 }
220
229 ssize_t asyncWait (WaitHandler handler, bool wantRead, bool wantWrite, bool flush = true) noexcept
230 {
231 if (JOIN_UNLIKELY (!_socket.opened ()))
232 {
234 return -1;
235 }
236
237 if (JOIN_UNLIKELY (wantRead == wantWrite))
238 {
240 return -1;
241 }
242
244 if (JOIN_UNLIKELY (wait == nullptr))
245 {
247 return -1;
248 }
249
250 wait->waitHandler = std::move (handler);
251 wait->op = IoOperation::makePoll (_socket.handle (), wantRead ? POLLIN : POLLOUT, this);
252 wait->op.state.store (IoOperation::State::Submitted, std::memory_order_release);
253
254 size_t index = _allocator.getIndex (wait);
255
256 if (_proactor->submit (wait->op, flush, false) == -1)
257 {
258 // LCOV_EXCL_START
259 releaseOperation (wait);
260 return -1;
261 // LCOV_EXCL_STOP
262 }
263
264 return static_cast<ssize_t> (index);
265 }
266
275 ssize_t asyncWaitMulti (WaitHandler handler, bool wantRead, bool wantWrite, bool flush = true) noexcept
276 {
277 if (JOIN_UNLIKELY (!_socket.opened ()))
278 {
280 return -1;
281 }
282
283 if (JOIN_UNLIKELY (wantRead == wantWrite))
284 {
286 return -1;
287 }
288
290 if (JOIN_UNLIKELY (wait == nullptr))
291 {
293 return -1;
294 }
295
296 wait->waitHandler = std::move (handler);
297 wait->op = IoOperation::makePollMulti (_socket.handle (), wantRead ? POLLIN : POLLOUT, this);
298 wait->op.state.store (IoOperation::State::Submitted, std::memory_order_release);
299
300 size_t index = _allocator.getIndex (wait);
301
302 if (_proactor->submit (wait->op, flush, false) == -1)
303 {
304 // LCOV_EXCL_START
305 releaseOperation (wait);
306 return -1;
307 // LCOV_EXCL_STOP
308 }
309
310 return static_cast<ssize_t> (index);
311 }
312
318 int cancel (size_t index) noexcept
319 {
320 if (JOIN_UNLIKELY (index >= _ops.size ()))
321 {
323 return -1;
324 }
325
326 IoOperation* op = _ops[index].load (std::memory_order_acquire);
327
328 return (op != nullptr) ? cancelOperation (*op) : 0;
329 }
330
331#ifdef JOIN_HAS_IO_URING
336 int flush () noexcept
337 {
338 return _proactor->flush (false);
339 }
340
346 template <size_t Count, size_t... Sizes>
347 int registerFixedBuffers (LocalMem::SlabAllocator<Count, Sizes...>& arena) noexcept
348 {
349 return _proactor->registerFixedBuffers (arena);
350 }
351
356 int unregisterFixedBuffers () noexcept
357 {
358 return _proactor->unregisterFixedBuffers ();
359 }
360#endif
361
366 Endpoint localEndpoint () const noexcept
367 {
368 return _socket.localEndpoint ();
369 }
370
375 bool opened () const noexcept
376 {
377 return _socket.opened ();
378 }
379
384 int family () const noexcept
385 {
386 return _socket.family ();
387 }
388
393 int type () const noexcept
394 {
395 return _socket.type ();
396 }
397
402 int protocol () const noexcept
403 {
404 return _socket.protocol ();
405 }
406
411 int handle () const noexcept
412 {
413 return _socket.handle ();
414 }
415
416 protected:
422 void onComplete (IoOperation& op, int result) override
423 {
424 std::error_code code =
425 (result < 0) ? std::error_code (-result, std::generic_category ()) : std::error_code ();
426
427 dispatch (op, code, (result > 0) ? static_cast<size_t> (result) : 0);
428 }
429
435 void onCancel (IoOperation& op, [[maybe_unused]] int result) override
436 {
437 dispatch (op, make_error_code (std::errc::operation_canceled), 0);
438 }
439
446 virtual void dispatch (IoOperation& op, const std::error_code& code, size_t size) noexcept
447 {
448 completeWait (reinterpret_cast<AsyncWait*> (&op), code, size);
449 }
450
457 void completeWait (AsyncWait* wait, const std::error_code& code, size_t revents) noexcept
458 {
459 std::error_code result = code;
460
461 if (!result)
462 {
463 bool ready = (revents & wait->op.data.poll.events) != 0;
464
465 if (JOIN_UNLIKELY (revents & POLLERR))
466 {
467 int err = 0;
468 socklen_t len = sizeof (err);
469 ::getsockopt (_socket.handle (), SOL_SOCKET, SO_ERROR, &err, &len);
470
471 if (JOIN_LIKELY (err != 0))
472 {
473 result = std::error_code (err, std::generic_category ());
474 }
475 else
476 {
477 result = make_error_code (Errc::OperationFailed); // LCOV_EXCL_LINE
478 }
479 }
480 else if (JOIN_UNLIKELY ((revents & POLLHUP) && !ready))
481 {
483 }
484 }
485
486 if (wait->op.more && !result && !(revents & POLLHUP))
487 {
488 if (JOIN_LIKELY (wait->waitHandler))
489 {
490 wait->waitHandler (result, true);
491 }
492
493 return;
494 }
495
496 WaitHandler handler = std::move (wait->waitHandler);
497
498 if (wait->op.more)
499 {
500 cancelOperation (wait->op);
501 }
502 else
503 {
504 releaseOperation (wait);
505 }
506
507 if (handler)
508 {
509 handler (result, false);
510 }
511 }
512
517 template <class Op>
518 Op* allocateOperation () noexcept
519 {
520 static_assert (sizeof (Op) <= _opSize, "operation larger than an allocator slot");
521
522 void* chunk = _allocator.allocate (sizeof (Op));
523 if (JOIN_UNLIKELY (chunk == nullptr))
524 {
525 return nullptr;
526 }
527
528 Op* operation = new (chunk) Op;
529 _ops[_allocator.getIndex (chunk)].store (&operation->op, std::memory_order_release);
530
531 return operation;
532 }
533
538 template <class Op>
539 void releaseOperation (Op* operation) noexcept
540 {
541 const uint32_t index = _allocator.getIndex (operation);
542
543 operation->~Op ();
544 _allocator.deallocate (operation);
545 _ops[index].store (nullptr, std::memory_order_release);
546 }
547
551 void suspendAll () noexcept
552 {
553 for (auto& slot : _ops)
554 {
555 IoOperation* op = slot.load (std::memory_order_acquire);
556 if (op != nullptr)
557 {
558 _proactor->suspend (*op);
559 }
560 }
561 }
562
566 void resumeAll () noexcept
567 {
568 for (auto& slot : _ops)
569 {
570 IoOperation* op = slot.load (std::memory_order_acquire);
571 if (op != nullptr)
572 {
573 _proactor->resume (*op, *this);
574 }
575 }
576 }
577
581 void cancelAll () noexcept
582 {
583 for (auto& slot : _ops)
584 {
585 IoOperation* op = slot.load (std::memory_order_acquire);
586 if (op != nullptr)
587 {
588 cancelOperation (*op);
589 }
590 }
591 }
592
597 bool pendingAny () const noexcept
598 {
599 for (auto& slot : _ops)
600 {
601 IoOperation* op = slot.load (std::memory_order_acquire);
602 if ((op != nullptr) && pending (*op))
603 {
604 return true;
605 }
606 }
607
608 return false;
609 }
610
616 int cancelOperation (IoOperation& op) noexcept
617 {
618 if (!inFlight (op))
619 {
620 return 0;
621 }
622
623 if (_proactor->cancel (op, true, true) == -1)
624 {
625 return (lastError == Errc::OperationFailed) ? 0 : -1;
626 }
627
628 return 0;
629 }
630
636 bool inFlight (const IoOperation& op) const noexcept
637 {
638 return op.state.load (std::memory_order_acquire) == IoOperation::State::Submitted;
639 }
640
646 bool pending (const IoOperation& op) const noexcept
647 {
648 IoOperation::State state = op.state.load (std::memory_order_acquire);
649
650 return (state == IoOperation::State::Submitted) || (state == IoOperation::State::Busy);
651 }
652
654 ProactorType* _proactor;
655
658
661
663 std::array<std::atomic<IoOperation*>, _opCount> _ops{};
664 };
665}
666
667#endif
adaptive backoff for busy-wait loops, requires Rdtsc::calibrate ().
Definition backoff.hpp:47
basic asynchronous socket class.
Definition protocol.hpp:85
BasicAsyncSocket(const BasicAsyncSocket &other)=delete
copy constructor.
virtual void dispatch(IoOperation &op, const std::error_code &code, size_t size) noexcept
invoke the completion handler of the given operation, then release it.
Definition async_socket.hpp:446
Socket _socket
underlying synchronous socket.
Definition async_socket.hpp:657
void completeWait(AsyncWait *wait, const std::error_code &code, size_t revents) noexcept
invoke the wait completion handler.
Definition async_socket.hpp:457
void close() noexcept
close the socket, cancelling the operations in flight.
Definition async_socket.hpp:171
int type() const noexcept
get the protocol communication semantic.
Definition async_socket.hpp:393
BasicAsyncSocket(ProactorType &proactor=ProactorThread::proactor())
create the socket instance.
Definition async_socket.hpp:76
std::array< std::atomic< IoOperation * >, _opCount > _ops
operations in flight.
Definition async_socket.hpp:663
void onCancel(IoOperation &op, int result) override
method called when an operation is cancelled.
Definition async_socket.hpp:435
BasicAsyncSocket(Socket &&sock, ProactorType &proactor=ProactorThread::proactor())
create the socket instance adopting an already opened socket.
Definition async_socket.hpp:86
typename AsyncWait::Wait WaitHandler
Definition async_socket.hpp:59
void releaseOperation(Op *operation) noexcept
return an operation to the allocator.
Definition async_socket.hpp:539
void onComplete(IoOperation &op, int result) override
method called when an operation completes.
Definition async_socket.hpp:422
int open(const Protocol &protocol=Protocol()) noexcept
open socket using the given protocol.
Definition async_socket.hpp:163
bool opened() const noexcept
check if the socket is opened.
Definition async_socket.hpp:375
int cancel(size_t index) noexcept
cancel the operation stored at the given index, if in flight.
Definition async_socket.hpp:318
bool pending(const IoOperation &op) const noexcept
check if an operation is in flight or completing.
Definition async_socket.hpp:646
int protocol() const noexcept
get socket protocol.
Definition async_socket.hpp:402
typename Protocol::Socket Socket
Definition async_socket.hpp:56
void resumeAll() noexcept
resume every suspended operation, redirecting it to this handler.
Definition async_socket.hpp:566
bool inFlight(const IoOperation &op) const noexcept
check if an operation is in flight.
Definition async_socket.hpp:636
~BasicAsyncSocket()
destroy the socket instance.
Definition async_socket.hpp:153
int family() const noexcept
get address family.
Definition async_socket.hpp:384
int unregisterBufferRing(uint16_t group)
unregister a provided buffer ring from the proactor driving this socket.
Definition async_socket.hpp:216
int handle() const noexcept
get socket native handle.
Definition async_socket.hpp:411
static constexpr size_t _opCount
number of operations in flight.
Definition async_socket.hpp:62
int cancelOperation(IoOperation &op) noexcept
cancel the given operation, if in flight.
Definition async_socket.hpp:616
BasicAsyncSocket & operator=(const BasicAsyncSocket &other)=delete
copy assignment operator.
Endpoint localEndpoint() const noexcept
determine the local endpoint associated with this socket.
Definition async_socket.hpp:366
BasicAsyncSocket(BasicAsyncSocket &&other) noexcept
move constructor.
Definition async_socket.hpp:102
typename Protocol::Endpoint Endpoint
Definition async_socket.hpp:57
int registerBufferRing(uint16_t group, LocalMem::SlabAllocator< Count, Size > &arena)
register a provided buffer ring on the proactor driving this socket.
Definition async_socket.hpp:206
static constexpr size_t _opSize
size of an operation slot.
Definition async_socket.hpp:67
Op * allocateOperation() noexcept
allocate an operation in the allocator.
Definition async_socket.hpp:518
void cancelAll() noexcept
cancel every operation in flight.
Definition async_socket.hpp:581
ssize_t asyncWait(WaitHandler handler, bool wantRead, bool wantWrite, bool flush=true) noexcept
start an asynchronous wait for the socket to become ready in one direction.
Definition async_socket.hpp:229
ProactorType * _proactor
proactor driving the operations.
Definition async_socket.hpp:654
bool pendingAny() const noexcept
check if at least one operation is in flight or completing.
Definition async_socket.hpp:597
OpAllocator _allocator
operations allocator.
Definition async_socket.hpp:660
ssize_t asyncWaitMulti(WaitHandler handler, bool wantRead, bool wantWrite, bool flush=true) noexcept
start an asynchronous multishot wait, staying armed until cancelled or failed.
Definition async_socket.hpp:275
void suspendAll() noexcept
suspend every operation in flight.
Definition async_socket.hpp:551
static BasicProactor & proactor()
get the Proactor instance owned by the singleton ProactorThread.
Definition proactor.hpp:1053
slab allocator owning a backend and managing one or more pools.
Definition slab_allocator.hpp:339
uint32_t getIndex(void *p) const noexcept
get the index of a chunk in the pool.
Definition slab_allocator.hpp:455
void * allocate(size_t size) noexcept
allocate memory from the first pool that fits (promotes if exhausted).
Definition slab_allocator.hpp:402
void deallocate(void *p) noexcept
return memory to the appropriate pool.
Definition slab_allocator.hpp:421
BasicSlabAllocator< LocalMem, Count, Sizes... > SlabAllocator
Definition memory.hpp:327
Definition acceptor.hpp:32
constexpr uint64_t nextPow2(uint64_t value) noexcept
round up to the next power of two.
Definition utils.hpp:475
std::error_code make_error_code(join::Errc code) noexcept
Create an std::error_code object.
Definition error.cpp:195
Definition error.hpp:144
asynchronous operation traits.
Definition async_operation.hpp:149
asynchronous wait operation.
Definition async_operation.hpp:48
IoOperation op
operation submitted to the proactor.
Definition async_operation.hpp:53
Function< void(const std::error_code &, bool), 16 > Wait
handler invoked on completion.
Definition async_operation.hpp:50
Wait waitHandler
handler invoked on completion.
Definition async_operation.hpp:56
Describes a single asynchronous operation submitted to the Proactor.
Definition io_operation.hpp:48
static IoOperation makePollMulti(int fd, uint32_t events, CompletionHandler *handler) noexcept
build a multishot poll operation, staying armed until cancelled or failed.
Definition io_operation.cpp:95
State
operation lifecycle state.
Definition io_operation.hpp:53
std::atomic< State > state
operation state.
Definition io_operation.hpp:404
static IoOperation makePoll(int fd, uint32_t events, CompletionHandler *handler) noexcept
build a poll operation.
Definition io_operation.cpp:81
#define JOIN_LIKELY(x)
Definition utils.hpp:50
#define JOIN_UNLIKELY(x)
Definition utils.hpp:51