--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/server/daemon/resourcepool.c Sat Sep 24 16:26:10 2022 +0200 @@ -0,0 +1,256 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2022 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "resourcepool.h" +#include "request.h" +#include "session.h" +#include "../public/nsapi.h" +#include "../util/atomic.h" + +#define RESOURCE_POOL_MAX_DEFAULT 32 + +#define RESOURCE_POOL_MAX_ALLOC 268435455 + +static UcxMap *resource_pool_types; + +int init_resource_pools(void) { + resource_pool_types = ucx_map_new(4); + return resource_pool_types ? 0 : 1; +} + +int resourcepool_register_type(const char *type_name, ResourceType *type_info) { + if(ucx_map_cstr_put(resource_pool_types, type_name, type_info)) { + log_ereport(LOG_CATASTROPHE, "resourcepool_register_type: OOM"); + return 1; + } + return 0; +} + + + +int resourcepool_new(ServerConfiguration *cfg, scstr_t type, scstr_t name, ConfigNode *node) { + ResourceType *restype = ucx_map_sstr_get(resource_pool_types, type); + if(!restype) { + log_ereport(LOG_MISCONFIG, "Unknown resource pool type: %s", type.ptr); + return 1; + } + + // convert ConfigNode to pblock + // no sub-objects allowed for this specific ConfigNode, therefore + // it can be represented as key-value-pairs + pblock *param = config_obj2pblock(cfg->pool, node); + if(!param) { + log_ereport(LOG_FAILURE, "resourcepool_new: OOM"); + return 1; + } + + ResourcePool *respool = pool_malloc(cfg->pool, sizeof(ResourcePool)); + if(!respool) { + log_ereport(LOG_FAILURE, "resourcepool_new: OOM"); + return 1; + } + respool->pool = cfg->pool; + + void *respool_data = restype->init(cfg->pool, name.ptr, param); + if(!respool_data) { + log_ereport(LOG_FAILURE, "Cannot create resource pool data: pool: %s type: %s", name.ptr, type.ptr); + return 1; + } + + respool->type = restype; + respool->data = respool_data; + respool->min = 4; // TODO: get from node + respool->max = RESOURCE_POOL_MAX_DEFAULT; // TODO: get from node + + respool->numcreated = 0; + respool->numresources = 0; + + + // don't allow too large resource pools + // this prevents the need to check malloc integer overflows + if(respool->max > RESOURCE_POOL_MAX_ALLOC) { + respool->max = RESOURCE_POOL_MAX_ALLOC; + log_ereport(LOG_WARN, "Resource pool %s: limit max to %d", name.ptr, respool->max); + } + + respool->resalloc = respool->max; + respool->resources = pool_malloc(cfg->pool, respool->resalloc * sizeof(ResourceDataPrivate*)); + + if(!respool->resources || ucx_map_sstr_put(cfg->resources, name, respool)) { + log_ereport(LOG_FAILURE, "Cannot add resource pool: OOM"); + // the only cleanup we have to do + restype->destroy(respool_data); + return 1; + } + + pthread_mutex_init(&respool->lock, NULL); + pthread_cond_init(&respool->available, NULL); + + return 0; +} + +static ResourceData* s_resourcepool_lookup(ServerConfiguration *cfg, Request *opt_rq, Session *opt_sn, const char *name, int flags) { + NSAPIRequest *request = (NSAPIRequest*)opt_rq; + NSAPISession *session = (NSAPISession*)opt_sn; + ResourceDataPrivate *resource = NULL; + + // was this resource already used by this request? + if(request && request->resources) { + resource = ucx_map_cstr_get(request->resources, name); + if(resource) { + return &resource->data; + } + } + + ResourcePool *respool = ucx_map_cstr_get(cfg->resources, name); + if(!respool) return NULL; + + + pthread_mutex_lock(&respool->lock); + WSBool createResource = FALSE; + if(respool->numcreated < respool->min) { + createResource = TRUE; + } + + if(createResource) { + // create a new resource and store it in the resourcepool + void *resourceData = respool->type->createresource(respool->data); + if(resourceData) { + respool->numcreated++; + + resource = pool_malloc(respool->pool, sizeof(ResourceDataPrivate)); + if(resource) { + resource->data.data = respool->type->getresourcedata(resourceData); + resource->data.resourcepool = respool; + resource->resdata = resourceData; + } else { + respool->type->freeresource(respool->data, resourceData); + log_ereport(LOG_CATASTROPHE, "resourcepool_lookup: OOM"); + } + } + // else: respool->type->createresource does logging in case of errors + } else if(respool->numresources > 0) { + resource = respool->resources[--respool->numresources]; + } else { + // wait for free resource + pthread_cond_wait(&respool->available, &respool->lock); + if(respool->numresources > 0) { + resource = respool->resources[--respool->numresources]; + } + } + + // save the resource in the request object, for caching and also + // for cleanup later + int err = 0; + if(resource) { + if(request && session) { + if(!request->resources) { + request->resources = ucx_map_new_a(&session->allocator, 8); + } + + if(request->resources) { + if(ucx_map_cstr_put(request->resources, name, resource)) { + err = 1; + } + } else { + err = 1; + } + } // else: lookup is outside of any request context + + if(respool->type->prepare(respool->data, resource->resdata)) { + err = -1; + } + } + + if(err) { + // err == 1 caused by OOM + log_ereport(LOG_FAILURE, "resourcepool_lookup: OOM"); + // cleanup + resourcepool_destroy_resource(resource); + resource = NULL; + } + + pthread_mutex_unlock(&respool->lock); + + return (ResourceData*)resource; +} + +ResourceData* resourcepool_cfg_lookup(ServerConfiguration *cfg, const char *name, int flags) { + return s_resourcepool_lookup(cfg, NULL, NULL, name, flags); +} + +ResourceData* resourcepool_lookup(Session *sn, Request *rq, const char *name, int flags) { + NSAPISession *session = (NSAPISession*)sn; + ServerConfiguration *cfg = session->config; + return s_resourcepool_lookup(cfg, rq, sn, name, flags); +} + +void resourcepool_free(Session *sn, Request *rq, ResourceData *resource) { + ResourceDataPrivate *res = (ResourceDataPrivate*)resource; + ResourcePool *respool = resource->resourcepool; + + if(respool->type->finish(respool->data, res->resdata)) { + log_ereport(LOG_FAILURE, "resourcepool_free: finish failed"); + } + + pthread_mutex_lock(&respool->lock); + + if(respool->numresources >= respool->resalloc) { + // actually respool->resalloc == respool->max + // and numresources should always be smaller + // however just be extra safe here + respool->resalloc += 8; + ResourceDataPrivate **new_res_array = pool_realloc( + respool->pool, + respool->resources, + respool->resalloc * sizeof(ResourceDataPrivate*)); + if(new_res_array) { + respool->resources = new_res_array; + } else { + log_ereport(LOG_FAILURE, "resourcepool_free: OOM"); + resourcepool_destroy_resource(res); + pthread_mutex_unlock(&respool->lock); + return; + } + } + + respool->resources[respool->numresources++] = res; + + pthread_cond_signal(&respool->available); + pthread_mutex_unlock(&respool->lock); +} + +void resourcepool_destroy_resource(ResourceDataPrivate *res) { + res->data.resourcepool->numcreated--; + res->data.resourcepool->type->freeresource(res->data.resourcepool->data, res->resdata); + pool_free(res->data.resourcepool->pool, res); +} + +void resourcepool_destroy(ResourcePool *respool) { + // TODO +}