[patches] [fio 1/1] fio: Fix race condition in fio.read

imarkov imarkov at tarantool.org
Tue Feb 27 11:06:25 MSK 2018


When read followed by yield from multiple fibers is performed,
ibuf shared among several fibers is corrupted and read data is
mixed.

The fix is to create new ibuf for each fio.read call in case
buffer is not specified.

Closes #3187
---
 src/lua/buffer.lua    |  6 +++++
 src/lua/fio.lua       |  5 ++--
 test/app/fio.result   | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++
 test/app/fio.test.lua | 33 +++++++++++++++++++++++
 4 files changed, 116 insertions(+), 2 deletions(-)

diff --git a/src/lua/buffer.lua b/src/lua/buffer.lua
index a72d8d1..29be015 100644
--- a/src/lua/buffer.lua
+++ b/src/lua/buffer.lua
@@ -128,6 +128,11 @@ local function ibuf_read(buf, size)
     return rpos
 end
 
+local function ibuf_destroy(buf)
+    checkibuf(buf, "destroy")
+    ffi.C.ibuf_destroy(buf)
+end
+
 local function ibuf_serialize(buf)
     local properties = { rpos = buf.rpos, wpos = buf.wpos }
     return { ibuf = properties }
@@ -136,6 +141,7 @@ end
 local ibuf_methods = {
     recycle = ibuf_recycle;
     reset = ibuf_reset;
+    destroy = ibuf_destroy,
 
     reserve = ibuf_reserve;
     alloc = ibuf_alloc;
diff --git a/src/lua/fio.lua b/src/lua/fio.lua
index d48c709..8797fba 100644
--- a/src/lua/fio.lua
+++ b/src/lua/fio.lua
@@ -40,14 +40,14 @@ fio_methods.read = function(self, buf, size)
     end
     if not ffi.istype(const_char_ptr_t, buf) then
         size = buf or size
-        tmpbuf = buffer.IBUF_SHARED
-        tmpbuf:reset()
+        tmpbuf = buffer.ibuf()
         buf = tmpbuf:reserve(size)
     end
     local res, err = internal.read(self.fh, buf, size)
     if res == nil then
         if tmpbuf ~= nil then
             tmpbuf:recycle()
+            tmpbuf:destroy()
         end
         return nil, err
     end
@@ -55,6 +55,7 @@ fio_methods.read = function(self, buf, size)
         tmpbuf:alloc(res)
         res = ffi.string(tmpbuf.rpos, tmpbuf:size())
         tmpbuf:recycle()
+        tmpbuf:destroy()
     end
     return res
 end
diff --git a/test/app/fio.result b/test/app/fio.result
index 0fc8582..1f12384 100644
--- a/test/app/fio.result
+++ b/test/app/fio.result
@@ -1067,6 +1067,80 @@ fio.unlink(tmpfile)
 ---
 - true
 ...
+tmp1 = fio.pathjoin(tmpdir, "tmp1")
+---
+...
+tmp2= fio.pathjoin(tmpdir, "tmp2")
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+function write_big_file(name, odd)
+    local fh = fio.open(name, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, 0777)
+    if odd then
+        for i = 1, 100000, 2 do
+            fh:write(i)
+        end
+    else
+        for i = 2, 100000,2 do
+            fh:write(i)
+        end
+    end
+    fh:write(name)
+    fh:seek(0)
+    return fh
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+fh1 = write_big_file(tmp1)
+---
+...
+fh2 = write_big_file(tmp2)
+---
+...
+fiber = require('fiber')
+---
+...
+digest = require('digest')
+---
+...
+str = fh1:read()
+---
+...
+fh1:seek(0)
+---
+- 0
+...
+hash = digest.crc32(str)
+---
+...
+ch = fiber.channel(1)
+---
+...
+f1 = fiber.create(function() str = fh1:read() ch:put(digest.crc32(str)) end)
+---
+...
+f2 = fiber.create(function() str = fh2:read() end)
+---
+...
+ch:get() == hash
+---
+- true
+...
+fio.unlink(tmp1)
+---
+- true
+...
+fio.unlink(tmp2)
+---
+- true
+...
 fio.rmdir(tmpdir)
 ---
 - true
diff --git a/test/app/fio.test.lua b/test/app/fio.test.lua
index cd0bc16..8c37baa 100644
--- a/test/app/fio.test.lua
+++ b/test/app/fio.test.lua
@@ -342,4 +342,37 @@ fio.path.lexists(link)
 
 fio.unlink(link)
 fio.unlink(tmpfile)
+tmp1 = fio.pathjoin(tmpdir, "tmp1")
+tmp2= fio.pathjoin(tmpdir, "tmp2")
+test_run:cmd("setopt delimiter ';'")
+function write_big_file(name, odd)
+    local fh = fio.open(name, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, 0777)
+    if odd then
+        for i = 1, 100000, 2 do
+            fh:write(i)
+        end
+    else
+        for i = 2, 100000,2 do
+            fh:write(i)
+        end
+    end
+    fh:write(name)
+    fh:seek(0)
+    return fh
+end;
+test_run:cmd("setopt delimiter ''");
+fh1 = write_big_file(tmp1)
+fh2 = write_big_file(tmp2)
+fiber = require('fiber')
+digest = require('digest')
+str = fh1:read()
+fh1:seek(0)
+hash = digest.crc32(str)
+ch = fiber.channel(1)
+f1 = fiber.create(function() str = fh1:read() ch:put(digest.crc32(str)) end)
+f2 = fiber.create(function() str = fh2:read() end)
+ch:get() == hash
+
+fio.unlink(tmp1)
+fio.unlink(tmp2)
 fio.rmdir(tmpdir)
-- 
2.7.4




More information about the Tarantool-patches mailing list