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 Proactor, 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:
77 : _proactor (&proactor)
78 {
79 }
80
86 explicit BasicAsyncSocket (Socket&& sock, Proactor& 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 _arena = std::move (other._arena);
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 _arena = std::move (other._arena);
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 Backoff backoff;
174
175 do
176 {
177 cancelAll ();
178
179 backoff ();
180 }
181 while (!_proactor->isProactorThread () && pendingAny ());
182
183 _socket.close ();
184 }
185
193 template <size_t Count, size_t Size>
195 {
196 return _proactor->registerBufferRing (group, arena);
197 }
198
204 int unregisterBufferRing (uint16_t group)
205 {
206 return _proactor->unregisterBufferRing (group);
207 }
208
217 ssize_t asyncWait (bool wantRead, bool wantWrite, WaitHandler handler, bool flush = true) noexcept
218 {
219 if (JOIN_UNLIKELY (!_socket.opened ()))
220 {
222 return -1;
223 }
224
225 if (JOIN_UNLIKELY (wantRead == wantWrite))
226 {
228 return -1;
229 }
230
232 if (JOIN_UNLIKELY (wait == nullptr))
233 {
235 return -1;
236 }
237
238 wait->waitHandler = std::move (handler);
239 wait->op = IoOperation::makePoll (_socket.handle (), wantRead ? POLLIN : POLLOUT, this);
240 wait->op.state.store (IoOperation::State::Submitted, std::memory_order_release);
241
242 size_t index = _arena.getIndex (wait);
243
244 if (_proactor->submit (wait->op, flush, false) == -1)
245 {
246 // LCOV_EXCL_START
247 releaseOp (wait);
248 return -1;
249 // LCOV_EXCL_STOP
250 }
251
252 return static_cast<ssize_t> (index);
253 }
254
263 ssize_t asyncWaitMulti (bool wantRead, bool wantWrite, WaitHandler handler, bool flush = true) noexcept
264 {
265 if (JOIN_UNLIKELY (!_socket.opened ()))
266 {
268 return -1;
269 }
270
271 if (JOIN_UNLIKELY (wantRead == wantWrite))
272 {
274 return -1;
275 }
276
278 if (JOIN_UNLIKELY (wait == nullptr))
279 {
281 return -1;
282 }
283
284 wait->waitHandler = std::move (handler);
285 wait->op = IoOperation::makePollMulti (_socket.handle (), wantRead ? POLLIN : POLLOUT, this);
286 wait->op.state.store (IoOperation::State::Submitted, std::memory_order_release);
287
288 size_t index = _arena.getIndex (wait);
289
290 if (_proactor->submit (wait->op, flush, false) == -1)
291 {
292 // LCOV_EXCL_START
293 releaseOp (wait);
294 return -1;
295 // LCOV_EXCL_STOP
296 }
297
298 return static_cast<ssize_t> (index);
299 }
300
306 int cancel (size_t index) noexcept
307 {
308 if (JOIN_UNLIKELY (index >= _ops.size ()))
309 {
311 return -1;
312 }
313
314 return cancelOp (_ops[index].load (std::memory_order_acquire));
315 }
316
317#ifdef JOIN_HAS_IO_URING
322 int flush () noexcept
323 {
324 return _proactor->flush (false);
325 }
326
332 template <size_t Count, size_t... Sizes>
333 int registerFixedBuffers (LocalMem::Allocator<Count, Sizes...>& arena) noexcept
334 {
335 return _proactor->registerFixedBuffers (arena);
336 }
337
342 int unregisterFixedBuffers () noexcept
343 {
344 return _proactor->unregisterFixedBuffers ();
345 }
346#endif
347
352 Endpoint localEndpoint () const noexcept
353 {
354 return _socket.localEndpoint ();
355 }
356
361 bool opened () const noexcept
362 {
363 return _socket.opened ();
364 }
365
370 int family () const noexcept
371 {
372 return _socket.family ();
373 }
374
379 int type () const noexcept
380 {
381 return _socket.type ();
382 }
383
388 int protocol () const noexcept
389 {
390 return _socket.protocol ();
391 }
392
397 int handle () const noexcept
398 {
399 return _socket.handle ();
400 }
401
402 protected:
408 void onComplete (IoOperation& op, int result) override
409 {
410 std::error_code code =
411 (result < 0) ? std::error_code (-result, std::generic_category ()) : std::error_code ();
412
413 dispatch (op, code, (result > 0) ? static_cast<size_t> (result) : 0);
414 }
415
421 void onCancel (IoOperation& op, [[maybe_unused]] int result) override
422 {
423 dispatch (op, make_error_code (std::errc::operation_canceled), 0);
424 }
425
432 virtual void dispatch (IoOperation& op, const std::error_code& code, size_t size) noexcept
433 {
434 completeWait (reinterpret_cast<AsyncWait*> (&op), code, size);
435 }
436
443 void completeWait (AsyncWait* wait, const std::error_code& code, size_t revents) noexcept
444 {
445 std::error_code result = code;
446
447 if (!result)
448 {
449 bool ready = (revents & wait->op.data.poll.events) != 0;
450
451 if (JOIN_UNLIKELY (revents & POLLERR))
452 {
453 int err = 0;
454 socklen_t len = sizeof (err);
455 ::getsockopt (_socket.handle (), SOL_SOCKET, SO_ERROR, &err, &len);
456
457 if (JOIN_LIKELY (err != 0))
458 {
459 result = std::error_code (err, std::generic_category ());
460 }
461 else
462 {
463 result = make_error_code (Errc::OperationFailed); // LCOV_EXCL_LINE
464 }
465 }
466 else if (JOIN_UNLIKELY ((revents & POLLHUP) && !ready))
467 {
469 }
470 }
471
472 if (wait->op.more && !result && !(revents & POLLHUP))
473 {
474 if (JOIN_LIKELY (wait->waitHandler))
475 {
476 wait->waitHandler (result, true);
477 }
478
479 return;
480 }
481
482 WaitHandler handler = std::move (wait->waitHandler);
483
484 if (wait->op.more)
485 {
486 cancelOp (&wait->op);
487 }
488 else
489 {
490 releaseOp (wait);
491 }
492
493 if (handler)
494 {
495 handler (result, false);
496 }
497 }
498
503 template <class Op>
504 Op* allocateOp () noexcept
505 {
506 static_assert (sizeof (Op) <= _opSize, "operation larger than an arena slot");
507
508 void* chunk = _arena.allocate (sizeof (Op));
509 if (JOIN_UNLIKELY (chunk == nullptr))
510 {
511 return nullptr;
512 }
513
514 Op* operation = new (chunk) Op ();
515 _ops[_arena.getIndex (chunk)].store (&operation->op, std::memory_order_release);
516
517 return operation;
518 }
519
524 template <class Op>
525 void releaseOp (Op* operation) noexcept
526 {
527 _ops[_arena.getIndex (operation)].store (nullptr, std::memory_order_release);
528 operation->~Op ();
529 _arena.deallocate (operation);
530 }
531
535 void suspendAll () noexcept
536 {
537 for (auto& slot : _ops)
538 {
539 _proactor->suspend (slot.load (std::memory_order_acquire));
540 }
541 }
542
546 void resumeAll () noexcept
547 {
548 for (auto& slot : _ops)
549 {
550 _proactor->resume (slot.load (std::memory_order_acquire), this);
551 }
552 }
553
557 void cancelAll () noexcept
558 {
559 for (auto& slot : _ops)
560 {
561 cancelOp (slot.load (std::memory_order_acquire));
562 }
563 }
564
569 bool pendingAny () const noexcept
570 {
571 for (auto& slot : _ops)
572 {
573 IoOperation* op = slot.load (std::memory_order_acquire);
574 if ((op != nullptr) && pending (*op))
575 {
576 return true;
577 }
578 }
579
580 return false;
581 }
582
588 int cancelOp (IoOperation* op) noexcept
589 {
590 if ((op == nullptr) || !inFlight (*op))
591 {
592 return 0;
593 }
594
595 if (_proactor->cancel (*op, true, true) == -1)
596 {
597 return (lastError == Errc::OperationFailed) ? 0 : -1;
598 }
599
600 return 0;
601 }
602
608 bool inFlight (const IoOperation& op) const noexcept
609 {
610 return op.state.load (std::memory_order_acquire) == IoOperation::State::Submitted;
611 }
612
618 bool pending (const IoOperation& op) const noexcept
619 {
620 IoOperation::State state = op.state.load (std::memory_order_acquire);
621
622 return (state == IoOperation::State::Submitted) || (state == IoOperation::State::Busy);
623 }
624
627
630
633
635 std::array<std::atomic<IoOperation*>, _opCount> _ops{};
636 };
637}
638
639#endif
adaptive backoff strategy for busy-wait loops.
Definition backoff.hpp:43
memory arena owning backend and managing one or more pools.
Definition memory.hpp:53
uint32_t getIndex(void *p) const noexcept
get the index of a chunk in the pool.
Definition allocator.hpp:498
void * allocate(size_t size) noexcept
allocate memory from the first pool that fits (promotes if exhausted).
Definition allocator.hpp:455
void deallocate(void *p) noexcept
return memory to the appropriate pool.
Definition allocator.hpp:474
basic asynchronous socket class.
Definition protocol.hpp:82
ssize_t asyncWaitMulti(bool wantRead, bool wantWrite, WaitHandler handler, bool flush=true) noexcept
start an asynchronous multishot wait, staying armed until cancelled or failed.
Definition async_socket.hpp:263
Socket _socket
underlying synchronous socket.
Definition async_socket.hpp:629
bool pending(const IoOperation &op) const noexcept
check if an operation is in flight or completing.
Definition async_socket.hpp:618
bool pendingAny() const noexcept
check if at least one operation is in flight or completing.
Definition async_socket.hpp:569
void close() noexcept
close the socket, cancelling the operations in flight.
Definition async_socket.hpp:171
~BasicAsyncSocket()
destroy the socket instance.
Definition async_socket.hpp:153
static constexpr size_t _opSize
size of an operation slot.
Definition async_socket.hpp:67
int registerBufferRing(uint16_t group, LocalMem::Allocator< Count, Size > &arena)
register a provided buffer ring on the proactor driving this socket.
Definition async_socket.hpp:194
void cancelAll() noexcept
cancel every operation in flight.
Definition async_socket.hpp:557
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:432
void releaseOp(Op *operation) noexcept
return an operation to the arena.
Definition async_socket.hpp:525
ssize_t asyncWait(bool wantRead, bool wantWrite, WaitHandler handler, bool flush=true) noexcept
start an asynchronous wait for the socket to become ready in one direction.
Definition async_socket.hpp:217
void onCancel(IoOperation &op, int result) override
method called when an operation is cancelled.
Definition async_socket.hpp:421
Endpoint localEndpoint() const noexcept
determine the local endpoint associated with this socket.
Definition async_socket.hpp:352
bool opened() const noexcept
check if the socket is opened.
Definition async_socket.hpp:361
int protocol() const noexcept
get socket protocol.
Definition async_socket.hpp:388
typename Protocol::Endpoint Endpoint
Definition async_socket.hpp:57
int cancelOp(IoOperation *op) noexcept
cancel the given operation, if in flight.
Definition async_socket.hpp:588
OpArena _arena
operations arena.
Definition async_socket.hpp:632
typename Protocol::Socket Socket
Definition async_socket.hpp:56
static constexpr size_t _opCount
number of operations in flight.
Definition async_socket.hpp:62
int unregisterBufferRing(uint16_t group)
unregister a provided buffer ring from the proactor driving this socket.
Definition async_socket.hpp:204
Op * allocateOp() noexcept
allocate an operation in the arena.
Definition async_socket.hpp:504
int handle() const noexcept
get socket native handle.
Definition async_socket.hpp:397
BasicAsyncSocket(BasicAsyncSocket &&other) noexcept
move constructor.
Definition async_socket.hpp:102
bool inFlight(const IoOperation &op) const noexcept
check if an operation is in flight.
Definition async_socket.hpp:608
BasicAsyncSocket(Socket &&sock, Proactor &proactor=ProactorThread::proactor())
create the socket instance adopting an already opened socket.
Definition async_socket.hpp:86
int type() const noexcept
get the protocol communication semantic.
Definition async_socket.hpp:379
void suspendAll() noexcept
suspend every operation in flight.
Definition async_socket.hpp:535
BasicAsyncSocket & operator=(const BasicAsyncSocket &other)=delete
copy assignment operator.
int cancel(size_t index) noexcept
cancel the operation stored at the given index, if in flight.
Definition async_socket.hpp:306
int family() const noexcept
get address family.
Definition async_socket.hpp:370
typename AsyncWait::Wait WaitHandler
Definition async_socket.hpp:59
std::array< std::atomic< IoOperation * >, _opCount > _ops
operations in flight.
Definition async_socket.hpp:635
BasicAsyncSocket(const BasicAsyncSocket &other)=delete
copy constructor.
Proactor * _proactor
proactor driving the operations.
Definition async_socket.hpp:626
int open(const Protocol &protocol=Protocol()) noexcept
open socket using the given protocol.
Definition async_socket.hpp:163
void resumeAll() noexcept
resume every suspended operation, redirecting it to this handler.
Definition async_socket.hpp:546
void completeWait(AsyncWait *wait, const std::error_code &code, size_t revents) noexcept
invoke the wait completion handler.
Definition async_socket.hpp:443
void onComplete(IoOperation &op, int result) override
method called when an operation completes.
Definition async_socket.hpp:408
BasicAsyncSocket(Proactor &proactor=ProactorThread::proactor())
create the socket instance.
Definition async_socket.hpp:76
static BasicProactor & proactor()
get the Proactor instance owned by the singleton ProactorThread.
Definition proactor.hpp:982
basic proactor class.
Definition proactor.hpp:156
int unregisterBufferRing(uint16_t group)
unregister a provided buffer ring.
Definition proactor_epoll_impl.hpp:154
void suspend(IoOperation *op) noexcept
suspend an I/O operation.
Definition proactor.hpp:799
int registerBufferRing(uint16_t group, LocalMem::Allocator< Count, Size > &arena)
register a provided buffer ring.
Definition proactor_epoll_impl.hpp:107
int submit(IoOperation &op, bool flush=false, bool sync=false) noexcept
submit an asynchronous operation to the proactor.
Definition proactor.hpp:655
bool isProactorThread() const noexcept
check if the calling thread is the proactor thread.
Definition proactor_epoll_impl.hpp:252
void resume(IoOperation *op, CompletionHandler *handler) noexcept
resume an I/O operation.
Definition proactor.hpp:837
int cancel(IoOperation &op, bool flush=false, bool sync=false) noexcept
cancel an in-flight operation.
Definition proactor.hpp:703
BasicArena< LocalMem, Count, Sizes... > Allocator
Definition memory.hpp:125
Definition acceptor.hpp:32
constexpr uint64_t nextPow2(uint64_t value) noexcept
round up to the next power of two.
Definition utils.hpp:506
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:147
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:47
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:87
State
operation lifecycle state.
Definition io_operation.hpp:52
std::atomic< State > state
operation state.
Definition io_operation.hpp:392
static IoOperation makePoll(int fd, uint32_t events, CompletionHandler *handler) noexcept
build a poll operation.
Definition io_operation.cpp:73
#define JOIN_LIKELY(x)
Definition utils.hpp:45
#define JOIN_UNLIKELY(x)
Definition utils.hpp:46