/* GObject - GLib Type, Object, Parameter and Signal Library * Copyright (C) 2009 Benjamin Otte * * SPDX-License-Identifier: LGPL-2.1-or-later * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see . */ #include "config.h" #include "../glib/gvalgrind.h" #include #include "gatomicarray.h" /* A GAtomicArray is a growable, mutable array of data * generally of the form of a header of a specific size and * then an array of items of a fixed size. * * It is possible to do lock-less read transactions from the * array without any protection against other reads or writes, * but such read operation must be aware that the data in the * atomic array can change at any time during the transaction, * and only at the end can we verify if the transaction succeeded * or not. Thus the reading transaction cannot for instance * dereference a pointer in the array inside the transaction. * * The size of an array however cannot change during a read * transaction. * * Writes to the array is done in a copy-update style, but there * is no real protection against multiple writers overwriting each * others updates, so writes must be protected by an external lock. */ G_LOCK_DEFINE_STATIC (array); typedef struct _FreeListNode FreeListNode; struct _FreeListNode { FreeListNode *next; }; /* This is really a list of array memory blocks, using the * first item as the next pointer to chain them together. * Protected by array lock */ static FreeListNode *freelist = NULL; /* must hold array lock */ static gpointer freelist_alloc (gsize size, gboolean reuse) { gpointer mem; FreeListNode *free, **prev; gsize real_size; if (reuse) { for (free = freelist, prev = &freelist; free != NULL; prev = &free->next, free = free->next) { if (G_ATOMIC_ARRAY_DATA_SIZE (free) == size) { *prev = free->next; return (gpointer)free; } } } real_size = sizeof (GAtomicArrayMetadata) + MAX (size, sizeof (FreeListNode)); mem = g_slice_alloc (real_size); mem = ((char *) mem) + sizeof (GAtomicArrayMetadata); G_ATOMIC_ARRAY_DATA_SIZE (mem) = size; #if ENABLE_VALGRIND VALGRIND_MALLOCLIKE_BLOCK (mem, real_size - sizeof (GAtomicArrayMetadata), FALSE, FALSE); #endif return mem; } /* must hold array lock */ static void freelist_free (gpointer mem) { FreeListNode *free; free = mem; free->next = freelist; freelist = free; } void _g_atomic_array_init (GAtomicArray *array) { array->data = NULL; } /* Get a copy of the data (if non-NULL) that * can be changed and then re-applied with * g_atomic_array_update(). * * If additional_element_size is > 0 then * then the new memory chunk is that much * larger, or there were no data we return * a chunk of header_size + additional_element_size. * This means you can use this to grow the * array part and it handles the first element * being added automatically. * * We don't support shrinking arrays, as if * we then re-grow we may reuse an old pointer * value and confuse the transaction check. */ gpointer _g_atomic_array_copy (GAtomicArray *array, gsize header_size, gsize additional_element_size) { guint8 *new, *old; gsize old_size, new_size; G_LOCK (array); old = g_atomic_pointer_get (&array->data); if (old) { old_size = G_ATOMIC_ARRAY_DATA_SIZE (old); new_size = old_size + additional_element_size; /* Don't reuse if copying to same size, as this may end up reusing the same pointer for the same array thus confusing the transaction check */ new = freelist_alloc (new_size, additional_element_size != 0); memcpy (new, old, old_size); } else if (additional_element_size != 0) { new_size = header_size + additional_element_size; new = freelist_alloc (new_size, TRUE); } else new = NULL; G_UNLOCK (array); return new; } /* Replace the data in the array with the new data, * freeing the old data (for reuse). The new data may * not be smaller than the current data. */ void _g_atomic_array_update (GAtomicArray *array, gpointer new_data) { guint8 *old; G_LOCK (array); old = g_atomic_pointer_exchange (&array->data, new_data); #ifdef G_DISABLE_ASSERT if (old && G_ATOMIC_ARRAY_DATA_SIZE (new_data) < G_ATOMIC_ARRAY_DATA_SIZE (old)) { g_atomic_pointer_set (&array->data, old); g_return_if_reached (); } #else g_assert (old == NULL || G_ATOMIC_ARRAY_DATA_SIZE (old) <= G_ATOMIC_ARRAY_DATA_SIZE (new_data)); #endif if (old) freelist_free (old); G_UNLOCK (array); }