fixeria has uploaded this change for review.
osmo_io: add osmo_iofd_flush() to transmit the pending Tx queue
osmo_iofd_write_msgb() and friends only enqueue the given message;
the actual transmission happens asynchronously from within
osmo_select_main(). An application that needs to say 'goodbye'
right before calling osmo_iofd_free() (think of a connection
teardown datagram, like TRXC "CMD POWEROFF") currently has no way
to do so reliably: osmo_iofd_free() discards the Tx queue, so a
message enqueued in the same event loop iteration never reaches
the socket.
Add osmo_iofd_flush(), which synchronously transmits the pending
Tx queue, stopping on -EAGAIN or other errors. Only the poll
backend implements it for now; the io_uring backend returns
-ENOTSUP, since in-flight SQE submissions make a synchronous
flush non-trivial there.
Change-Id: I4e09e91e472a4441977a2c199c07c2cb8005632f
---
M include/osmocom/core/osmo_io.h
M src/core/libosmocore.map
M src/core/osmo_io.c
M src/core/osmo_io_internal.h
M src/core/osmo_io_poll.c
M tests/osmo_io/osmo_io_test.c
M tests/osmo_io/osmo_io_test.ok
7 files changed, 144 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/libosmocore refs/changes/02/43102/1
diff --git a/include/osmocom/core/osmo_io.h b/include/osmocom/core/osmo_io.h
index c92492c..a526a7c 100644
--- a/include/osmocom/core/osmo_io.h
+++ b/include/osmocom/core/osmo_io.h
@@ -220,6 +220,7 @@
int osmo_iofd_unregister(struct osmo_io_fd *iofd);
unsigned int osmo_iofd_txqueue_len(struct osmo_io_fd *iofd);
void osmo_iofd_txqueue_clear(struct osmo_io_fd *iofd);
+int osmo_iofd_flush(struct osmo_io_fd *iofd);
int osmo_iofd_close(struct osmo_io_fd *iofd);
void osmo_iofd_free(struct osmo_io_fd *iofd);
diff --git a/src/core/libosmocore.map b/src/core/libosmocore.map
index 0cb696b..90b1ef2 100644
--- a/src/core/libosmocore.map
+++ b/src/core/libosmocore.map
@@ -268,6 +268,7 @@
osmo_io_get_backend;
osmo_io_backend_names;
osmo_iofd_close;
+osmo_iofd_flush;
osmo_iofd_free;
osmo_iofd_get_data;
osmo_iofd_get_ioops;
diff --git a/src/core/osmo_io.c b/src/core/osmo_io.c
index f14f03b..b0acc04 100644
--- a/src/core/osmo_io.c
+++ b/src/core/osmo_io.c
@@ -1114,6 +1114,39 @@
}
}
+/*! Flush the transmit queue of the given osmo_io_fd.
+ *
+ * This function attempts to synchronously transmit all messages currently
+ * pending in the transmit queue, stopping at the first message that cannot
+ * be written without blocking. This is useful during shutdown, to make
+ * sure that pending messages (e.g. a final 'goodbye' datagram) actually
+ * leave the socket before the file descriptor gets closed: unlike
+ * osmo_iofd_write_msgb(), which only enqueues, actual transmission
+ * normally happens once the event loop (osmo_select_main()) iterates.
+ *
+ * The write/sendto/sendmsg call-back is invoked for every transmitted
+ * message, just like on transmission from the event loop.
+ *
+ * \param[in] iofd the file descriptor
+ * \returns 0 if the transmit queue was fully drained; -EAGAIN if a message
+ * could not be transmitted without blocking; -ENOTSUP if the
+ * osmo_io backend does not implement flushing; other negative
+ * values on send errors */
+int osmo_iofd_flush(struct osmo_io_fd *iofd)
+{
+ int rc;
+
+ if (iofd->fd < 0 || IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))
+ return -EBADF;
+ if (osmo_iofd_ops.flush == NULL)
+ return -ENOTSUP;
+
+ rc = osmo_iofd_ops.flush(iofd);
+ if (rc == 0) /* nothing left to transmit */
+ osmo_iofd_ops.write_disable(iofd);
+ return rc;
+}
+
/*! Free the given osmo_io_fd.
*
* The iofd will be automatically closed before via osmo_iofd_close() [which in turn will unregister
diff --git a/src/core/osmo_io_internal.h b/src/core/osmo_io_internal.h
index 81a70f3..5f4614f 100644
--- a/src/core/osmo_io_internal.h
+++ b/src/core/osmo_io_internal.h
@@ -35,6 +35,7 @@
void (*read_enable)(struct osmo_io_fd *iofd);
void (*read_disable)(struct osmo_io_fd *iofd);
void (*notify_connected)(struct osmo_io_fd *iofd);
+ int (*flush)(struct osmo_io_fd *iofd);
};
#define IOFD_FLAG_CLOSED (1<<0)
diff --git a/src/core/osmo_io_poll.c b/src/core/osmo_io_poll.c
index b861675..f07c501 100644
--- a/src/core/osmo_io_poll.c
+++ b/src/core/osmo_io_poll.c
@@ -258,6 +258,42 @@
osmo_fd_write_enable(&iofd->u.poll.ofd);
}
+static int iofd_poll_flush(struct osmo_io_fd *iofd)
+{
+ struct iofd_msghdr *msghdr;
+ int rc;
+
+ while ((msghdr = iofd_txqueue_dequeue(iofd))) {
+ /* a zero-length write is progress, unlike a partial write of 0
+ * bytes (see the interpretation in iofd_handle_send_completion()) */
+ bool zero_len = msgb_length(msghdr->msg[0]) == 0;
+
+ switch (iofd->mode) {
+ case OSMO_IO_FD_MODE_READ_WRITE:
+ rc = writev(iofd->fd, msghdr->iov, msghdr->io_len);
+ break;
+ case OSMO_IO_FD_MODE_RECVFROM_SENDTO:
+ case OSMO_IO_FD_MODE_RECVMSG_SENDMSG:
+ rc = sendmsg(iofd->fd, &msghdr->hdr, msghdr->flags);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ rc = (rc < 0 && errno > 0) ? -errno : rc;
+ /* completes transmitted msgbs; re-enqueues the msghdr at the
+ * front of the queue on -EAGAIN and on partial writes */
+ iofd_handle_send_completion(iofd, rc, msghdr);
+
+ if (rc < 0)
+ return rc;
+ if (rc == 0 && !zero_len)
+ return -EAGAIN; /* no progress was made, give up */
+ }
+
+ return 0;
+}
+
const struct iofd_backend_ops iofd_poll_ops = {
.register_fd = iofd_poll_register,
.unregister_fd = iofd_poll_unregister,
@@ -267,6 +303,7 @@
.read_enable = iofd_poll_read_enable,
.read_disable = iofd_poll_read_disable,
.notify_connected = iofd_poll_notify_connected,
+ .flush = iofd_poll_flush,
};
#endif /* ifndef EMBEDDED */
diff --git a/tests/osmo_io/osmo_io_test.c b/tests/osmo_io/osmo_io_test.c
index ca7fa67..8d245fb 100644
--- a/tests/osmo_io/osmo_io_test.c
+++ b/tests/osmo_io/osmo_io_test.c
@@ -251,6 +251,69 @@
osmo_select_main(1);
}
+static unsigned int flush_num_compl = 0;
+
+static void flush_sendto_cb(struct osmo_io_fd *iofd, int rc, struct msgb *msg,
+ const struct osmo_sockaddr *daddr)
+{
+ printf("%s: sendto() returned rc=%d\n", osmo_iofd_get_name(iofd), rc);
+ flush_num_compl++;
+}
+
+struct osmo_io_ops ioops_flush = {
+ .sendto_cb = flush_sendto_cb,
+ .recvfrom_cb = recvfrom_cb,
+};
+
+static void test_flush(void)
+{
+ int fds[2] = {0, 0}, rc;
+ struct osmo_io_fd *iofd;
+ struct msgb *msg;
+ uint8_t buf[64];
+
+ TEST_START();
+
+ rc = socketpair(AF_UNIX, SOCK_DGRAM, 0, fds);
+ OSMO_ASSERT(rc == 0);
+
+ iofd = osmo_iofd_setup(ctx, fds[0], "ep1", OSMO_IO_FD_MODE_RECVFROM_SENDTO, &ioops_flush, NULL);
+ osmo_iofd_register(iofd, fds[0]);
+
+ /* flushing an empty Tx queue is a no-op */
+ rc = osmo_iofd_flush(iofd);
+ OSMO_ASSERT(rc == 0 || rc == -ENOTSUP);
+
+ for (unsigned int i = 0; i < 3; i++) {
+ msg = msgb_alloc(1024, "Test data");
+ memcpy(msgb_put(msg, sizeof(TESTDATA)), TESTDATA, sizeof(TESTDATA));
+ osmo_iofd_sendto_msgb(iofd, msg, 0, NULL);
+ }
+
+ rc = osmo_iofd_flush(iofd);
+ if (rc == -ENOTSUP) {
+ /* backend without flush support (io_uring): drain via the event
+ * loop instead, so that the test output remains identical */
+ while (flush_num_compl < 3)
+ osmo_select_main(1);
+ } else {
+ OSMO_ASSERT(rc == 0);
+ }
+ OSMO_ASSERT(osmo_iofd_txqueue_len(iofd) == 0);
+
+ /* all three datagrams must have hit the socket by now */
+ for (unsigned int i = 0; i < 3; i++) {
+ rc = recv(fds[1], buf, sizeof(buf), MSG_DONTWAIT);
+ printf("ep2: recv() returned rc=%d\n", rc);
+ }
+
+ osmo_iofd_free(iofd);
+ close(fds[1]);
+
+ for (int i = 0; i < 128; i++)
+ osmo_select_main(1);
+}
+
int segmentation_cb(struct osmo_io_fd *iofd, struct msgb *msg)
{
printf("%s: segmentation_cb() returning %d\n", osmo_iofd_get_name(iofd), 4);
@@ -668,6 +731,7 @@
test_file();
test_connected();
test_unconnected();
+ test_flush();
test_segmentation();
test_segmentation_uint16_max(10000, UINT16_MAX, 0);
test_segmentation_uint16_max(10000, UINT16_MAX - 320, 320);
diff --git a/tests/osmo_io/osmo_io_test.ok b/tests/osmo_io/osmo_io_test.ok
index d368db4..5a9193c 100644
--- a/tests/osmo_io/osmo_io_test.ok
+++ b/tests/osmo_io/osmo_io_test.ok
@@ -16,6 +16,13 @@
ep1: sendto() returned rc=16
ep2: recvfrom() msg with len=16
01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10
+Running test_flush
+ep1: sendto() returned rc=16
+ep1: sendto() returned rc=16
+ep1: sendto() returned rc=16
+ep2: recv() returned rc=16
+ep2: recv() returned rc=16
+ep2: recv() returned rc=16
Running test_segmentation
Enable write
seg_iofd: write() returned rc=12
To view, visit change 43102. To unsubscribe, or for help writing mail filters, visit settings.