EventsPerSock is a unordered map of shared_ptrs of Sockwrappers and Events
EventsPerSock is an unordered map of raw socket file descriptors (SOCKET) and Events
Dash implements "wakeup select pipe", which is constructed and destroyed using WakeupPipes, an entity outside Sock's control.
Dash needs to be able to insert the read pipe raw socket into equivalent of the recv socket set and query for it later on.
It would be technically possible, though cumbersome, to wrap the read pipe raw socket in a Sock and overwrite the destructor if it wasn't for the support of edge-triggered modes which have an event-socket relationship, as opposed to level triggered modes, that have a socket-event relationship.
Sockets passed in an EventsPerSock map will always return with event data for every corresponding entry.
Sockets passed in an EventsPerSock map may return with event data for its corresponding entry.
The behaviour defined for Bitcoin will also be presented in Dash if the socket events mode (SEM) is poll or select. Otherwise, it will be behave as described.
This is due to the inversion of the socket-event relationship in edge-triggered modes (epoll and kqueue), as alluded to earlier. As edge-triggered modes return events and their corresponding socket (sockets registered through EdgeTriggeredEvents::RegisterEntity() and friends), the EventsPerSock map, should there be events reported, will have its contents completely discarded and substituted with the results of {epoll, kqueue}.
You must have a Sock entity to call Sock::WaitMany()
You can directly access Sock::WaitMany()'s underlying logic through calling Sock::IWaitMany() (and access any specific event mode's implementation) without a Sock entity.
This change has been made as Bitcoin's behaviour was to call WaitMany by seeking to the first element to access it. This was possible because the unordered map consisted of Sock entities. As that isn't the case for Dash and WaitMany doesn't truly rely on instance-specific member values of a particular Sock instance (the values it relies on should remain constant throughout program runtime), it can be safely made a static function and that was exactly what was done.
It has been named IWaitMany() as one of Sock's purposes is mockability and WaitMany() (simply a passthrough to IWaitMany() but leveraging member values) has been defined as a virtual function.
In the interest of preventing future conflicts in backports, its characteristics haven't been changed, opting to use new function names for Dash-specific functionality.
Sock's usage of platform-specific APIs is decided exclusively at compile-time.
Sock's usage of platform-specific APIs is determined by what is supported at compile-time and decided at runtime (mostly).
Before this pull request, the only usage of Sock::Wait() (which is transformed into Sock::WaitMany() in this pull request) was in I2P code (source), supported only poll and select (source) and behaved as described for Bitcoin.
The described behaviour for Dash was only applicable for CConnman::SocketEvents(). But, as SocketEvents() is being replaced wholesale with WaitMany(), WaitMany() needed to be adapted to mirror SocketEvents() behaviour.
This has resulted in changes to Sock that also now require knowledge of the expected runtime SEM and file descriptor (if using an edge-triggered mode).
Note: Some portions of the codebase do not possess this knowledge and will default to using select as their SEM
Sock::Wait() and Sock::WaitMany() behave identically
Sock::Wait() will respect the SEM selection argument if it is level-triggered but will fallback to poll or select (determined at compile-time) if the SEM selection is edge-triggered.
Due to the event-socket relationship of edge-triggered modes, they are unsuitable for querying the state of a particular socket (which is necessary if socket creation is asynchronous, see here and here).
Because of that and a) the unliklihood of the socket probed being registered with EdgeTriggeredEvents::RegisterEntity() and b) the overhead involved in fetching a list, filtering out for the particular socket we care about and flagging the result, it is more practical to use an LT-SEM instead.
Forcing LT-SEM is possible by calling IWaitMany with lt_only=true.
Breaking Changes
None expected. Behaviour should remain unchanged.
Checklist:
Go over all the following points, and put an x in all the boxes that apply.
[ ] I have performed a self-review of my own code
[ ] I have commented my code, particularly in hard-to-understand areas
[ ] I have added or updated relevant unit/integration/functional/e2e tests
[ ] I have made corresponding changes to the documentation
[x] I have assigned this pull request to a milestone (for repository code-owners and collaborators only)
Additional information
Deviations from upstream
EventsPerSock
is a unordered map ofshared_ptr
s ofSock
wrappers andEvents
EventsPerSock
is an unordered map of raw socket file descriptors (SOCKET
) andEvents
WakeupPipes
, an entity outsideSock
's control.Dash needs to be able to insert the read pipe raw socket into equivalent of the
recv
socket set and query for it later on.It would be technically possible, though cumbersome, to wrap the read pipe raw socket in a
Sock
and overwrite the destructor if it wasn't for the support of edge-triggered modes which have an event-socket relationship, as opposed to level triggered modes, that have a socket-event relationship.EventsPerSock
map will always return with event data for every corresponding entry.EventsPerSock
map may return with event data for its corresponding entry.poll
orselect
. Otherwise, it will be behave as described.This is due to the inversion of the socket-event relationship in edge-triggered modes (
epoll
andkqueue
), as alluded to earlier. As edge-triggered modes return events and their corresponding socket (sockets registered throughEdgeTriggeredEvents::RegisterEntity()
and friends), theEventsPerSock
map, should there be events reported, will have its contents completely discarded and substituted with the results of {epoll
,kqueue
}.Sock
entity to callSock::WaitMany()
Sock::WaitMany()
's underlying logic through callingSock::IWaitMany()
(and access any specific event mode's implementation) without aSock
entity.WaitMany
by seeking to the first element to access it. This was possible because the unordered map consisted ofSock
entities. As that isn't the case for Dash andWaitMany
doesn't truly rely on instance-specific member values of a particularSock
instance (the values it relies on should remain constant throughout program runtime), it can be safely made astatic
function and that was exactly what was done.It has been named
IWaitMany()
as one ofSock
's purposes is mockability andWaitMany()
(simply a passthrough toIWaitMany()
but leveraging member values) has been defined as avirtual
function.In the interest of preventing future conflicts in backports, its characteristics haven't been changed, opting to use new function names for Dash-specific functionality.
Sock
's usage of platform-specific APIs is decided exclusively at compile-time.Sock
's usage of platform-specific APIs is determined by what is supported at compile-time and decided at runtime (mostly).Sock::Wait()
(which is transformed intoSock::WaitMany()
in this pull request) was in I2P code (source), supported onlypoll
andselect
(source) and behaved as described for Bitcoin.The described behaviour for Dash was only applicable for
CConnman::SocketEvents()
. But, asSocketEvents()
is being replaced wholesale withWaitMany()
,WaitMany()
needed to be adapted to mirrorSocketEvents()
behaviour.This has resulted in changes to
Sock
that also now require knowledge of the expected runtime SEM and file descriptor (if using an edge-triggered mode).Note: Some portions of the codebase do not possess this knowledge and will default to using
select
as their SEMSock::Wait()
andSock::WaitMany()
behave identicallySock::Wait()
will respect the SEM selection argument if it is level-triggered but will fallback topoll
orselect
(determined at compile-time) if the SEM selection is edge-triggered.Because of that and a) the unliklihood of the socket probed being registered with
EdgeTriggeredEvents::RegisterEntity()
and b) the overhead involved in fetching a list, filtering out for the particular socket we care about and flagging the result, it is more practical to use an LT-SEM instead.Forcing LT-SEM is possible by calling
IWaitMany
withlt_only=true
.Breaking Changes
None expected. Behaviour should remain unchanged.
Checklist:
Go over all the following points, and put an
x
in all the boxes that apply.