From 7b4a05d535f0ecc070e9b506ad0a667e24b67f71 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Mon, 18 Mar 2019 17:17:43 +0100 Subject: context: Add support for [per-thread] global talloc contexts Rather than having applications maintain their own talloc cotexts, let's offer some root talloc contexts in libosmocore. Let's also make them per thread right from the beginning. This will help some multi-threaded applications to use talloc in a thread-safe way. Change-Id: Iae39cd57274bf6753ecaf186f229e582b42662e3 --- include/osmocom/core/select.h | 1 + include/osmocom/core/talloc.h | 28 +++++++++++++-- src/Makefile.am | 2 +- src/context.c | 52 +++++++++++++++++++++++++++ src/select.c | 41 ++++++++++++++++++--- tests/Makefile.am | 5 +++ tests/context/context_test.c | 84 +++++++++++++++++++++++++++++++++++++++++++ tests/context/context_test.ok | 4 +++ tests/testsuite.at | 6 ++++ 9 files changed, 214 insertions(+), 9 deletions(-) create mode 100644 src/context.c create mode 100644 tests/context/context_test.c create mode 100644 tests/context/context_test.ok diff --git a/include/osmocom/core/select.h b/include/osmocom/core/select.h index e4787b09..a200b6f3 100644 --- a/include/osmocom/core/select.h +++ b/include/osmocom/core/select.h @@ -51,6 +51,7 @@ int osmo_fd_register(struct osmo_fd *fd); void osmo_fd_unregister(struct osmo_fd *fd); void osmo_fd_close(struct osmo_fd *fd); int osmo_select_main(int polling); +int osmo_select_main_ctx(int polling); struct osmo_fd *osmo_fd_get_by_fd(int fd); diff --git a/include/osmocom/core/talloc.h b/include/osmocom/core/talloc.h index 191a463f..c68a56cf 100644 --- a/include/osmocom/core/talloc.h +++ b/include/osmocom/core/talloc.h @@ -1,5 +1,27 @@ -/*! \file talloc.h - * Convenience wrapper. libosmocore used to ship its own internal copy of - * talloc, before libtalloc became a standard component on most systems */ +/*! \file talloc.h */ #pragma once #include + +/*! per-thread talloc contexts. This works around the problem that talloc is not + * thread-safe. However, one can simply have a different set of talloc contexts for each + * thread, and ensure that allocations made on one thread are always only free'd on that + * very same thread. + * WARNING: Users must make sure they free() on the same thread as they allocate!! */ +struct osmo_talloc_contexts { + /*! global per-thread talloc context. */ + void *global; + /*! volatile select-dispatch context. This context is completely free'd and + * re-created every time the main select loop in osmo_select_main() returns from + * select(2) and calls per-fd callback functions. This allows users of this + * facility to allocate temporary objects like string buffers, message buffers + * and the like which are automatically free'd when going into the next select() + * system call */ + void *select; +}; + +extern __thread struct osmo_talloc_contexts *osmo_ctx; + +/* short-hand #defines for the osmo talloc contexts (OTC) that can be used to pass + * to the various _c functions like msgb_alloc_c() */ +#define OTC_GLOBAL (osmo_ctx->global) +#define OTC_SELECT (osmo_ctx->select) diff --git a/src/Makefile.am b/src/Makefile.am index e65e0c97..714b5eac 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,7 +13,7 @@ endif lib_LTLIBRARIES = libosmocore.la libosmocore_la_LIBADD = $(BACKTRACE_LIB) $(TALLOC_LIBS) $(LIBRARY_RT) -libosmocore_la_SOURCES = timer.c timer_gettimeofday.c timer_clockgettime.c \ +libosmocore_la_SOURCES = context.c timer.c timer_gettimeofday.c timer_clockgettime.c \ select.c signal.c msgb.c bits.c \ bitvec.c bitcomp.c counter.c fsm.c \ write_queue.c utils.c socket.c \ diff --git a/src/context.c b/src/context.c new file mode 100644 index 00000000..bad012bd --- /dev/null +++ b/src/context.c @@ -0,0 +1,52 @@ +/*! \file context.c + * talloc context handling. + * + * (C) 2019 by Harald Welte + * All Rights Reserverd. + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#include +#include +#include +#include + +__thread struct osmo_talloc_contexts *osmo_ctx; + +int osmo_ctx_init(const char *id) +{ + osmo_ctx = talloc_named(NULL, sizeof(*osmo_ctx), "global-%s", id); + if (!osmo_ctx) + return -ENOMEM; + memset(osmo_ctx, 0, sizeof(*osmo_ctx)); + osmo_ctx->global = osmo_ctx; + osmo_ctx->select = talloc_named_const(osmo_ctx->global, 0, "select"); + if (!osmo_ctx->select) { + talloc_free(osmo_ctx); + return -ENOMEM; + } + return 0; +} + +/* initialize osmo_ctx on main tread */ +static __attribute__((constructor)) void on_dso_load_ctx(void) +{ + OSMO_ASSERT(osmo_ctx_init("main") == 0); +} + +/*! @} */ diff --git a/src/select.c b/src/select.c index 7ce135f5..34a133c0 100644 --- a/src/select.c +++ b/src/select.c @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include "../config.h" @@ -233,11 +235,7 @@ restart: return work; } -/*! select main loop integration - * \param[in] polling should we pollonly (1) or block on select (0) - * \returns 0 if no fd handled; 1 if fd handled; negative in case of error - */ -int osmo_select_main(int polling) +static int _osmo_select_main(int polling) { fd_set readset, writeset, exceptset; int rc; @@ -259,10 +257,43 @@ int osmo_select_main(int polling) /* fire timers */ osmo_timers_update(); + OSMO_ASSERT(osmo_ctx->select); + /* call registered callback functions */ return osmo_fd_disp_fds(&readset, &writeset, &exceptset); } +/*! select main loop integration + * \param[in] polling should we pollonly (1) or block on select (0) + * \returns 0 if no fd handled; 1 if fd handled; negative in case of error + */ +int osmo_select_main(int polling) +{ + int rc = _osmo_select_main(polling); +#ifndef EMBEDDED + if (talloc_total_size(osmo_ctx->select) != 0) { + LOGP(DLGLOBAL, LOGL_FATAL, "You cannot use the 'select' volatile " + "context if you don't use osmo_select_main_ctx()!\n"); + OSMO_ASSERT(0); + } +#endif + return rc; +} + +#ifndef EMBEDDED +/*! select main loop integration with temporary select-dispatch talloc context + * \param[in] polling should we pollonly (1) or block on select (0) + * \returns 0 if no fd handled; 1 if fd handled; negative in case of error + */ +int osmo_select_main_ctx(int polling) +{ + int rc = _osmo_select_main(polling); + /* free all the children of the volatile 'select' scope context */ + talloc_free_children(osmo_ctx->select); + return rc; +} +#endif + /*! find an osmo_fd based on the integer fd * \param[in] fd file descriptor to use as search key * \returns \ref osmo_fd for \ref fd; NULL in case it doesn't exist */ diff --git a/tests/Makefile.am b/tests/Makefile.am index a8a06c53..92edf752 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -32,6 +32,7 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \ tdef/tdef_vty_test_dynamic \ sockaddr_str/sockaddr_str_test \ use_count/use_count_test \ + context/context_test \ $(NULL) if ENABLE_MSGFILE @@ -251,6 +252,9 @@ sockaddr_str_sockaddr_str_test_LDADD = $(LDADD) use_count_use_count_test_SOURCES = use_count/use_count_test.c use_count_use_count_test_LDADD = $(LDADD) +context_context_test_SOURCES = context/context_test.c +context_context_test_LDADD = $(LDADD) + # The `:;' works around a Bash 3.2 bug when the output is not writeable. $(srcdir)/package.m4: $(top_srcdir)/configure.ac :;{ \ @@ -322,6 +326,7 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \ tdef/tdef_vty_test_dynamic.vty \ sockaddr_str/sockaddr_str_test.ok \ use_count/use_count_test.ok use_count/use_count_test.err \ + context/context_test.ok \ $(NULL) DISTCLEANFILES = atconfig atlocal conv/gsm0503_test_vectors.c diff --git a/tests/context/context_test.c b/tests/context/context_test.c new file mode 100644 index 00000000..e9a55593 --- /dev/null +++ b/tests/context/context_test.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +static struct osmo_fd g_evfd; + +static void *alloc_res_select; +static void *alloc_res_global; + +static int destructor_called; + +static int talloc_destructor(void *ptr) +{ + printf("destructor was called automatically\n"); + /* ensure the destructor is only called for the chunk allocated from the + * volatile select context */ + OSMO_ASSERT(ptr == alloc_res_select); + destructor_called += 1; + return 0; +} + +static int evfd_cb(struct osmo_fd *ofd, unsigned int what) +{ + uint64_t rval; + int rc; + + rc = read(ofd->fd, &rval, sizeof(rval)); + OSMO_ASSERT(rc == sizeof(rval)); + + printf("allocating from select context\n"); + alloc_res_select = talloc_named_const(OTC_SELECT, 23, "alloc_select"); + OSMO_ASSERT(alloc_res_select); + talloc_set_destructor(alloc_res_select, talloc_destructor); + + printf("allocating from global context\n"); + alloc_res_global = talloc_named_const(OTC_GLOBAL, 42, "alloc_global"); + OSMO_ASSERT(alloc_res_global); + talloc_set_destructor(alloc_res_global, talloc_destructor); + return 0; +} + +const struct log_info_cat default_categories[] = { +}; + +static struct log_info info = { + .cat = default_categories, + .num_cat = ARRAY_SIZE(default_categories), +}; + +int main(int argc, char **argv) +{ + int rc; + + osmo_init_logging2(OTC_GLOBAL, &info); + + rc = eventfd(0, 0); + OSMO_ASSERT(rc >= 0); + osmo_fd_setup(&g_evfd, rc, OSMO_FD_READ, evfd_cb, NULL, 0); + osmo_fd_register(&g_evfd); + + /* make sure the select loop will immediately call the callback */ + uint64_t val = 1; + rc = write(g_evfd.fd, &val, sizeof(val)); + OSMO_ASSERT(rc == sizeof(val)); + + /* enter osmo_select_main_ctx() once */ + printf("entering osmo_select_main\n"); + osmo_select_main_ctx(1); + + /* the allocation must have happened, and the destructor must have been called + * automatically exactly once */ + OSMO_ASSERT(destructor_called == 1); + + exit(0); +} diff --git a/tests/context/context_test.ok b/tests/context/context_test.ok new file mode 100644 index 00000000..3984a397 --- /dev/null +++ b/tests/context/context_test.ok @@ -0,0 +1,4 @@ +entering osmo_select_main +allocating from select context +allocating from global context +destructor was called automatically diff --git a/tests/testsuite.at b/tests/testsuite.at index 0fc96467..a043f0c7 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -350,3 +350,9 @@ cat $abs_srcdir/use_count/use_count_test.ok > expout cat $abs_srcdir/use_count/use_count_test.err > experr AT_CHECK([$abs_top_builddir/tests/use_count/use_count_test], [0], [expout], [experr]) AT_CLEANUP + +AT_SETUP([context]) +AT_KEYWORDS([context]) +cat $abs_srcdir/context/context_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/context/context_test], [0], [expout], [ignore]) +AT_CLEANUP -- cgit v1.2.3