AnyEvent-FDpasser
view release on metacpan or search on metacpan
In order to pass a file descriptor between processes, a new descriptor
needs to be allocated in the receiving process. Therefore, the "recvmsg"
and "ioctl" system calls used to implement descriptor passing can fail
unexpectedly. Failing to create a descriptor is especially bad when
transfering descriptors since the outcome is not well specified. Linux
doesn't even mention this possible failure mode in the recvmsg() man
page. BSD manuals indicate that EMSGSIZE will be returned and any
descriptors in transit will be closed. If a descriptor is closed it can
never be delivered to the application, even if the full descriptor table
problem clears up.
So what should we do? We could silently ignore it when a descriptor
fails to transfer, but then we run the risk of desynchronising the
descriptor stream. Another possibility is indicating to the application
that this descriptor has failed to transfer and is now lost forever.
Unfortunately this complicates the error handling an application must
do, especially if the descriptor is linked to other descriptors which
must then be received and (if they make it) closed. Finally, we could
just give up, call the on_error callback, destory the passer object and
punt the problem back to the application.
None of the above "solutions" are very appealing so this module uses a
trick known as the "close-dup slot reservation" trick. Actually I just
made that name up now but it sounds pretty cool don't you think? The
idea is that when the passer object is created, we "dup" a file
descriptor and store it in the object. This module creates a pipe when
the passer object is made, closes one side of the pipe and keeps the
other around. This "sentinel" descriptor exists solely to take up an
entry in our descriptor table: we will never write to it, read from it,
or poll it.
When it comes time to receive a descriptor, we close the sentinel
descriptor, receive the descriptor from the sending process, and then
attempt to dup another descriptor. Because we just cleared a descriptor
table entry, there should always be a free descriptor to create.
If duping fails, we stop trying to receive any further descriptors and
instead retry at regular intervals (while not interrupting the event
loop). Hopefully eventually the full descriptor table issue will clear
up and we will be able to resume receiving descriptors.
Note that a descriptor could be created between closing and receiving if
your program uses asynchronous signal handlers or threads that create
descriptors, so don't do that. Signals that are handled synchronously
(like normal AnyEvent signal watchers) are fine.
This trick is similar to a trick described in Marc Lehmann's libev POD
document, section "special problem of accept()ing when you can't,"
although the purpose of employing the trick in this module is somewhat
different.
TESTS AND SYSTEM ASSUMPTIONS
All the following tests should work with BSD4.4, BSD4.3, and SysV
interfaces (where available).
Bidirectional
A passer is bidirectional and can be used to both send and receive
descriptors, even simultaneously.
There are tests (basic_socketpair.t and basic_filesystem.t) to verify
this.
Non-blocking
A process may initiate push_recv_fh on a passer and this process will
not block while it is waiting for the other end to call push_send_fh
(and vice versa).
There are tests (recv_before_send.t and send_before_recv.t) to verify
this.
FIFO ordering
The order descriptors are sent with push_send_fh is the same order that
they are received on at the other end with push_recv_fh.
There is a test (buffer_exercise.t) to verify this and some other basic
buffering properties.
Preserves blocking status
After a fork, the non-blocking status of a descriptor is preserved so if
you are doing a socketpair followed by a fork it is acceptable to set
the non-blocking status of both descriptors in the parent.
Also, the non-blocking status of a descriptor passed with this module is
preserved after it is passed so it is not necessary to reset nonblocking
status on descriptors.
There is a test (non_blocking_fhs.t) to verify this and some other
assumptions for any given system.
Passing passers
Passing a descriptor and then using this descriptor as an argument to
the existing_fh mode of this module to construct another passer is
supported.
There is a test (send_passer_over_passer.t) to verify this assumption
for any given system.
Descriptor table full
Even when the descriptor table fills up intermittently, no descriptors
being passed should be lost.
There is a test (full_descriptor_table.t) to verify this.
SEE ALSO
<The AnyEvent::FDpasser github repo>
This module gets its name from File::FDpasser which does roughly the
same thing as this module except this module provides a non-blocking
interface, buffers the sending and receiving of descriptors, doesn't
lose descriptors in the event of a full descriptor table, and doesn't
print un-silenceable messages to stderr from the XS code.
Socket::PassAccessRights is another module similar to File::FDpasser. It
supports BSD4.3 and BSD4.4 interfaces.
Sprocket::Util::FDpasser is an example of a non-blocking interface to
File::FDpasser. It is based on POE whereas this module is (obviously)
based on AnyEvent.
A related module is Socket::MsgHdr which provides complete control over
ancillary data construction and parsing and is therefore useful for more
than just passing descriptors. However, this module does not provide a
non-blocking interface or buffering, and only supports the BSD4.4
interface (so it doesn't work for passing descriptors on Solaris).
BUGS
This module doesn't support windows. Theoretically windows support could
be added with some annoying combination of "DuplicateHandle" and
"WSADuplicateSocket" but I don't care enough to implement it at this
time.
If there are multiple outstanding filehandles to be sent, for
performance reasons this module could (on BSD4.4 systems) batch them
together into one "cmsg" and then execute one "sendmsg()" system call.
Unfortunately, that would make the close-dup trick less efficient. Maybe
there is a sweet spot?
It would be nice to auto-detect the best interface (BSD4.4/BSD4.3/SysV)
to use for a given system.
AUTHOR
Doug Hoyte, "<doug@hcsw.org>"
COPYRIGHT & LICENSE
Copyright 2012 Doug Hoyte.
This module is licensed under the same terms as perl itself.
( run in 2.341 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )