pespin has submitted this change. (
https://gerrit.osmocom.org/c/libosmocore/+/41925?usp=email )
Change subject: Implement log file target using osmo_io
......................................................................
Implement log file target using osmo_io
Reuse (struct log_target)->tgt_file->wqueue->except_cb to store the iofd
pointer internally, since we are not allowed to change the struct
log_target because it's public and we don't want to break the ABI.
Using the wqueue except_cb is fine since that field was never used.
Related: OS#6918
Change-Id: Ieb6420246454ef59442b1fd7b1d14e2c00fa69a5
---
M src/core/logging_file.c
M tests/logging/logging_test.c
M tests/logging/logging_test.err
3 files changed, 143 insertions(+), 132 deletions(-)
Approvals:
jolly: Looks good to me, but someone else must approve
Jenkins Builder: Verified
laforge: Looks good to me, but someone else must approve
pespin: Looks good to me, approved
diff --git a/src/core/logging_file.c b/src/core/logging_file.c
index 413a0a7..aa5ef63 100644
--- a/src/core/logging_file.c
+++ b/src/core/logging_file.c
@@ -38,18 +38,22 @@
#include <osmocom/core/utils.h>
#include <osmocom/core/select.h>
#include <osmocom/core/write_queue.h>
+#include <osmocom/core/osmo_io.h>
#include <osmocom/core/logging_internal.h>
/* maximum number of log statements we queue in file/stderr target write queue
*/
#define LOG_WQUEUE_LEN 156
+/* NOTE: We use target->tgt_file.wqueue->except_cb to store the struct
osmo_io_fd, because the
+ * struct log_target is public and we cannot add pointers to it under
tgt->tgt_file...
+ * It can be moved to target->tgt_file.iofd if we are ever able to make struct
log_target private... */
/*! close and re-open a log file (for log file rotation)
* \param[in] target log target to re-open
* \returns 0 in case of success; negative otherwise */
int log_target_file_reopen(struct log_target *target)
{
- struct osmo_wqueue *wq;
+ struct osmo_io_fd *iofd;
int rc;
OSMO_ASSERT(target->type == LOG_TGT_TYPE_FILE ||
@@ -67,20 +71,22 @@
}
OSMO_ASSERT(target->tgt_file.wqueue);
- wq = target->tgt_file.wqueue;
- if (wq->bfd.fd >= 0) {
- osmo_fd_unregister(&wq->bfd);
- close(wq->bfd.fd);
- wq->bfd.fd = -1;
- }
+ iofd = (struct osmo_io_fd *)target->tgt_file.wqueue->except_cb;
+ OSMO_ASSERT(iofd);
+ osmo_iofd_close(iofd);
+ target->tgt_file.wqueue->bfd.fd = -1; /* Keep public field changes
despite not used internally... */
rc = open(target->tgt_file.fname, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK,
0660);
if (rc < 0)
return -errno;
- wq->bfd.fd = rc;
- rc = osmo_fd_register(&wq->bfd);
- if (rc < 0)
- return rc;
+
+ rc = osmo_iofd_register(iofd, rc);
+ if (rc < 0) {
+ osmo_iofd_free(iofd);
+ target->tgt_file.wqueue->except_cb = NULL; /*
target->tgt_file.iofd = NULL */
+ return -EIO;
+ }
+ target->tgt_file.wqueue->bfd.fd = rc; /* Keep public field changes
despite not used internally... */
return 0;
}
@@ -91,7 +97,6 @@
* object. */
void log_target_file_destroy(struct log_target *target)
{
- struct osmo_wqueue *wq;
OSMO_ASSERT(target->type == LOG_TGT_TYPE_FILE ||
target->type == LOG_TGT_TYPE_STDERR);
@@ -101,38 +106,21 @@
fclose(target->tgt_file.out);
target->tgt_file.out = NULL;
}
- wq = target->tgt_file.wqueue;
- if (wq) {
- if (wq->bfd.fd >= 0) {
- osmo_fd_unregister(&wq->bfd);
- if (target->type == LOG_TGT_TYPE_FILE)
- close(wq->bfd.fd);
- wq->bfd.fd = -1;
- }
- osmo_wqueue_clear(wq);
- talloc_free(wq);
- target->tgt_file.wqueue = NULL;
+
+ if (target->tgt_file.wqueue && target->tgt_file.wqueue->except_cb) { /*
target->tgt_file.iofd */
+ osmo_iofd_free((struct osmo_io_fd
*)target->tgt_file.wqueue->except_cb);
+ target->tgt_file.wqueue->except_cb = NULL; /*
target->tgt_file.iofd = NULL */
+ target->tgt_file.wqueue->bfd.fd = -1; /* Keep public field
changes despite not used internally... */
}
+
talloc_free((void *)target->tgt_file.fname);
target->tgt_file.fname = NULL;
}
-/* write-queue tells us we should write another msgb (log line) to the output
fd */
-static int _file_wq_write_cb(struct osmo_fd *ofd, struct msgb *msg)
-{
- int rc;
-
- rc = write(ofd->fd, msgb_data(msg), msgb_length(msg));
- if (rc < 0)
- return rc;
- if (rc != msgb_length(msg)) {
- /* pull the number of bytes we have already written */
- msgb_pull(msg, rc);
- /* ask write_queue to re-insert the msgb at the head of the
queue */
- return -EAGAIN;
- }
- return 0;
-}
+static struct osmo_io_ops log_target_file_io_ops = {
+ .read_cb = NULL,
+ .write_cb = NULL,
+};
/* output via buffered, blocking stdio streams */
static void _file_output_stream(struct log_target *target, unsigned int level,
@@ -147,37 +135,53 @@
static void _file_raw_output(struct log_target *target, int subsys, unsigned
int level, const char *file,
int line, int cont, const char *format, va_list ap)
{
+ OSMO_ASSERT(target->tgt_file.wqueue &&
target->tgt_file.wqueue->except_cb);
struct msgb *msg;
+ struct osmo_io_fd *iofd = (struct osmo_io_fd
*)target->tgt_file.wqueue->except_cb;
+ void *pool_ctx = osmo_iofd_get_data(iofd);
int rc;
- OSMO_ASSERT(target->tgt_file.wqueue);
- msg = msgb_alloc_c(target->tgt_file.wqueue, MAX_LOG_SIZE,
"log_file_msg");
+ msg = msgb_alloc_c(pool_ctx, MAX_LOG_SIZE, "log_file_msg");
if (!msg)
return;
/* we simply enqueue the log message to a write queue here, to avoid
any blocking
* writes on the output file. The write queue will tell us once the
file is writable
* and call _file_wq_write_cb() */
- rc = log_output_buf((char *)msgb_data(msg), msgb_tailroom(msg), target,
subsys, level, file, line, cont, format, ap);
+ rc = log_output_buf((char *)msgb_data(msg), msgb_tailroom(msg), target,
subsys, level,
+ file, line, cont, format, ap);
msgb_put(msg, rc);
- /* attempt a synchronous, non-blocking write, if the write queue is
empty */
- if (target->tgt_file.wqueue->current_length == 0) {
- rc = _file_wq_write_cb(&target->tgt_file.wqueue->bfd, msg);
- if (rc == 0) {
- /* the write was complete, we can exit early */
- msgb_free(msg);
- return;
- }
- }
- /* if we reach here, either we already had elements in the write_queue,
or the synchronous write
- * failed: enqueue the message to the write_queue (backlog) */
- if (osmo_wqueue_enqueue_quiet(target->tgt_file.wqueue, msg) < 0) {
+ rc = osmo_iofd_write_msgb(iofd, msg);
+ if (rc < 0) {
msgb_free(msg);
/* TODO: increment some counter so we can see that messages
were dropped */
}
}
+void _log_target_file_setup_talloc_pool(struct log_target *target)
+{
+ OSMO_ASSERT(target->tgt_file.wqueue &&
target->tgt_file.wqueue->except_cb);
+ struct osmo_io_fd *iofd = (struct osmo_io_fd
*)target->tgt_file.wqueue->except_cb;
+ if (osmo_iofd_get_data(iofd))
+ return; /* mempool already allocated */
+
+#ifndef ENABLE_PSEUDOTALLOC
+ void *pool_ctx;
+ /* Allocate a talloc pool to avoid malloc() on the first 156
+ * concurrently queued msgbs (~640KB per gsmtap_log target).
+ * Once the talloc_pool is full, new normal talloc chunks will be used.
*/
+ pool_ctx = _talloc_pooled_object(target, 0, "file_log_msgb_pool",
+ LOG_WQUEUE_LEN,
+ (sizeof(struct msgb) + MAX_LOG_SIZE) *
LOG_WQUEUE_LEN);
+ osmo_iofd_set_data(iofd, pool_ctx);
+#else
+ /* talloc pools not supported by pseudotalloc, allocate on usual msgb
ctx instead: */
+ extern void *tall_msgb_ctx;
+ osmo_iofd_set_data(iofd, tall_msgb_ctx);
+#endif /* ifndef ENABLE_PSEUDOTALLOC */
+}
+
/*! switch from non-blocking/write-queue to blocking + buffered stream output
* \param[in] target log target which we should switch
* \return 0 on success; 1 if already switched before; negative on error
@@ -185,7 +189,8 @@
*/
int log_target_file_switch_to_stream(struct log_target *target)
{
- struct osmo_wqueue *wq;
+ struct osmo_io_fd *iofd;
+ unsigned int prev_queue_len;
if (!target)
return -ENODEV;
@@ -195,36 +200,70 @@
return 1;
}
- wq = target->tgt_file.wqueue;
- OSMO_ASSERT(wq);
-
/* re-open output as stream */
if (target->type == LOG_TGT_TYPE_STDERR)
target->tgt_file.out = stderr;
else
target->tgt_file.out = fopen(target->tgt_file.fname, "a");
+
if (!target->tgt_file.out)
return -EIO;
- /* synchronously write anything left in the queue */
- while (!llist_empty(&wq->msg_queue)) {
- struct msgb *msg = msgb_dequeue(&wq->msg_queue);
- fwrite(msgb_data(msg), msgb_length(msg), 1,
target->tgt_file.out);
- msgb_free(msg);
- }
+ iofd = (struct osmo_io_fd *)target->tgt_file.wqueue->except_cb;
+ prev_queue_len = osmo_iofd_txqueue_len(iofd);
- /* now that everything succeeded, we can finally close the old output
fd */
- osmo_fd_unregister(&wq->bfd);
- if (target->type == LOG_TGT_TYPE_FILE)
- close(wq->bfd.fd);
- wq->bfd.fd = -1;
-
+ /* now that everything succeeded, we can finally close the old iofd */
+ osmo_iofd_free(iofd);
+ target->tgt_file.wqueue->except_cb = NULL; /* target->tgt_file.iofd =
NULL */
+ target->tgt_file.wqueue->bfd.fd = -1; /* Keep public field changes
despite not used internally... */
/* release the queue itself */
- talloc_free(wq);
+ talloc_free(target->tgt_file.wqueue);
target->tgt_file.wqueue = NULL;
target->output = _file_output_stream;
target->raw_output = NULL;
+
+ if (prev_queue_len > 0)
+ LOGP(DLGLOBAL, LOGL_NOTICE,
+ "Dropped %u messages switching log target file to
stream\n", prev_queue_len);
+
+ return 0;
+}
+
+/* Owns fd on success, closes fd on error. */
+int _log_target_file_setup_iofd(struct log_target *target, int fd)
+{
+ struct osmo_io_fd *iofd;
+ int rc;
+
+ /* XXX: This wq is only created to keep public log_target fields
+ * similar. It's not really used anymore internally, other than holding
a
+ * struct osmo_io_fd in wq->except_cb...*/
+ target->tgt_file.wqueue = talloc_zero(target, struct osmo_wqueue);
+ OSMO_ASSERT(target->tgt_file.wqueue);
+ osmo_wqueue_init(target->tgt_file.wqueue, LOG_WQUEUE_LEN);
+
+ iofd = osmo_iofd_setup(target, fd, target->tgt_file.fname,
+ OSMO_IO_FD_MODE_READ_WRITE,
+ &log_target_file_io_ops, NULL);
+ if (!iofd) {
+ close(fd);
+ return -EIO;
+ }
+ target->tgt_file.wqueue->except_cb = (int (*)(struct osmo_fd *))iofd;
+ target->tgt_file.wqueue->bfd.fd = fd; /* Keep public field changes
despite not used internally... */
+
+ _log_target_file_setup_talloc_pool(target);
+ osmo_iofd_set_txqueue_max_length(iofd,
OSMO_MAX(osmo_iofd_get_txqueue_max_length(iofd), LOG_WQUEUE_LEN));
+
+ rc = osmo_iofd_register(iofd, -1);
+ if (rc < 0) {
+ osmo_iofd_free(iofd);
+ target->tgt_file.wqueue->except_cb = NULL;
+ target->tgt_file.wqueue->bfd.fd = -1; /* Keep public field
changes despite not used internally... */
+ talloc_free(target->tgt_file.wqueue);
+ return -EIO;
+ }
return 0;
}
@@ -235,8 +274,7 @@
*/
int log_target_file_switch_to_wqueue(struct log_target *target)
{
- struct osmo_wqueue *wq;
- int rc;
+ int rc, fd;
if (!target)
return -ENODEV;
@@ -246,35 +284,19 @@
return 1;
}
- /* we create a ~640kB sized talloc pool within the write-queue to
ensure individual
- * log lines (stored as msgbs) will not put result in malloc() calls,
and also to
- * reduce the OOM probability within logging, as the pool is already
allocated */
- wq = talloc_pooled_object(target, struct osmo_wqueue, LOG_WQUEUE_LEN,
- LOG_WQUEUE_LEN*(sizeof(struct
msgb)+MAX_LOG_SIZE));
- if (!wq)
- return -ENOMEM;
- osmo_wqueue_init(wq, LOG_WQUEUE_LEN);
-
fflush(target->tgt_file.out);
- if (target->type == LOG_TGT_TYPE_FILE) {
- rc = open(target->tgt_file.fname,
O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660);
- if (rc < 0) {
- talloc_free(wq);
- return -errno;
- }
- } else {
- rc = STDERR_FILENO;
- }
- wq->bfd.fd = rc;
- wq->bfd.when = OSMO_FD_WRITE;
- wq->write_cb = _file_wq_write_cb;
+ if (target->type == LOG_TGT_TYPE_FILE)
+ fd = open(target->tgt_file.fname,
O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660);
+ else /* LOG_TGT_TYPE_STDERR: dup file so we can close it later with
osmo_iofd_free() */
+ fd = dup(STDERR_FILENO);
- rc = osmo_fd_register(&wq->bfd);
- if (rc < 0) {
- talloc_free(wq);
- return -EIO;
- }
- target->tgt_file.wqueue = wq;
+ if (fd < 0)
+ return -errno;
+
+ rc = _log_target_file_setup_iofd(target, fd);
+ if (rc < 0)
+ return rc;
+
target->raw_output = _file_raw_output;
target->output = NULL;
@@ -293,45 +315,30 @@
struct log_target *log_target_create_file(const char *fname)
{
struct log_target *target;
- struct osmo_wqueue *wq;
- int rc;
+ int rc, fd;
target = log_target_create();
if (!target)
return NULL;
target->type = LOG_TGT_TYPE_FILE;
- /* we create a ~640kB sized talloc pool within the write-queue to
ensure individual
- * log lines (stored as msgbs) will not put result in malloc() calls,
and also to
- * reduce the OOM probability within logging, as the pool is already
allocated */
- wq = talloc_pooled_object(target, struct osmo_wqueue, LOG_WQUEUE_LEN,
- LOG_WQUEUE_LEN*(sizeof(struct
msgb)+MAX_LOG_SIZE));
- if (!wq) {
- log_target_destroy(target);
- return NULL;
- }
- osmo_wqueue_init(wq, LOG_WQUEUE_LEN);
- wq->bfd.fd = open(fname, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660);
- if (wq->bfd.fd < 0) {
- talloc_free(wq);
- log_target_destroy(target);
- return NULL;
- }
- wq->bfd.when = OSMO_FD_WRITE;
- wq->write_cb = _file_wq_write_cb;
-
- rc = osmo_fd_register(&wq->bfd);
- if (rc < 0) {
- talloc_free(wq);
- log_target_destroy(target);
- return NULL;
- }
-
- target->tgt_file.wqueue = wq;
- target->raw_output = _file_raw_output;
target->tgt_file.fname = talloc_strdup(target, fname);
+ OSMO_ASSERT(target->tgt_file.fname);
+ target->raw_output = _file_raw_output;
+
+ fd = open(fname, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660);
+ if (fd < 0)
+ goto free_ret;
+
+ rc = _log_target_file_setup_iofd(target, fd);
+ if (rc < 0)
+ goto free_ret;
return target;
+
+free_ret:
+ log_target_destroy(target);
+ return NULL;
}
#endif
diff --git a/tests/logging/logging_test.c b/tests/logging/logging_test.c
index 3548b1d..27ad550 100644
--- a/tests/logging/logging_test.c
+++ b/tests/logging/logging_test.c
@@ -16,6 +16,7 @@
*/
#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
#include <osmocom/core/utils.h>
#include <stdlib.h>
@@ -138,5 +139,8 @@
log_set_print_filename_pos(stderr_target, LOG_FILENAME_POS_LINE_END);
DEBUGP(DLGLOBAL, "A message with source info printed last\n");
+ for (int i = 0; i < 20; i++)
+ osmo_select_main(1);
+
return 0;
}
diff --git a/tests/logging/logging_test.err b/tests/logging/logging_test.err
index 01ab878..fb5181c 100644
--- a/tests/logging/logging_test.err
+++ b/tests/logging/logging_test.err
@@ -7,5 +7,5 @@
DLGLOBAL You should see this on DLGLOBAL (d)
DLGLOBAL You should see this on DLGLOBAL (e)
DLGLOBAL You should see this (DLGLOBAL on DEBUG)
-DLGLOBAL logging_test.c:137 A message with source info printed first
-DLGLOBAL A message with source info printed last (logging_test.c:139)
+DLGLOBAL logging_test.c:138 A message with source info printed first
+DLGLOBAL A message with source info printed last (logging_test.c:140)
--
To view, visit https://gerrit.osmocom.org/c/libosmocore/+/41925?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: libosmocore
Gerrit-Branch: master
Gerrit-Change-Id: Ieb6420246454ef59442b1fd7b1d14e2c00fa69a5
Gerrit-Change-Number: 41925
Gerrit-PatchSet: 17
Gerrit-Owner: pespin <[email protected]>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <[email protected]>
Gerrit-Reviewer: jolly <[email protected]>
Gerrit-Reviewer: laforge <[email protected]>
Gerrit-Reviewer: osmith <[email protected]>
Gerrit-Reviewer: pespin <[email protected]>