Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
28 changes: 22 additions & 6 deletions driver/driver_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)");
}
}

Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
68 changes: 50 additions & 18 deletions driver/ipp_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 (;;) {
Expand Down
1 change: 1 addition & 0 deletions driver/ipp_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion driver/printertag.s
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion driver/printertag_classic.s
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
1 change: 1 addition & 0 deletions driver/spool.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
10 changes: 8 additions & 2 deletions src/MintPrintSettings.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down
Loading