| /***************************************************************************** |
| * list.c |
| ***************************************************************************** |
| * Copyright (C) 2001-2002 The Regents of the University of California. |
| * Copyright (C) 2021 NVIDIA Corporation. |
| * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). |
| * Written by Chris Dunlap <cdunlap@llnl.gov>. |
| * |
| * This file is from LSD-Tools, the LLNL Software Development Toolbox. |
| * |
| * LSD-Tools 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. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * LSD-Tools 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 LSD-Tools; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| ***************************************************************************** |
| * Refer to "list.h" for documentation on public functions. |
| *****************************************************************************/ |
| |
| #include "config.h" |
| |
| #include <pthread.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "src/common/list.h" |
| #include "src/common/log.h" |
| #include "src/common/macros.h" |
| #include "src/common/xassert.h" |
| #include "src/common/xmalloc.h" |
| |
| /* |
| ** Define slurm-specific aliases for use by plugins, see slurm_xlator.h |
| ** for details. |
| */ |
| strong_alias(list_create, slurm_list_create); |
| strong_alias(list_destroy, slurm_list_destroy); |
| strong_alias(list_is_empty, slurm_list_is_empty); |
| strong_alias(list_count, slurm_list_count); |
| strong_alias(list_shallow_copy, slurm_list_shallow_copy); |
| strong_alias(list_append, slurm_list_append); |
| strong_alias(list_append_list, slurm_list_append_list); |
| strong_alias(list_transfer, slurm_list_transfer); |
| strong_alias(list_transfer_max, slurm_list_transfer_max); |
| strong_alias(list_transfer_unique, slurm_list_transfer_unique); |
| strong_alias(list_push, list_prepend); |
| strong_alias(list_push, slurm_list_prepend); |
| strong_alias(list_find_first, slurm_list_find_first); |
| strong_alias(list_find_first_ro, slurm_list_find_first_ro); |
| strong_alias(list_delete_all, slurm_list_delete_all); |
| strong_alias(list_delete_first, slurm_list_delete_first); |
| strong_alias(list_delete_ptr, slurm_list_delete_ptr); |
| strong_alias(list_for_each, slurm_list_for_each); |
| strong_alias(list_for_each_ro, slurm_list_for_each_ro); |
| strong_alias(list_for_each_max, slurm_list_for_each_max); |
| strong_alias(list_flush, slurm_list_flush); |
| strong_alias(list_flush_max, slurm_list_flush_max); |
| strong_alias(list_sort, slurm_list_sort); |
| strong_alias(list_flip, slurm_list_flip); |
| strong_alias(list_push, slurm_list_push); |
| strong_alias(list_pop, slurm_list_pop); |
| strong_alias(list_peek, slurm_list_peek); |
| strong_alias(list_append, list_enqueue); |
| strong_alias(list_append, slurm_list_enqueue); |
| strong_alias(list_pop, list_dequeue); |
| strong_alias(list_pop, slurm_list_dequeue); |
| strong_alias(list_iterator_create, slurm_list_iterator_create); |
| strong_alias(list_iterator_reset, slurm_list_iterator_reset); |
| strong_alias(list_iterator_destroy, slurm_list_iterator_destroy); |
| strong_alias(list_next, slurm_list_next); |
| strong_alias(list_insert, slurm_list_insert); |
| strong_alias(list_find, slurm_list_find); |
| strong_alias(list_remove, slurm_list_remove); |
| strong_alias(list_remove_first, slurm_list_remove_first); |
| strong_alias(list_delete_item, slurm_list_delete_item); |
| |
| /*************** |
| * Constants * |
| ***************/ |
| #define LIST_MAGIC 0xDEADBEEF |
| #define LIST_ITR_MAGIC 0xDEADBEFF |
| |
| /**************** |
| * Data Types * |
| ****************/ |
| |
| typedef struct listNode { |
| void *data; /* node's data */ |
| struct listNode *next; /* next node in list */ |
| } list_node_t; |
| |
| struct listIterator { |
| unsigned int magic; /* sentinel for asserting validity */ |
| list_t *list; /* the list being iterated */ |
| list_node_t *pos; /* the next node to be iterated */ |
| list_node_t **prev; /* addr of 'next' ptr to prv It node */ |
| list_itr_t *iNext; /* iterator chain for list_destroy() */ |
| }; |
| |
| struct xlist { |
| unsigned int magic; /* sentinel for asserting validity */ |
| int count; /* number of nodes in list */ |
| list_node_t *head; /* head of the list */ |
| list_node_t **tail; /* addr of last node's 'next' ptr */ |
| list_itr_t *iNext; /* iterator chain for list_destroy() */ |
| ListDelF fDel; /* function to delete node data */ |
| pthread_rwlock_t mutex; /* mutex to protect access to list */ |
| pthread_t tid; /* id of thread holding write lock */ |
| list_node_t *free_nodes; /* head of unused nodes */ |
| list_node_t *node_allocations; /* memory for additional nodes */ |
| list_node_t nodes[]; /* THIS MUST BE THE LAST PART OF THE STRUCT */ |
| }; |
| |
| /* |
| * Fix as many list_node_t elements into a 4k page alongside the list as |
| * possible. Leave at least 32 bytes off to account for xmalloc() and malloc() |
| * overhead. |
| */ |
| static const int nodes_in_page = (4064 - sizeof(list_t)) / sizeof(list_node_t); |
| |
| /**************** |
| * Macros |
| ****************/ |
| #define LIST_THREAD_LOCK_DEF pthread_t tid = pthread_self(); bool locked = false; |
| |
| #define LIST_THREAD_LOCK(l, write_lock) \ |
| do { \ |
| /* \ |
| * If I am on the same thread and already locked, \ |
| * don't lock again. \ |
| */ \ |
| if (l->tid != tid) { \ |
| if (write_lock) { \ |
| slurm_rwlock_wrlock(&l->mutex); \ |
| /* Only set tid under a write lock */ \ |
| l->tid = tid; \ |
| } else { \ |
| slurm_rwlock_rdlock(&l->mutex); \ |
| xassert(!l->tid); \ |
| } \ |
| locked = true; \ |
| } else { \ |
| debug3("%s: list lock already held by this thread", \ |
| __func__); \ |
| } \ |
| } while (0) |
| |
| #define LIST_THREAD_UNLOCK(l, write_lock) \ |
| do { \ |
| if (locked) { \ |
| /* Only clear tid under a write lock */ \ |
| if (write_lock) \ |
| l->tid = 0; \ |
| slurm_rwlock_unlock(&l->mutex); \ |
| } \ |
| } while (0) |
| |
| /**************** |
| * Prototypes * |
| ****************/ |
| |
| static void _list_node_create(list_t *l, list_node_t **pp, void *x); |
| static void *_list_node_destroy(list_t *l, list_node_t **pp); |
| static void *_list_find_first_locked(list_t *l, ListFindF f, void *key); |
| |
| #ifndef NDEBUG |
| static int _list_mutex_is_locked(pthread_rwlock_t *mutex); |
| #endif |
| |
| /*************** |
| * Functions * |
| ***************/ |
| |
| extern list_t *list_create(ListDelF f) |
| { |
| list_t *l = xmalloc(sizeof(*l) + (nodes_in_page * sizeof(list_node_t))); |
| |
| l->magic = LIST_MAGIC; |
| l->head = NULL; |
| l->tail = &l->head; |
| l->iNext = NULL; |
| l->fDel = f; |
| l->count = 0; |
| slurm_rwlock_init(&l->mutex); |
| |
| l->node_allocations = NULL; |
| l->free_nodes = &l->nodes[0]; |
| for (int i = 0; i < (nodes_in_page - 1); i++) |
| l->nodes[i].next = &l->nodes[i + 1]; |
| |
| return l; |
| } |
| |
| extern void list_destroy(list_t *l) |
| { |
| list_itr_t *i, *iTmp; |
| list_node_t *p, *pTmp; |
| |
| xassert(l != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&l->mutex); |
| |
| i = l->iNext; |
| while (i) { |
| xassert(i->magic == LIST_ITR_MAGIC); |
| i->magic = ~LIST_ITR_MAGIC; |
| iTmp = i->iNext; |
| xfree(i); |
| i = iTmp; |
| } |
| p = l->head; |
| while (p) { |
| if (p->data && l->fDel) |
| l->fDel(p->data); |
| p = p->next; |
| } |
| p = l->node_allocations; |
| while (p) { |
| pTmp = p->next; |
| xfree(p); |
| p = pTmp; |
| } |
| l->magic = ~LIST_MAGIC; |
| slurm_rwlock_unlock(&l->mutex); |
| slurm_rwlock_destroy(&l->mutex); |
| xfree(l); |
| } |
| |
| extern int list_is_empty(list_t *l) |
| { |
| int n; |
| |
| xassert(l != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_rdlock(&l->mutex); |
| n = l->count; |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return (n == 0); |
| } |
| |
| /* |
| * Return the number of items in list [l]. |
| * If [l] is NULL, return 0. |
| */ |
| extern int list_count(list_t *l) |
| { |
| int n; |
| LIST_THREAD_LOCK_DEF; |
| |
| if (!l) |
| return 0; |
| |
| xassert(l->magic == LIST_MAGIC); |
| LIST_THREAD_LOCK(l, false); |
| n = l->count; |
| LIST_THREAD_UNLOCK(l, false); |
| |
| return n; |
| } |
| |
| extern list_t *list_shallow_copy(list_t *l) |
| { |
| list_t *m = list_create(NULL); |
| |
| (void) list_append_list(m, l); |
| |
| return m; |
| } |
| |
| extern void list_append(list_t *l, void *x) |
| { |
| LIST_THREAD_LOCK_DEF; |
| |
| xassert(l != NULL); |
| xassert(x != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| LIST_THREAD_LOCK(l, true); |
| _list_node_create(l, l->tail, x); |
| LIST_THREAD_UNLOCK(l, true); |
| } |
| |
| extern int list_append_list(list_t *l, list_t *sub) |
| { |
| int n = 0; |
| list_node_t *p; |
| |
| xassert(l != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| xassert(l->fDel == NULL); |
| xassert(sub != NULL); |
| xassert(sub->magic == LIST_MAGIC); |
| |
| slurm_rwlock_wrlock(&l->mutex); |
| slurm_rwlock_wrlock(&sub->mutex); |
| p = sub->head; |
| while (p) { |
| _list_node_create(l, l->tail, p->data); |
| n++; |
| p = p->next; |
| } |
| |
| slurm_rwlock_unlock(&sub->mutex); |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return n; |
| } |
| |
| /* |
| * Pops off list [sub] to [l] with maximum number of entries. |
| * Set max = 0 to transfer all entries. |
| * Note: list [l] must have the same destroy function as list [sub]. |
| * Note: list [sub] may be returned empty, but not destroyed. |
| * Returns a count of the number of items added to list [l]. |
| */ |
| extern int list_transfer_max(list_t *l, list_t *sub, int max) |
| { |
| void *v; |
| int n = 0; |
| |
| xassert(l); |
| xassert(sub); |
| xassert(l->magic == LIST_MAGIC); |
| xassert(sub->magic == LIST_MAGIC); |
| xassert(l->fDel == sub->fDel); |
| |
| slurm_rwlock_wrlock(&l->mutex); |
| slurm_rwlock_wrlock(&sub->mutex); |
| while ((!max || n <= max) && (v = _list_node_destroy(sub, &sub->head))) { |
| _list_node_create(l, l->tail, v); |
| n++; |
| } |
| slurm_rwlock_unlock(&sub->mutex); |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return n; |
| } |
| |
| extern int list_transfer_match(list_t *l, list_t *sub, ListFindF f, void *key) |
| { |
| list_node_t **pp; |
| void *v; |
| int n = 0; |
| |
| xassert(l); |
| xassert(sub); |
| xassert(l != sub); |
| xassert(l->magic == LIST_MAGIC); |
| xassert(sub->magic == LIST_MAGIC); |
| xassert(l->fDel == sub->fDel); |
| |
| slurm_rwlock_wrlock(&l->mutex); |
| slurm_rwlock_wrlock(&sub->mutex); |
| |
| pp = &l->head; |
| while (*pp) { |
| if (f((*pp)->data, key)) { |
| if ((v = _list_node_destroy(l, pp))) |
| n++; |
| |
| _list_node_create(sub, sub->tail, v); |
| } else { |
| pp = &(*pp)->next; |
| } |
| } |
| |
| slurm_rwlock_unlock(&sub->mutex); |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return n; |
| } |
| |
| /* |
| * Pops off list [sub] to [l]. |
| * Set max = 0 to transfer all entries. |
| * Note: list [l] must have the same destroy function as list [sub]. |
| * Note: list [sub] will be returned empty, but not destroyed. |
| * Returns a count of the number of items added to list [l]. |
| */ |
| extern int list_transfer(list_t *l, list_t *sub) |
| { |
| return list_transfer_max(l, sub, 0); |
| } |
| |
| /* |
| * Pop off elements in list [sub] to [l], unless already in [l]. |
| * Note: list [l] must have the same destroy function as list [sub]. |
| * Note: list [l] could contain repeated elements, but those aren't removed. |
| * Note: list [sub] will be returned with repeated elements or empty, |
| * but never destroyed. |
| * Returns a count of the number of items added to list [l]. |
| */ |
| extern int list_transfer_unique(list_t *l, ListFindF f, list_t *sub) |
| { |
| list_node_t **pp; |
| void *v; |
| int n = 0; |
| |
| xassert(l); |
| xassert(f); |
| xassert(sub); |
| xassert(l->magic == LIST_MAGIC); |
| xassert(sub->magic == LIST_MAGIC); |
| xassert(l->fDel == sub->fDel); |
| |
| slurm_rwlock_wrlock(&l->mutex); |
| slurm_rwlock_wrlock(&sub->mutex); |
| |
| pp = &sub->head; |
| while (*pp) { |
| v = (*pp)->data; |
| |
| /* Is this element already in destination list? */ |
| if (!_list_find_first_locked(l, f, v)) { |
| /* Not found: Transfer the element */ |
| _list_node_create(l, l->tail, v); |
| /* Destroy increases index */ |
| _list_node_destroy(sub, pp); |
| n++; |
| } else |
| /* Found: Just increase index */ |
| pp = &(*pp)->next; |
| } |
| |
| slurm_rwlock_unlock(&sub->mutex); |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return n; |
| } |
| |
| static void *_list_find_first_locked(list_t *l, ListFindF f, void *key) |
| { |
| for (list_node_t *p = l->head; p; p = p->next) { |
| if (f(p->data, key)) |
| return p->data; |
| } |
| |
| return NULL; |
| } |
| |
| static void *_list_find_first_lock(list_t *l, ListFindF f, void *key, |
| bool write_lock) |
| { |
| void *v = NULL; |
| LIST_THREAD_LOCK_DEF; |
| |
| xassert(l != NULL); |
| xassert(f != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| |
| LIST_THREAD_LOCK(l, write_lock); |
| |
| v = _list_find_first_locked(l, f, key); |
| |
| LIST_THREAD_UNLOCK(l, write_lock); |
| |
| return v; |
| } |
| |
| extern void *list_find_first(list_t *l, ListFindF f, void *key) |
| { |
| return _list_find_first_lock(l, f, key, true); |
| } |
| |
| /* |
| * Same as list_find_first, but use a rdlock instead of wrlock |
| */ |
| extern void *list_find_first_ro(list_t *l, ListFindF f, void *key) |
| { |
| return _list_find_first_lock(l, f, key, false); |
| } |
| |
| extern void *list_remove_first(list_t *l, ListFindF f, void *key) |
| { |
| list_node_t **pp; |
| void *v = NULL; |
| |
| xassert(l != NULL); |
| xassert(f != NULL); |
| xassert(key != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&l->mutex); |
| |
| pp = &l->head; |
| while (*pp) { |
| if (f((*pp)->data, key)) { |
| v = _list_node_destroy(l, pp); |
| break; |
| } else { |
| pp = &(*pp)->next; |
| } |
| } |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return v; |
| } |
| |
| extern int list_delete_all(list_t *l, ListFindF f, void *key) |
| { |
| list_node_t **pp; |
| void *v; |
| int n = 0; |
| |
| xassert(l != NULL); |
| xassert(f != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&l->mutex); |
| |
| pp = &l->head; |
| while (*pp) { |
| if (f((*pp)->data, key)) { |
| if ((v = _list_node_destroy(l, pp))) { |
| if (l->fDel) |
| l->fDel(v); |
| n++; |
| } |
| } |
| else { |
| pp = &(*pp)->next; |
| } |
| } |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return n; |
| } |
| |
| extern int list_delete_first(list_t *l, ListFindF f, void *key) |
| { |
| list_node_t **pp; |
| void *v; |
| int n = 0; |
| |
| xassert(l != NULL); |
| xassert(f != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&l->mutex); |
| |
| pp = &l->head; |
| while (*pp) { |
| int rc = f((*pp)->data, key); |
| |
| if (rc > 0) { |
| if ((v = _list_node_destroy(l, pp))) { |
| if (l->fDel) |
| l->fDel(v); |
| } |
| n = 1; |
| break; |
| } else if (rc < 0) { |
| n = -1; |
| break; |
| } else { |
| pp = &(*pp)->next; |
| } |
| } |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return n; |
| } |
| |
| extern int list_delete_ptr(list_t *l, void *key) |
| { |
| list_node_t **pp; |
| void *v; |
| int n = 0; |
| |
| xassert(l); |
| xassert(key); |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&l->mutex); |
| |
| pp = &l->head; |
| while (*pp) { |
| if ((*pp)->data == key) { |
| if ((v = _list_node_destroy(l, pp))) { |
| if (l->fDel) |
| l->fDel(v); |
| n = 1; |
| break; |
| } |
| } else |
| pp = &(*pp)->next; |
| } |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return n; |
| } |
| |
| extern int list_for_each(list_t *l, ListForF f, void *arg) |
| { |
| int max = -1; /* all values */ |
| return list_for_each_max(l, &max, f, arg, 1, true); |
| } |
| |
| extern int list_for_each_ro(list_t *l, ListForF f, void *arg) |
| { |
| int max = -1; /* all values */ |
| return list_for_each_max(l, &max, f, arg, 1, false); |
| } |
| |
| extern int list_for_each_nobreak(list_t *l, ListForF f, void *arg) |
| { |
| int max = -1; /* all values */ |
| return list_for_each_max(l, &max, f, arg, 0, true); |
| } |
| |
| extern int list_for_each_max(list_t *l, int *max, ListForF f, void *arg, |
| int break_on_fail, int write_lock) |
| { |
| list_node_t *p; |
| int n = 0; |
| bool failed = false; |
| LIST_THREAD_LOCK_DEF; |
| |
| xassert(l != NULL); |
| xassert(f != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| |
| LIST_THREAD_LOCK(l, write_lock); |
| |
| for (p = l->head; (*max == -1 || n < *max) && p; p = p->next) { |
| n++; |
| if (f(p->data, arg) < 0) { |
| failed = true; |
| if (break_on_fail) |
| break; |
| } |
| } |
| *max = l->count - n; |
| |
| LIST_THREAD_UNLOCK(l, write_lock); |
| |
| if (failed) |
| n = -n; |
| |
| return n; |
| } |
| |
| extern int list_flush(list_t *l) |
| { |
| return list_flush_max(l, -1); |
| } |
| |
| extern int list_flush_max(list_t *l, int max) |
| { |
| list_node_t **pp; |
| void *v; |
| int n = 0; |
| |
| xassert(l != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&l->mutex); |
| |
| pp = &l->head; |
| for (int i = 0; (max < 0 || i < max) && *pp; i++) { |
| if ((v = _list_node_destroy(l, pp))) { |
| if (l->fDel) |
| l->fDel(v); |
| n++; |
| } |
| } |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return n; |
| } |
| |
| extern void list_push(list_t *l, void *x) |
| { |
| xassert(l != NULL); |
| xassert(x != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&l->mutex); |
| _list_node_create(l, &l->head, x); |
| slurm_rwlock_unlock(&l->mutex); |
| } |
| |
| /* |
| * Handle translation between ListCmpF and signature required by qsort. |
| * glibc has this as __compar_fn_t, but that's non-standard so we define |
| * our own instead. |
| */ |
| typedef int (*ConstListCmpF) (__const void *, __const void *); |
| |
| /* |
| * This function uses the libC qsort(). |
| */ |
| extern void list_sort(list_t *l, ListCmpF f) |
| { |
| char **v; |
| int n; |
| int lsize; |
| void *e; |
| list_itr_t *i; |
| |
| xassert(l != NULL); |
| xassert(f != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&l->mutex); |
| |
| if (l->count <= 1) { |
| slurm_rwlock_unlock(&l->mutex); |
| return; |
| } |
| |
| lsize = l->count; |
| v = xmalloc(lsize * sizeof(char *)); |
| |
| n = 0; |
| while ((e = _list_node_destroy(l, &l->head))) { |
| v[n] = e; |
| ++n; |
| } |
| |
| qsort(v, n, sizeof(char *), (ConstListCmpF)f); |
| |
| for (n = 0; n < lsize; n++) { |
| _list_node_create(l, l->tail, v[n]); |
| } |
| |
| xfree(v); |
| |
| /* Reset all iterators on the list to point |
| * to the head of the list. |
| */ |
| for (i = l->iNext; i; i = i->iNext) { |
| xassert(i->magic == LIST_ITR_MAGIC); |
| i->pos = i->list->head; |
| i->prev = &i->list->head; |
| } |
| |
| slurm_rwlock_unlock(&l->mutex); |
| } |
| |
| /* |
| * list_flip - not called list_reverse due to collision with MariaDB |
| */ |
| extern void list_flip(list_t *l) |
| { |
| list_node_t *old_head, *prev = NULL, *curr, *next = NULL; |
| list_itr_t *i; |
| |
| xassert(l); |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&l->mutex); |
| |
| if (l->count <= 1) { |
| slurm_rwlock_unlock(&l->mutex); |
| return; |
| } |
| |
| old_head = curr = l->head; |
| while (curr) { |
| next = curr->next; |
| curr->next = prev; |
| prev = curr; |
| curr = next; |
| } |
| l->head = prev; |
| l->tail = &old_head->next; |
| |
| /* |
| * Reset all iterators on the list to point |
| * to the head of the list. |
| */ |
| for (i = l->iNext; i; i = i->iNext) { |
| xassert(i->magic == LIST_ITR_MAGIC); |
| i->pos = i->list->head; |
| i->prev = &i->list->head; |
| } |
| |
| slurm_rwlock_unlock(&l->mutex); |
| } |
| |
| extern void *list_pop(list_t *l) |
| { |
| void *v; |
| |
| xassert(l != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&l->mutex); |
| v = _list_node_destroy(l, &l->head); |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return v; |
| } |
| |
| extern void *list_peek(list_t *l) |
| { |
| void *v; |
| |
| xassert(l != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_rdlock(&l->mutex); |
| |
| v = (l->head) ? l->head->data : NULL; |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return v; |
| } |
| |
| /* list_enqueue() is aliased to list_append() */ |
| |
| /* list_dequeue() is aliased to list_pop() */ |
| |
| extern list_itr_t *list_iterator_create(list_t *l) |
| { |
| list_itr_t *i = xmalloc(sizeof(*i)); |
| |
| xassert(l != NULL); |
| |
| i->magic = LIST_ITR_MAGIC; |
| i->list = l; |
| xassert(l->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&l->mutex); |
| |
| i->pos = l->head; |
| i->prev = &l->head; |
| i->iNext = l->iNext; |
| l->iNext = i; |
| |
| slurm_rwlock_unlock(&l->mutex); |
| |
| return i; |
| } |
| |
| extern void list_iterator_reset(list_itr_t *i) |
| { |
| xassert(i != NULL); |
| xassert(i->magic == LIST_ITR_MAGIC); |
| xassert(i->list->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&i->list->mutex); |
| |
| i->pos = i->list->head; |
| i->prev = &i->list->head; |
| |
| slurm_rwlock_unlock(&i->list->mutex); |
| } |
| |
| extern void list_iterator_destroy(list_itr_t *i) |
| { |
| list_itr_t **pi; |
| |
| xassert(i != NULL); |
| xassert(i->magic == LIST_ITR_MAGIC); |
| xassert(i->list->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&i->list->mutex); |
| |
| for (pi = &i->list->iNext; *pi; pi = &(*pi)->iNext) { |
| xassert((*pi)->magic == LIST_ITR_MAGIC); |
| if (*pi == i) { |
| *pi = (*pi)->iNext; |
| break; |
| } |
| } |
| slurm_rwlock_unlock(&i->list->mutex); |
| |
| i->magic = ~LIST_ITR_MAGIC; |
| xfree(i); |
| } |
| |
| static void *_list_next_locked(list_itr_t *i) |
| { |
| list_node_t *p; |
| |
| if ((p = i->pos)) |
| i->pos = p->next; |
| if (*i->prev != p) |
| i->prev = &(*i->prev)->next; |
| |
| return (p ? p->data : NULL); |
| } |
| |
| extern void *list_next(list_itr_t *i) |
| { |
| void *rc; |
| |
| xassert(i != NULL); |
| xassert(i->magic == LIST_ITR_MAGIC); |
| xassert(i->list->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&i->list->mutex); |
| |
| rc = _list_next_locked(i); |
| |
| slurm_rwlock_unlock(&i->list->mutex); |
| |
| return rc; |
| } |
| |
| extern void *list_peek_next(list_itr_t *i) |
| { |
| list_node_t *p; |
| |
| xassert(i != NULL); |
| xassert(i->magic == LIST_ITR_MAGIC); |
| xassert(i->list->magic == LIST_MAGIC); |
| slurm_rwlock_rdlock(&i->list->mutex); |
| |
| p = i->pos; |
| |
| slurm_rwlock_unlock(&i->list->mutex); |
| |
| return (p ? p->data : NULL); |
| } |
| |
| extern void list_insert(list_itr_t *i, void *x) |
| { |
| xassert(i != NULL); |
| xassert(x != NULL); |
| xassert(i->magic == LIST_ITR_MAGIC); |
| xassert(i->list->magic == LIST_MAGIC); |
| |
| slurm_rwlock_wrlock(&i->list->mutex); |
| _list_node_create(i->list, i->prev, x); |
| slurm_rwlock_unlock(&i->list->mutex); |
| } |
| |
| extern void *list_find(list_itr_t *i, ListFindF f, void *key) |
| { |
| void *v; |
| |
| xassert(i != NULL); |
| xassert(f != NULL); |
| xassert(key != NULL); |
| xassert(i->magic == LIST_ITR_MAGIC); |
| |
| slurm_rwlock_wrlock(&i->list->mutex); |
| xassert(i->list->magic == LIST_MAGIC); |
| |
| while ((v = _list_next_locked(i)) && !f(v, key)) {;} |
| |
| slurm_rwlock_unlock(&i->list->mutex); |
| |
| return v; |
| } |
| |
| extern void *list_remove(list_itr_t *i) |
| { |
| void *v = NULL; |
| |
| xassert(i != NULL); |
| xassert(i->magic == LIST_ITR_MAGIC); |
| xassert(i->list->magic == LIST_MAGIC); |
| slurm_rwlock_wrlock(&i->list->mutex); |
| |
| if (*i->prev != i->pos) |
| v = _list_node_destroy(i->list, i->prev); |
| slurm_rwlock_unlock(&i->list->mutex); |
| |
| return v; |
| } |
| |
| extern int list_delete_item(list_itr_t *i) |
| { |
| void *v; |
| |
| xassert(i != NULL); |
| xassert(i->magic == LIST_ITR_MAGIC); |
| |
| if ((v = list_remove(i))) { |
| if (i->list->fDel) |
| i->list->fDel(v); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Inserts data pointed to by [x] into list [l] after [pp], |
| * the address of the previous node's "next" ptr. |
| * Returns a ptr to data [x], or NULL if insertion fails. |
| * This routine assumes the list is already locked upon entry. |
| */ |
| static void _list_node_create(list_t *l, list_node_t **pp, void *x) |
| { |
| list_node_t *p; |
| list_itr_t *i; |
| |
| xassert(l != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| xassert(_list_mutex_is_locked(&l->mutex)); |
| xassert(pp != NULL); |
| xassert(x != NULL); |
| |
| /* |
| * Ran out of spare nodes. Allocate another page worth of nodes, |
| * then link them together in the free_nodes list. |
| */ |
| if (!l->free_nodes) { |
| list_node_t *ln = xcalloc(nodes_in_page, |
| sizeof(list_node_t)); |
| /* |
| * Index 0 is used to link the node_allocations together, |
| * and thus is unavailable for use as a list node. |
| */ |
| ln[0].next = l->node_allocations; |
| l->node_allocations = &ln[0]; |
| l->free_nodes = &ln[1]; |
| for (int i = 1; i < (nodes_in_page - 1); i++) |
| ln[i].next = &ln[i + 1]; |
| } |
| |
| p = l->free_nodes; |
| l->free_nodes = p->next; |
| |
| p->data = x; |
| if (!(p->next = *pp)) |
| l->tail = &p->next; |
| *pp = p; |
| l->count++; |
| |
| for (i = l->iNext; i; i = i->iNext) { |
| xassert(i->magic == LIST_ITR_MAGIC); |
| if (i->prev == pp) |
| i->prev = &p->next; |
| else if (i->pos == p->next) |
| i->pos = p; |
| xassert((i->pos == *i->prev) || |
| ((*i->prev) && (i->pos == (*i->prev)->next))); |
| } |
| } |
| |
| /* |
| * Removes the node pointed to by [*pp] from from list [l], |
| * where [pp] is the address of the previous node's "next" ptr. |
| * Returns the data ptr associated with list item being removed, |
| * or NULL if [*pp] points to the NULL element. |
| * This routine assumes the list is already locked upon entry. |
| */ |
| static void *_list_node_destroy(list_t *l, list_node_t **pp) |
| { |
| void *v; |
| list_node_t *p; |
| list_itr_t *i; |
| |
| xassert(l != NULL); |
| xassert(l->magic == LIST_MAGIC); |
| xassert(_list_mutex_is_locked(&l->mutex)); |
| xassert(pp != NULL); |
| |
| if (!(p = *pp)) |
| return NULL; |
| |
| v = p->data; |
| if (!(*pp = p->next)) |
| l->tail = pp; |
| l->count--; |
| |
| for (i = l->iNext; i; i = i->iNext) { |
| xassert(i->magic == LIST_ITR_MAGIC); |
| if (i->pos == p) |
| i->pos = p->next, i->prev = pp; |
| else if (i->prev == &p->next) |
| i->prev = pp; |
| xassert((i->pos == *i->prev) || |
| ((*i->prev) && (i->pos == (*i->prev)->next))); |
| } |
| |
| /* push into free_nodes list */ |
| p->next = l->free_nodes; |
| l->free_nodes = p; |
| |
| return v; |
| } |
| |
| #ifndef NDEBUG |
| static int _list_mutex_is_locked(pthread_rwlock_t *mutex) |
| { |
| /* Returns true if the mutex is locked; o/w, returns false. |
| */ |
| int rc; |
| |
| xassert(mutex != NULL); |
| rc = slurm_rwlock_trywrlock(mutex); |
| return(rc == EBUSY ? 1 : 0); |
| } |
| #endif /* !NDEBUG */ |