quota.c revision 1.8 1 1.5 christos /* $NetBSD: quota.c,v 1.8 2022/09/23 12:15:33 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.1 christos #include <isc/util.h>
23 1.1 christos
24 1.7 christos #define QUOTA_MAGIC ISC_MAGIC('Q', 'U', 'O', 'T')
25 1.7 christos #define VALID_QUOTA(p) ISC_MAGIC_VALID(p, QUOTA_MAGIC)
26 1.7 christos
27 1.7 christos #define QUOTA_CB_MAGIC ISC_MAGIC('Q', 'T', 'C', 'B')
28 1.7 christos #define VALID_QUOTA_CB(p) ISC_MAGIC_VALID(p, QUOTA_CB_MAGIC)
29 1.7 christos
30 1.3 christos void
31 1.4 christos isc_quota_init(isc_quota_t *quota, unsigned int max) {
32 1.6 christos atomic_init("a->max, max);
33 1.6 christos atomic_init("a->used, 0);
34 1.6 christos atomic_init("a->soft, 0);
35 1.6 christos atomic_init("a->waiting, 0);
36 1.6 christos ISC_LIST_INIT(quota->cbs);
37 1.6 christos isc_mutex_init("a->cblock);
38 1.7 christos quota->magic = QUOTA_MAGIC;
39 1.1 christos }
40 1.1 christos
41 1.1 christos void
42 1.1 christos isc_quota_destroy(isc_quota_t *quota) {
43 1.7 christos REQUIRE(VALID_QUOTA(quota));
44 1.7 christos quota->magic = 0;
45 1.7 christos
46 1.4 christos INSIST(atomic_load("a->used) == 0);
47 1.6 christos INSIST(atomic_load("a->waiting) == 0);
48 1.6 christos INSIST(ISC_LIST_EMPTY(quota->cbs));
49 1.6 christos atomic_store_release("a->max, 0);
50 1.6 christos atomic_store_release("a->used, 0);
51 1.6 christos atomic_store_release("a->soft, 0);
52 1.6 christos isc_mutex_destroy("a->cblock);
53 1.1 christos }
54 1.1 christos
55 1.1 christos void
56 1.4 christos isc_quota_soft(isc_quota_t *quota, unsigned int soft) {
57 1.7 christos REQUIRE(VALID_QUOTA(quota));
58 1.6 christos atomic_store_release("a->soft, soft);
59 1.1 christos }
60 1.1 christos
61 1.1 christos void
62 1.4 christos isc_quota_max(isc_quota_t *quota, unsigned int max) {
63 1.7 christos REQUIRE(VALID_QUOTA(quota));
64 1.6 christos atomic_store_release("a->max, max);
65 1.4 christos }
66 1.4 christos
67 1.4 christos unsigned int
68 1.4 christos isc_quota_getmax(isc_quota_t *quota) {
69 1.7 christos REQUIRE(VALID_QUOTA(quota));
70 1.4 christos return (atomic_load_relaxed("a->max));
71 1.4 christos }
72 1.4 christos
73 1.4 christos unsigned int
74 1.4 christos isc_quota_getsoft(isc_quota_t *quota) {
75 1.7 christos REQUIRE(VALID_QUOTA(quota));
76 1.4 christos return (atomic_load_relaxed("a->soft));
77 1.4 christos }
78 1.4 christos
79 1.4 christos unsigned int
80 1.4 christos isc_quota_getused(isc_quota_t *quota) {
81 1.7 christos REQUIRE(VALID_QUOTA(quota));
82 1.4 christos return (atomic_load_relaxed("a->used));
83 1.1 christos }
84 1.1 christos
85 1.6 christos static isc_result_t
86 1.6 christos quota_reserve(isc_quota_t *quota) {
87 1.1 christos isc_result_t result;
88 1.6 christos uint_fast32_t max = atomic_load_acquire("a->max);
89 1.6 christos uint_fast32_t soft = atomic_load_acquire("a->soft);
90 1.6 christos uint_fast32_t used = atomic_load_acquire("a->used);
91 1.6 christos do {
92 1.6 christos if (max != 0 && used >= max) {
93 1.6 christos return (ISC_R_QUOTA);
94 1.6 christos }
95 1.6 christos if (soft != 0 && used >= soft) {
96 1.6 christos result = ISC_R_SOFTQUOTA;
97 1.6 christos } else {
98 1.1 christos result = ISC_R_SUCCESS;
99 1.4 christos }
100 1.6 christos } while (!atomic_compare_exchange_weak_acq_rel("a->used, &used,
101 1.6 christos used + 1));
102 1.1 christos return (result);
103 1.1 christos }
104 1.1 christos
105 1.6 christos /* Must be quota->cbslock locked */
106 1.6 christos static void
107 1.6 christos enqueue(isc_quota_t *quota, isc_quota_cb_t *cb) {
108 1.6 christos REQUIRE(cb != NULL);
109 1.6 christos ISC_LIST_ENQUEUE(quota->cbs, cb, link);
110 1.6 christos atomic_fetch_add_release("a->waiting, 1);
111 1.6 christos }
112 1.6 christos
113 1.6 christos /* Must be quota->cbslock locked */
114 1.6 christos static isc_quota_cb_t *
115 1.6 christos dequeue(isc_quota_t *quota) {
116 1.6 christos isc_quota_cb_t *cb = ISC_LIST_HEAD(quota->cbs);
117 1.6 christos INSIST(cb != NULL);
118 1.6 christos ISC_LIST_DEQUEUE(quota->cbs, cb, link);
119 1.6 christos atomic_fetch_sub_relaxed("a->waiting, 1);
120 1.6 christos return (cb);
121 1.6 christos }
122 1.6 christos
123 1.6 christos static void
124 1.6 christos quota_release(isc_quota_t *quota) {
125 1.6 christos /*
126 1.6 christos * This is opportunistic - we might race with a failing quota_attach_cb
127 1.6 christos * and not detect that something is waiting, but eventually someone will
128 1.6 christos * be releasing quota and will detect it, so we don't need to worry -
129 1.6 christos * and we're saving a lot by not locking cblock every time.
130 1.6 christos */
131 1.6 christos
132 1.6 christos if (atomic_load_acquire("a->waiting) > 0) {
133 1.6 christos isc_quota_cb_t *cb = NULL;
134 1.6 christos LOCK("a->cblock);
135 1.6 christos if (atomic_load_relaxed("a->waiting) > 0) {
136 1.6 christos cb = dequeue(quota);
137 1.6 christos }
138 1.6 christos UNLOCK("a->cblock);
139 1.6 christos if (cb != NULL) {
140 1.6 christos cb->cb_func(quota, cb->data);
141 1.6 christos return;
142 1.6 christos }
143 1.6 christos }
144 1.6 christos
145 1.6 christos INSIST(atomic_fetch_sub_release("a->used, 1) > 0);
146 1.1 christos }
147 1.1 christos
148 1.5 christos static isc_result_t
149 1.6 christos doattach(isc_quota_t *quota, isc_quota_t **p) {
150 1.1 christos isc_result_t result;
151 1.5 christos REQUIRE(p != NULL && *p == NULL);
152 1.5 christos
153 1.6 christos result = quota_reserve(quota);
154 1.5 christos if (result == ISC_R_SUCCESS || result == ISC_R_SOFTQUOTA) {
155 1.5 christos *p = quota;
156 1.5 christos }
157 1.5 christos
158 1.1 christos return (result);
159 1.1 christos }
160 1.1 christos
161 1.5 christos isc_result_t
162 1.7 christos isc_quota_attach(isc_quota_t *quota, isc_quota_t **quotap) {
163 1.7 christos REQUIRE(VALID_QUOTA(quota));
164 1.7 christos REQUIRE(quotap != NULL && *quotap == NULL);
165 1.7 christos
166 1.7 christos return (isc_quota_attach_cb(quota, quotap, NULL));
167 1.5 christos }
168 1.5 christos
169 1.5 christos isc_result_t
170 1.7 christos isc_quota_attach_cb(isc_quota_t *quota, isc_quota_t **quotap,
171 1.7 christos isc_quota_cb_t *cb) {
172 1.7 christos REQUIRE(VALID_QUOTA(quota));
173 1.7 christos REQUIRE(cb == NULL || VALID_QUOTA_CB(cb));
174 1.7 christos REQUIRE(quotap != NULL && *quotap == NULL);
175 1.7 christos
176 1.7 christos isc_result_t result = doattach(quota, quotap);
177 1.6 christos if (result == ISC_R_QUOTA && cb != NULL) {
178 1.6 christos LOCK("a->cblock);
179 1.6 christos enqueue(quota, cb);
180 1.6 christos UNLOCK("a->cblock);
181 1.6 christos }
182 1.6 christos return (result);
183 1.6 christos }
184 1.6 christos
185 1.6 christos void
186 1.6 christos isc_quota_cb_init(isc_quota_cb_t *cb, isc_quota_cb_func_t cb_func, void *data) {
187 1.6 christos ISC_LINK_INIT(cb, link);
188 1.6 christos cb->cb_func = cb_func;
189 1.6 christos cb->data = data;
190 1.7 christos cb->magic = QUOTA_CB_MAGIC;
191 1.5 christos }
192 1.5 christos
193 1.1 christos void
194 1.7 christos isc_quota_detach(isc_quota_t **quotap) {
195 1.7 christos REQUIRE(quotap != NULL && VALID_QUOTA(*quotap));
196 1.7 christos isc_quota_t *quota = *quotap;
197 1.7 christos *quotap = NULL;
198 1.7 christos
199 1.7 christos quota_release(quota);
200 1.1 christos }
201