From 20ef29913eb7b029b69b854d620c5bebef586e2d Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Wed, 15 Nov 2023 20:28:46 -0600 Subject: [PATCH] Progress: Further work in TacocaT. The send f_socket_write_stream() is failing. Probably due to some incomplete step. Take advantage of this and implement the retry logic and error handling. --- sources/c/tacocat/main/common/define.h | 12 +++++----- sources/c/tacocat/main/print/error.c | 38 ++++++++++++++++++++++++------ sources/c/tacocat/main/print/error.h | 42 ++++++++++++++++++++++++++++------ sources/c/tacocat/main/receive.c | 16 ++++++------- sources/c/tacocat/main/send.c | 41 ++++++++++++++++++++++++--------- 5 files changed, 110 insertions(+), 39 deletions(-) diff --git a/sources/c/tacocat/main/common/define.h b/sources/c/tacocat/main/common/define.h index 7037f7a..df04868 100644 --- a/sources/c/tacocat/main/common/define.h +++ b/sources/c/tacocat/main/common/define.h @@ -158,9 +158,9 @@ extern "C" { continue; \ } - #define macro_kt_receive_process_handle_error_exit_1(main, method, name, status, flag, id_data) \ + #define macro_kt_receive_process_handle_error_exit_1(main, method, network, status, name, flag, id_data) \ if (F_status_is_error(status)) { \ - kt_tacocat_print_error_on(&main->program.error, macro_kt_tacocat_f(method), kt_tacocat_receive_s, name, status); \ + kt_tacocat_print_error_on(&main->program.error, macro_kt_tacocat_f(method), kt_tacocat_receive_s, network, status, name); \ \ if (id_data) { \ f_file_close_id(id_data); \ @@ -171,18 +171,18 @@ extern "C" { return; \ } - #define macro_kt_receive_process_begin_handle_error_exit_1(main, method, name, status, flag) \ + #define macro_kt_receive_process_begin_handle_error_exit_1(main, method, network, status, name, flag) \ if (F_status_is_error(status)) { \ - kt_tacocat_print_error_on(&main->program.error, macro_kt_tacocat_f(method), kt_tacocat_receive_s, name, status); \ + kt_tacocat_print_error_on(&main->program.error, macro_kt_tacocat_f(method), kt_tacocat_receive_s, network, status, name); \ \ flag = 0; \ \ return; \ } - #define macro_kt_send_process_handle_error_exit_1(main, method, name, status, flag) \ + #define macro_kt_send_process_handle_error_exit_1(main, method, network, status, name, flag) \ if (F_status_is_error(status)) { \ - kt_tacocat_print_error_on(&main->program.error, macro_kt_tacocat_f(method), kt_tacocat_send_s, name, status); \ + kt_tacocat_print_error_on(&main->program.error, macro_kt_tacocat_f(method), kt_tacocat_send_s, network, status, name); \ \ return F_done_not; \ } diff --git a/sources/c/tacocat/main/print/error.c b/sources/c/tacocat/main/print/error.c index d8f068b..1d05db0 100644 --- a/sources/c/tacocat/main/print/error.c +++ b/sources/c/tacocat/main/print/error.c @@ -41,7 +41,7 @@ extern "C" { #endif // _di_kt_tacocat_print_error_file_ #ifndef _di_kt_tacocat_print_error_on_ - f_status_t kt_tacocat_print_error_on(fl_print_t * const print, const f_string_t function, f_string_static_t on, const f_string_static_t network, const f_status_t status) { + f_status_t kt_tacocat_print_error_on(fl_print_t * const print, const f_string_t function, const f_string_static_t on, const f_string_static_t network, const f_status_t status, const f_string_static_t name) { if (!print) return F_status_set_error(F_output_not); if (print->verbosity < f_console_verbosity_error_e) return F_output_not; @@ -52,6 +52,8 @@ extern "C" { fl_print_format(f_string_format_Q_single_s.string, print->to, print->set->notable, on, print->set->notable); fl_print_format(" %[for '%]", print->to, print->set->error, print->set->error, f_string_eol_s); fl_print_format(f_string_format_Q_single_s.string, print->to, print->set->notable, network, print->set->notable); + fl_print_format("%[' with file '%]", print->to, print->set->error, print->set->error, f_string_eol_s); + fl_print_format(f_string_format_Q_single_s.string, print->to, print->set->notable, name, print->set->notable); fl_print_format("%['.%]%r", print->to, print->set->error, print->set->error, f_string_eol_s); f_file_stream_unlock(print->to); @@ -63,7 +65,7 @@ extern "C" { #endif // _di_kt_tacocat_print_error_ #ifndef _di_kt_tacocat_print_error_on_buffer_too_large_ - f_status_t kt_tacocat_print_error_on_buffer_too_large(fl_print_t * const print, f_string_static_t on, const f_string_static_t network, const f_number_unsigned_t size_expect, const f_number_unsigned_t size_got) { + f_status_t kt_tacocat_print_error_on_buffer_too_large(fl_print_t * const print, const f_string_static_t on, const f_string_static_t network, const f_number_unsigned_t size_expect, const f_number_unsigned_t size_got) { if (!print) return F_status_set_error(F_output_not); if (print->verbosity < f_console_verbosity_error_e) return F_output_not; @@ -87,7 +89,7 @@ extern "C" { #endif // _di_kt_tacocat_print_error_on_buffer_too_large_ #ifndef _di_kt_tacocat_print_error_on_file_too_large_ - f_status_t kt_tacocat_print_error_on_file_too_large(fl_print_t * const print, f_string_static_t file, f_string_static_t on, const f_string_static_t network, const f_number_unsigned_t size_expect, const f_number_unsigned_t size_got) { + f_status_t kt_tacocat_print_error_on_file_too_large(fl_print_t * const print, f_string_static_t file, const f_string_static_t on, const f_string_static_t network, const f_number_unsigned_t size_expect, const f_number_unsigned_t size_got) { if (!print) return F_status_set_error(F_output_not); if (print->verbosity < f_console_verbosity_error_e) return F_output_not; @@ -113,7 +115,7 @@ extern "C" { #endif // _di_kt_tacocat_print_error_on_file_too_large_ #ifndef _di_kt_tacocat_print_error_on_busy_ - f_status_t kt_tacocat_print_error_on_busy(fl_print_t * const print, f_string_static_t on, const f_string_static_t network) { + f_status_t kt_tacocat_print_error_on_busy(fl_print_t * const print, const f_string_static_t on, const f_string_static_t network) { if (!print) return F_status_set_error(F_output_not); if (print->verbosity < f_console_verbosity_error_e) return F_output_not; @@ -133,7 +135,7 @@ extern "C" { #endif // _di_kt_tacocat_print_error_on_busy_ #ifndef _di_kt_tacocat_print_error_on_file_receive_ - f_status_t kt_tacocat_print_error_on_file_receive(fl_print_t * const print, const f_string_t function, f_string_static_t on, const f_string_static_t network, const f_status_t status, const f_string_static_t name, const f_string_static_t operation) { + f_status_t kt_tacocat_print_error_on_file_receive(fl_print_t * const print, const f_string_t function, const f_string_static_t on, const f_string_static_t network, const f_status_t status, const f_string_static_t name, const f_string_static_t operation) { if (!print || !print->custom) return F_status_set_error(F_output_not); if (print->verbosity < f_console_verbosity_error_e) return F_output_not; @@ -155,7 +157,7 @@ extern "C" { #endif // _di_kt_tacocat_print_error_on_file_receive_ #ifndef _di_kt_tacocat_print_error_on_file_send_ - f_status_t kt_tacocat_print_error_on_file_send(fl_print_t * const print, const f_string_t function, f_string_static_t on, const f_string_static_t network, const f_status_t status, const f_string_static_t name, const f_string_static_t operation) { + f_status_t kt_tacocat_print_error_on_file_send(fl_print_t * const print, const f_string_t function, const f_string_static_t on, const f_string_static_t network, const f_status_t status, const f_string_static_t name, const f_string_static_t operation) { if (!print || !print->custom) return F_status_set_error(F_output_not); if (print->verbosity < f_console_verbosity_error_e) return F_output_not; @@ -176,8 +178,30 @@ extern "C" { } #endif // _di_kt_tacocat_print_error_on_file_send_ +#ifndef _di_kt_tacocat_print_error_on_max_retries_ + f_status_t kt_tacocat_print_error_on_max_retries(fl_print_t * const print, const f_string_static_t on, const f_string_static_t network, const f_string_static_t name) { + + if (!print) return F_status_set_error(F_output_not); + if (print->verbosity < f_console_verbosity_error_e) return F_output_not; + + f_file_stream_lock(print->to); + + fl_print_format("%[%QMax retry on failure reached while trying to%] ", print->to, print->set->error, print->prefix, print->set->error); + fl_print_format(f_string_format_Q_single_s.string, print->to, print->set->notable, on, print->set->notable); + fl_print_format(" %[for '%]", print->to, print->set->error, print->set->error, f_string_eol_s); + fl_print_format(f_string_format_Q_single_s.string, print->to, print->set->notable, network, print->set->notable); + fl_print_format("%[' with file '%]", print->to, print->set->error, print->set->error, f_string_eol_s); + fl_print_format(f_string_format_Q_single_s.string, print->to, print->set->notable, name, print->set->notable); + fl_print_format("%['.%]%r", print->to, print->set->error, print->set->error, f_string_eol_s); + + f_file_stream_unlock(print->to); + + return F_okay; + } +#endif // _di_kt_tacocat_print_error_on_max_retries_ + #ifndef _di_kt_tacocat_print_error_on_packet_too_small_ - f_status_t kt_tacocat_print_error_on_packet_too_small(fl_print_t * const print, f_string_static_t on, const f_string_static_t network, const f_number_unsigned_t size_expect, const f_number_unsigned_t size_got) { + f_status_t kt_tacocat_print_error_on_packet_too_small(fl_print_t * const print, const f_string_static_t on, const f_string_static_t network, const f_number_unsigned_t size_expect, const f_number_unsigned_t size_got) { if (!print) return F_status_set_error(F_output_not); if (print->verbosity < f_console_verbosity_error_e) return F_output_not; diff --git a/sources/c/tacocat/main/print/error.h b/sources/c/tacocat/main/print/error.h index bdd5244..373b7b8 100644 --- a/sources/c/tacocat/main/print/error.h +++ b/sources/c/tacocat/main/print/error.h @@ -121,7 +121,7 @@ extern "C" { * @see fll_error_file_print() */ #ifndef _di_kt_tacocat_print_error_on_ - extern f_status_t kt_tacocat_print_error_on(fl_print_t * const print, const f_string_t function, f_string_static_t on, const f_string_static_t network, const f_status_t status); + extern f_status_t kt_tacocat_print_error_on(fl_print_t * const print, const f_string_t function, const f_string_static_t on, const f_string_static_t network, const f_status_t status, const f_string_static_t name); #endif // _di_kt_tacocat_print_error_on_ /** @@ -149,7 +149,7 @@ extern "C" { * @see fll_error_file_print() */ #ifndef _di_kt_tacocat_print_error_on_buffer_too_large_ - extern f_status_t kt_tacocat_print_error_on_buffer_too_large(fl_print_t * const print, f_string_static_t on, const f_string_static_t network, const f_number_unsigned_t size_expect, const f_number_unsigned_t size_got); + extern f_status_t kt_tacocat_print_error_on_buffer_too_large(fl_print_t * const print, const f_string_static_t on, const f_string_static_t network, const f_number_unsigned_t size_expect, const f_number_unsigned_t size_got); #endif // _di_kt_tacocat_print_error_on_buffer_too_large_ /** @@ -179,7 +179,7 @@ extern "C" { * @see fll_error_file_print() */ #ifndef _di_kt_tacocat_print_error_on_file_too_large_ - extern f_status_t kt_tacocat_print_error_on_file_too_large(fl_print_t * const print, f_string_static_t file, f_string_static_t on, const f_string_static_t network, const f_number_unsigned_t size_expect, const f_number_unsigned_t size_got); + extern f_status_t kt_tacocat_print_error_on_file_too_large(fl_print_t * const print, f_string_static_t file, const f_string_static_t on, const f_string_static_t network, const f_number_unsigned_t size_expect, const f_number_unsigned_t size_got); #endif // _di_kt_tacocat_print_error_on_file_too_large_ /** @@ -203,7 +203,7 @@ extern "C" { * @see fll_error_file_print() */ #ifndef _di_kt_tacocat_print_error_on_busy_ - extern f_status_t kt_tacocat_print_error_on_busy(fl_print_t * const print, f_string_static_t on, const f_string_static_t network); + extern f_status_t kt_tacocat_print_error_on_busy(fl_print_t * const print, const f_string_static_t on, const f_string_static_t network); #endif // _di_kt_tacocat_print_error_on_busy_ /** @@ -236,7 +236,7 @@ extern "C" { * @see fll_error_file_print() */ #ifndef _di_kt_tacocat_print_error_on_file_receive_ - extern f_status_t kt_tacocat_print_error_on_file_receive(fl_print_t * const print, const f_string_t function, f_string_static_t on, const f_string_static_t network, const f_status_t status, const f_string_static_t name, const f_string_static_t operation); + extern f_status_t kt_tacocat_print_error_on_file_receive(fl_print_t * const print, const f_string_t function, const f_string_static_t on, const f_string_static_t network, const f_status_t status, const f_string_static_t name, const f_string_static_t operation); #endif // _di_kt_tacocat_print_error_on_file_receive_ /** @@ -269,10 +269,38 @@ extern "C" { * @see fll_error_file_print() */ #ifndef _di_kt_tacocat_print_error_on_file_send_ - extern f_status_t kt_tacocat_print_error_on_file_send(fl_print_t * const print, const f_string_t function, f_string_static_t on, const f_string_static_t network, const f_status_t status, const f_string_static_t name, const f_string_static_t operation); + extern f_status_t kt_tacocat_print_error_on_file_send(fl_print_t * const print, const f_string_t function, const f_string_static_t on, const f_string_static_t network, const f_status_t status, const f_string_static_t name, const f_string_static_t operation); #endif // _di_kt_tacocat_print_error_on_file_send_ /** + * Print error message regarding maximum retries after error reached. + * + * This could be on any error, such as errors on file load, memory, access, or network failures. + * + * @param print + * The output structure to print to. + * + * This does not alter print.custom.setting.state.status. + * @param on + * The network connection direction, which should either be "receive" or "send". + * @param network + * The name of the network in which the error is related. + * @param name + * Th name of the file. + * + * @return + * F_okay on success. + * F_output_not on success, but no printing is performed. + * + * F_output_not (with error bit) if setting is NULL. + * + * @see fll_error_file_print() + */ +#ifndef _di_kt_tacocat_print_error_on_max_retries_ + extern f_status_t kt_tacocat_print_error_on_max_retries(fl_print_t * const print, const f_string_static_t on, const f_string_static_t network, const f_string_static_t name); +#endif // _di_kt_tacocat_print_error_on_max_retries_ + +/** * Print network-related error message for when the connection is busy. * * @param print @@ -297,7 +325,7 @@ extern "C" { * @see fll_error_file_print() */ #ifndef _di_kt_tacocat_print_error_on_packet_too_small_ - extern f_status_t kt_tacocat_print_error_on_packet_too_small(fl_print_t * const print, f_string_static_t on, const f_string_static_t network, const f_number_unsigned_t size_expect, const f_number_unsigned_t size_got); + extern f_status_t kt_tacocat_print_error_on_packet_too_small(fl_print_t * const print, const f_string_static_t on, const f_string_static_t network, const f_number_unsigned_t size_expect, const f_number_unsigned_t size_got); #endif // _di_kt_tacocat_print_error_on_packet_too_small_ /** diff --git a/sources/c/tacocat/main/receive.c b/sources/c/tacocat/main/receive.c index a75e1a2..ea44280 100644 --- a/sources/c/tacocat/main/receive.c +++ b/sources/c/tacocat/main/receive.c @@ -102,14 +102,14 @@ extern "C" { // Make sure the buffer is large enough for payload processing block reads. set->status = f_memory_array_increase_by(set->socket.size_read, sizeof(f_char_t), (void **) &set->buffer.string, &set->buffer.used, &set->buffer.size); - macro_kt_receive_process_handle_error_exit_1(main, f_memory_array_increase_by, set->network, set->status, set->flag, &set->socket.id_data); + macro_kt_receive_process_handle_error_exit_1(main, f_memory_array_increase_by, set->network, set->status, set->name, set->flag, &set->socket.id_data); } if (set->flag & kt_tacocat_socket_flag_receive_block_payload_e) { size_t length_read = 0; set->status = f_socket_read_stream(&set->socket, 0, (void *) set->buffer.string, &length_read); - macro_kt_receive_process_handle_error_exit_1(main, f_socket_read_stream, set->network, set->status, set->flag, &set->socket.id_data); + macro_kt_receive_process_handle_error_exit_1(main, f_socket_read_stream, set->network, set->status, set->name, set->flag, &set->socket.id_data); if (length_read) { set->buffer.used = length_read; @@ -145,7 +145,7 @@ extern "C" { // Report the resize error but do not fail. if (F_status_is_error(set->status)) { - kt_tacocat_print_error_on(&main->program.error, macro_kt_tacocat_f(f_memory_array_resize), kt_tacocat_receive_s, set->network, set->status); + kt_tacocat_print_error_on(&main->program.error, macro_kt_tacocat_f(f_memory_array_resize), kt_tacocat_receive_s, set->network, set->status, set->name); } } } @@ -162,10 +162,10 @@ extern "C" { set->status = f_socket_accept(&set->socket); // The socket failed to accept and so there is no data socket id to close. - macro_kt_receive_process_begin_handle_error_exit_1(main, f_socket_accept, set->network, set->status, set->flag); + macro_kt_receive_process_begin_handle_error_exit_1(main, f_socket_accept, set->network, set->status, set->name, set->flag); set->status = f_memory_array_increase_by(kt_tacocat_packet_peek_d + 1, sizeof(f_char_t), (void **) &set->buffer.string, &set->buffer.used, &set->buffer.size); - macro_kt_receive_process_begin_handle_error_exit_1(main, f_memory_array_increase_by, set->network, set->status, set->flag); + macro_kt_receive_process_begin_handle_error_exit_1(main, f_memory_array_increase_by, set->network, set->status, set->name, set->flag); set->socket.size_read = kt_tacocat_packet_peek_d; @@ -173,7 +173,7 @@ extern "C" { set->socket.size_read = size_read; - macro_kt_receive_process_begin_handle_error_exit_1(main, f_socket_read_stream, set->network, set->status, set->flag); + macro_kt_receive_process_begin_handle_error_exit_1(main, f_socket_read_stream, set->network, set->status, set->name, set->flag); set->buffer.used += length_read; @@ -223,7 +223,7 @@ extern "C" { } set->status = f_fss_simple_packet_extract_range(set->buffer, &set->packet); - macro_kt_receive_process_begin_handle_error_exit_1(main, f_fss_simple_packet_extract_range, set->network, set->status, set->flag); + macro_kt_receive_process_begin_handle_error_exit_1(main, f_fss_simple_packet_extract_range, set->network, set->status, set->name, set->flag); if (set->status == F_packet_too_small || set->packet.size < kt_tacocat_packet_minimum_d) { kt_tacocat_print_error_on_packet_too_small(&main->program.error, kt_tacocat_receive_s, set->network, kt_tacocat_packet_peek_d, set->buffer.used); @@ -246,7 +246,7 @@ extern "C" { // Report the resize error but do not fail. if (F_status_is_error(set->status)) { - kt_tacocat_print_error_on(&main->program.error, macro_kt_tacocat_f(f_memory_array_resize), kt_tacocat_receive_s, set->network, set->status); + kt_tacocat_print_error_on(&main->program.error, macro_kt_tacocat_f(f_memory_array_resize), kt_tacocat_receive_s, set->network, set->status, set->name); } } diff --git a/sources/c/tacocat/main/send.c b/sources/c/tacocat/main/send.c index 10002de..e082d5c 100644 --- a/sources/c/tacocat/main/send.c +++ b/sources/c/tacocat/main/send.c @@ -29,21 +29,28 @@ extern "C" { return 0; } - if (main->setting.send.array[i].status == F_done) { - if (ready != F_done_not) { + if (F_status_set_fine(main->setting.send.array[i].status) == F_done) { + if (F_status_is_error(main->setting.send.array[i].status) || ready != F_done_not) { ready = F_done; } } else { // @todo the kt_tacocat_receive_process() and kt_tacocat_send_process() have different return designs, figure out which design to use and be consistent. // @todo in all cases error or success, when done be sure set->file is closed. + // @todo on error during partial transfer either attempt to resend, attempt to send failure packet, or abandon. if (kt_tacocat_send_process(main, &main->setting.send.array[i]) == F_done) { - if (ready != F_done_not) { + if (F_status_is_error(main->setting.send.array[i].status)) { + ++main->setting.send.array[i].retry; + } + else if (ready != F_done_not) { ready = F_done; } } else { - // @todo on error during partial transfer either attempt to resend, attempt to send failure packet, or abandon. + if (F_status_is_error(main->setting.send.array[i].status)) { + ++main->setting.send.array[i].retry; + } + ready = F_done_not; } } @@ -94,7 +101,7 @@ extern "C" { } if (F_status_is_error(set->status)) { - macro_kt_send_process_handle_error_exit_1(main, f_memory_array_increase_by, set->network, set->status, set->flag); + macro_kt_send_process_handle_error_exit_1(main, f_memory_array_increase_by, set->network, set->status, set->name, set->flag); } // Index 0 is the status. @@ -138,6 +145,18 @@ extern "C" { set->flag = kt_tacocat_socket_flag_send_size_e; } + if (set->retry >= kt_tacocat_startup_retry_max_d) { + f_file_close(&set->file); + f_socket_disconnect(&set->socket, f_socket_close_write_e); + + // Keep error bit but set state to done to designate that nothing else is to be done. + set->status = F_status_set_error(F_done); + + kt_tacocat_print_error_on_max_retries(&main->program.error, kt_tacocat_send_s, set->network, set->name); + + return F_done; + } + if (set->flag == kt_tacocat_socket_flag_send_size_e) { // Total is used here to explicitly pass a pointer of off_t rather than a pointer of size_t cast to an off_t. @@ -189,7 +208,7 @@ extern "C" { } if (F_status_is_error(set->status)) { - macro_kt_send_process_handle_error_exit_1(main, f_file_read_block, set->network, set->status, set->flag); + macro_kt_send_process_handle_error_exit_1(main, f_file_read_block, set->network, set->status, set->name, set->flag); } set->abstruses.array[2].value.is.a_unsigned = set->buffer.used - f_fss_payload_object_payload_s.used - f_fss_payload_object_end_s.used; @@ -205,7 +224,7 @@ extern "C" { state_local.data = &set->write_state; fl_fss_payload_header_map(set->abstruses, &set->headers, &state_local); - macro_kt_send_process_handle_error_exit_1(main, fl_fss_payload_header_map, set->network, state_local.status, set->flag); + macro_kt_send_process_handle_error_exit_1(main, fl_fss_payload_header_map, set->network, state_local.status, set->name, set->flag); set->flag = kt_tacocat_socket_flag_send_combine_e; } @@ -255,7 +274,7 @@ extern "C" { } if (F_status_is_error_not(set->status)) { - macro_kt_send_process_handle_error_exit_1(main, f_string_dynamic_append, set->network, set->status, set->flag); + macro_kt_send_process_handle_error_exit_1(main, f_string_dynamic_append, set->network, set->status, set->name, set->flag); } set->header.string[set->header.used] = 0; @@ -264,7 +283,7 @@ extern "C" { if (set->flag == kt_tacocat_socket_flag_send_connect_e) { set->status = f_socket_connect(set->socket); - macro_kt_send_process_handle_error_exit_1(main, f_socket_connect, set->network, set->status, set->flag); + macro_kt_send_process_handle_error_exit_1(main, f_socket_connect, set->network, set->status, set->name, set->flag); set->flag = kt_tacocat_socket_flag_send_header_e; } @@ -273,7 +292,7 @@ extern "C" { size_t written = 0; set->status = f_socket_write_stream(&set->socket, 0, (void *) (set->header.string + set->size_done), &written); - macro_kt_send_process_handle_error_exit_1(main, f_socket_write_stream, set->network, set->status, set->flag); + macro_kt_send_process_handle_error_exit_1(main, f_socket_write_stream, set->network, set->status, set->name, set->flag); set->size_done += written; @@ -293,7 +312,7 @@ extern "C" { size_t written = 0; set->status = f_socket_write_stream(&set->socket, 0, (void *) (set->buffer.string + set->size_done), &written); - macro_kt_send_process_handle_error_exit_1(main, f_socket_write_stream, set->network, set->status, set->flag); + macro_kt_send_process_handle_error_exit_1(main, f_socket_write_stream, set->network, set->status, set->name, set->flag); set->size_done += written; -- 1.8.3.1