In e185412872, the TCP accept quota code
became broken in a subtle way - the quota would get initialized on the
first accept for the server socket and then deleted from the server
socket, so it would never get applied again.
Properly fixing this required a bigger refactoring of the isc_quota API
code to make it much simpler. The new code decouples the ownership of
the quota and acquiring/releasing the quota limit.
After (during) the refactoring it became more clear that we need to use
the callback from the child side of the accepted connection, and not the
server side.
151 lines
3.6 KiB
C
151 lines
3.6 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
/*! \file */
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <isc/atomic.h>
|
|
#include <isc/quota.h>
|
|
#include <isc/util.h>
|
|
|
|
#define QUOTA_MAGIC ISC_MAGIC('Q', 'U', 'O', 'T')
|
|
#define VALID_QUOTA(p) ISC_MAGIC_VALID(p, QUOTA_MAGIC)
|
|
|
|
void
|
|
isc_quota_init(isc_quota_t *quota, unsigned int max) {
|
|
atomic_init("a->max, max);
|
|
atomic_init("a->used, 0);
|
|
atomic_init("a->soft, 0);
|
|
atomic_init("a->waiting, 0);
|
|
ISC_LIST_INIT(quota->jobs);
|
|
isc_mutex_init("a->cblock);
|
|
ISC_LINK_INIT(quota, link);
|
|
quota->magic = QUOTA_MAGIC;
|
|
}
|
|
|
|
void
|
|
isc_quota_soft(isc_quota_t *quota, unsigned int soft) {
|
|
REQUIRE(VALID_QUOTA(quota));
|
|
atomic_store_relaxed("a->soft, soft);
|
|
}
|
|
|
|
void
|
|
isc_quota_max(isc_quota_t *quota, unsigned int max) {
|
|
REQUIRE(VALID_QUOTA(quota));
|
|
atomic_store_relaxed("a->max, max);
|
|
}
|
|
|
|
unsigned int
|
|
isc_quota_getmax(isc_quota_t *quota) {
|
|
REQUIRE(VALID_QUOTA(quota));
|
|
return (atomic_load_relaxed("a->max));
|
|
}
|
|
|
|
unsigned int
|
|
isc_quota_getsoft(isc_quota_t *quota) {
|
|
REQUIRE(VALID_QUOTA(quota));
|
|
return (atomic_load_relaxed("a->soft));
|
|
}
|
|
|
|
unsigned int
|
|
isc_quota_getused(isc_quota_t *quota) {
|
|
REQUIRE(VALID_QUOTA(quota));
|
|
return (atomic_load_relaxed("a->used));
|
|
}
|
|
|
|
/* Must be quota->cblock locked */
|
|
static void
|
|
enqueue(isc_quota_t *quota, isc_job_t *cb) {
|
|
REQUIRE(cb != NULL);
|
|
ISC_LIST_ENQUEUE(quota->jobs, cb, link);
|
|
atomic_fetch_add_relaxed("a->waiting, 1);
|
|
}
|
|
|
|
/* Must be quota->cblock locked */
|
|
static isc_job_t *
|
|
dequeue(isc_quota_t *quota) {
|
|
isc_job_t *cb = ISC_LIST_HEAD(quota->jobs);
|
|
if (cb != NULL) {
|
|
ISC_LIST_DEQUEUE(quota->jobs, cb, link);
|
|
atomic_fetch_sub_relaxed("a->waiting, 1);
|
|
}
|
|
return (cb);
|
|
}
|
|
|
|
void
|
|
isc_quota_release(isc_quota_t *quota) {
|
|
uint_fast32_t used;
|
|
|
|
/*
|
|
* This is opportunistic - we might race with a failing quota_attach_cb
|
|
* and not detect that something is waiting, but eventually someone will
|
|
* be releasing quota and will detect it, so we don't need to worry -
|
|
* and we're saving a lot by not locking cblock every time.
|
|
*/
|
|
|
|
if (atomic_load_acquire("a->waiting) > 0) {
|
|
isc_job_t *cb = NULL;
|
|
LOCK("a->cblock);
|
|
cb = dequeue(quota);
|
|
UNLOCK("a->cblock);
|
|
if (cb != NULL) {
|
|
cb->cb(cb->cbarg);
|
|
return;
|
|
}
|
|
}
|
|
|
|
used = atomic_fetch_sub_release("a->used, 1);
|
|
INSIST(used > 0);
|
|
}
|
|
|
|
isc_result_t
|
|
isc_quota_acquire_cb(isc_quota_t *quota, isc_job_t *job, isc_job_cb cb,
|
|
void *cbarg) {
|
|
REQUIRE(VALID_QUOTA(quota));
|
|
REQUIRE(job == NULL || cb != NULL);
|
|
|
|
uint_fast32_t used = atomic_fetch_add_release("a->used, 1);
|
|
|
|
uint_fast32_t max = atomic_load_relaxed("a->max);
|
|
if (max != 0 && used >= max) {
|
|
(void)atomic_fetch_sub_relaxed("a->used, 1);
|
|
if (job != NULL) {
|
|
job->cb = cb;
|
|
job->cbarg = cbarg;
|
|
LOCK("a->cblock);
|
|
enqueue(quota, job);
|
|
UNLOCK("a->cblock);
|
|
}
|
|
return (ISC_R_QUOTA);
|
|
}
|
|
|
|
uint_fast32_t soft = atomic_load_relaxed("a->soft);
|
|
if (soft != 0 && used >= soft) {
|
|
return (ISC_R_SOFTQUOTA);
|
|
}
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
void
|
|
isc_quota_destroy(isc_quota_t *quota) {
|
|
REQUIRE(VALID_QUOTA(quota));
|
|
quota->magic = 0;
|
|
|
|
INSIST(atomic_load("a->used) == 0);
|
|
INSIST(atomic_load("a->waiting) == 0);
|
|
INSIST(ISC_LIST_EMPTY(quota->jobs));
|
|
isc_mutex_destroy("a->cblock);
|
|
}
|