Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v2] fio/coio: Handle partial writes
@ 2019-12-04 11:17 Cyrill Gorcunov
  2019-12-04 11:25 ` Cyrill Gorcunov
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Cyrill Gorcunov @ 2019-12-04 11:17 UTC (permalink / raw)
  To: tml

Writting less bytes than requested is perfectly fine
from OS point of view. In turn our fio.write/pwrite
api simply returns 'true' even if only a part of
a buffer has been written.

We can't change the api without breaking backward
compatibility (otherwise we could simply return number
of bytes written, instead of current true/false sign).

For this sake both coio_write and coio_pwrite tries
to deliver complete data in a cycle.

Fixes #4651

Signed-off-by: Cyrill Gorcunov <gorcunov@gmail.com>
---
branch gorcunov/gh-4651-partial-write-2
v2:
 - Use write_should_retry helper to not duplicate code
 - Return -1 on any other error, so that fio.write() return
   false

 src/lib/core/coio_file.c | 80 ++++++++++++++++++++++++++++++++++------
 src/lib/core/errinj.h    |  1 +
 test/app/fio.result      | 73 ++++++++++++++++++++++++++++++++++++
 test/app/fio.test.lua    | 21 +++++++++++
 test/box/errinj.result   |  2 +
 5 files changed, 166 insertions(+), 11 deletions(-)

diff --git a/src/lib/core/coio_file.c b/src/lib/core/coio_file.c
index 62388344e..af5fb30ce 100644
--- a/src/lib/core/coio_file.c
+++ b/src/lib/core/coio_file.c
@@ -161,13 +161,49 @@ coio_file_close(int fd)
 	return coio_wait_done(req, &eio);
 }
 
+static inline int
+write_should_retry(void)
+{
+	/* Retry on O_NONBLOCK */
+	if (errno == EAGAIN)
+		return true;
+	/* Retry if been interrupted */
+	if (errno == EINTR)
+		return true;
+	/* Unexpected error */
+	return false;
+}
+
 ssize_t
 coio_pwrite(int fd, const void *buf, size_t count, off_t offset)
 {
-	INIT_COEIO_FILE(eio);
-	eio_req *req = eio_write(fd, (void *) buf, count, offset,
-				 0, coio_complete, &eio);
-	return coio_wait_done(req, &eio);
+	struct errinj *inj = errinj(ERRINJ_COIO_WRITE_CHUNK, ERRINJ_INT);
+	ssize_t left = count, pos = 0, res, chunk;
+	eio_req *req;
+
+	while (left > 0) {
+		INIT_COEIO_FILE(eio);
+
+		if (inj != NULL && inj->iparam > 0)
+			chunk = (ssize_t)inj->iparam;
+		else
+			chunk = left;
+
+		req = eio_write(fd, (void *)buf + pos, chunk,
+				offset + pos, EIO_PRI_DEFAULT,
+				coio_complete, &eio);
+		res = coio_wait_done(req, &eio);
+		if (res < 0) {
+			if (!write_should_retry()) {
+				pos = -1;
+				break;
+			}
+		} else {
+			left -= res;
+			pos += res;
+		}
+	}
+	return pos;
 }
 
 ssize_t
@@ -200,7 +236,12 @@ coio_preadn(int fd, void *buf, size_t count, off_t offset)
 static void
 coio_do_write(eio_req *req)
 {
+	struct errinj *inj = errinj(ERRINJ_COIO_WRITE_CHUNK, ERRINJ_INT);
 	struct coio_file_task *eio = (struct coio_file_task *)req->data;
+
+	if (inj != NULL && inj->iparam > 0)
+		eio->write.count = (ssize_t)inj->iparam;
+
 	req->result = write(eio->write.fd, eio->write.buf, eio->write.count);
 	eio->errorno = errno;
 }
@@ -208,13 +249,30 @@ coio_do_write(eio_req *req)
 ssize_t
 coio_write(int fd, const void *buf, size_t count)
 {
-	INIT_COEIO_FILE(eio);
-	eio.write.buf = buf;
-	eio.write.count = count;
-	eio.write.fd = fd;
-	eio_req *req = eio_custom(coio_do_write, 0,
-				  coio_complete, &eio);
-	return coio_wait_done(req, &eio);
+	ssize_t left = count, pos = 0, res;
+	eio_req *req;
+
+	while (left > 0) {
+		INIT_COEIO_FILE(eio);
+
+		eio.write.buf	= buf + pos;
+		eio.write.count	= left;
+		eio.write.fd	= fd;
+
+		req = eio_custom(coio_do_write, EIO_PRI_DEFAULT,
+				 coio_complete, &eio);
+		res = coio_wait_done(req, &eio);
+		if (res < 0) {
+			if (!write_should_retry()) {
+				pos = -1;
+				break;
+			}
+		} else {
+			left -= res;
+			pos += res;
+		}
+	}
+	return pos;
 }
 
 static void
diff --git a/src/lib/core/errinj.h b/src/lib/core/errinj.h
index 3072a00ea..f90554d06 100644
--- a/src/lib/core/errinj.h
+++ b/src/lib/core/errinj.h
@@ -134,6 +134,7 @@ struct errinj {
 	_(ERRINJ_SQL_NAME_NORMALIZATION, ERRINJ_BOOL, {.bparam = false}) \
 	_(ERRINJ_COIO_SENDFILE_CHUNK, ERRINJ_INT, {.iparam = -1}) \
 	_(ERRINJ_SWIM_FD_ONLY, ERRINJ_BOOL, {.bparam = false}) \
+	_(ERRINJ_COIO_WRITE_CHUNK, ERRINJ_INT, {.iparam = -1}) \
 
 ENUM0(errinj_id, ERRINJ_LIST);
 extern struct errinj errinjs[];
diff --git a/test/app/fio.result b/test/app/fio.result
index f83c43f44..6eafa24e6 100644
--- a/test/app/fio.result
+++ b/test/app/fio.result
@@ -776,6 +776,12 @@ file5 = fio.pathjoin(tree, 'file.5')
 file6 = fio.pathjoin(tree, 'file.6')
 ---
 ...
+file7 = fio.pathjoin(tree, 'file.7')
+---
+...
+file8 = fio.pathjoin(tree, 'file.8')
+---
+...
 fh1 = fio.open(file1, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, tonumber('0777', 8))
 ---
 ...
@@ -865,6 +871,73 @@ fh6:close()
 ---
 - true
 ...
+-- test partial write/pwrite via one byte transfer
+fh7 = fio.open(file7, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, tonumber('0644', 8))
+---
+...
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', 1)
+---
+- ok
+...
+fh7:write("one byte transfer, write")
+---
+- true
+...
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', -1)
+---
+- ok
+...
+fh7:seek(0)
+---
+- 0
+...
+fh7:read()
+---
+- one byte transfer, write
+...
+fh7:close()
+---
+- true
+...
+fh8 = fio.open(file8, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, tonumber('0644', 8))
+---
+...
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', 1)
+---
+- ok
+...
+fh8:pwrite("one byte transfer, ", 0)
+---
+- true
+...
+fh8:seek(0)
+---
+- 0
+...
+fh8:read()
+---
+- 'one byte transfer, '
+...
+fh8:pwrite("pwrite", 19)
+---
+- true
+...
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', -1)
+---
+- ok
+...
+fh8:seek(0)
+---
+- 0
+...
+fh8:read()
+---
+- one byte transfer, pwrite
+...
+fh8:close()
+---
+- true
+...
 res, err = fio.copyfile(fio.pathjoin(tmp1, 'not_exists.txt'), tmp1)
 ---
 ...
diff --git a/test/app/fio.test.lua b/test/app/fio.test.lua
index 56c957d8a..3ed06bcda 100644
--- a/test/app/fio.test.lua
+++ b/test/app/fio.test.lua
@@ -251,6 +251,8 @@ file3 = fio.pathjoin(tree, 'file.3')
 file4 = fio.pathjoin(tree, 'file.4')
 file5 = fio.pathjoin(tree, 'file.5')
 file6 = fio.pathjoin(tree, 'file.6')
+file7 = fio.pathjoin(tree, 'file.7')
+file8 = fio.pathjoin(tree, 'file.8')
 
 fh1 = fio.open(file1, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, tonumber('0777', 8))
 fh1:write("gogo")
@@ -280,6 +282,25 @@ fh6:seek(0)
 fh6:read()
 fh6:close()
 
+-- test partial write/pwrite via one byte transfer
+fh7 = fio.open(file7, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, tonumber('0644', 8))
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', 1)
+fh7:write("one byte transfer, write")
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', -1)
+fh7:seek(0)
+fh7:read()
+fh7:close()
+fh8 = fio.open(file8, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, tonumber('0644', 8))
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', 1)
+fh8:pwrite("one byte transfer, ", 0)
+fh8:seek(0)
+fh8:read()
+fh8:pwrite("pwrite", 19)
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', -1)
+fh8:seek(0)
+fh8:read()
+fh8:close()
+
 res, err = fio.copyfile(fio.pathjoin(tmp1, 'not_exists.txt'), tmp1)
 res
 err:match("failed to copy") ~= nil
diff --git a/test/box/errinj.result b/test/box/errinj.result
index a148346e8..9b831769f 100644
--- a/test/box/errinj.result
+++ b/test/box/errinj.result
@@ -25,6 +25,8 @@ errinj.info()
     state: 0
   ERRINJ_WAL_WRITE:
     state: false
+  ERRINJ_COIO_WRITE_CHUNK:
+    state: -1
   ERRINJ_HTTPC_EXECUTE:
     state: false
   ERRINJ_VYRUN_DATA_READ:
-- 
2.20.1

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

* Re: [Tarantool-patches] [PATCH v2] fio/coio: Handle partial writes
  2019-12-04 11:17 [Tarantool-patches] [PATCH v2] fio/coio: Handle partial writes Cyrill Gorcunov
@ 2019-12-04 11:25 ` Cyrill Gorcunov
  2019-12-04 11:34 ` Konstantin Osipov
  2019-12-04 14:35 ` [Tarantool-patches] [PATCH v3] " Cyrill Gorcunov
  2 siblings, 0 replies; 8+ messages in thread
From: Cyrill Gorcunov @ 2019-12-04 11:25 UTC (permalink / raw)
  To: tml

On Wed, Dec 04, 2019 at 02:17:49PM +0300, Cyrill Gorcunov wrote:
...
> +static inline int
> +write_should_retry(void)
> +{
> +	/* Retry on O_NONBLOCK */
> +	if (errno == EAGAIN)
> +		return true;
> +	/* Retry if been interrupted */
> +	if (errno == EINTR)
> +		return true;
> +	/* Unexpected error */
> +	return false;
> +}

Guys, I just thought, previously if the file we're writting to
has been opened with o_nonblock and eagain happened we were
returning an error. With this patch we gonna retry write operation,
and this changes behaviour. Maybe I should consider EINTR only?

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

* Re: [Tarantool-patches] [PATCH v2] fio/coio: Handle partial writes
  2019-12-04 11:17 [Tarantool-patches] [PATCH v2] fio/coio: Handle partial writes Cyrill Gorcunov
  2019-12-04 11:25 ` Cyrill Gorcunov
@ 2019-12-04 11:34 ` Konstantin Osipov
  2019-12-04 11:48   ` Cyrill Gorcunov
  2019-12-04 14:35 ` [Tarantool-patches] [PATCH v3] " Cyrill Gorcunov
  2 siblings, 1 reply; 8+ messages in thread
From: Konstantin Osipov @ 2019-12-04 11:34 UTC (permalink / raw)
  To: Cyrill Gorcunov; +Cc: tml

* Cyrill Gorcunov <gorcunov@gmail.com> [19/12/04 14:21]:
> +static inline int
> +write_should_retry(void)
> +{
> +	/* Retry on O_NONBLOCK */
> +	if (errno == EAGAIN)
> +		return true;

Usually you add EWOULDBLOCK here too.

> +	/* Retry if been interrupted */
> +	if (errno == EINTR)
> +		return true;

Not sure you should handle this. It's up to the client whether to
make or not a write interruptible, it is managed by fcntl. 
Otherwise you may block indefinitely inside a syscall.



-- 
Konstantin Osipov, Moscow, Russia

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

* Re: [Tarantool-patches] [PATCH v2] fio/coio: Handle partial writes
  2019-12-04 11:34 ` Konstantin Osipov
@ 2019-12-04 11:48   ` Cyrill Gorcunov
  0 siblings, 0 replies; 8+ messages in thread
From: Cyrill Gorcunov @ 2019-12-04 11:48 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: tml

On Wed, Dec 04, 2019 at 02:34:47PM +0300, Konstantin Osipov wrote:
> * Cyrill Gorcunov <gorcunov@gmail.com> [19/12/04 14:21]:
> > +static inline int
> > +write_should_retry(void)
> > +{
> > +	/* Retry on O_NONBLOCK */
> > +	if (errno == EAGAIN)
> > +		return true;
> 
> Usually you add EWOULDBLOCK here too.

True. It somehow escaped from the drafts while I've been
merging the series of fixes. Thanks!

> > +	/* Retry if been interrupted */
> > +	if (errno == EINTR)
> > +		return true;
> 
> Not sure you should handle this. It's up to the client whether to
> make or not a write interruptible, it is managed by fcntl. 
> Otherwise you may block indefinitely inside a syscall.

OK, thanks, will drop.

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

* [Tarantool-patches] [PATCH v3] fio/coio: Handle partial writes
  2019-12-04 11:17 [Tarantool-patches] [PATCH v2] fio/coio: Handle partial writes Cyrill Gorcunov
  2019-12-04 11:25 ` Cyrill Gorcunov
  2019-12-04 11:34 ` Konstantin Osipov
@ 2019-12-04 14:35 ` Cyrill Gorcunov
  2020-02-20 22:10   ` Alexander Turenko
  2 siblings, 1 reply; 8+ messages in thread
From: Cyrill Gorcunov @ 2019-12-04 14:35 UTC (permalink / raw)
  To: tml

Writting less bytes than requested is fine. In turn our
fio.write/pwrite api simply returns 'true' even if only
some part of a buffer has been written. Thus make coio_write
and coio_pwrite to write the whole data in a cycle.

Fixes #4651

Signed-off-by: Cyrill Gorcunov <gorcunov@gmail.com>
---
branch gorcunov/gh-4651-partial-write-3

v3:
 - Just count bytes written returning any error happened

 src/lib/core/coio_file.c | 63 ++++++++++++++++++++++++++++------
 src/lib/core/errinj.h    |  1 +
 test/app/fio.result      | 73 ++++++++++++++++++++++++++++++++++++++++
 test/app/fio.test.lua    | 21 ++++++++++++
 test/box/errinj.result   |  2 ++
 5 files changed, 149 insertions(+), 11 deletions(-)

diff --git a/src/lib/core/coio_file.c b/src/lib/core/coio_file.c
index 62388344e..fb704f938 100644
--- a/src/lib/core/coio_file.c
+++ b/src/lib/core/coio_file.c
@@ -164,10 +164,31 @@ coio_file_close(int fd)
 ssize_t
 coio_pwrite(int fd, const void *buf, size_t count, off_t offset)
 {
-	INIT_COEIO_FILE(eio);
-	eio_req *req = eio_write(fd, (void *) buf, count, offset,
-				 0, coio_complete, &eio);
-	return coio_wait_done(req, &eio);
+	struct errinj *inj = errinj(ERRINJ_COIO_WRITE_CHUNK, ERRINJ_INT);
+	ssize_t left = count, pos = 0, res, chunk;
+	eio_req *req;
+
+	while (left > 0) {
+		INIT_COEIO_FILE(eio);
+
+		if (inj != NULL && inj->iparam > 0)
+			chunk = (ssize_t)inj->iparam;
+		else
+			chunk = left;
+
+		req = eio_write(fd, (void *)buf + pos, chunk,
+				offset + pos, EIO_PRI_DEFAULT,
+				coio_complete, &eio);
+		res = coio_wait_done(req, &eio);
+		if (res < 0) {
+			pos = -1;
+			break;
+		} else {
+			left -= res;
+			pos += res;
+		}
+	}
+	return pos;
 }
 
 ssize_t
@@ -200,7 +221,12 @@ coio_preadn(int fd, void *buf, size_t count, off_t offset)
 static void
 coio_do_write(eio_req *req)
 {
+	struct errinj *inj = errinj(ERRINJ_COIO_WRITE_CHUNK, ERRINJ_INT);
 	struct coio_file_task *eio = (struct coio_file_task *)req->data;
+
+	if (inj != NULL && inj->iparam > 0)
+		eio->write.count = (ssize_t)inj->iparam;
+
 	req->result = write(eio->write.fd, eio->write.buf, eio->write.count);
 	eio->errorno = errno;
 }
@@ -208,13 +234,28 @@ coio_do_write(eio_req *req)
 ssize_t
 coio_write(int fd, const void *buf, size_t count)
 {
-	INIT_COEIO_FILE(eio);
-	eio.write.buf = buf;
-	eio.write.count = count;
-	eio.write.fd = fd;
-	eio_req *req = eio_custom(coio_do_write, 0,
-				  coio_complete, &eio);
-	return coio_wait_done(req, &eio);
+	ssize_t left = count, pos = 0, res;
+	eio_req *req;
+
+	while (left > 0) {
+		INIT_COEIO_FILE(eio);
+
+		eio.write.buf	= buf + pos;
+		eio.write.count	= left;
+		eio.write.fd	= fd;
+
+		req = eio_custom(coio_do_write, EIO_PRI_DEFAULT,
+				 coio_complete, &eio);
+		res = coio_wait_done(req, &eio);
+		if (res < 0) {
+			pos = -1;
+			break;
+		} else {
+			left -= res;
+			pos += res;
+		}
+	}
+	return pos;
 }
 
 static void
diff --git a/src/lib/core/errinj.h b/src/lib/core/errinj.h
index 3072a00ea..f90554d06 100644
--- a/src/lib/core/errinj.h
+++ b/src/lib/core/errinj.h
@@ -134,6 +134,7 @@ struct errinj {
 	_(ERRINJ_SQL_NAME_NORMALIZATION, ERRINJ_BOOL, {.bparam = false}) \
 	_(ERRINJ_COIO_SENDFILE_CHUNK, ERRINJ_INT, {.iparam = -1}) \
 	_(ERRINJ_SWIM_FD_ONLY, ERRINJ_BOOL, {.bparam = false}) \
+	_(ERRINJ_COIO_WRITE_CHUNK, ERRINJ_INT, {.iparam = -1}) \
 
 ENUM0(errinj_id, ERRINJ_LIST);
 extern struct errinj errinjs[];
diff --git a/test/app/fio.result b/test/app/fio.result
index f83c43f44..6eafa24e6 100644
--- a/test/app/fio.result
+++ b/test/app/fio.result
@@ -776,6 +776,12 @@ file5 = fio.pathjoin(tree, 'file.5')
 file6 = fio.pathjoin(tree, 'file.6')
 ---
 ...
+file7 = fio.pathjoin(tree, 'file.7')
+---
+...
+file8 = fio.pathjoin(tree, 'file.8')
+---
+...
 fh1 = fio.open(file1, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, tonumber('0777', 8))
 ---
 ...
@@ -865,6 +871,73 @@ fh6:close()
 ---
 - true
 ...
+-- test partial write/pwrite via one byte transfer
+fh7 = fio.open(file7, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, tonumber('0644', 8))
+---
+...
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', 1)
+---
+- ok
+...
+fh7:write("one byte transfer, write")
+---
+- true
+...
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', -1)
+---
+- ok
+...
+fh7:seek(0)
+---
+- 0
+...
+fh7:read()
+---
+- one byte transfer, write
+...
+fh7:close()
+---
+- true
+...
+fh8 = fio.open(file8, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, tonumber('0644', 8))
+---
+...
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', 1)
+---
+- ok
+...
+fh8:pwrite("one byte transfer, ", 0)
+---
+- true
+...
+fh8:seek(0)
+---
+- 0
+...
+fh8:read()
+---
+- 'one byte transfer, '
+...
+fh8:pwrite("pwrite", 19)
+---
+- true
+...
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', -1)
+---
+- ok
+...
+fh8:seek(0)
+---
+- 0
+...
+fh8:read()
+---
+- one byte transfer, pwrite
+...
+fh8:close()
+---
+- true
+...
 res, err = fio.copyfile(fio.pathjoin(tmp1, 'not_exists.txt'), tmp1)
 ---
 ...
diff --git a/test/app/fio.test.lua b/test/app/fio.test.lua
index 56c957d8a..3ed06bcda 100644
--- a/test/app/fio.test.lua
+++ b/test/app/fio.test.lua
@@ -251,6 +251,8 @@ file3 = fio.pathjoin(tree, 'file.3')
 file4 = fio.pathjoin(tree, 'file.4')
 file5 = fio.pathjoin(tree, 'file.5')
 file6 = fio.pathjoin(tree, 'file.6')
+file7 = fio.pathjoin(tree, 'file.7')
+file8 = fio.pathjoin(tree, 'file.8')
 
 fh1 = fio.open(file1, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, tonumber('0777', 8))
 fh1:write("gogo")
@@ -280,6 +282,25 @@ fh6:seek(0)
 fh6:read()
 fh6:close()
 
+-- test partial write/pwrite via one byte transfer
+fh7 = fio.open(file7, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, tonumber('0644', 8))
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', 1)
+fh7:write("one byte transfer, write")
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', -1)
+fh7:seek(0)
+fh7:read()
+fh7:close()
+fh8 = fio.open(file8, { 'O_RDWR', 'O_TRUNC', 'O_CREAT' }, tonumber('0644', 8))
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', 1)
+fh8:pwrite("one byte transfer, ", 0)
+fh8:seek(0)
+fh8:read()
+fh8:pwrite("pwrite", 19)
+errinj.set('ERRINJ_COIO_WRITE_CHUNK', -1)
+fh8:seek(0)
+fh8:read()
+fh8:close()
+
 res, err = fio.copyfile(fio.pathjoin(tmp1, 'not_exists.txt'), tmp1)
 res
 err:match("failed to copy") ~= nil
diff --git a/test/box/errinj.result b/test/box/errinj.result
index a148346e8..9b831769f 100644
--- a/test/box/errinj.result
+++ b/test/box/errinj.result
@@ -25,6 +25,8 @@ errinj.info()
     state: 0
   ERRINJ_WAL_WRITE:
     state: false
+  ERRINJ_COIO_WRITE_CHUNK:
+    state: -1
   ERRINJ_HTTPC_EXECUTE:
     state: false
   ERRINJ_VYRUN_DATA_READ:
-- 
2.20.1

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

* Re: [Tarantool-patches] [PATCH v3] fio/coio: Handle partial writes
  2019-12-04 14:35 ` [Tarantool-patches] [PATCH v3] " Cyrill Gorcunov
@ 2020-02-20 22:10   ` Alexander Turenko
  2020-02-21 12:02     ` Cyrill Gorcunov
  0 siblings, 1 reply; 8+ messages in thread
From: Alexander Turenko @ 2020-02-20 22:10 UTC (permalink / raw)
  To: Cyrill Gorcunov; +Cc: tml

I have three comments and all are up to you.

Please, rebase the patch on top of the current master (errinj.result was
changed).

WBR, Alexander Turenko.

On Wed, Dec 04, 2019 at 05:35:11PM +0300, Cyrill Gorcunov wrote:
> Writting less bytes than requested is fine. In turn our
> fio.write/pwrite api simply returns 'true' even if only
> some part of a buffer has been written. Thus make coio_write
> and coio_pwrite to write the whole data in a cycle.

I would expect that coio_*() functions will have the same behaviour as
its libc counterparts except that they'll yield a current fiber instead
of blocking a current thread.

So spin in a loop looks more as fio.c work for me, however I would not
say that I have a strict vision here.

If you'll decide to keep this logic in coio, at least left comments in
the header file for those functions.

> 
> Fixes #4651
> 
> Signed-off-by: Cyrill Gorcunov <gorcunov@gmail.com>
> ---
> branch gorcunov/gh-4651-partial-write-3

> +	struct errinj *inj = errinj(ERRINJ_COIO_WRITE_CHUNK, ERRINJ_INT);
> <...>
> +
> +		if (inj != NULL && inj->iparam > 0)
> +			chunk = (ssize_t)inj->iparam;
> +		else
> +			chunk = left;
> +

AFAIR, we have macros for error injections, which allow to avoid
producing any extra machine code for a release build. It seems they can
be used here. Can you elaborate, please?

Nit: We usually split a cast operator from its argument with a
whitespace, e.g. `(ssize_t) inj->iparam`.

>  
>  ssize_t
> @@ -200,7 +221,12 @@ coio_preadn(int fd, void *buf, size_t count, off_t offset)
>  static void
>  coio_do_write(eio_req *req)
>  {
> +	struct errinj *inj = errinj(ERRINJ_COIO_WRITE_CHUNK, ERRINJ_INT);
>  	struct coio_file_task *eio = (struct coio_file_task *)req->data;
> +
> +	if (inj != NULL && inj->iparam > 0)
> +		eio->write.count = (ssize_t)inj->iparam;
> +

(If you'll keep the loop in coio.) I would do it in coio_write() itself:
this way the logic in coio_pwrite() and coio_write() will be quite
similar and so easier to verify if I don't miss something.

Just wonder: is there any reason to use eio_custom() for write? pwrite
uses eio_write(), which support both write and pwrite (it checks whether
the offset >= 0.

I tracked it down to:

commit b9616280533a6bba1bf4739ce11a57461262560f
Author: Dmitry E. Oboukhov <unera@debian.org>
Date:   Mon Aug 11 20:03:30 2014 +0400

    fiber-io tests.

And it is the whole description.

Anyway, I just curious.

>  	req->result = write(eio->write.fd, eio->write.buf, eio->write.count);
>  	eio->errorno = errno;
>  }
> @@ -208,13 +234,28 @@ coio_do_write(eio_req *req)
>  ssize_t
>  coio_write(int fd, const void *buf, size_t count)
>  {
> -	INIT_COEIO_FILE(eio);
> -	eio.write.buf = buf;
> -	eio.write.count = count;
> -	eio.write.fd = fd;
> -	eio_req *req = eio_custom(coio_do_write, 0,
> -				  coio_complete, &eio);
> -	return coio_wait_done(req, &eio);
> +	ssize_t left = count, pos = 0, res;
> +	eio_req *req;
> +
> +	while (left > 0) {
> +		INIT_COEIO_FILE(eio);
> +
> +		eio.write.buf	= buf + pos;
> +		eio.write.count	= left;
> +		eio.write.fd	= fd;
> +
> +		req = eio_custom(coio_do_write, EIO_PRI_DEFAULT,
> +				 coio_complete, &eio);
> +		res = coio_wait_done(req, &eio);
> +		if (res < 0) {
> +			pos = -1;
> +			break;
> +		} else {
> +			left -= res;
> +			pos += res;
> +		}
> +	}
> +	return pos;
>  }

> diff --git a/test/box/errinj.result b/test/box/errinj.result
> index a148346e8..9b831769f 100644
> --- a/test/box/errinj.result
> +++ b/test/box/errinj.result
> @@ -25,6 +25,8 @@ errinj.info()
>      state: 0
>    ERRINJ_WAL_WRITE:
>      state: false
> +  ERRINJ_COIO_WRITE_CHUNK:
> +    state: -1

Now it should be rebased on top of master, this hunk was changed.

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

* Re: [Tarantool-patches] [PATCH v3] fio/coio: Handle partial writes
  2020-02-20 22:10   ` Alexander Turenko
@ 2020-02-21 12:02     ` Cyrill Gorcunov
  2020-02-21 14:48       ` Alexander Turenko
  0 siblings, 1 reply; 8+ messages in thread
From: Cyrill Gorcunov @ 2020-02-21 12:02 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tml

On Fri, Feb 21, 2020 at 01:10:26AM +0300, Alexander Turenko wrote:
> I have three comments and all are up to you.
> 
> Please, rebase the patch on top of the current master (errinj.result was
> changed).

OK, will do.

> On Wed, Dec 04, 2019 at 05:35:11PM +0300, Cyrill Gorcunov wrote:
> > Writting less bytes than requested is fine. In turn our
> > fio.write/pwrite api simply returns 'true' even if only
> > some part of a buffer has been written. Thus make coio_write
> > and coio_pwrite to write the whole data in a cycle.
> 
> I would expect that coio_*() functions will have the same behaviour as
> its libc counterparts except that they'll yield a current fiber instead
> of blocking a current thread.

They do yeild current fiber until completion notification arrives.
coio_wait_done does exactly that.

> 
> So spin in a loop looks more as fio.c work for me, however I would not
> say that I have a strict vision here.
> 
> If you'll decide to keep this logic in coio, at least left comments in
> the header file for those functions.

I'm not sure I follow you here. Our all coio_ helpers do the same thing:
they yield a current fiber and wait for completion.

> > 
> > Fixes #4651
> > 
> > Signed-off-by: Cyrill Gorcunov <gorcunov@gmail.com>
> > ---
> > branch gorcunov/gh-4651-partial-write-3
> 
> > +	struct errinj *inj = errinj(ERRINJ_COIO_WRITE_CHUNK, ERRINJ_INT);
> > <...>
> > +
> > +		if (inj != NULL && inj->iparam > 0)
> > +			chunk = (ssize_t)inj->iparam;
> > +		else
> > +			chunk = left;
> > +
> 
> AFAIR, we have macros for error injections, which allow to avoid
> producing any extra machine code for a release build. It seems they can
> be used here. Can you elaborate, please?

I think I didn't use them to follow current inj code used
in this file, for unification sake. But indeed maybe macors
will look better. Will take a look.

> 
> Nit: We usually split a cast operator from its argument with a
> whitespace, e.g. `(ssize_t) inj->iparam`.

No we don't, there are number of examples where we put cast
right before operand and I think it is a way more correct
because code reader should consider such cast as signle entry
together with operator itself. So I prefer this style.

> >  ssize_t
> > @@ -200,7 +221,12 @@ coio_preadn(int fd, void *buf, size_t count, off_t offset)
> >  static void
> >  coio_do_write(eio_req *req)
> >  {
> > +	struct errinj *inj = errinj(ERRINJ_COIO_WRITE_CHUNK, ERRINJ_INT);
> >  	struct coio_file_task *eio = (struct coio_file_task *)req->data;
> > +
> > +	if (inj != NULL && inj->iparam > 0)
> > +		eio->write.count = (ssize_t)inj->iparam;
> > +
> 
> (If you'll keep the loop in coio.) I would do it in coio_write() itself:
> this way the logic in coio_pwrite() and coio_write() will be quite
> similar and so easier to verify if I don't miss something.

I've no objection here, can move into coio_write.

> 
> Just wonder: is there any reason to use eio_custom() for write? pwrite
> uses eio_write(), which support both write and pwrite (it checks whether
> the offset >= 0.

Sounds reasonable. Letme recheck everything again and resend the patch.

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

* Re: [Tarantool-patches] [PATCH v3] fio/coio: Handle partial writes
  2020-02-21 12:02     ` Cyrill Gorcunov
@ 2020-02-21 14:48       ` Alexander Turenko
  0 siblings, 0 replies; 8+ messages in thread
From: Alexander Turenko @ 2020-02-21 14:48 UTC (permalink / raw)
  To: Cyrill Gorcunov; +Cc: tml

> > On Wed, Dec 04, 2019 at 05:35:11PM +0300, Cyrill Gorcunov wrote:
> > > Writting less bytes than requested is fine. In turn our
> > > fio.write/pwrite api simply returns 'true' even if only
> > > some part of a buffer has been written. Thus make coio_write
> > > and coio_pwrite to write the whole data in a cycle.
> > 
> > I would expect that coio_*() functions will have the same behaviour as
> > its libc counterparts except that they'll yield a current fiber instead
> > of blocking a current thread.
> 
> They do yeild current fiber until completion notification arrives.
> coio_wait_done does exactly that.

I meant handling of a partial write: whether a function should
block/yield or report amount of written bytes to a caller.

So, my points here are the following:

* coio_write() should reflect write() in handling partial writes;
* fio lua module should handle partial writes on its own.

There is one interesting thing I found:

We have src/lib/core/fio.[ch], which handles partial writes, but
src/lua/fio.[ch] (Lua module) does not use it. It looks unusual.

> 
> > > 
> > > Fixes #4651
> > > 
> > > Signed-off-by: Cyrill Gorcunov <gorcunov@gmail.com>
> > > ---
> > > branch gorcunov/gh-4651-partial-write-3
> > 
> > > +	struct errinj *inj = errinj(ERRINJ_COIO_WRITE_CHUNK, ERRINJ_INT);
> > > <...>
> > > +
> > > +		if (inj != NULL && inj->iparam > 0)
> > > +			chunk = (ssize_t)inj->iparam;
> > > +		else
> > > +			chunk = left;
> > > +
> > 
> > AFAIR, we have macros for error injections, which allow to avoid
> > producing any extra machine code for a release build. It seems they can
> > be used here. Can you elaborate, please?
> 
> I think I didn't use them to follow current inj code used
> in this file, for unification sake. But indeed maybe macors
> will look better. Will take a look.
> 
> > 
> > Nit: We usually split a cast operator from its argument with a
> > whitespace, e.g. `(ssize_t) inj->iparam`.
> 
> No we don't, there are number of examples where we put cast
> right before operand and I think it is a way more correct
> because code reader should consider such cast as signle entry
> together with operator itself. So I prefer this style.

You're right.

1. `man 1 indent` shows -ncs (--no-space-after-casts) in the Linux style
   description.
2. It is also copy-pasted to [1] without -npsl
   (--dont-break-procedure-type).
3. Vladimir D. uses it in this way in vinyl code.
4. The type cast operation has the same priority as other unary
   operations.

Don't know why I was sure that a space should be here.

[1]: https://github.com/tarantool/tarantool/wiki/Code-review-procedure-guidelines---checklist

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

end of thread, other threads:[~2020-02-21 14:48 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-12-04 11:17 [Tarantool-patches] [PATCH v2] fio/coio: Handle partial writes Cyrill Gorcunov
2019-12-04 11:25 ` Cyrill Gorcunov
2019-12-04 11:34 ` Konstantin Osipov
2019-12-04 11:48   ` Cyrill Gorcunov
2019-12-04 14:35 ` [Tarantool-patches] [PATCH v3] " Cyrill Gorcunov
2020-02-20 22:10   ` Alexander Turenko
2020-02-21 12:02     ` Cyrill Gorcunov
2020-02-21 14:48       ` Alexander Turenko

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