changeset 728:858f4208d9cb

farray.sh: Resource management by reference counting done
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 07 Oct 2024 12:36:18 +0200
parents 35c29e9919ba
children 05a325c7d5c5
files share/local-bsdtools/farray.sh tests/farray-alist.t tests/farray-array.t tests/farray-object.t
diffstat 4 files changed, 241 insertions(+), 88 deletions(-) [+]
line wrap: on
line diff
--- a/share/local-bsdtools/farray.sh	Sun Oct 06 17:23:09 2024 +0200
+++ b/share/local-bsdtools/farray.sh	Mon Oct 07 12:36:18 2024 +0200
@@ -462,8 +462,10 @@
     eval __farr_len=\$\{${__farr_gvrname}__+SET\}
     [ -n "${__farr_len}" ] && _farr_fatal "farray \`${__farr_name}' already exists: existing token \`${__farr_token}'"
 
-    # Really create the storage by initializing its length
+    # Really create the storage by initializing its length ...
     eval ${__farr_gvrname}__=0
+    # ... and its reference count
+    eval ${__farr_gvrname}_C=1
     # And associate the token with the array name
     eval "${__farr_name}=\"\${_farr_array_token_prefix}\${__farr_token}\""
 
@@ -498,6 +500,8 @@
 _farr_array_get_meta() {
     local __farr_gm_name_or_value
 
+    local __farr_tmp_refcount
+
     __farr_gm_name_or_value="${1-}"
     case "${__farr_gm_name_or_value}" in
         '')
@@ -531,7 +535,8 @@
 
     eval __farr_len=\$\{${__farr_gvrname}__:+SET\}
     [ -z "${__farr_len}" ] && _farr_fatal "farray \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no storage for token \`${__farr_token}'"
-#    eval __farr_len="\$((\${${__farr_gvrname}__} + 0))"
+    eval __farr_tmp_refcount=\$\{${__farr_gvrname}_C:+SET\}
+    [ -z "${__farr_tmp_refcount}" ] && _farr_fatal "farray \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no refcnt for token \`${__farr_token}'"
     eval __farr_len=\"\$\{${__farr_gvrname}__\}\"
     return 0
 }
@@ -557,6 +562,8 @@
 _farr_array_tryget_meta() {
     local __farr_gm_name_or_value
 
+    local __farr_tmp_refcount
+
     __farr_token=''
     __farr_gvrname=''
     __farr_len=''
@@ -601,6 +608,11 @@
         _farr_err "farray \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no storage for token \`${__farr_token}'"
         return 1
     fi
+    eval __farr_tmp_refcount=\$\{${__farr_gvrname}_C:+SET\}
+    if [ -z "${__farr_tmp_refcount}" ] ; then
+        _farr_err "farray \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no refcnt for token \`${__farr_token}'"
+        return 1
+    fi
     eval __farr_len=\"\$\{${__farr_gvrname}__\}\"
     return 0
 }
@@ -634,6 +646,8 @@
 _farr_array_tryget_meta_nonfatal() {
     local __farr_gm_name_or_value
 
+    local __farr_tmp_refcount
+
     __farr_token=''
     __farr_gvrname=''
     __farr_len=''
@@ -672,6 +686,8 @@
 
     eval __farr_len=\$\{${__farr_gvrname}__:+SET\}
     [ -z "${__farr_len}" ] && return 1
+    eval __farr_tmp_refcount=\$\{${__farr_gvrname}_C:+SET\}
+    [ -z "${__farr_tmp_refcount}" ] && return 1
     eval __farr_len=\"\$\{${__farr_gvrname}__\}\"
     return 0
 }
@@ -741,6 +757,7 @@
         # Set value Escape properly: use $' ' and escape any
         # backslashes and single quotes.
         #
+        _farr_acquire_object "${__farr_newval}"
         eval ${__farr_gvrname}_${__farr_len_1}="$(_farr_quote_for_eval "${__farr_newval}")"
         # the implementation below line does not escape properly
         #   eval ${__farr_gvrname}_${__farr_len_1}="\"${__farr_value}\""
@@ -771,6 +788,7 @@
     local __farr_name __farr_index __farr_value
 
     local __farr_token __farr_gvrname __farr_len __farr_len_1
+    local __farr_old_value
 
     _farr_array_get_meta "$@"
     _farr_make_index __farr_index "${2-}" "${__farr_len}"
@@ -780,11 +798,15 @@
 
     # For proper quoting: see farray_append
     if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then
+        _farr_acquire_object "${__farr_value}"
         # replace a value at an existing index
+        eval __farr_old_value=\"\$\{${__farr_gvrname}_${__farr_index}\}\"
+        _farr_release_object "${__farr_old_value}"
         eval ${__farr_gvrname}_${__farr_index}="$(_farr_quote_for_eval "${__farr_value}")"
     else
         __farr_len_1=$((__farr_len + 1))
         if [ ${__farr_index} -eq ${__farr_len_1} ]; then
+            _farr_acquire_object "${__farr_value}"
             # append value
             eval ${__farr_gvrname}_${__farr_len_1}="$(_farr_quote_for_eval "${__farr_value}")"
             # and set new length
@@ -808,6 +830,7 @@
     local __farr_varname __farr_name __farr_index
 
     local __farr_token __farr_gvrname __farr_len
+    local __farr_get_value
 
     __farr_varname="${1-}"
     [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
@@ -819,7 +842,9 @@
 	_farr_fatal "array index out of bounds"
     fi
 
-    eval "${__farr_varname}"=\"\$\{${__farr_gvrname}_${__farr_index}\}\"
+    eval __farr_get_value=\"\$\{${__farr_gvrname}_${__farr_index}\}\"
+    _farr_acquire_object "${__farr_get_value}"
+    eval "${__farr_varname}"=\"\$\{__farr_get_value\}\"
 }
 
 
@@ -843,6 +868,7 @@
     local __farr_varname __farr_name __farr_index
 
     local __farr_token __farr_gvrname __farr_len
+    local __farr_get_value
 
     __farr_varname="${1-}"
     [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
@@ -855,7 +881,9 @@
 	return 1
     fi
 
-    eval "${__farr_varname}"=\"\$\{${__farr_gvrname}_${__farr_index}\}\"
+    eval __farr_get_value=\"\$\{${__farr_gvrname}_${__farr_index}\}\"
+    _farr_acquire_object "${__farr_get_value}"
+    eval "${__farr_varname}"=\"\$\{__farr_get_value\}\"
     return 0
 }
 
@@ -871,6 +899,7 @@
     local __farr_name __farr_index
 
     local __farr_token __farr_gvrname __farr_len __farr_idx
+    local __farr_del_value
 
     _farr_array_get_meta "$@"
     _farr_make_index __farr_index "${2-}" "${__farr_len}"
@@ -879,6 +908,11 @@
 	_farr_fatal "array index out of bounds"
     fi
 
+    # Release the item at index
+    eval __farr_del_value=\"\$\{${__farr_gvrname}_${__farr_index}\}\"
+    _farr_release_object "${__farr_del_value}"
+
+    # Move all other items down by one
     __farr_idx=${__farr_index}
     while [ ${__farr_idx} -lt ${__farr_len} ]; do
         # copy the following value to the current index
@@ -1003,11 +1037,13 @@
         # Just replace
         __farr_off=0
         while [ ${__farr_off} -lt ${__farr_length} ]; do
+            eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\"
             if [ -n "${__farr_del_name}" ]; then
-                eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\"
                 farray_append "${__farr_del_name}" "${__farr_v}"
             fi
+            _farr_release_object "${__farr_v}"
             eval __farr_v=\"\$\{${__farr_r_gvrname}_$((__farr_off + 1))\}\"
+            _farr_acquire_object "${__farr_v}"
             eval ${__farr_l_gvrname}_$((__farr_index + __farr_off))=\"\$\{__farr_v\}\"
             __farr_off=$((__farr_off + 1))
         done
@@ -1016,24 +1052,27 @@
         __farr_delta=$((__farr_length - __farr_r_len))
         __farr_off=0
         while [ ${__farr_off} -lt ${__farr_r_len} ]; do
+            eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\"
             if [ -n "${__farr_del_name}" ]; then
-                eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\"
                 farray_append "${__farr_del_name}" "${__farr_v}"
             fi
+            _farr_release_object "${__farr_v}"
             eval __farr_v=\"\$\{${__farr_r_gvrname}_$((__farr_off + 1))\}\"
+            _farr_acquire_object "${__farr_v}"
             eval ${__farr_l_gvrname}_$((__farr_index + __farr_off))=\"\$\{__farr_v\}\"
             __farr_off=$((__farr_off + 1))
         done
         # Copy / unset the rest that is to delete
         while [ ${__farr_off} -lt ${__farr_length} ]; do
+            eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\"
             if [ -n "${__farr_del_name}" ]; then
-                eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\"
                 farray_append "${__farr_del_name}" "${__farr_v}"
             fi
+            _farr_release_object "${__farr_v}"
             eval unset ${__farr_l_gvrname}_$((__farr_index + __farr_off))
             __farr_off=$((__farr_off + 1))
         done
-        # Move the rest
+        # Move the rest -- here no refcount changes
         __farr_src_idx=$((__farr_index + __farr_length))
         __farr_dst_idx=$((__farr_index + __farr_r_len))
         while [ ${__farr_src_idx} -le ${__farr_l_len} ]; do
@@ -1050,15 +1089,21 @@
         __farr_delta=$((__farr_r_len - __farr_length))
         __farr_off=0
         while [ ${__farr_off} -lt ${__farr_length} ]; do
+            eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\"
+
             if [ -n "${__farr_del_name}" ]; then
-                eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\"
                 farray_append "${__farr_del_name}" "${__farr_v}"
             fi
+            _farr_release_object "${__farr_v}"
             eval __farr_v=\"\$\{${__farr_r_gvrname}_$((__farr_off + 1))\}\"
+            _farr_acquire_object "${__farr_v}"
             eval ${__farr_l_gvrname}_$((__farr_index + __farr_off))=\"\$\{__farr_v\}\"
             __farr_off=$((__farr_off + 1))
         done
+        #
         # Make room from last item to the first non-deleted
+        # -- no refcount changes
+        #
         __farr_src_idx=${__farr_l_len}
         __farr_dst_idx=$((__farr_src_idx + __farr_delta))
         while [ ${__farr_src_idx} -ge $((__farr_index + __farr_length)) ]; do
@@ -1075,6 +1120,7 @@
         #
         while [ ${__farr_off} -lt ${__farr_r_len} ]; do
             eval __farr_v=\"\$\{${__farr_r_gvrname}_$((__farr_off + 1))\}\"
+            _farr_acquire_object "${__farr_v}"
             eval ${__farr_l_gvrname}_$((__farr_index + __farr_off))=\"\$\{__farr_v\}\"
             __farr_off=$((__farr_off + 1))
         done
@@ -1091,30 +1137,17 @@
 #: Args:
 #:   $1 (str): The name of the existing array.
 #:
-#: Other Parameters:
-#:   --unset-only: If given then no recursive deletion of other complex
-#:                 objects are done.
-#:
 farray_clear() {
-    local __farr_name  __farr_opt_unset_only
+    local __farr_name
 
     local __farr_token __farr_gvrname __farr_len __farr_idx __farr_del_value
 
-    __farr_opt_unset_only="${1-}"
-    if [ "${__farr_opt_unset_only}" = '--unset-only' ] ; then
-        shift
-    else
-        __farr_opt_unset_only=''
-    fi
-
     _farr_array_get_meta "$@"
 
     __farr_idx=1
     while [ ${__farr_idx} -le ${__farr_len} ]; do
-        if [ -n "${__farr_opt_unset_only}" ] ; then
-            eval __farr_del_value=\"\$\{${__farr_gvrname}_${__farr_idx}\}\"
-            _farr_destroy_object "${__farr_del_value}"
-        fi
+        eval __farr_del_value=\"\$\{${__farr_gvrname}_${__farr_idx}\}\"
+        _farr_release_object "${__farr_del_value}"
 	eval unset ${__farr_gvrname}_${__farr_idx}
 	__farr_idx=$((__farr_idx + 1))
     done
@@ -1139,20 +1172,37 @@
 
     local __farr_token __farr_gvrname __farr_len
     local __farr_idx __farr_del_value
+    local __farr_refcnt
 
     _farr_array_tryget_meta "$@" || return 1
 
-    # Remove "storage"
+    #
+    # Decrement the reference count.
+    # Note that the existence of the reference count proper is already
+    # checked by `_farr_array_tryget_meta`.
+    #
+    eval __farr_refcnt=\$\{${__farr_gvrname}_C\}
+    __farr_refcnt=$((__farr_refcnt - 1))
+    eval ${__farr_gvrname}_C=\$\{__farr_refcnt\}
+    if [ "${__farr_refcnt}" -ne 0 ] ; then
+        # Clean out the array name from the token always
+        [ -n "${__farr_name}" ] && eval "${__farr_name}=''"
+        return 0
+    fi
+
+    # Remove "storage" because the reference count is 0
     __farr_idx=1
     while [ ${__farr_idx} -le ${__farr_len} ]; do
         eval __farr_del_value=\"\$\{${__farr_gvrname}_${__farr_idx}\}\"
-        _farr_destroy_object "${__farr_del_value}"
+        _farr_release_object "${__farr_del_value}"
 	eval unset ${__farr_gvrname}_${__farr_idx}
 	__farr_idx=$((__farr_idx + 1))
     done
 
-    # Remove length itself
+    # Remove length itself ...
     eval unset ${__farr_gvrname}__
+    # ... and the reference count
+    eval unset ${__farr_gvrname}_C
     # Clean out the array name from the token
     [ -n "${__farr_name}" ] && eval "${__farr_name}=''"
     return 0
@@ -1467,6 +1517,7 @@
 
     local __farr_token __farr_gvrname __farr_len __farr_idx __farr_rv
     local __farr_gm_name_or_value
+    local __farr_feval
 
     __farr_gm_name_or_value="${1-}"
     _farr_array_get_meta "$@"
@@ -1478,8 +1529,11 @@
 
     __farr_idx=1
     while [ ${__farr_idx} -le ${__farr_len} ]; do
-	eval "${__farr_callback} \"\${__farr_name:-\"\${__farr_gm_name_or_value}\"}\" \"\${__farr_idx}\" \"\${${__farr_gvrname}_${__farr_idx}}\" \"\$@\""
+        eval __farr_feval=\"\$\{${__farr_gvrname}_${__farr_idx}\}\"
+        _farr_acquire_object "${__farr_feval}"
+	eval "${__farr_callback} \"\${__farr_name:-\"\${__farr_gm_name_or_value}\"}\" \"\${__farr_idx}\" \"\${__farr_feval}\" \"\$@\""
 	__farr_rv=$?
+        _farr_release_object "${__farr_feval}"
 	[ ${__farr_rv} -ne 0 ] && return ${__farr_rv}
 	__farr_idx=$((__farr_idx + 1))
     done
@@ -1496,6 +1550,7 @@
 
     local __farr_token __farr_gvrname __farr_len __farr_idx __farr_rv
     local __farr_gm_name_or_value
+    local __farr_feval
 
     __farr_gm_name_or_value="${1-}"
     _farr_array_get_meta "$@"
@@ -1506,8 +1561,11 @@
 
     __farr_idx=${__farr_len}
     while [ ${__farr_idx} -gt 0 ]; do
-	eval "${__farr_callback} \"\${__farr_name:-\"\${__farr_gm_name_or_value}\"}\" \"\${__farr_idx}\" \"\${${__farr_gvrname}_${__farr_idx}}\" \"\$@\""
+        eval __farr_feval=\"\$\{${__farr_gvrname}_${__farr_idx}\}\"
+        _farr_acquire_object "${__farr_feval}"
+	eval "${__farr_callback} \"\${__farr_name:-\"\${__farr_gm_name_or_value}\"}\" \"\${__farr_idx}\" \"\${__farr_feval}\" \"\$@\""
 	__farr_rv=$?
+        _farr_release_object "${__farr_feval}"
 	[ ${__farr_rv} -ne 0 ] && return ${__farr_rv}
 	__farr_idx=$((__farr_idx - 1))
     done
@@ -1644,8 +1702,10 @@
     eval __farr_len=\$\{${__farr_objname}__+SET\}
     [ -n "${__farr_len}" ] && _farr_fatal "falist \`${__farr_name}' already exists: existing token \`${__farr_token}'"
 
-    # Really create the storage by initializing its length
+    # Really create the storage by initializing its length ...
     eval ${__farr_objname}__=0
+    # ... and its reference count
+    eval ${__farr_objname}_C=1
 
     # And associate the token with the array name
     eval "${__farr_name}=\"\${_farr_alist_token_prefix}\${__farr_token}\""
@@ -1682,6 +1742,8 @@
 _farr_alist_get_meta() {
     local __farr_gm_name_or_value
 
+    local __farr_tmp_refcount
+
     __farr_gm_name_or_value="${1-}"
     case "${__farr_gm_name_or_value}" in
         '')
@@ -1717,7 +1779,8 @@
 
     eval __farr_len=\$\{${__farr_objname}__:+SET\}
     [ -z "${__farr_len}" ] && _farr_fatal "falist \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no object for token \`${__farr_token}'"
-    # eval __farr_len="\$((\${${__farr_objname}__} + 0))"
+    eval __farr_tmp_refcount=\$\{${__farr_objname}_C:+SET\}
+    [ -z "${__farr_tmp_refcount}" ] && _farr_fatal "falist \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no refcnt for token \`${__farr_token}'"
     eval __farr_len="\${${__farr_objname}__}"
     return 0
 }
@@ -1745,6 +1808,8 @@
 _farr_alist_tryget_meta() {
     local __farr_gm_name_or_value
 
+    local __farr_tmp_refcount
+
     __farr_gm_name_or_value="${1-}"
     case "${__farr_gm_name_or_value}" in
         '')
@@ -1782,8 +1847,15 @@
     __farr_valname=${_farr_alist_value_prefix}${__farr_token}
 
     eval __farr_len=\$\{${__farr_objname}__:+SET\}
-    [ -z "${__farr_len}" ] && _farr_fatal "falist \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no object for token \`${__farr_token}'"
-    # eval __farr_len="\$((\${${__farr_objname}__} + 0))"
+    if [ -z "${__farr_len}" ] ; then
+        _farr_err "falist \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no object for token \`${__farr_token}'"
+        return 1
+    fi
+    eval __farr_tmp_refcount=\$\{${__farr_objname}_C:+SET\}
+    if [ -z "${__farr_tmp_refcount}" ] ; then
+        _farr_err "falist \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no refcnt for token \`${__farr_token}'"
+        return 1
+    fi
     eval __farr_len="\${${__farr_objname}__}"
     return 0
 }
@@ -1836,34 +1908,22 @@
 #: Args:
 #:   $1 (str): The name of the existing alist.
 #:
-#: Other Parameters:
-#:   --unset-only: If given then no recursive deletion of other complex
-#:                 objects are done.
-#:
 falist_clear() {
-    local __farr_name __farr_opt_unset_only
+    local __farr_name
 
     local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
     local __farr_idx __farr_del_value
 
-    __farr_opt_unset_only="${1-}"
-    if [ "${__farr_opt_unset_only}" = '--unset-only' ] ; then
-        shift
-    else
-        __farr_opt_unset_only=''
-    fi
-
     _farr_alist_get_meta "$@"
 
     # Remove "storage"
     __farr_idx=1
     while [ ${__farr_idx} -le ${__farr_len} ]; do
-        if [ -n "${__farr_opt_unset_only}" ] ; then
-            eval __farr_del_value=\"\$\{${__farr_valname}_${__farr_idx}\}\"
-            _farr_destroy_object "${__farr_del_value}"
-            eval __farr_del_value=\"\$\{${__farr_keyname}_${__farr_idx}\}\"
-            _farr_destroy_object "${__farr_del_value}"
-        fi
+        # XXX FIXME: no arrays/alists as keys: should we check this
+        #eval __farr_del_value=\"\$\{${__farr_valname}_${__farr_idx}\}\"
+        #_farr_release_object "${__farr_del_value}"
+        eval __farr_del_value=\"\$\{${__farr_keyname}_${__farr_idx}\}\"
+        _farr_release_object "${__farr_del_value}"
         eval unset ${__farr_valname}_${__farr_idx}
         eval unset ${__farr_keyname}_${__farr_idx}
         __farr_idx=$((__farr_idx + 1))
@@ -1890,23 +1950,41 @@
 
     local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
     local __farr_idx __farr_del_value
+    local __farr_refcnt
 
     _farr_alist_tryget_meta "$@" || return 1
 
-    # Remove "storage"
+    #
+    # Decrement the reference count.
+    # Note that the existence of the reference count proper is already
+    # checked by `_farr_alist_tryget_meta`.
+    #
+    eval __farr_refcnt=\$\{${__farr_objname}_C\}
+    __farr_refcnt=$((__farr_refcnt - 1))
+    eval ${__farr_objname}_C=\$\{__farr_refcnt\}
+    if [ "${__farr_refcnt}" -ne 0 ] ; then
+        # Clean out the alist name from the token always
+        [ -n "${__farr_name}" ] && eval "${__farr_name}=''"
+        return 0
+    fi
+
+    # Remove "storage" because the reference count is 0
     __farr_idx=1
     while [ ${__farr_idx} -le ${__farr_len} ]; do
         eval __farr_del_value=\"\$\{${__farr_valname}_${__farr_idx}\}\"
-        _farr_destroy_object "${__farr_del_value}"
-        eval __farr_del_value=\"\$\{${__farr_keyname}_${__farr_idx}\}\"
-        _farr_destroy_object "${__farr_del_value}"
+        _farr_release_object "${__farr_del_value}"
+        # XXX FIXME: no arrays/alists as keys: should we check this
+        #eval __farr_del_value=\"\$\{${__farr_keyname}_${__farr_idx}\}\"
+        #_farr_release_object "${__farr_del_value}"
         eval unset ${__farr_valname}_${__farr_idx}
         eval unset ${__farr_keyname}_${__farr_idx}
         __farr_idx=$((__farr_idx + 1))
     done
 
-    # Remove object (length) itself
+    # Remove object (length) itself ...
     eval unset ${__farr_objname}__
+    # ... and its reference count
+    eval unset ${__farr_objname}_C
     # Clean out the alist name from the token
     [ -n "${__farr_name}" ] && eval "${__farr_name}=''"
     return 0
@@ -1930,6 +2008,7 @@
 
     local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
     local __farr_idx __farr_elkey
+    local __farr_old_value
 
     _farr_alist_get_meta "$@"
     shift
@@ -1946,6 +2025,9 @@
             if [ -n "${__farr_elkey}" ]; then
                 eval __farr_elkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\"
                 if [ "${__farr_elkey}" = "${__farr_key}" ]; then
+                    eval __farr_old_value=\"\$\{${__farr_valname}_${__farr_idx}\}\"
+                    _farr_release_object "${__farr_old_value}"
+                    _farr_acquire_object "${__farr_value}"
                     eval ${__farr_valname}_${__farr_idx}="$(_farr_quote_for_eval "${__farr_value}")"
                     return 0
                 fi
@@ -1962,6 +2044,7 @@
         __farr_len=${__farr_idx}
         #   ... the key/value pairs to storage
         eval ${__farr_keyname}_${__farr_len}="$(_farr_quote_for_eval "${__farr_key}")"
+        _farr_acquire_object "${__farr_value}"
         eval ${__farr_valname}_${__farr_len}="$(_farr_quote_for_eval "${__farr_value}")"
         #   ... the new length
         eval ${__farr_objname}__=${__farr_len}
@@ -2009,6 +2092,7 @@
             if [ -n "${__farr_elkey}" ]; then
                 eval __farr_elkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\"
                 if [ "${__farr_elkey}" = "${__farr_key}" ]; then
+                    # key is already set: would be non-unique
                     return 1
                 fi
             else
@@ -2024,6 +2108,7 @@
         __farr_len=${__farr_idx}
         #   ... the key/value pairs to storage
         eval ${__farr_keyname}_${__farr_len}="$(_farr_quote_for_eval "${__farr_key}")"
+        _farr_acquire_object "${__farr_value}"
         eval ${__farr_valname}_${__farr_len}="$(_farr_quote_for_eval "${__farr_value}")"
         #   ... the new length
         eval ${__farr_objname}__=${__farr_len}
@@ -2077,6 +2162,7 @@
             eval __farr_l_key=\"\$\{${__farr_l_keyname}_${__farr_l_idx}\}\"
             if [ "${__farr_l_key}" = "${__farr_r_key}" ]; then
                 eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_r_idx}\}\"
+                _farr_acquire_object "${__farr_value}"
                 eval ${__farr_l_valname}_${__farr_l_idx}=\"\$\{__farr_value\}\"
                 break
             fi
@@ -2091,6 +2177,7 @@
             #   ... the key/value pairs to storage
             eval ${__farr_l_keyname}_${__farr_l_idx}=\"\$\{__farr_r_key\}\"
             eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_r_idx}\}\"
+            _farr_acquire_object "${__farr_value}"
             eval ${__farr_l_valname}_${__farr_l_idx}=\"\$\{__farr_value\}\"
             #   ... the new length in storage
             eval ${__farr_l_objname}__=${__farr_l_idx}
@@ -2121,6 +2208,7 @@
 
     local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
     local __farr_idx __farr_getkey
+    local __farr_get_value
 
     __farr_varname="${1-}"
     [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
@@ -2135,7 +2223,9 @@
         if [ -n "${__farr_getkey}" ]; then
             eval __farr_getkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\"
             if [ "${__farr_getkey}" = "${__farr_key}" ]; then
-                eval "${__farr_varname}"=\"\$\{${__farr_valname}_${__farr_idx}\}\"
+                eval __farr_get_value=\"\$\{${__farr_valname}_${__farr_idx}\}\"
+                _farr_acquire_object "${__farr_get_value}"
+                eval "${__farr_varname}"=\"\$\{__farr_get_value\}\"
                 return 0
             fi
         else
@@ -2165,6 +2255,7 @@
 
     local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len
     local __farr_idx __farr_getkey
+    local __farr_get_value
 
     __farr_varname="${1-}"
     [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name"
@@ -2179,7 +2270,9 @@
         if [ -n "${__farr_getkey}" ]; then
             eval __farr_getkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\"
             if [ "${__farr_getkey}" = "${__farr_key}" ]; then
-                eval "${__farr_varname}"=\"\$\{${__farr_valname}_${__farr_idx}\}\"
+                eval __farr_get_value=\"\$\{${__farr_valname}_${__farr_idx}\}\"
+                _farr_acquire_object "${__farr_get_value}"
+                eval "${__farr_varname}"=\"\$\{__farr_get_value\}\"
                 return 0
             fi
         else
@@ -2226,6 +2319,7 @@
     if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then
         eval __farr_getikey=\"\$\{${__farr_keyname}_${__farr_index}+SET\}\"
         if [ -n "${__farr_getikey}" ]; then
+            # no refcount change because no complex values allowed yet
             eval "${__farr_varname}"=\"\$\{${__farr_keyname}_${__farr_index}\}\"
             return 0
         else
@@ -2272,7 +2366,9 @@
     if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then
         eval __farr_getival=\"\$\{${__farr_valname}_${__farr_index}+SET\}\"
         if [ -n "${__farr_getival}" ]; then
-            eval "${__farr_varname}"=\"\$\{${__farr_valname}_${__farr_index}\}\"
+            eval __farr_getival=\"\$\{${__farr_valname}_${__farr_index}\}\"
+            _farr_acquire_object "${__farr_getival}"
+            eval "${__farr_varname}"=\"\$\{__farr_getival\}\"
             return 0
         else
             _farr_fatal "value unexpectedly unset (index ${__farr_index})"
@@ -2325,7 +2421,9 @@
             eval __farr_getival=\"\$\{${__farr_valname}_${__farr_index}+SET\}\"
             if [ -n "${__farr_getival}" ]; then
                 eval "${__farr_key_varname}"=\"\$\{${__farr_keyname}_${__farr_index}\}\"
-                eval "${__farr_value_varname}"=\"\$\{${__farr_valname}_${__farr_index}\}\"
+                eval __farr_getival=\"\$\{${__farr_valname}_${__farr_index}\}\"
+                _farr_acquire_object "${__farr_getival}"
+                eval "${__farr_value_varname}"=\"\$\{__farr_getival\}\"
                 return 0
             else
                 _farr_fatal "value unexpectedly unset (index ${__farr_index})"
@@ -2385,6 +2483,7 @@
 #:   $2 (int): The current length of the storage object (i.e. the current
 #:             length of the alist)
 #:   $3 (int): The storage index that will be deleted
+#:   $4: if no-null use release semantics (used for values)
 #:
 #: Returns:
 #:   int: Always 0
@@ -2399,13 +2498,22 @@
 #:
 _farr_alist_del_storage_at_index() {
     local __farr_storage_name __farr_storage_length __farr_storage_index
-
-    local __farr_idx
+    local __farr_use_release
+
+    local __farr_idx __farr_del_item
 
     __farr_storage_name="$1"
     __farr_storage_length="$2"
     __farr_storage_index="$3"
-
+    __farr_use_release="$4"
+
+    # Release the item at index -- if requested
+    if [ -n "${__farr_use_release}" ] ; then
+        eval __farr_del_item=\"\$\{${__farr_storage_name}_${__farr_idx}\}\"
+        _farr_release_object "${__farr_del_item}"
+    fi
+
+    # Move all other items down by one
     __farr_idx=${__farr_storage_index}
     while [ ${__farr_idx} -lt ${__farr_storage_length} ] ; do
         # copy the following value to the current index
@@ -2445,8 +2553,8 @@
         if [ -n "${__farr_curkey}" ]; then
             eval __farr_curkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\"
             if [ "${__farr_curkey}" = "${__farr_delkey}" ]; then
-                _farr_alist_del_storage_at_index "${__farr_valname}" ${__farr_len} ${__farr_idx}
-                _farr_alist_del_storage_at_index "${__farr_keyname}" ${__farr_len} ${__farr_idx}
+                _farr_alist_del_storage_at_index "${__farr_valname}" "${__farr_len}" "${__farr_idx}" '1'
+                _farr_alist_del_storage_at_index "${__farr_keyname}" "${__farr_len}" "${__farr_idx}" ""
                 # Reduce the length by 1
                 eval ${__farr_objname}__=$((__farr_len - 1))
                 return 0
@@ -2807,8 +2915,10 @@
             eval __farr_feval=\"\$\{${__farr_valname}_${__farr_feidx}+SET\}\"
             if [ -n "${__farr_feval}" ]; then
                 eval __farr_feval=\"\$\{${__farr_valname}_${__farr_feidx}\}\"
+                _farr_acquire_object "${__farr_feval}"
                 eval "${__farr_callback} \"\${__farr_name:-\"\${__farr_gm_name_or_value}\"}\" \"\${__farr_fekey}\" \"\${__farr_feval}\" \"\${__farr_feidx}\" \"\$@\""
                 __farr_rv=$?
+                _farr_release_object "${__farr_feval}"
                 [ ${__farr_rv} -ne 0 ] && return ${__farr_rv}
             else
                 _farr_fatal "alist \`${__farr_name:-"${__farr_gm_name_or_value}"}': missing value index"
@@ -2898,10 +3008,55 @@
 
 
 #:
-#: Destroy any object.
-#:
-#: If an object represents an array or alist its contents will be destroyed
-#: properly.
+#: Acquire any object.
+#:
+#: If an object represents an array or alist its reference count will be
+#: increased.
+#:
+#: Args:
+#:   $1: The object's value
+#:
+#: Returns:
+#:   int: 0 if the incrementation went properly, 1 otherwise
+#:
+_farr_acquire_object() {
+
+    local __farr_tmp_refcount __farr_tmp_token
+
+    case "$1" in
+        '')
+            return 0
+            ;;
+        "${_farr_array_token_prefix}"*)
+            __farr_tmp_token="${1#"${_farr_array_token_prefix}"}"
+            eval __farr_tmp_refcount=\$\{${_farr_array_prefix}${__farr_tmp_token}_C:+SET\}
+            [ -z "${__farr_tmp_refcount}" ] && return 1
+            eval __farr_tmp_refcount=\$\{${_farr_array_prefix}${__farr_tmp_token}_C\}
+            __farr_tmp_refcount=$((__farr_tmp_refcount + 1))
+            eval ${_farr_array_prefix}${__farr_tmp_token}_C=\$\{__farr_tmp_refcount\}
+            return 0
+            ;;
+        "${_farr_alist_token_prefix}"*)
+            __farr_tmp_token="${1#"${_farr_alist_token_prefix}"}"
+            eval __farr_tmp_refcount=\$\{${_farr_alist_prefix}${__farr_tmp_token}_C:+SET\}
+            [ -z "${__farr_tmp_refcount}" ] && return 1
+            eval __farr_tmp_refcount=\$\{${_farr_alist_prefix}${__farr_tmp_token}_C\}
+            __farr_tmp_refcount=$((__farr_tmp_refcount + 1))
+            eval ${_farr_alist_prefix}${__farr_tmp_token}_C=\$\{__farr_tmp_refcount\}
+            return 0
+            ;;
+        *)
+            return 0
+            ;;
+    esac
+}
+
+
+#:
+#: Release any object.
+#:
+#: If an object represents an array or alist its reference count will be
+#: decreased properly.
 #:
 #: Args:
 #:   $1: The object's value
@@ -2909,7 +3064,7 @@
 #: Returns:
 #:   int: 0 if the destruction went properly without errors, 1 otherwise
 #:
-_farr_destroy_object() {
+_farr_release_object() {
 
     case "$1" in
         '')
--- a/tests/farray-alist.t	Sun Oct 06 17:23:09 2024 +0200
+++ b/tests/farray-alist.t	Mon Oct 07 12:36:18 2024 +0200
@@ -420,13 +420,13 @@
   $ falist_debug LIST
   DEBUG: alist `LIST' has length 1
   DEBUG:     `k1' -> `v2'
-  $ _farr_destroy_object "$LIST"
+  $ _farr_release_object "$LIST"
   $ check_no_alist_artifacts
-  $ (_farr_destroy_object "$LIST")
+  $ (_farr_release_object "$LIST")
   ERROR: falist `_farr_KV\?\?:([a-f0-9]+)' not created properly: no object for token `\1' (re)
-  [70]
+  [1]
   $ LIST=''
-  $ _farr_destroy_object "$LIST"
+  $ _farr_release_object "$LIST"
   $ check_no_alist_artifacts
 
 
@@ -436,8 +436,8 @@
   $ falist_create LIST
   $ falist_set LIST K1 V1
   $ falist_create ITEM1 K11 V11 K22 V22
-# This also transfers ownership
   $ falist_set LIST K2 "$ITEM1"
+  $ falist_destroy ITEM1
   $ falist_debug LIST
   DEBUG: alist `LIST' has length 2
   DEBUG:     `K1' -> `V1'
--- a/tests/farray-array.t	Sun Oct 06 17:23:09 2024 +0200
+++ b/tests/farray-array.t	Mon Oct 07 12:36:18 2024 +0200
@@ -1093,7 +1093,8 @@
   [70]
 
   $ check_no_array_artifacts
-  _farr_A_[0-9a-f]+__=0 (re)
+  _farr_A_[0-9a-f]+_C\=1 (re)
+  _farr_A_[0-9a-f]+__\=0 (re)
   [1]
 
   $ farray_destroy DUP
@@ -1143,13 +1144,13 @@
   DEBUG:   its contents:
   DEBUG:     1: `i1'
   DEBUG:     2: `i2'
-  $ _farr_destroy_object "$TEST"
+  $ _farr_release_object "$TEST"
   $ check_no_array_artifacts
-  $ (_farr_destroy_object "$TEST")
+  $ (_farr_release_object "$TEST")
   ERROR: farray `_farr_A\?:([a-f0-9]+)' not created properly: no storage for token `\1' (re)
   [1]
   $ TEST=''
-  $ _farr_destroy_object "$TEST"
+  $ _farr_release_object "$TEST"
   $ check_no_array_artifacts
 
 
@@ -1158,8 +1159,8 @@
 
   $ farray_create TEST i1 i2
   $ farray_create ITEM1 i11 i22
-This also transfers ownership
   $ farray_append TEST "$ITEM1"
+  $ farray_destroy ITEM1
   $ farray_debug TEST
   DEBUG: array `TEST' has length 3
   DEBUG:   its contents:
--- a/tests/farray-object.t	Sun Oct 06 17:23:09 2024 +0200
+++ b/tests/farray-object.t	Mon Oct 07 12:36:18 2024 +0200
@@ -17,13 +17,12 @@
 
   $ farray_create ARRAY1 i1 i2
   $ farray_create ITEM1 i11 i22
-This also transfers ownership
   $ farray_append ARRAY1 "$ITEM1"
-  $ ITEM1=''
+  $ farray_destroy ITEM1
   $ falist_create LIST2 k1 v1 k2 v2 k3 v3
 This also transfers ownership
   $ farray_append ARRAY1 "$LIST2"
-  $ LIST2=''
+  $ falist_destroy LIST2
   $ farray_debug ARRAY1
   DEBUG: array `ARRAY1' has length 4
   DEBUG:   its contents:
@@ -49,13 +48,11 @@
 
   $ falist_create LIST1 k1 v1
   $ falist_create ITEM1 k11 v11 k22 v22 k33 v33 k44 v44
-This also transfers ownership
   $ falist_set LIST1 k2 "$ITEM1"
-  $ ITEM1=''
+  $ falist_destroy ITEM1
   $ farray_create ARRAY2 a1 a2 a3
-This also transfers ownership
   $ falist_set LIST1 k3 "$ARRAY2"
-  $ ARRAY2=''
+  $ farray_destroy ARRAY2
   $ falist_debug LIST1
   DEBUG: alist `LIST1' has length 3
   DEBUG:     `k1' -> `v1'