From 4c818aedc71f2d64e346ef514c1cf03b827299b3 Mon Sep 17 00:00:00 2001 From: Darren Banfi <33876110+boingball@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:45:56 +0100 Subject: [PATCH 1/9] Track IPP document upload progress --- driver/ipp_client.h | 1 + 1 file changed, 1 insertion(+) diff --git a/driver/ipp_client.h b/driver/ipp_client.h index ef55890..db8d661 100644 --- a/driver/ipp_client.h +++ b/driver/ipp_client.h @@ -9,6 +9,7 @@ struct MPIPPResult { LONG http_status; UWORD ipp_status; ULONG document_bytes; + ULONG document_bytes_sent; }; /* Opens bsdsocket.library V4 and creates a harmless unconnected socket. From 0cfaec50f86b0d123f369199f30aa90f91c273bd Mon Sep 17 00:00:00 2001 From: Darren Banfi <33876110+boingball@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:45:57 +0100 Subject: [PATCH 2/9] Allow slow printers to drain large uploads --- driver/ipp_client.c | 68 +++++++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/driver/ipp_client.c b/driver/ipp_client.c index d06b86d..2988b0b 100644 --- a/driver/ipp_client.c +++ b/driver/ipp_client.c @@ -26,7 +26,16 @@ typedef long ssize_t; * never get control back. */ #define MP_IPP_CONNECT_TIMEOUT_SECS 8 #define MP_IPP_RECV_TIMEOUT_SECS 20 -#define MP_IPP_SEND_TIMEOUT_SECS 20 +#define MP_IPP_CONTROL_SEND_TIMEOUT_SECS 20 +/* A small-buffer printer may stop draining TCP while it processes or prints + * data already received. Keep the short control timeout above for headers, + * but allow a long document to make no upload progress for up to three + * minutes before treating the connection as stalled (issue #91). */ +#define MP_IPP_DOCUMENT_SEND_TIMEOUT_SECS 180 + +#define MP_IPP_SEND_FAILED 0 +#define MP_IPP_SEND_OK 1 +#define MP_IPP_SEND_TIMEOUT -1 /* Connect with a bound on how long a dead/unreachable printer can stall the * spool Process. Modeled on src/MintPrintSettings.c's mp_connect_with_timeout, @@ -203,19 +212,20 @@ static int mp_append_ulong(char *dst, ULONG cap, ULONG *pos, ULONG value) /* send() bounded so a printer that accepts the connection and then simply * stops reading (TCP window stays full, never drains) cannot stall the - * spool Process for the rest of a large print job's transfer - only - * connect()/recv() were bounded before this. Each chunk still waits for - * write-readiness up to MP_IPP_SEND_TIMEOUT_SECS; as long as the printer - * keeps draining *some* data before each such wait expires, a job of any - * size still sends to completion - only a stalled connection (no forward - * progress within one timeout window) gives up. */ -static int mp_safe_send(int sock, const UBYTE *buf, ULONG len) + * spool Process forever. timeout_secs is deliberately caller-selected: + * control traffic remains tightly bounded, while document data gets enough + * time for a small-buffer printer to process a page and resume reading. + * sent_out records real forward progress even when a later wait/send fails. */ +static int mp_safe_send(int sock, const UBYTE *buf, ULONG len, + int timeout_secs, ULONG *sent_out) { ULONG sent_total = 0; long nonblock = 1; long block = 0; BOOL nonblocking = (IoctlSocket(sock, FIONBIO, (char *)&nonblock) == 0); + if (sent_out) *sent_out = 0; + while (sent_total < len) { ULONG left = len - sent_total; LONG want = (LONG)(left > 4096UL ? 4096UL : left); @@ -228,26 +238,29 @@ static int mp_safe_send(int sock, const UBYTE *buf, ULONG len) FD_ZERO(&wfds); FD_SET(sock, &wfds); - tv.tv_sec = MP_IPP_SEND_TIMEOUT_SECS; + tv.tv_sec = timeout_secs; tv.tv_usec = 0; ready = WaitSelect(sock + 1, NULL, &wfds, NULL, &tv, NULL); if (ready <= 0 || !FD_ISSET(sock, &wfds)) { IoctlSocket(sock, FIONBIO, (char *)&block); - return 0; + if (sent_out) *sent_out = sent_total; + return ready == 0 ? MP_IPP_SEND_TIMEOUT : MP_IPP_SEND_FAILED; } } sent = send(sock, (char *)(buf + sent_total), want, 0); if (sent <= 0) { if (nonblocking) IoctlSocket(sock, FIONBIO, (char *)&block); - return 0; + if (sent_out) *sent_out = sent_total; + return MP_IPP_SEND_FAILED; } sent_total += (ULONG)sent; } if (nonblocking) IoctlSocket(sock, FIONBIO, (char *)&block); - return 1; + if (sent_out) *sent_out = sent_total; + return MP_IPP_SEND_OK; } static int mp_put8(UBYTE *p, ULONG cap, ULONG *off, UBYTE v) @@ -540,8 +553,10 @@ LONG mp_ipp_query_imageable_margins(const struct MPConfig *cfg, rc = -8; goto done; } - if (!mp_safe_send(sock, (const UBYTE *)g_margin_http, hp) || - !mp_safe_send(sock, g_margin_ipp, io)) { + if (mp_safe_send(sock, (const UBYTE *)g_margin_http, hp, + MP_IPP_CONTROL_SEND_TIMEOUT_SECS, NULL) != MP_IPP_SEND_OK || + mp_safe_send(sock, g_margin_ipp, io, + MP_IPP_CONTROL_SEND_TIMEOUT_SECS, NULL) != MP_IPP_SEND_OK) { rc = -9; goto done; } @@ -779,6 +794,7 @@ LONG mp_ipp_print_document(const struct MPConfig *cfg, CONST_STRPTR filename, result->http_status = 0; result->ipp_status = 0xffff; result->document_bytes = 0; + result->document_bytes_sent = 0; } if (!DOSBase || !cfg || !cfg->host[0] || cfg->port == 0 || cfg->path[0] != '/' || !filename || !document_format) return -1; @@ -896,14 +912,30 @@ LONG mp_ipp_print_document(const struct MPConfig *cfg, CONST_STRPTR filename, if (connect_rc < 0) { rc = -10; goto done; } } - if (!mp_safe_send(sock, (const UBYTE *)http, hp) || - !mp_safe_send(sock, ipp, io)) { rc = -11; goto done; } + if (mp_safe_send(sock, (const UBYTE *)http, hp, + MP_IPP_CONTROL_SEND_TIMEOUT_SECS, NULL) != MP_IPP_SEND_OK || + mp_safe_send(sock, ipp, io, + MP_IPP_CONTROL_SEND_TIMEOUT_SECS, NULL) != MP_IPP_SEND_OK) { + rc = -11; goto done; + } for (;;) { - LONG got = Read(fh, filebuf, sizeof(filebuf)); + LONG got; + ULONG chunk_sent = 0; + int send_rc; + + got = Read(fh, filebuf, sizeof(filebuf)); if (got < 0) { rc = -12; goto done; } if (got == 0) break; - if (!mp_safe_send(sock, filebuf, (ULONG)got)) { rc = -13; goto done; } + + send_rc = mp_safe_send(sock, filebuf, (ULONG)got, + MP_IPP_DOCUMENT_SEND_TIMEOUT_SECS, + &chunk_sent); + if (result) result->document_bytes_sent += chunk_sent; + if (send_rc != MP_IPP_SEND_OK) { + rc = send_rc == MP_IPP_SEND_TIMEOUT ? -19 : -13; + goto done; + } } for (;;) { From b6963f3cadec46d7929f7f571e9bd1d56d66fd70 Mon Sep 17 00:00:00 2001 From: Darren Banfi <33876110+boingball@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:45:59 +0100 Subject: [PATCH 3/9] Initialize IPP upload progress --- driver/spool.c | 1 + 1 file changed, 1 insertion(+) diff --git a/driver/spool.c b/driver/spool.c index da3fa2f..1a3fb89 100644 --- a/driver/spool.c +++ b/driver/spool.c @@ -688,6 +688,7 @@ LONG mp_spool_ipp_submit(const struct MPConfig *cfg, CONST_STRPTR filename, m.ipp_result.http_status = 0; m.ipp_result.ipp_status = 0xffff; m.ipp_result.document_bytes = 0; + m.ipp_result.document_bytes_sent = 0; m.result = -1; mp_spool_send(&m); /* m.result carries mp_ipp_print_document()'s return */ From 6c0db0d55b0c8cb80c49b2f440edd2779b571d21 Mon Sep 17 00:00:00 2001 From: Darren Banfi <33876110+boingball@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:46:01 +0100 Subject: [PATCH 4/9] Diagnose stalled IPP document uploads --- driver/driver_core.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/driver/driver_core.c b/driver/driver_core.c index 6afe3a9..31c2f6c 100644 --- a/driver/driver_core.c +++ b/driver/driver_core.c @@ -55,7 +55,7 @@ * MP_DRIVER_REV (the version half) only bumps for something that * warrants a new version number outright, not on every rebuild. */ #define MP_DRIVER_REV 41 -#define MP_DRIVER_SUBREV 17 +#define MP_DRIVER_SUBREV 18 struct ExecBase *SysBase = NULL; struct DosLibrary *DOSBase = NULL; @@ -564,6 +564,11 @@ static void mp_write_job_status(const char *state, mp_log_append(" "); mp_log_append_long((LONG)result->ipp_status); mp_log_append("\n"); + mp_log_append("BYTES="); + mp_log_append_long((LONG)result->document_bytes_sent); + mp_log_append(" "); + mp_log_append_long((LONG)result->document_bytes); + mp_log_append("\n"); } mp_spool_status_write((CONST_STRPTR)g_job_status_path, g_log_line, g_log_pos); @@ -594,21 +599,29 @@ static void mp_log_3(const char *event, LONG a, LONG b, LONG c) } /* Same "error/http/status" triple every IPP submission already logs via - * mp_log_3(), plus a plain-English line for the two timeout-specific error - * codes (see ipp_client.c's mp_connect_with_timeout()/ - * mp_recv_with_timeout()) that would otherwise look like any other -10/-14 - * failure in the log - e.g. issue #66, a printer that accepts a job and - * then silently hangs instead of ever responding. */ + * mp_log_3(), plus byte progress and a plain-English line for timeout-specific + * errors. The upload progress makes a small-buffer printer distinguishable + * from a connection that failed before any document data moved. */ static void mp_log_ipp_result(const char *event, const struct MPIPPResult *result) { mp_log_3(event, result->error, result->http_status, (LONG)result->ipp_status); + if (result->document_bytes_sent > 0 || result->error == -13 || + result->error == -19) { + mp_log_3("IPP document bytes sent/total/stall-seconds", + (LONG)result->document_bytes_sent, + (LONG)result->document_bytes, 180); + } if (result->error == -17) { mp_log_text("No response from printer (timed out waiting to read " "the IPP reply - printer accepted the job, then hung)"); } else if (result->error == -18) { mp_log_text("No response from printer (timed out waiting to " "connect - printer unreachable/offline)"); + } else if (result->error == -19) { + mp_log_text("Document upload stalled for 180 seconds " + "(printer stopped accepting data; receive buffer may " + "be full)"); } } @@ -1593,6 +1606,7 @@ static LONG mp_page_submit_and_track(ULONG rows_for_streak) result.http_status = 0; result.ipp_status = 0; result.document_bytes = g_job_file_bytes; + result.document_bytes_sent = 0; mp_log_3("Duplex page queued pages/rows/bytes", (LONG)g_duplex_page_count, (LONG)rows_for_streak, (LONG)g_job_file_bytes); @@ -1606,6 +1620,7 @@ static LONG mp_page_submit_and_track(ULONG rows_for_streak) result.http_status = 0; result.ipp_status = 0; result.document_bytes = g_job_file_bytes; + result.document_bytes_sent = 0; mp_log_text("Capture-only regression job retained; network submission skipped"); } else { mp_write_job_status("SUBMITTING", NULL); @@ -2204,6 +2219,7 @@ VOID PRT_STDARGS DriverClose(struct IORequest *ior) result.http_status = 0; result.ipp_status = 0; result.document_bytes = g_job_file_bytes; + result.document_bytes_sent = 0; mp_log_text("Capture-only duplex job retained; network submission skipped"); mp_log_ipp_result("Capture duplex result error/http/status", &result); From 83d8529d48319cc023e02f8baa857b30dcfe60a8 Mon Sep 17 00:00:00 2001 From: Darren Banfi <33876110+boingball@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:46:03 +0100 Subject: [PATCH 5/9] Report stalled spooler retries --- src/MintPrintSettings.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/MintPrintSettings.c b/src/MintPrintSettings.c index a5ffa76..64a5be5 100644 --- a/src/MintPrintSettings.c +++ b/src/MintPrintSettings.c @@ -6233,6 +6233,7 @@ static void mp_spool_error_reason(LONG error, LONG http_status, case -18: reason = "Connection timed out"; break; case -11: case -13: reason = "Sending job failed (connection dropped)"; break; + case -19: reason = "Sending job stalled (printer buffer full?)"; break; case -12: reason = "Error reading job file"; break; case -14: reason = "Printer sent an invalid response"; break; case -17: reason = "Printer accepted job but never responded"; break; @@ -6603,6 +6604,7 @@ static BOOL mp_spool_retry_job(struct MPSpoolJobEntry *job, int unit_index) result.http_status = 0; result.ipp_status = 0xffff; result.document_bytes = 0; + result.document_bytes_sent = 0; rc = mp_ipp_print_document(&cfg, (CONST_STRPTR)job->job_path, document_format, &result); @@ -6618,8 +6620,12 @@ static BOOL mp_spool_retry_job(struct MPSpoolJobEntry *job, int unit_index) Write(sfh, buf, n); if (rc != 0) { n = snprintf(buf, sizeof(buf), "ERROR=%ld %ld %d\n", - (long)result.error, (long)result.http_status, - (int)result.ipp_status); + (long)result.error, (long)result.http_status, + (int)result.ipp_status); + Write(sfh, buf, n); + n = snprintf(buf, sizeof(buf), "BYTES=%lu %lu\n", + (unsigned long)result.document_bytes_sent, + (unsigned long)result.document_bytes); Write(sfh, buf, n); } Close(sfh); From 4ef8bd5d0cd55a9d1f773a51100ec650a758a8fb Mon Sep 17 00:00:00 2001 From: Darren Banfi <33876110+boingball@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:46:04 +0100 Subject: [PATCH 6/9] Bump MintPRINT driver to 41.18 --- driver/printertag.s | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/printertag.s b/driver/printertag.s index 3f15a9d..b3acc2f 100644 --- a/driver/printertag.s +++ b/driver/printertag.s @@ -104,5 +104,5 @@ printerName: * something that warrants a new version number outright. */ mp_driver_version_marker: - .asciz "$VER: MintPRINT 41.17 (03.09.2026)" + .asciz "$VER: MintPRINT 41.18 (03.09.2026)" .even From 79b3b6c7e034d0e2920fcec2f49e3b9d8224780d Mon Sep 17 00:00:00 2001 From: Darren Banfi <33876110+boingball@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:46:06 +0100 Subject: [PATCH 7/9] Bump MintPRINT driver to 41.18 --- driver/printertag_classic.s | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/printertag_classic.s b/driver/printertag_classic.s index 15709a5..8d46940 100644 --- a/driver/printertag_classic.s +++ b/driver/printertag_classic.s @@ -80,7 +80,7 @@ printerName: * binary for that release. */ mp_driver_version_marker: - .asciz "$VER: MintPRINT 41.17 (03.09.2026)" + .asciz "$VER: MintPRINT 41.18 (03.09.2026)" .even /* Human-readable marker useful when inspecting a built driver. */ From 6e04559d6462c0b4aa5497717b00a8239db0caea Mon Sep 17 00:00:00 2001 From: Darren Banfi <33876110+boingball@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:46:07 +0100 Subject: [PATCH 8/9] Document slow-printer upload compatibility --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 281bd69..590a0fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,13 @@ to this page; replace that summary when preparing the next release. ## Unreleased -_No unreleased changes._ +- **Large jobs tolerate slow printer back-pressure (driver 41.18).** The + document-body upload now allows up to three minutes without TCP progress, + while connection, control-message and final-response timeouts remain short. + This targets small-buffer printers such as the Canon TS8360 from issue #91, + which can pause network reads while processing a long PWG Raster job. + Failures now distinguish an upload stall as error `-19` and record the + document bytes sent/total in the debug log and retained status sidecar. ## 1.3.1 From 1a3193a32e30f6be52125a22357d5182b4eaa6eb Mon Sep 17 00:00:00 2001 From: Darren Banfi <33876110+boingball@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:46:45 +0100 Subject: [PATCH 9/9] Align upload progress initialization --- driver/driver_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/driver_core.c b/driver/driver_core.c index 31c2f6c..0b9e228 100644 --- a/driver/driver_core.c +++ b/driver/driver_core.c @@ -2219,7 +2219,7 @@ VOID PRT_STDARGS DriverClose(struct IORequest *ior) result.http_status = 0; result.ipp_status = 0; result.document_bytes = g_job_file_bytes; - result.document_bytes_sent = 0; + result.document_bytes_sent = 0; mp_log_text("Capture-only duplex job retained; network submission skipped"); mp_log_ipp_result("Capture duplex result error/http/status", &result);