Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH v2] Fix lua socket polling in case of a spurious wakeup
@ 2018-09-24 19:53 Georgy Kirichenko
  2018-10-03 15:08 ` Vladislav Shpilevoy
  2018-10-05 12:54 ` Vladimir Davydov
  0 siblings, 2 replies; 5+ messages in thread
From: Georgy Kirichenko @ 2018-09-24 19:53 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Georgy Kirichenko

socket_writable/socket_readable handles socket.iowait spurious wakeup
until event is happened or timeout is exceeded.

https://github.com/tarantool/tarantool/issues/3344
https://github.com/tarantool/tarantool/tree/g.kirichenko/gh-3344-socket-io-spurios-wakeup

Changes in v2:
 - Fixes according to Vlad's review

Fixes: #3344
---
 src/lua/socket.lua       | 52 ++++++++++++++-----------------
 test/app/socket.result   | 66 ++++++++++++++++++++++++++++++++++++++++
 test/app/socket.test.lua | 28 +++++++++++++++++
 3 files changed, 117 insertions(+), 29 deletions(-)

diff --git a/src/lua/socket.lua b/src/lua/socket.lua
index 8f5e2926b..02c93381b 100644
--- a/src/lua/socket.lua
+++ b/src/lua/socket.lua
@@ -332,12 +332,17 @@ local function do_wait(self, what, timeout)
     self._errno = nil
     timeout = timeout or TIMEOUT_INFINITY
 
-    local res = internal.iowait(fd, what, timeout)
-    if res == 0 then
-        self._errno = boxerrno.ETIMEDOUT
-        return 0
+    local deadline = fiber.clock() + timeout
+
+    while fiber.clock() <= deadline do
+        local events = internal.iowait(fd, what, deadline - fiber.clock())
+        fiber.testcancel()
+        if events ~= 0 and events ~= '' then
+            return events
+        end
     end
-    return res
+    self._errno = boxerrno.ETIMEDOUT
+    return 0
 end
 
 local function socket_readable(self, timeout)
@@ -666,7 +671,7 @@ local function read(self, limit, timeout, check, ...)
         return data
     end
 
-    while timeout > 0 do
+    while timeout >= 0 do
         local started = fiber.clock()
 
         assert(rbuf:size() < limit)
@@ -693,15 +698,9 @@ local function read(self, limit, timeout, check, ...)
             return nil
         end
 
-        if not socket_readable(self, timeout) then
-            return nil
-        end
-        if timeout <= 0 then
-            break
-        end
+        socket_readable(self, timeout)
         timeout = timeout - ( fiber.clock() - started )
     end
-    self._errno = boxerrno.ETIMEDOUT
     return nil
 end
 
@@ -740,7 +739,7 @@ local function socket_write(self, octets, timeout)
     end
 
     local started = fiber.clock()
-    while true do
+    while timeout >= 0 do
         local written = syswrite(self, p, e - p)
         if written == 0 then
             return p - s -- eof
@@ -754,11 +753,10 @@ local function socket_write(self, octets, timeout)
             return nil
         end
 
+        socket_writable(self, timeout)
         timeout = timeout - (fiber.clock() - started)
-        if timeout <= 0 or not socket_writable(self, timeout) then
-            break
-        end
     end
+    return p - s
 end
 
 local function socket_send(self, octets, flags)
@@ -997,16 +995,12 @@ local function socket_tcp_connect(s, address, port, timeout)
     -- Wait until the connection is established or ultimately fails.
     -- In either condition the socket becomes writable. To tell these
     -- conditions appart SO_ERROR must be consulted (man connect).
-    if socket_writable(s, timeout) then
+    local deadline = timeout + fiber.clock()
+    if socket_writable(s, deadline - fiber.clock()) then
         s._errno = socket_getsockopt(s, 'SOL_SOCKET', 'SO_ERROR')
-    else
-        s._errno = boxerrno.ETIMEDOUT
-    end
-    if s._errno ~= 0 then
-        return nil
+        return s._errno == 0 or nil
     end
-    -- Connected
-    return true
+    return nil
 end
 
 local function tcp_connect(host, port, timeout)
@@ -1387,7 +1381,7 @@ end
 local function lsocket_tcp_accept(self)
     check_socket(self)
     local deadline = fiber.clock() + (self.timeout or TIMEOUT_INFINITY)
-    repeat
+    while socket_readable(self, deadline - fiber.clock()) do
         local client = socket_accept(self)
         if client then
             setmetatable(client, lsocket_tcp_client_mt)
@@ -1397,7 +1391,7 @@ local function lsocket_tcp_accept(self)
         if not errno_is_transient[errno] then
             break
         end
-    until not socket_readable(self, deadline - fiber.clock())
+    end
     return nil, socket_error(self)
 end
 
@@ -1452,7 +1446,7 @@ local function lsocket_tcp_receive(self, pattern, prefix)
     elseif pattern == "*a" then
         local result = { prefix }
         local deadline = fiber.clock() + (self.timeout or TIMEOUT_INFINITY)
-        repeat
+        while socket_readable(self, deadline - fiber.clock()) do
             local data = socket_sysread(self)
             if data == nil then
                 if not errno_is_transient[self._errno] then
@@ -1463,7 +1457,7 @@ local function lsocket_tcp_receive(self, pattern, prefix)
             else
                 table.insert(result, data)
             end
-        until not socket_readable(self, deadline - fiber.clock())
+        end
         if #result == 1 then
             return nil, 'closed', table.concat(result)
         end
diff --git a/test/app/socket.result b/test/app/socket.result
index 729c1eb3f..2f002a37e 100644
--- a/test/app/socket.result
+++ b/test/app/socket.result
@@ -2779,6 +2779,72 @@ listening_socket:close()
 ---
 - true
 ...
+--gh-3344 connection should not fail is there is a spurious wakeup for io fiber
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+echo_fiber = nil
+server = socket.tcp_server('localhost', 0, { handler = function(s)
+    echo_fiber = fiber.self()
+    while true do
+        local b = s:read(1, 0.1)
+        if b ~= nil then
+            s:write(b)
+        end
+    end
+end, name = 'echoserv'});
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+addr = server:name()
+---
+...
+client = socket.tcp_connect(addr.host, addr.port)
+---
+...
+echo_fiber ~= nil
+---
+- true
+...
+client:write('hello')
+---
+- 5
+...
+client:read(5, 0.1) == 'hello'
+---
+- true
+...
+-- send spurious wakeup
+fiber.wakeup(echo_fiber)
+---
+...
+fiber.sleep(0)
+---
+...
+client:write('world')
+---
+- 5
+...
+client:read(5, 0.1) == 'world'
+---
+- true
+...
+-- cancel fiber
+fiber.cancel(echo_fiber)
+---
+...
+client:read(1, 0.1) == ''
+---
+- true
+...
+server:close()
+---
+- true
+...
 test_run:cmd("clear filter")
 ---
 - true
diff --git a/test/app/socket.test.lua b/test/app/socket.test.lua
index 9b896522b..c0b001449 100644
--- a/test/app/socket.test.lua
+++ b/test/app/socket.test.lua
@@ -939,4 +939,32 @@ receiving_socket:close()
 sending_socket:close()
 listening_socket:close()
 
+--gh-3344 connection should not fail is there is a spurious wakeup for io fiber
+test_run:cmd("setopt delimiter ';'")
+echo_fiber = nil
+server = socket.tcp_server('localhost', 0, { handler = function(s)
+    echo_fiber = fiber.self()
+    while true do
+        local b = s:read(1, 0.1)
+        if b ~= nil then
+            s:write(b)
+        end
+    end
+end, name = 'echoserv'});
+test_run:cmd("setopt delimiter ''");
+addr = server:name()
+client = socket.tcp_connect(addr.host, addr.port)
+echo_fiber ~= nil
+client:write('hello')
+client:read(5, 0.1) == 'hello'
+-- send spurious wakeup
+fiber.wakeup(echo_fiber)
+fiber.sleep(0)
+client:write('world')
+client:read(5, 0.1) == 'world'
+-- cancel fiber
+fiber.cancel(echo_fiber)
+client:read(1, 0.1) == ''
+server:close()
+
 test_run:cmd("clear filter")
-- 
2.19.0

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [tarantool-patches] [PATCH v2] Fix lua socket polling in case of a spurious wakeup
  2018-09-24 19:53 [tarantool-patches] [PATCH v2] Fix lua socket polling in case of a spurious wakeup Georgy Kirichenko
@ 2018-10-03 15:08 ` Vladislav Shpilevoy
  2018-10-05 12:54 ` Vladimir Davydov
  1 sibling, 0 replies; 5+ messages in thread
From: Vladislav Shpilevoy @ 2018-10-03 15:08 UTC (permalink / raw)
  To: tarantool-patches, Georgy Kirichenko, Vladimir Davydov

Hi! Thanks for the fixes! LGTM. Vova, please, review or push.

On 24/09/2018 22:53, Georgy Kirichenko wrote:
> socket_writable/socket_readable handles socket.iowait spurious wakeup
> until event is happened or timeout is exceeded.
> 
> https://github.com/tarantool/tarantool/issues/3344
> https://github.com/tarantool/tarantool/tree/g.kirichenko/gh-3344-socket-io-spurios-wakeup
> 
> Changes in v2:
>   - Fixes according to Vlad's review
> 
> Fixes: #3344
> ---
>   src/lua/socket.lua       | 52 ++++++++++++++-----------------
>   test/app/socket.result   | 66 ++++++++++++++++++++++++++++++++++++++++
>   test/app/socket.test.lua | 28 +++++++++++++++++
>   3 files changed, 117 insertions(+), 29 deletions(-)
> 
> diff --git a/src/lua/socket.lua b/src/lua/socket.lua
> index 8f5e2926b..02c93381b 100644
> --- a/src/lua/socket.lua
> +++ b/src/lua/socket.lua
> @@ -332,12 +332,17 @@ local function do_wait(self, what, timeout)
>       self._errno = nil
>       timeout = timeout or TIMEOUT_INFINITY
>   
> -    local res = internal.iowait(fd, what, timeout)
> -    if res == 0 then
> -        self._errno = boxerrno.ETIMEDOUT
> -        return 0
> +    local deadline = fiber.clock() + timeout
> +
> +    while fiber.clock() <= deadline do
> +        local events = internal.iowait(fd, what, deadline - fiber.clock())
> +        fiber.testcancel()
> +        if events ~= 0 and events ~= '' then
> +            return events
> +        end
>       end
> -    return res
> +    self._errno = boxerrno.ETIMEDOUT
> +    return 0
>   end
>   
>   local function socket_readable(self, timeout)
> @@ -666,7 +671,7 @@ local function read(self, limit, timeout, check, ...)
>           return data
>       end
>   
> -    while timeout > 0 do
> +    while timeout >= 0 do
>           local started = fiber.clock()
>   
>           assert(rbuf:size() < limit)
> @@ -693,15 +698,9 @@ local function read(self, limit, timeout, check, ...)
>               return nil
>           end
>   
> -        if not socket_readable(self, timeout) then
> -            return nil
> -        end
> -        if timeout <= 0 then
> -            break
> -        end
> +        socket_readable(self, timeout)
>           timeout = timeout - ( fiber.clock() - started )
>       end
> -    self._errno = boxerrno.ETIMEDOUT
>       return nil
>   end
>   
> @@ -740,7 +739,7 @@ local function socket_write(self, octets, timeout)
>       end
>   
>       local started = fiber.clock()
> -    while true do
> +    while timeout >= 0 do
>           local written = syswrite(self, p, e - p)
>           if written == 0 then
>               return p - s -- eof
> @@ -754,11 +753,10 @@ local function socket_write(self, octets, timeout)
>               return nil
>           end
>   
> +        socket_writable(self, timeout)
>           timeout = timeout - (fiber.clock() - started)
> -        if timeout <= 0 or not socket_writable(self, timeout) then
> -            break
> -        end
>       end
> +    return p - s
>   end
>   
>   local function socket_send(self, octets, flags)
> @@ -997,16 +995,12 @@ local function socket_tcp_connect(s, address, port, timeout)
>       -- Wait until the connection is established or ultimately fails.
>       -- In either condition the socket becomes writable. To tell these
>       -- conditions appart SO_ERROR must be consulted (man connect).
> -    if socket_writable(s, timeout) then
> +    local deadline = timeout + fiber.clock()
> +    if socket_writable(s, deadline - fiber.clock()) then
>           s._errno = socket_getsockopt(s, 'SOL_SOCKET', 'SO_ERROR')
> -    else
> -        s._errno = boxerrno.ETIMEDOUT
> -    end
> -    if s._errno ~= 0 then
> -        return nil
> +        return s._errno == 0 or nil
>       end
> -    -- Connected
> -    return true
> +    return nil
>   end
>   
>   local function tcp_connect(host, port, timeout)
> @@ -1387,7 +1381,7 @@ end
>   local function lsocket_tcp_accept(self)
>       check_socket(self)
>       local deadline = fiber.clock() + (self.timeout or TIMEOUT_INFINITY)
> -    repeat
> +    while socket_readable(self, deadline - fiber.clock()) do
>           local client = socket_accept(self)
>           if client then
>               setmetatable(client, lsocket_tcp_client_mt)
> @@ -1397,7 +1391,7 @@ local function lsocket_tcp_accept(self)
>           if not errno_is_transient[errno] then
>               break
>           end
> -    until not socket_readable(self, deadline - fiber.clock())
> +    end
>       return nil, socket_error(self)
>   end
>   
> @@ -1452,7 +1446,7 @@ local function lsocket_tcp_receive(self, pattern, prefix)
>       elseif pattern == "*a" then
>           local result = { prefix }
>           local deadline = fiber.clock() + (self.timeout or TIMEOUT_INFINITY)
> -        repeat
> +        while socket_readable(self, deadline - fiber.clock()) do
>               local data = socket_sysread(self)
>               if data == nil then
>                   if not errno_is_transient[self._errno] then
> @@ -1463,7 +1457,7 @@ local function lsocket_tcp_receive(self, pattern, prefix)
>               else
>                   table.insert(result, data)
>               end
> -        until not socket_readable(self, deadline - fiber.clock())
> +        end
>           if #result == 1 then
>               return nil, 'closed', table.concat(result)
>           end
> diff --git a/test/app/socket.result b/test/app/socket.result
> index 729c1eb3f..2f002a37e 100644
> --- a/test/app/socket.result
> +++ b/test/app/socket.result
> @@ -2779,6 +2779,72 @@ listening_socket:close()
>   ---
>   - true
>   ...
> +--gh-3344 connection should not fail is there is a spurious wakeup for io fiber
> +test_run:cmd("setopt delimiter ';'")
> +---
> +- true
> +...
> +echo_fiber = nil
> +server = socket.tcp_server('localhost', 0, { handler = function(s)
> +    echo_fiber = fiber.self()
> +    while true do
> +        local b = s:read(1, 0.1)
> +        if b ~= nil then
> +            s:write(b)
> +        end
> +    end
> +end, name = 'echoserv'});
> +---
> +...
> +test_run:cmd("setopt delimiter ''");
> +---
> +- true
> +...
> +addr = server:name()
> +---
> +...
> +client = socket.tcp_connect(addr.host, addr.port)
> +---
> +...
> +echo_fiber ~= nil
> +---
> +- true
> +...
> +client:write('hello')
> +---
> +- 5
> +...
> +client:read(5, 0.1) == 'hello'
> +---
> +- true
> +...
> +-- send spurious wakeup
> +fiber.wakeup(echo_fiber)
> +---
> +...
> +fiber.sleep(0)
> +---
> +...
> +client:write('world')
> +---
> +- 5
> +...
> +client:read(5, 0.1) == 'world'
> +---
> +- true
> +...
> +-- cancel fiber
> +fiber.cancel(echo_fiber)
> +---
> +...
> +client:read(1, 0.1) == ''
> +---
> +- true
> +...
> +server:close()
> +---
> +- true
> +...
>   test_run:cmd("clear filter")
>   ---
>   - true
> diff --git a/test/app/socket.test.lua b/test/app/socket.test.lua
> index 9b896522b..c0b001449 100644
> --- a/test/app/socket.test.lua
> +++ b/test/app/socket.test.lua
> @@ -939,4 +939,32 @@ receiving_socket:close()
>   sending_socket:close()
>   listening_socket:close()
>   
> +--gh-3344 connection should not fail is there is a spurious wakeup for io fiber
> +test_run:cmd("setopt delimiter ';'")
> +echo_fiber = nil
> +server = socket.tcp_server('localhost', 0, { handler = function(s)
> +    echo_fiber = fiber.self()
> +    while true do
> +        local b = s:read(1, 0.1)
> +        if b ~= nil then
> +            s:write(b)
> +        end
> +    end
> +end, name = 'echoserv'});
> +test_run:cmd("setopt delimiter ''");
> +addr = server:name()
> +client = socket.tcp_connect(addr.host, addr.port)
> +echo_fiber ~= nil
> +client:write('hello')
> +client:read(5, 0.1) == 'hello'
> +-- send spurious wakeup
> +fiber.wakeup(echo_fiber)
> +fiber.sleep(0)
> +client:write('world')
> +client:read(5, 0.1) == 'world'
> +-- cancel fiber
> +fiber.cancel(echo_fiber)
> +client:read(1, 0.1) == ''
> +server:close()
> +
>   test_run:cmd("clear filter")
> 

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [tarantool-patches] [PATCH v2] Fix lua socket polling in case of a spurious wakeup
  2018-09-24 19:53 [tarantool-patches] [PATCH v2] Fix lua socket polling in case of a spurious wakeup Georgy Kirichenko
  2018-10-03 15:08 ` Vladislav Shpilevoy
@ 2018-10-05 12:54 ` Vladimir Davydov
  2018-10-10 10:13   ` [PATCH v3] socket: fix polling in case of " Georgy Kirichenko
  1 sibling, 1 reply; 5+ messages in thread
From: Vladimir Davydov @ 2018-10-05 12:54 UTC (permalink / raw)
  To: Georgy Kirichenko; +Cc: tarantool-patches

On Mon, Sep 24, 2018 at 10:53:04PM +0300, Georgy Kirichenko wrote:
> socket_writable/socket_readable handles socket.iowait spurious wakeup
> until event is happened or timeout is exceeded.
> 
> https://github.com/tarantool/tarantool/issues/3344
> https://github.com/tarantool/tarantool/tree/g.kirichenko/gh-3344-socket-io-spurios-wakeup
> 
> Changes in v2:
>  - Fixes according to Vlad's review

Nit: please put the links and the change log after ---, otherwise it
looks like they are a part of the commit message.

Also, according to our guideline, one should prefix the subject line
with a subsystem name, when it makes sense. In your case it should be

  socket: fix polling in case of spurious wakeup

> 
> Fixes: #3344

Nit: should be

Closes #3344

> ---
>  src/lua/socket.lua       | 52 ++++++++++++++-----------------
>  test/app/socket.result   | 66 ++++++++++++++++++++++++++++++++++++++++
>  test/app/socket.test.lua | 28 +++++++++++++++++
>  3 files changed, 117 insertions(+), 29 deletions(-)
> 
> diff --git a/src/lua/socket.lua b/src/lua/socket.lua
> index 8f5e2926b..02c93381b 100644
> --- a/src/lua/socket.lua
> +++ b/src/lua/socket.lua
> @@ -332,12 +332,17 @@ local function do_wait(self, what, timeout)
>      self._errno = nil
>      timeout = timeout or TIMEOUT_INFINITY
>  
> -    local res = internal.iowait(fd, what, timeout)
> -    if res == 0 then
> -        self._errno = boxerrno.ETIMEDOUT
> -        return 0
> +    local deadline = fiber.clock() + timeout
> +
> +    while fiber.clock() <= deadline do
> +        local events = internal.iowait(fd, what, deadline - fiber.clock())
> +        fiber.testcancel()
> +        if events ~= 0 and events ~= '' then
> +            return events
> +        end
>      end
> -    return res
> +    self._errno = boxerrno.ETIMEDOUT
> +    return 0
>  end
>  
>  local function socket_readable(self, timeout)
> @@ -666,7 +671,7 @@ local function read(self, limit, timeout, check, ...)
>          return data
>      end
>  
> -    while timeout > 0 do
> +    while timeout >= 0 do
>          local started = fiber.clock()
>  
>          assert(rbuf:size() < limit)
> @@ -693,15 +698,9 @@ local function read(self, limit, timeout, check, ...)
>              return nil
>          end
>  
> -        if not socket_readable(self, timeout) then
> -            return nil
> -        end
> -        if timeout <= 0 then
> -            break
> -        end
> +        socket_readable(self, timeout)
>          timeout = timeout - ( fiber.clock() - started )
>      end
> -    self._errno = boxerrno.ETIMEDOUT
>      return nil

I guess it's nitpicking, but calling socket_readable() without checking
what it returns looks confusing to me :-(

May be rewrite it with

  repeat
      local started = fiber.clock()
      ...
      timeout = timeout - (fiber.clock() - started)
  until not socket_readable(self, timeout)
  return nil

?

>  end
>  
> @@ -740,7 +739,7 @@ local function socket_write(self, octets, timeout)
>      end
>  
>      local started = fiber.clock()
> -    while true do
> +    while timeout >= 0 do

By the look at how 'timeout' is updated below, 'started' should be set
inside the loop. Looks like a bug, which is worth fixing in the scope of
this patch.

>          local written = syswrite(self, p, e - p)
>          if written == 0 then
>              return p - s -- eof
> @@ -754,11 +753,10 @@ local function socket_write(self, octets, timeout)
>              return nil
>          end
>  
> +        socket_writable(self, timeout)
>          timeout = timeout - (fiber.clock() - started)
> -        if timeout <= 0 or not socket_writable(self, timeout) then
> -            break
> -        end

Again, I'd rewrite this with repeat-until:

  repeat
      local started = fiber.clock()
      ...
      timeout = timeout - (fiber.clock() - started)
  until not socket_writable(self, timeout)
  return p > s and p - s or nil

>      end
> +    return p - s

I think this function should return nil on timeout in case nothing was
written. Why did you change that?

>  end
>  
>  local function socket_send(self, octets, flags)
> @@ -997,16 +995,12 @@ local function socket_tcp_connect(s, address, port, timeout)
>      -- Wait until the connection is established or ultimately fails.
>      -- In either condition the socket becomes writable. To tell these
>      -- conditions appart SO_ERROR must be consulted (man connect).
> -    if socket_writable(s, timeout) then
> +    local deadline = timeout + fiber.clock()
> +    if socket_writable(s, deadline - fiber.clock()) then

What's the point of using 'deadline' here?

>          s._errno = socket_getsockopt(s, 'SOL_SOCKET', 'SO_ERROR')
> -    else
> -        s._errno = boxerrno.ETIMEDOUT
> -    end
> -    if s._errno ~= 0 then
> -        return nil
> +        return s._errno == 0 or nil
>      end
> -    -- Connected
> -    return true
> +    return nil

Again, nitpicking. I understand why you removed '_errno = ETIMEDOUT',
but why change the rest? It looked pretty clean.

>  end
>  
>  local function tcp_connect(host, port, timeout)
> @@ -1387,7 +1381,7 @@ end
>  local function lsocket_tcp_accept(self)
>      check_socket(self)
>      local deadline = fiber.clock() + (self.timeout or TIMEOUT_INFINITY)
> -    repeat
> +    while socket_readable(self, deadline - fiber.clock()) do
>          local client = socket_accept(self)
>          if client then
>              setmetatable(client, lsocket_tcp_client_mt)
> @@ -1397,7 +1391,7 @@ local function lsocket_tcp_accept(self)
>          if not errno_is_transient[errno] then
>              break
>          end
> -    until not socket_readable(self, deadline - fiber.clock())
> +    end

Why did you replace repeat-until with while-do? Now, we will call iowait
even if the socket is ready. That is, it was one syscall for the fast
path (wait-less) before this change, now it's two. That said, this
change looks rather controversial to me, and is definitely out of the
scope of this patch. Let's please leave it as is.

>      return nil, socket_error(self)
>  end
>  
> @@ -1452,7 +1446,7 @@ local function lsocket_tcp_receive(self, pattern, prefix)
>      elseif pattern == "*a" then
>          local result = { prefix }
>          local deadline = fiber.clock() + (self.timeout or TIMEOUT_INFINITY)
> -        repeat
> +        while socket_readable(self, deadline - fiber.clock()) do
>              local data = socket_sysread(self)
>              if data == nil then
>                  if not errno_is_transient[self._errno] then
> @@ -1463,7 +1457,7 @@ local function lsocket_tcp_receive(self, pattern, prefix)
>              else
>                  table.insert(result, data)
>              end
> -        until not socket_readable(self, deadline - fiber.clock())
> +        end

Ditto.

>          if #result == 1 then
>              return nil, 'closed', table.concat(result)
>          end

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3] socket: fix polling in case of spurious wakeup
  2018-10-05 12:54 ` Vladimir Davydov
@ 2018-10-10 10:13   ` Georgy Kirichenko
  2018-10-10 11:23     ` Vladimir Davydov
  0 siblings, 1 reply; 5+ messages in thread
From: Georgy Kirichenko @ 2018-10-10 10:13 UTC (permalink / raw)
  To: tarantool-patches; +Cc: vdavydov.dev, Georgy Kirichenko

socket_writable/socket_readable handles socket.iowait spurious wakeup
until event is happened or timeout is exceeded.

Closes: #3344
---
https://github.com/tarantool/tarantool/issues/3344
https://github.com/tarantool/tarantool/tree/g.kirichenko/gh-3344-socket-io-spurios-wakeup

Changes in v3:
 - Fixes according to Vova's review

Changes in v2:
 - Fixes according to Vlad's review

 src/lua/socket.lua       | 46 +++++++++++-----------------
 test/app/socket.result   | 66 ++++++++++++++++++++++++++++++++++++++++
 test/app/socket.test.lua | 28 +++++++++++++++++
 3 files changed, 112 insertions(+), 28 deletions(-)

diff --git a/src/lua/socket.lua b/src/lua/socket.lua
index 8f5e2926b..7f9b40fb1 100644
--- a/src/lua/socket.lua
+++ b/src/lua/socket.lua
@@ -332,12 +332,17 @@ local function do_wait(self, what, timeout)
     self._errno = nil
     timeout = timeout or TIMEOUT_INFINITY
 
-    local res = internal.iowait(fd, what, timeout)
-    if res == 0 then
-        self._errno = boxerrno.ETIMEDOUT
-        return 0
-    end
-    return res
+    repeat
+        local started = fiber.clock()
+        local events = internal.iowait(fd, what, timeout)
+        fiber.testcancel()
+        if events ~= 0 and events ~= '' then
+            return events
+        end
+        timeout = timeout - (fiber.clock() - started)
+    until timeout < 0
+    self._errno = boxerrno.ETIMEDOUT
+    return 0
 end
 
 local function socket_readable(self, timeout)
@@ -666,9 +671,8 @@ local function read(self, limit, timeout, check, ...)
         return data
     end
 
-    while timeout > 0 do
-        local started = fiber.clock()
-
+    local deadline = fiber.clock() + timeout
+    repeat
         assert(rbuf:size() < limit)
         local to_read = math.min(limit - rbuf:size(), buffer.READAHEAD)
         local data = rbuf:reserve(to_read)
@@ -692,16 +696,7 @@ local function read(self, limit, timeout, check, ...)
         elseif not errno_is_transient[self._errno] then
             return nil
         end
-
-        if not socket_readable(self, timeout) then
-            return nil
-        end
-        if timeout <= 0 then
-            break
-        end
-        timeout = timeout - ( fiber.clock() - started )
-    end
-    self._errno = boxerrno.ETIMEDOUT
+    until not socket_readable(self, deadline - fiber.clock())
     return nil
 end
 
@@ -739,8 +734,8 @@ local function socket_write(self, octets, timeout)
         return 0
     end
 
-    local started = fiber.clock()
-    while true do
+    local deadline = fiber.clock() + timeout
+    repeat
         local written = syswrite(self, p, e - p)
         if written == 0 then
             return p - s -- eof
@@ -754,11 +749,8 @@ local function socket_write(self, octets, timeout)
             return nil
         end
 
-        timeout = timeout - (fiber.clock() - started)
-        if timeout <= 0 or not socket_writable(self, timeout) then
-            break
-        end
-    end
+    until not socket_writable(self, deadline - fiber.clock())
+    return nil
 end
 
 local function socket_send(self, octets, flags)
@@ -999,8 +991,6 @@ local function socket_tcp_connect(s, address, port, timeout)
     -- conditions appart SO_ERROR must be consulted (man connect).
     if socket_writable(s, timeout) then
         s._errno = socket_getsockopt(s, 'SOL_SOCKET', 'SO_ERROR')
-    else
-        s._errno = boxerrno.ETIMEDOUT
     end
     if s._errno ~= 0 then
         return nil
diff --git a/test/app/socket.result b/test/app/socket.result
index 729c1eb3f..2f002a37e 100644
--- a/test/app/socket.result
+++ b/test/app/socket.result
@@ -2779,6 +2779,72 @@ listening_socket:close()
 ---
 - true
 ...
+--gh-3344 connection should not fail is there is a spurious wakeup for io fiber
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+echo_fiber = nil
+server = socket.tcp_server('localhost', 0, { handler = function(s)
+    echo_fiber = fiber.self()
+    while true do
+        local b = s:read(1, 0.1)
+        if b ~= nil then
+            s:write(b)
+        end
+    end
+end, name = 'echoserv'});
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+addr = server:name()
+---
+...
+client = socket.tcp_connect(addr.host, addr.port)
+---
+...
+echo_fiber ~= nil
+---
+- true
+...
+client:write('hello')
+---
+- 5
+...
+client:read(5, 0.1) == 'hello'
+---
+- true
+...
+-- send spurious wakeup
+fiber.wakeup(echo_fiber)
+---
+...
+fiber.sleep(0)
+---
+...
+client:write('world')
+---
+- 5
+...
+client:read(5, 0.1) == 'world'
+---
+- true
+...
+-- cancel fiber
+fiber.cancel(echo_fiber)
+---
+...
+client:read(1, 0.1) == ''
+---
+- true
+...
+server:close()
+---
+- true
+...
 test_run:cmd("clear filter")
 ---
 - true
diff --git a/test/app/socket.test.lua b/test/app/socket.test.lua
index 9b896522b..c0b001449 100644
--- a/test/app/socket.test.lua
+++ b/test/app/socket.test.lua
@@ -939,4 +939,32 @@ receiving_socket:close()
 sending_socket:close()
 listening_socket:close()
 
+--gh-3344 connection should not fail is there is a spurious wakeup for io fiber
+test_run:cmd("setopt delimiter ';'")
+echo_fiber = nil
+server = socket.tcp_server('localhost', 0, { handler = function(s)
+    echo_fiber = fiber.self()
+    while true do
+        local b = s:read(1, 0.1)
+        if b ~= nil then
+            s:write(b)
+        end
+    end
+end, name = 'echoserv'});
+test_run:cmd("setopt delimiter ''");
+addr = server:name()
+client = socket.tcp_connect(addr.host, addr.port)
+echo_fiber ~= nil
+client:write('hello')
+client:read(5, 0.1) == 'hello'
+-- send spurious wakeup
+fiber.wakeup(echo_fiber)
+fiber.sleep(0)
+client:write('world')
+client:read(5, 0.1) == 'world'
+-- cancel fiber
+fiber.cancel(echo_fiber)
+client:read(1, 0.1) == ''
+server:close()
+
 test_run:cmd("clear filter")
-- 
2.19.1

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v3] socket: fix polling in case of spurious wakeup
  2018-10-10 10:13   ` [PATCH v3] socket: fix polling in case of " Georgy Kirichenko
@ 2018-10-10 11:23     ` Vladimir Davydov
  0 siblings, 0 replies; 5+ messages in thread
From: Vladimir Davydov @ 2018-10-10 11:23 UTC (permalink / raw)
  To: Georgy Kirichenko; +Cc: tarantool-patches

Pushed to 1.10

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2018-10-10 11:23 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-09-24 19:53 [tarantool-patches] [PATCH v2] Fix lua socket polling in case of a spurious wakeup Georgy Kirichenko
2018-10-03 15:08 ` Vladislav Shpilevoy
2018-10-05 12:54 ` Vladimir Davydov
2018-10-10 10:13   ` [PATCH v3] socket: fix polling in case of " Georgy Kirichenko
2018-10-10 11:23     ` Vladimir Davydov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox