[v3,1/1] timer: fix resource leak in finalize
Checks
Commit Message
Currently, whenever timer library is initialized, the memory
is leaked because there is no telling when primary or secondary
processes get to use the state, and there is no way to
initialize/deinitialize timer library state without race
conditions [1] because the data itself must live in shared memory.
Add a spinlock to the shared mem config to have a way to
exclusively initialize/deinitialize the timer library without
any races, and implement the synchronization mechanism based
on this lock in the timer library.
Also, update the API doc. Note that the behavior of the API
itself did not change - the requirement to call init in every
process was simply not documented explicitly.
Fixes: c0749f7096c7 ("timer: allow management in shared memory")
[1] See the following email thread:
https://mails.dpdk.org/archives/dev/2019-May/131498.html
Signed-off-by: Erik Gabriel Carrillo <erik.g.carrillo@intel.com>
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
Acked-by: Erik Gabriel Carrillo <erik.g.carrillo@intel.com>
Reviewed-by: David Marchand <david.marchand@redhat.com>
---
lib/librte_eal/common/eal_common_mcfg.c | 14 +++++++
lib/librte_eal/common/eal_memcfg.h | 2 +
.../common/include/rte_eal_memconfig.h | 22 ++++++++++
lib/librte_eal/rte_eal_version.map | 2 +
lib/librte_timer/rte_timer.c | 41 +++++++++++++------
lib/librte_timer/rte_timer.h | 5 ++-
6 files changed, 71 insertions(+), 15 deletions(-)
Comments
05/07/2019 19:22, Anatoly Burakov:
> Currently, whenever timer library is initialized, the memory
> is leaked because there is no telling when primary or secondary
> processes get to use the state, and there is no way to
> initialize/deinitialize timer library state without race
> conditions [1] because the data itself must live in shared memory.
>
> Add a spinlock to the shared mem config to have a way to
> exclusively initialize/deinitialize the timer library without
> any races, and implement the synchronization mechanism based
> on this lock in the timer library.
>
> Also, update the API doc. Note that the behavior of the API
> itself did not change - the requirement to call init in every
> process was simply not documented explicitly.
>
> Fixes: c0749f7096c7 ("timer: allow management in shared memory")
>
> [1] See the following email thread:
> https://mails.dpdk.org/archives/dev/2019-May/131498.html
>
> Signed-off-by: Erik Gabriel Carrillo <erik.g.carrillo@intel.com>
> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> Acked-by: Erik Gabriel Carrillo <erik.g.carrillo@intel.com>
> Reviewed-by: David Marchand <david.marchand@redhat.com>
Applied, thanks
@@ -147,3 +147,17 @@ rte_mcfg_mempool_write_unlock(void)
struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
rte_rwlock_write_unlock(&mcfg->mplock);
}
+
+void
+rte_mcfg_timer_lock(void)
+{
+ struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
+ rte_spinlock_lock(&mcfg->tlock);
+}
+
+void
+rte_mcfg_timer_unlock(void)
+{
+ struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
+ rte_spinlock_unlock(&mcfg->tlock);
+}
@@ -11,6 +11,7 @@
#include <rte_memory.h>
#include <rte_memzone.h>
#include <rte_pause.h>
+#include <rte_spinlock.h>
#include <rte_rwlock.h>
#include <rte_tailq.h>
@@ -36,6 +37,7 @@ struct rte_mem_config {
rte_rwlock_t mlock; /**< used by memzones for thread safety. */
rte_rwlock_t qlock; /**< used by tailqs for thread safety. */
rte_rwlock_t mplock; /**< used by mempool library for thread safety. */
+ rte_spinlock_t tlock; /**< used by timer library for thread safety. */
rte_rwlock_t memory_hotplug_lock;
/**< Indicates whether memory hotplug request is in progress. */
@@ -5,6 +5,8 @@
#ifndef _RTE_EAL_MEMCONFIG_H_
#define _RTE_EAL_MEMCONFIG_H_
+#include <rte_compat.h>
+
/**
* @file
*
@@ -87,6 +89,26 @@ rte_mcfg_mempool_write_lock(void);
void
rte_mcfg_mempool_write_unlock(void);
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Lock the internal EAL Timer Library lock for exclusive access.
+ */
+__rte_experimental
+void
+rte_mcfg_timer_lock(void);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Unlock the internal EAL Timer Library lock for exclusive access.
+ */
+__rte_experimental
+void
+rte_mcfg_timer_unlock(void);
+
#ifdef __cplusplus
}
#endif
@@ -405,4 +405,6 @@ EXPERIMENTAL {
# added in 19.08
rte_lcore_cpuset;
rte_lcore_to_cpu_id;
+ rte_mcfg_timer_lock;
+ rte_mcfg_timer_unlock;
};
@@ -13,6 +13,7 @@
#include <rte_atomic.h>
#include <rte_common.h>
#include <rte_cycles.h>
+#include <rte_eal_memconfig.h>
#include <rte_per_lcore.h>
#include <rte_memory.h>
#include <rte_launch.h>
@@ -61,6 +62,8 @@ struct rte_timer_data {
};
#define RTE_MAX_DATA_ELS 64
+static const struct rte_memzone *rte_timer_data_mz;
+static int *volatile rte_timer_mz_refcnt;
static struct rte_timer_data *rte_timer_data_arr;
static const uint32_t default_data_id;
static uint32_t rte_timer_subsystem_initialized;
@@ -157,28 +160,30 @@ rte_timer_subsystem_init_v1905(void)
int i, lcore_id;
static const char *mz_name = "rte_timer_mz";
const size_t data_arr_size =
- RTE_MAX_DATA_ELS * sizeof(*rte_timer_data_arr);
+ RTE_MAX_DATA_ELS * sizeof(*rte_timer_data_arr);
+ const size_t mem_size = data_arr_size + sizeof(*rte_timer_mz_refcnt);
bool do_full_init = true;
if (rte_timer_subsystem_initialized)
return -EALREADY;
-reserve:
- rte_errno = 0;
- mz = rte_memzone_reserve_aligned(mz_name, data_arr_size, SOCKET_ID_ANY,
- 0, RTE_CACHE_LINE_SIZE);
+ rte_mcfg_timer_lock();
+
+ mz = rte_memzone_lookup(mz_name);
if (mz == NULL) {
- if (rte_errno == EEXIST) {
- mz = rte_memzone_lookup(mz_name);
- if (mz == NULL)
- goto reserve;
-
- do_full_init = false;
- } else
+ mz = rte_memzone_reserve_aligned(mz_name, mem_size,
+ SOCKET_ID_ANY, 0, RTE_CACHE_LINE_SIZE);
+ if (mz == NULL) {
+ rte_mcfg_timer_unlock();
return -ENOMEM;
- }
+ }
+ do_full_init = true;
+ } else
+ do_full_init = false;
+ rte_timer_data_mz = mz;
rte_timer_data_arr = mz->addr;
+ rte_timer_mz_refcnt = (void *)((char *)mz->addr + data_arr_size);
if (do_full_init) {
for (i = 0; i < RTE_MAX_DATA_ELS; i++) {
@@ -195,6 +200,9 @@ rte_timer_subsystem_init_v1905(void)
}
rte_timer_data_arr[default_data_id].internal_flags |= FL_ALLOCATED;
+ (*rte_timer_mz_refcnt)++;
+
+ rte_mcfg_timer_unlock();
rte_timer_subsystem_initialized = 1;
@@ -210,6 +218,13 @@ rte_timer_subsystem_finalize(void)
if (!rte_timer_subsystem_initialized)
return;
+ rte_mcfg_timer_lock();
+
+ if (--(*rte_timer_mz_refcnt) == 0)
+ rte_memzone_free(rte_timer_data_mz);
+
+ rte_mcfg_timer_unlock();
+
rte_timer_subsystem_initialized = 0;
}
@@ -172,10 +172,11 @@ int rte_timer_data_dealloc(uint32_t id);
* Initializes internal variables (list, locks and so on) for the RTE
* timer library.
*
+ * @note
+ * This function must be called in every process before using the library.
+ *
* @return
* - 0: Success
- * - -EEXIST: Returned in secondary process when primary process has not
- * yet initialized the timer subsystem
* - -ENOMEM: Unable to allocate memory needed to initialize timer
* subsystem
*/