Skip to content

Commit 92aaf70

Browse files
klondiDvdGiessen
authored andcommitted
py/modio: Handle partial writes on BufferedWriter.
To simplify the logic create bufwriter_do_write. This function keeps track of the data left on self->len and, if a partial write happens, this function uses memmove to move the remaining data back to the beginning of the buffer. This allows simplifying bufwriter_write and bufwriter_flush significantly. bufwriter_flush now only needs to call this function if the buffer has some data stored and check for any error codes. Additionally, if the buffer is only partially flushed we notify the user too (so that they know there might be data left). bufwriter_write now just needs to call this function whenever the buffer gets full and copy the input into the buffer. It will return when either no data is written at all or when all of the input is consumed. In the case of a partial write it returns exactly the amount of data which was written. Additionally allow caching of errors to better handle partial writes. Until now if an error occurred during the write, the error would be raised and the caller had no way to know if any data was written at all (for example in prior calls if more than one block of data was passed as input). Now when we have written out some data and an error happens, we reset the buffer to the state it would have if it did not contain the data that was not written (and which was not buffered previously), and then, we return the data that was written (if any) or raise an error if no data from the input was written. This allows the programmer better control of writes. In particular, the programmer will know exactly how much of its last input data was written, consequently allowing it to handle whatever data left to be written in a better way. Signed-off-by: Francisco Blas (klondike) Izquierdo Riera <klondike@klondike.es>
1 parent 883dc41 commit 92aaf70

File tree

1 file changed

+69
-30
lines changed

1 file changed

+69
-30
lines changed

py/modio.c

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#include <assert.h>
2828
#include <string.h>
2929

30+
#include "py/mpconfig.h"
31+
#include "py/misc.h"
3032
#include "py/runtime.h"
3133
#include "py/builtin.h"
3234
#include "py/stream.h"
@@ -113,6 +115,7 @@ typedef struct _mp_obj_bufwriter_t {
113115
mp_obj_t stream;
114116
size_t alloc;
115117
size_t len;
118+
int error;
116119
byte buf[0];
117120
} mp_obj_bufwriter_t;
118121

@@ -123,61 +126,97 @@ static mp_obj_t bufwriter_make_new(const mp_obj_type_t *type, size_t n_args, siz
123126
o->stream = args[0];
124127
o->alloc = alloc;
125128
o->len = 0;
129+
o->error = 0;
126130
return o;
127131
}
128132

133+
// Writes out the data stored in the buffer so far
134+
static int bufwriter_do_write(mp_obj_bufwriter_t *self) {
135+
int rv = 0;
136+
// This cannot return 0 without an error
137+
mp_uint_t out_sz = mp_stream_write_exactly(self->stream, self->buf, self->len, &rv);
138+
self->len -= out_sz;
139+
// Copy the non written characters back to the beginning
140+
if (self->len != 0) {
141+
// Use memmove since there might be overlaps
142+
memmove(self->buf, self->buf + out_sz, self->len);
143+
}
144+
return rv;
145+
}
146+
129147
static mp_uint_t bufwriter_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) {
130148
mp_obj_bufwriter_t *self = MP_OBJ_TO_PTR(self_in);
131149

132150
mp_uint_t org_size = size;
151+
// Alloc should always remain the same so cache it.
152+
size_t alloc = self->alloc;
153+
mp_uint_t rem = 0; // No data has been copied from the buffer so far
133154

134-
while (size > 0) {
135-
mp_uint_t rem = self->alloc - self->len;
136-
if (size < rem) {
137-
memcpy(self->buf + self->len, buf, size);
138-
self->len += size;
139-
return org_size;
140-
}
155+
if (self->error != 0) {
156+
*errcode = self->error;
157+
self->error = 0;
158+
return MP_STREAM_ERROR;
159+
}
141160

161+
// Using this form allows ensuring that we try to empty the buffer if it is
162+
// full without trying to extract any data first.
163+
while (true) {
142164
// Buffer flushing policy here is to flush entire buffer all the time.
143165
// This allows e.g. to have a block device as backing storage and write
144166
// entire block to it. memcpy below is not ideal and could be optimized
145167
// in some cases. But the way it is now it at least ensures that buffer
146168
// is word-aligned, to guard against obscure cases when it matters, e.g.
147169
// https://github.com/micropython/micropython/issues/1863
170+
// Try to empty the buffer first
171+
if (self->len == alloc) {
172+
if ((*errcode = bufwriter_do_write(self)) != 0) {
173+
// If this is the first write with data from the new buffer
174+
// But no data from the new buffer was written out,
175+
// then remove the data added from the new buffer and raise
176+
// the error
177+
if (org_size == size + rem && self->len >= rem) {
178+
// Remove the extra non-written data from the buffer and error
179+
self->len -= rem;
180+
return MP_STREAM_ERROR;
181+
}
182+
// Some data from the new buffer has been written rollback as much
183+
// as possible and return what was written then raise an error
184+
// on the next call.
185+
size += self->len;
186+
self->len = 0;
187+
self->error = *errcode;
188+
*errcode = 0;
189+
return org_size - size;
190+
}
191+
}
192+
// No data left to write
193+
if (size == 0) {
194+
return org_size;
195+
}
196+
197+
rem = MIN(alloc - self->len, size);
148198
memcpy(self->buf + self->len, buf, rem);
199+
self->len += rem;
149200
buf = (byte *)buf + rem;
150201
size -= rem;
151-
mp_uint_t out_sz = mp_stream_write_exactly(self->stream, self->buf, self->alloc, errcode);
152-
(void)out_sz;
153-
if (*errcode != 0) {
154-
return MP_STREAM_ERROR;
155-
}
156-
// TODO: try to recover from a case of non-blocking stream, e.g. move
157-
// remaining chunk to the beginning of buffer.
158-
assert(out_sz == self->alloc);
159-
self->len = 0;
160202
}
161-
162-
return org_size;
163203
}
164204

165205
static mp_obj_t bufwriter_flush(mp_obj_t self_in) {
166206
mp_obj_bufwriter_t *self = MP_OBJ_TO_PTR(self_in);
167-
168-
if (self->len != 0) {
169-
int err;
170-
mp_uint_t out_sz = mp_stream_write_exactly(self->stream, self->buf, self->len, &err);
171-
(void)out_sz;
172-
// TODO: try to recover from a case of non-blocking stream, e.g. move
173-
// remaining chunk to the beginning of buffer.
174-
assert(out_sz == self->len);
175-
self->len = 0;
176-
if (err != 0) {
177-
mp_raise_OSError(err);
207+
int err = self->error;
208+
if (err == 0 && self->len != 0) {
209+
err = bufwriter_do_write(self);
210+
// If we couldn't flush the whole buffer notify the user.
211+
if (err == 0 && self->len != 0) {
212+
err = MP_EAGAIN;
178213
}
179214
}
180-
215+
// If there is an error raise it
216+
if (err != 0) {
217+
self->error = 0;
218+
mp_raise_OSError(err);
219+
}
181220
return mp_const_none;
182221
}
183222
static MP_DEFINE_CONST_FUN_OBJ_1(bufwriter_flush_obj, bufwriter_flush);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy