aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Bennett <steveb@workware.net.au>2022-02-23 08:34:03 +1000
committerSteve Bennett <steveb@workware.net.au>2022-02-23 12:30:38 +1000
commit8faa0cb2805808e9f350acdc24d1f4de875f5aa3 (patch)
tree62eaab64548a06b6c8475f4c2de2fb2bd628f8a9
parent043325531af41ba102ff79ea4e251dfc201e287b (diff)
downloadjimtcl-8faa0cb2805808e9f350acdc24d1f4de875f5aa3.zip
jimtcl-8faa0cb2805808e9f350acdc24d1f4de875f5aa3.tar.gz
jimtcl-8faa0cb2805808e9f350acdc24d1f4de875f5aa3.tar.bz2
aio: gets: Improve behaviour for non-blocking streams
Previously calling gets on a non-blocking stream could easily result in a partial line. Now if a partial line is read, return zero/empty to indicate that nothing is available while storing the partial line. The next call to gets (typically within a readable script) will continue appending to the previous partial line until a complete line can be returned. Signed-off-by: Steve Bennett <steveb@workware.net.au>
-rw-r--r--examples/nonblocking-gets.tcl38
-rw-r--r--jim-aio.c54
2 files changed, 75 insertions, 17 deletions
diff --git a/examples/nonblocking-gets.tcl b/examples/nonblocking-gets.tcl
new file mode 100644
index 0000000..a9ac4bd
--- /dev/null
+++ b/examples/nonblocking-gets.tcl
@@ -0,0 +1,38 @@
+#!/usr/bin/env jimsh
+
+# Tests that 'gets' on a non-blocking socket
+# does not return partial lines
+
+lassign [socket pipe] r w
+
+if {[os.fork] == 0} {
+ # The child will be our client
+ $r close
+ # Output increasingly long lines
+ loop i 10000 {
+ $w puts [string repeat a $i]
+ }
+} else {
+ # The server reads lines with gets.
+ # Each one should be one longer than the last
+ $w close
+
+ set exp 0
+ $r ndelay 1
+ $r readable {
+ while {[$r gets buf] >= 0} {
+ set len [string length $buf]
+ if {$len != $exp} {
+ puts "Read line of length $len but expected $exp"
+ incr done
+ break
+ }
+ incr exp
+ }
+ if {[$r eof]} {
+ incr done
+ }
+ }
+
+ vwait done
+}
diff --git a/jim-aio.c b/jim-aio.c
index 7aeb4bf..684a1fd 100644
--- a/jim-aio.c
+++ b/jim-aio.c
@@ -171,6 +171,7 @@ typedef struct AioFile
int addr_family;
void *ssl;
const JimAioFopsType *fops;
+ Jim_Obj *getline_partial; /* In case of fgets() returning EAGAIN, partial line stored here */
} AioFile;
static int stdio_writer(struct AioFile *af, const char *buf, int len)
@@ -682,6 +683,9 @@ static void JimAioDelProc(Jim_Interp *interp, void *privData)
if (!(af->flags & AIO_KEEPOPEN)) {
fclose(af->fp);
}
+ if (af->getline_partial) {
+ Jim_FreeNewObj(interp, af->getline_partial);
+ }
Jim_Free(af);
}
@@ -860,33 +864,49 @@ static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
char buf[AIO_BUF_LEN];
Jim_Obj *objPtr;
int len;
+ int eof_or_partial = 0;
errno = 0;
- objPtr = Jim_NewStringObj(interp, NULL, 0);
- while (1) {
- buf[AIO_BUF_LEN - 1] = '_';
-
- if (af->fops->getline(af, buf, AIO_BUF_LEN) == NULL)
- break;
+ if (af->getline_partial) {
+ /* A partial line was read previously, so append to it */
+ objPtr = af->getline_partial;
+ af->getline_partial = NULL;
+ }
+ else {
+ objPtr = Jim_NewStringObj(interp, NULL, 0);
+ }
- if (buf[AIO_BUF_LEN - 1] == '\0' && buf[AIO_BUF_LEN - 2] != '\n') {
- Jim_AppendString(interp, objPtr, buf, AIO_BUF_LEN - 1);
- }
- else {
+ while (1) {
+ if (af->fops->getline(af, buf, AIO_BUF_LEN)) {
len = strlen(buf);
-
- if (len && (buf[len - 1] == '\n')) {
- /* strip "\n" */
- len--;
+ if (len && buf[len - 1] == '\n') {
+ /* strip "\n" and we are done */
+ Jim_AppendString(interp, objPtr, buf, len - 1);
+ break;
}
+ /* Otherwise just append what we have */
Jim_AppendString(interp, objPtr, buf, len);
+ }
+
+ if (errno == EAGAIN) {
+ if (Jim_Length(objPtr)) {
+ /* Stash the partial line */
+ af->getline_partial = objPtr;
+ /* And indicate that no line is (yet) available */
+ objPtr = Jim_NewStringObj(interp, NULL, 0);
+ }
+ eof_or_partial = 1;
+ break;
+ }
+ else if (af->fops->eof(af)) {
+ eof_or_partial = 1;
break;
}
}
- if (JimCheckStreamError(interp, af)) {
+ if (Jim_Length(objPtr) == 0 && JimCheckStreamError(interp, af)) {
/* I/O error */
Jim_FreeNewObj(interp, objPtr);
return JIM_ERR;
@@ -900,8 +920,8 @@ static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
len = Jim_Length(objPtr);
- if (len == 0 && af->fops->eof(af)) {
- /* On EOF returns -1 if varName was specified */
+ if (eof_or_partial && len == 0) {
+ /* On EOF or partial line with empty result, returns -1 if varName was specified */
len = -1;
}
Jim_SetResultInt(interp, len);