[Tarantool-patches] [PATCH] socket: fix error while closing socket.tcp_server

lvasiliev lvasiliev at tarantool.org
Tue May 19 22:52:54 MSK 2020


Hi! Thanks for the review.

On 13.05.2020 17:21, Alexander Turenko wrote:
> TL;DR: I would ask you a case that will show the difference you mention.
> 
> The same, but with more words, below.
> 
> WBR, Alexander Turenko.
> 
I couldn't find a valid way to close the socket outside. So, you
won this fight :). I sent a new version of the patchset.
>>>> @@ -1082,6 +1085,9 @@ local function tcp_server_loop(server, s, addr)
>>>>        fiber.name(format("%s/%s:%s", server.name, addr.host, addr.port), {truncate = true})
>>>>        log.info("started")
>>>>        while socket_readable(s) do
>>>> +        if s._server_socket_closed then
>>>> +            break
>>>> +        end
>>>
>>> Can we use the same check `<s>._gc_socket.fd >= 0` as in check_socket()
>>> (we can wrap it to a function like socket_is_closed())?
>>>
>>> It seems that using the same way for determining whether a socket is
>>> closed owuld simplify the code a bit.
>>>
>>> WBR, Alexander Turenko.
>>>
>> If I understand correctly, in this case we must not only check whether
>> the socket is closed or not, but also check whether it was closed
>> without errors by checking errno. In fact, we come to my first patchset,
> 
> Execution path is the following:
> 
>   | tcp_server()
>   |     local s, addr = tcp_server_bind(...)
>   |     if not s then <...> end
>   |     <s is correct socket here>
>   |     fiber.create(tcp_server_loop, server, s, addr)
>   |         while socket_readable(s) do -- it is yield
>   |             <a check should be added here>
>   |             local sc, from = socket_accept(s)
>   |             <...>
>   |         end
> 
> What can appear with <s> when we're in yield? It can be closed or
> mangled in some bad way by a user (say, `socket._gc_socket = {}`). The
> latter is invalid usage, so we should handle only closing the socket.
> 
> The next question is what condition we should use to check whether a
> socket is closed. Look at check_socket() and socket_close() functions
> (marked your new code with '+' marks):
> 
>   | local function check_socket(socket)
>   |     local gc_socket = type(socket) == 'table' and socket._gc_socket
>   |     if ffi.istype(gc_socket_t, gc_socket) then
>   |         local fd = gc_socket.fd
>   |         if fd >= 0 then
>   |             return fd
>   |         else
>   |             error("attempt to use closed socket")
>   |         end
>   |     else
>   |         local msg = "Usage: socket:method()"
>   |         if socket ~= nil then msg = msg .. ", called with non-socket" end
>   |         error(msg)
>   |     end
>   | end
> 
>   | local gc_socket_sentinel = ffi.new(gc_socket_t, { fd = -1 })
>   |
>   | local function socket_close(socket)
>   |     local fd = check_socket(socket)
>   +     if socket._server_socket_closed ~= nil then
>   +         socket._server_socket_closed = true
>   +     end
>   |     socket._errno = nil
>   |     local r = ffi.C.coio_close(fd)
>   |     -- .fd is const to prevent tampering
>   |     ffi.copy(socket._gc_socket, gc_socket_sentinel, ffi.sizeof(gc_socket_t))
>   |     if r ~= 0 then
>   |         socket._errno = boxerrno()
>   |         return false
>   |     end
>   |     return true
>   | end
> 
> check_socket() use `<s>._gc_socket.fd >= 0` to determine whether socket
> is closed. I don't see a reason to add another way to determine it. But
> I would extract the check into a function:
> 
>   | local function socket_is_closed(socket)
>   |     return socket._gc_socket.fd < 0
>   | end
> 
> If I miss some case, please, show reproducer code, which reveals it.
> 
>> where I disable throwing an exception from the check_socket(). Disabling
> 
> Does not look the same: check_socket() is used everywhere in the API
> functions.
I was thinking of something like that you can return nil in case of
a closed socket and throw exception in case of an invalid argument.
> 
>> throwing an exception is the right way because now our API doesn't
>> correspond to the documentation (I will create a ticket for this). But
>> fixing a socket check leads to big changes in the code that are not
>> related to the problem and will not pass the review. I can wrap the flag
> 
> We discussed Leonid's question externally. It is about raising an error
> for a closed socket handle. I would consider closed socket as an illegal
> parameter and raise an error as for any other illegal parameter error.
> At least until someone will show that a correct code may pass a closed
> socket to a socket module function / a handle method due to some
> external influence (when other end of socket becomes closed or so).
> 
> Our documentation usually miss to mention that an illegal parameter
> error is raised, not returned. This is not good, but it is usual
> convention for our modules in fact.
> 
For closed socket a separate exception used without usual "Usage ...".
I suggest not discussing the problem of API documentation here. I was 
wrong when started.
>> check in a method, but then all sockets should have a flag and it's look
>> like overkill (this will duplicate the functionality of the
>> check_socket() function after fix).
> 
> Since check_socket() change you propose (don't raise an error) is
> questionable I cannot take it as the argument.
Ok.
> 
>> In addition, we don't check EV_ERROR and it seems to me that this can
>> also lead to the fact that the check you proposed may be insufficient.
> 
> To be honest, I don't get how it is related to the question we discuss,
> but anyway.
> 
>  From http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod
> 
>   | EV_ERROR
>   |
>   | An unspecified error has occurred, the watcher has been stopped. This
>   | might happen because the watcher could not be properly started
>   | because libev ran out of memory, a file descriptor was found to be
>   | closed or any other problem. Libev considers these application bugs.
>   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   |
>   | You best act on it by reporting the problem and somehow coping with
>   | the watcher being stopped. Note that well-written programs should not
>   | receive an error ever, so when your watcher receives it, this usually
>   | indicates a bug in your program.
>   |
>   | Libev will usually signal a few "dummy" events together with an
>   | error, for example it might indicate that a fd is readable or
>   | writable, and if your callbacks is well-written it can just attempt
>   |                                                        ^^^^^^^^^^^^
>   | the operation and cope with the error from read() or write(). This
>   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   | will not work in multi-threaded programs, though, as the fd could
>   | already be closed and reused for another thing, so beware.
> 
> So, first: it should not appear at all. Second, we can just read() /
> write() and look at the return value + errno.
> 


More information about the Tarantool-patches mailing list