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("a->max, max); 31 1.6 christos atomic_init("a->used, 0); 32 1.6 christos atomic_init("a->soft, 0); 33 1.10 christos cds_wfcq_init("a->jobs.head, "a->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("a->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("a->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("a->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("a->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("a->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("a->jobs.head, "a->jobs.tail); 78 1.10 christos if (node == NULL) { 79 1.11 christos uint_fast32_t used = atomic_fetch_sub_acq_rel("a->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("a->jobs.head, "a->jobs.tail)) 90 1.11 christos { 91 1.11 christos atomic_fetch_add_acq_rel("a->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("a->used, 1); 109 1.10 christos uint_fast32_t max = atomic_load_relaxed("a->max); 110 1.10 christos if (max != 0 && used >= max) { 111 1.11 christos (void)atomic_fetch_sub_acq_rel("a->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("a->jobs.head, "a->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 "a->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("a->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("a->used) == 0); 158 1.10 christos INSIST(cds_wfcq_empty("a->jobs.head, "a->jobs.tail)); 159 1.7 christos 160 1.10 christos cds_wfcq_destroy("a->jobs.head, "a->jobs.tail); 161 1.1 christos } 162