libc: write loop for continuous files

This patch improves the libc's write operation to iterate on partial
writes to continuous files until the original write count is reached.
The split of large write operations into small partial writes as
dictated by the VFS infrastructure (e.g., constained by I/O buffer
sizes) becomes invisible to the libc-using application.

Issue #3507
Issue #2303
This commit is contained in:
Norman Feske 2019-11-02 18:54:56 +01:00 committed by Christian Helmuth
parent 54643d6878
commit fab2fc874f

View File

@ -667,40 +667,118 @@ ssize_t Libc::Vfs_plugin::write(File_descriptor *fd, const void *buf,
} else {
Vfs::file_size const initial_seek { handle->seek() };
struct Check : Suspend_functor
{
bool retry { false };
bool retry { false };
Vfs::Vfs_handle *handle;
void const *buf;
::size_t count;
Vfs::file_size &out_count;
Result &out_result;
Vfs::File_system &_root_fs;
char const * const _fd_path;
Vfs::Vfs_handle *_handle;
void const *_buf;
::size_t _count;
::off_t _offset = 0;
Vfs::file_size &_out_count;
Result &_out_result;
unsigned _iteration = 0;
Check(Vfs::Vfs_handle *handle, void const *buf,
bool _fd_refers_to_continuous_file()
{
if (!_fd_path) {
warning("Vfs_plugin: _fd_refers_to_continuous_file: missing fd_path");
return false;
}
typedef Vfs::Directory_service::Stat_result Result;
Vfs::Directory_service::Stat stat { };
if (VFS_THREAD_SAFE(_root_fs.stat(_fd_path, stat)) != Result::STAT_OK)
return false;
return stat.type == Vfs::Node_type::CONTINUOUS_FILE;
};
Check(Vfs::File_system &root_fs, char const *fd_path,
Vfs::Vfs_handle *handle, void const *buf,
::size_t count, Vfs::file_size &out_count,
Result &out_result)
: handle(handle), buf(buf), count(count), out_count(out_count),
out_result(out_result)
:
_root_fs(root_fs), _fd_path(fd_path), _handle(handle), _buf(buf),
_count(count), _out_count(out_count), _out_result(out_result)
{ }
bool suspend() override
{
try {
out_result = VFS_THREAD_SAFE(handle->fs().write(handle, (char const *)buf,
count, out_count));
retry = false;
} catch (Vfs::File_io_service::Insufficient_buffer) {
retry = true;
}
for (;;) {
return retry;
/* number of bytes written in one iteration */
Vfs::file_size partial_out_count = 0;
try {
char const * const src = (char const *)_buf + _offset;
_out_result = VFS_THREAD_SAFE(_handle->fs().write(_handle, src,
_count, partial_out_count));
} catch (Vfs::File_io_service::Insufficient_buffer) {
retry = true;
return retry;
}
if (_out_result != Result::WRITE_OK) {
retry = false;
return retry;
}
/* increment byte count reported to caller */
_out_count += partial_out_count;
bool const write_complete = (partial_out_count == _count);
if (write_complete) {
retry = false;
return retry;
}
/*
* If the write has not consumed all bytes, set up
* another partial write iteration with the remaining
* bytes as 'count'.
*
* The costly 'fd_refers_to_continuous_file' is called
* for the first iteration only.
*/
bool const continuous_file = (_iteration > 0 || _fd_refers_to_continuous_file());
if (!continuous_file) {
warning("partial write on transactional file");
_out_result = Result::WRITE_ERR_IO;
retry = false;
return retry;
}
_iteration++;
bool const stalled = (partial_out_count == 0);
if (stalled) {
retry = true;
return retry;
}
/* issue new write operation for remaining bytes */
_count -= partial_out_count;
_offset += partial_out_count;
_handle->advance_seek(partial_out_count);
}
}
} check(handle, buf, count, out_count, out_result);
} check(_root_fs, fd->fd_path, handle, buf, count, out_count, out_result);
do {
suspend(check);
} while (check.retry);
/* XXX reset seek pointer after loop (will be advanced below by out_count) */
handle->seek(initial_seek);
}
Plugin::resume_all();