Home | History | Annotate | Line # | Download | only in isc
      1  1.10  christos /*	$NetBSD: quota.c,v 1.11 2025/05/21 14:48:05 christos Exp $	*/
      2   1.1  christos 
      3   1.1  christos /*
      4   1.1  christos  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5   1.1  christos  *
      6   1.8  christos  * SPDX-License-Identifier: MPL-2.0
      7   1.8  christos  *
      8   1.1  christos  * This Source Code Form is subject to the terms of the Mozilla Public
      9   1.1  christos  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10   1.7  christos  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11   1.1  christos  *
     12   1.1  christos  * See the COPYRIGHT file distributed with this work for additional
     13   1.1  christos  * information regarding copyright ownership.
     14   1.1  christos  */
     15   1.1  christos 
     16   1.1  christos /*! \file */
     17   1.1  christos 
     18   1.1  christos #include <stddef.h>
     19   1.1  christos 
     20   1.4  christos #include <isc/atomic.h>
     21   1.1  christos #include <isc/quota.h>
     22  1.10  christos #include <isc/urcu.h>
     23   1.1  christos #include <isc/util.h>
     24   1.1  christos 
     25   1.7  christos #define QUOTA_MAGIC    ISC_MAGIC('Q', 'U', 'O', 'T')
     26   1.7  christos #define VALID_QUOTA(p) ISC_MAGIC_VALID(p, QUOTA_MAGIC)
     27   1.7  christos 
     28   1.3  christos void
     29   1.4  christos isc_quota_init(isc_quota_t *quota, unsigned int max) {
     30   1.6  christos 	atomic_init(&quota->max, max);
     31   1.6  christos 	atomic_init(&quota->used, 0);
     32   1.6  christos 	atomic_init(&quota->soft, 0);
     33  1.10  christos 	cds_wfcq_init(&quota->jobs.head, &quota->jobs.tail);
     34   1.9  christos 	ISC_LINK_INIT(quota, link);
     35   1.7  christos 	quota->magic = QUOTA_MAGIC;
     36   1.1  christos }
     37   1.1  christos 
     38   1.1  christos void
     39   1.4  christos isc_quota_soft(isc_quota_t *quota, unsigned int soft) {
     40   1.7  christos 	REQUIRE(VALID_QUOTA(quota));
     41  1.10  christos 	atomic_store_relaxed(&quota->soft, soft);
     42   1.1  christos }
     43   1.1  christos 
     44   1.1  christos void
     45   1.4  christos isc_quota_max(isc_quota_t *quota, unsigned int max) {
     46   1.7  christos 	REQUIRE(VALID_QUOTA(quota));
     47  1.10  christos 	atomic_store_relaxed(&quota->max, max);
     48   1.4  christos }
     49   1.4  christos 
     50   1.4  christos unsigned int
     51   1.4  christos isc_quota_getmax(isc_quota_t *quota) {
     52   1.7  christos 	REQUIRE(VALID_QUOTA(quota));
     53  1.10  christos 	return atomic_load_relaxed(&quota->max);
     54   1.4  christos }
     55   1.4  christos 
     56   1.4  christos unsigned int
     57   1.4  christos isc_quota_getsoft(isc_quota_t *quota) {
     58   1.7  christos 	REQUIRE(VALID_QUOTA(quota));
     59  1.10  christos 	return atomic_load_relaxed(&quota->soft);
     60   1.4  christos }
     61   1.4  christos 
     62   1.4  christos unsigned int
     63   1.4  christos isc_quota_getused(isc_quota_t *quota) {
     64   1.7  christos 	REQUIRE(VALID_QUOTA(quota));
     65  1.11  christos 	return atomic_load_acquire(&quota->used);
     66   1.1  christos }
     67   1.1  christos 
     68  1.10  christos void
     69  1.10  christos isc_quota_release(isc_quota_t *quota) {
     70  1.11  christos 	struct cds_wfcq_node *node;
     71   1.6  christos 	/*
     72  1.10  christos 	 * We are using the cds_wfcq_dequeue_blocking() variant here that
     73  1.10  christos 	 * has an internal mutex because we need synchronization on
     74  1.10  christos 	 * multiple dequeues running from different threads.
     75   1.6  christos 	 */
     76  1.11  christos again:
     77  1.11  christos 	node = cds_wfcq_dequeue_blocking(&quota->jobs.head, &quota->jobs.tail);
     78  1.10  christos 	if (node == NULL) {
     79  1.11  christos 		uint_fast32_t used = atomic_fetch_sub_acq_rel(&quota->used, 1);
     80  1.10  christos 		INSIST(used > 0);
     81  1.11  christos 
     82  1.11  christos 		/*
     83  1.11  christos 		 * If this was the last quota released and in the meantime a
     84  1.11  christos 		 * new job has appeared in the queue, then give it a chance
     85  1.11  christos 		 * to run, otherwise it could get stuck there until a new quota
     86  1.11  christos 		 * is acquired and released again.
     87  1.11  christos 		 */
     88  1.11  christos 		if (used == 1 &&
     89  1.11  christos 		    !cds_wfcq_empty(&quota->jobs.head, &quota->jobs.tail))
     90  1.11  christos 		{
     91  1.11  christos 			atomic_fetch_add_acq_rel(&quota->used, 1);
     92  1.11  christos 			goto again;
     93  1.11  christos 		}
     94  1.11  christos 
     95  1.10  christos 		return;
     96   1.6  christos 	}
     97   1.6  christos 
     98  1.10  christos 	isc_job_t *job = caa_container_of(node, isc_job_t, wfcq_node);
     99  1.10  christos 	job->cb(job->cbarg);
    100   1.1  christos }
    101   1.1  christos 
    102   1.5  christos isc_result_t
    103  1.10  christos isc_quota_acquire_cb(isc_quota_t *quota, isc_job_t *job, isc_job_cb cb,
    104  1.10  christos 		     void *cbarg) {
    105   1.7  christos 	REQUIRE(VALID_QUOTA(quota));
    106  1.10  christos 	REQUIRE(job == NULL || cb != NULL);
    107   1.7  christos 
    108  1.11  christos 	uint_fast32_t used = atomic_fetch_add_acq_rel(&quota->used, 1);
    109  1.10  christos 	uint_fast32_t max = atomic_load_relaxed(&quota->max);
    110  1.10  christos 	if (max != 0 && used >= max) {
    111  1.11  christos 		(void)atomic_fetch_sub_acq_rel(&quota->used, 1);
    112  1.10  christos 		if (job != NULL) {
    113  1.10  christos 			job->cb = cb;
    114  1.10  christos 			job->cbarg = cbarg;
    115  1.10  christos 			cds_wfcq_node_init(&job->wfcq_node);
    116  1.10  christos 
    117  1.10  christos 			/*
    118  1.10  christos 			 * The cds_wfcq_enqueue() is non-blocking (no internal
    119  1.10  christos 			 * mutex involved), so it offers a slight advantage.
    120  1.10  christos 			 */
    121  1.10  christos 			cds_wfcq_enqueue(&quota->jobs.head, &quota->jobs.tail,
    122  1.10  christos 					 &job->wfcq_node);
    123  1.11  christos 
    124  1.11  christos 			/*
    125  1.11  christos 			 * While we were initializing and enqueuing a new node,
    126  1.11  christos 			 * quotas might have been released, and if no quota is
    127  1.11  christos 			 * used any more, then our newly enqueued job won't
    128  1.11  christos 			 * have a chance to get running until a new quota is
    129  1.11  christos 			 * acquired and released. To avoid a hangup, check
    130  1.11  christos 			 * quota->used again, if it's 0 then simulate a quota
    131  1.11  christos 			 * acquire/release for the current job to run as soon as
    132  1.11  christos 			 * possible, although we will still return ISC_R_QUOTA
    133  1.11  christos 			 * to the caller.
    134  1.11  christos 			 */
    135  1.11  christos 			if (atomic_compare_exchange_strong_acq_rel(
    136  1.11  christos 				    &quota->used, &(uint_fast32_t){ 0 }, 1))
    137  1.11  christos 			{
    138  1.11  christos 				isc_quota_release(quota);
    139  1.11  christos 			}
    140  1.10  christos 		}
    141  1.10  christos 		return ISC_R_QUOTA;
    142  1.10  christos 	}
    143   1.5  christos 
    144  1.10  christos 	uint_fast32_t soft = atomic_load_relaxed(&quota->soft);
    145  1.10  christos 	if (soft != 0 && used >= soft) {
    146  1.10  christos 		return ISC_R_SOFTQUOTA;
    147  1.10  christos 	}
    148   1.7  christos 
    149  1.10  christos 	return ISC_R_SUCCESS;
    150   1.6  christos }
    151   1.6  christos 
    152   1.6  christos void
    153  1.10  christos isc_quota_destroy(isc_quota_t *quota) {
    154  1.10  christos 	REQUIRE(VALID_QUOTA(quota));
    155  1.10  christos 	quota->magic = 0;
    156   1.5  christos 
    157  1.11  christos 	INSIST(atomic_load_acquire(&quota->used) == 0);
    158  1.10  christos 	INSIST(cds_wfcq_empty(&quota->jobs.head, &quota->jobs.tail));
    159   1.7  christos 
    160  1.10  christos 	cds_wfcq_destroy(&quota->jobs.head, &quota->jobs.tail);
    161   1.1  christos }
    162