From a5b2248e727b64e781ffb5659e3c9d1499055d17 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Sun, 14 Oct 2018 06:12:27 +0800 Subject: [PATCH] lwsac Introduce an api for efficiently dealing with allocations for large, unknown amounts of objects. --- CMakeLists.txt | 11 + cmake/lws_config.h.in | 2 + doc-assets/lwsac.svg | 131 ++++++++++++ include/libwebsockets.h | 1 + include/libwebsockets/lws-lwsac.h | 191 +++++++++++++++++ lib/misc/lwsac/README.md | 63 ++++++ lib/misc/lwsac/cached-file.c | 202 ++++++++++++++++++ lib/misc/lwsac/lwsac.c | 182 ++++++++++++++++ lib/misc/lwsac/private.h | 48 +++++ minimal-examples/api-tests/README.md | 5 + .../api-tests/api-test-lwsac/CMakeLists.txt | 73 +++++++ .../api-tests/api-test-lwsac/README.md | 22 ++ .../api-tests/api-test-lwsac/main.c | 81 +++++++ .../api-tests/api-test-lwsac/selftest.sh | 24 +++ 14 files changed, 1036 insertions(+) create mode 100644 doc-assets/lwsac.svg create mode 100644 include/libwebsockets/lws-lwsac.h create mode 100644 lib/misc/lwsac/README.md create mode 100644 lib/misc/lwsac/cached-file.c create mode 100644 lib/misc/lwsac/lwsac.c create mode 100644 lib/misc/lwsac/private.h create mode 100644 minimal-examples/api-tests/README.md create mode 100644 minimal-examples/api-tests/api-test-lwsac/CMakeLists.txt create mode 100644 minimal-examples/api-tests/api-test-lwsac/README.md create mode 100644 minimal-examples/api-tests/api-test-lwsac/main.c create mode 100755 minimal-examples/api-tests/api-test-lwsac/selftest.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index c8e933345..0a206c37a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,6 +110,7 @@ option(LWS_WITH_GCOV "Build with gcc gcov coverage instrumentation" OFF) option(LWS_WITH_EXPORT_LWSTARGETS "Export libwebsockets CMake targets. Disable if they conflict with an outer cmake project." ON) option(LWS_REPRODUCIBLE "Build libwebsockets reproducible. It removes the build user and hostname from the build" ON) option(LWS_WITH_MINIMAL_EXAMPLES "Also build the normally standalone minimal examples, for QA" OFF) +option(LWS_WITH_LWSAC "lwsac Chunk Allocation api" ON) # # End of user settings # @@ -149,6 +150,10 @@ if (WIN32 OR LWS_WITH_ESP32) set(LWS_UNIX_SOCK 0) endif() +if (LWS_WITH_ESP32) + set(LWS_WITH_LWSAC 0) +endif() + project(libwebsockets C) set(PACKAGE "libwebsockets") @@ -875,6 +880,12 @@ if (LWS_WITH_PEER_LIMITS) lib/misc/peer-limits.c) endif() +if (LWS_WITH_LWSAC) + list(APPEND SOURCES + lib/misc/lwsac/lwsac.c + lib/misc/lwsac/cached-file.c) +endif() + if (NOT LWS_WITHOUT_CLIENT) list(APPEND SOURCES lib/core/connect.c diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index 6c6d1e3ba..343ec95cb 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -15,6 +15,8 @@ #cmakedefine LWS_ROLE_CGI #cmakedefine LWS_ROLE_DBUS +#cmakedefine LWS_WITH_LWSAC + /* Define to 1 to use wolfSSL/CyaSSL as a replacement for OpenSSL. * LWS_OPENSSL_SUPPORT needs to be set also for this to work. */ #cmakedefine USE_WOLFSSL diff --git a/doc-assets/lwsac.svg b/doc-assets/lwsac.svg new file mode 100644 index 000000000..1a8ba874d --- /dev/null +++ b/doc-assets/lwsac.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + struct lwsac + allocated area + + + struct lwsac *head + nextheadcurr + + ofsalloc_size + + + + + + + + + + ptr aligned + + struct lwsac + allocated area + + + struct lwsac *head + nextheadcurr + + ofsalloc_size + + + + + + + lwsac_use area + + + + ptr aligned + alignment padding + + + lwsac_use area + ptr aligned + alignment padding + + + + + ptr aligned + + struct lwsac + allocated area + + + + + struct lwsac *head + nextheadcurr + + ofsalloc_size + + + + + + + lwsac_use area + + + + alignment padding + + + lwsac_use area + ptr aligned + alignment padding + + + + struct lwsac + allocated area + + nextheadcurr + ofsalloc_size + + + + + + + + + + + lwsac_use area + ptr aligned + alignment padding + + + + + NULL + + NULL + + NULL + + + + empty, generic lwsac + lwsac with 2 "uses" + lwsac with 2 "uses", 3rd requires a new one + + + diff --git a/include/libwebsockets.h b/include/libwebsockets.h index c4fc84ad8..3d63dffe1 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -409,6 +409,7 @@ struct lws; #include #include #include +#include #if defined(LWS_WITH_TLS) diff --git a/include/libwebsockets/lws-lwsac.h b/include/libwebsockets/lws-lwsac.h new file mode 100644 index 000000000..1f914b66a --- /dev/null +++ b/include/libwebsockets/lws-lwsac.h @@ -0,0 +1,191 @@ +/* + * libwebsockets - lws alloc chunk + * + * Copyright (C) 2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * included from libwebsockets.h + */ + +/** \defgroup log lwsac + * + * ##Allocated Chunks + * + * If you know you will be allocating a large, unknown number of same or + * differently sized objects, it's certainly possible to do it with libc + * malloc. However the allocation cost in time and memory overhead can + * add up, and deallocation means walking the structure of every object and + * freeing them in turn. + * + * lwsac (LWS Allocated Chunks) allocates chunks intended to be larger + * than your objects (4000 bytes by default) which you linearly allocate from + * using lwsac_use(). + * + * If your next request won't fit in the current chunk, a new chunk is added + * to the chain of chunks and the allocaton done from there. If the request + * is larger than the chunk size, an oversize chunk is created to satisfy it. + * + * When you are finished with the allocations, you call lwsac_free() and + * free all the *chunks*. So you may have thousands of objects in the chunks, + * but they are all destroyed with the chunks without having to deallocate them + * one by one pointlessly. + */ +///@{ + +struct lwsac; +typedef unsigned char * lwsac_cached_file_t; + + +#define lws_list_ptr_container(P,T,M) ((T *)((char *)(P) - offsetof(T, M))) + +/* + * linked-list helper that's commonly useful to manage lists of things + * allocated using lwsac. + * + * These lists point to their corresponding "next" member in the target, NOT + * the original containing struct. To get the containing struct, you must use + * lws_list_ptr_container() to convert. + * + * It's like that because it means we no longer have to have the next pointer + * at the start of the struct, and we can have the same struct on multiple + * linked-lists with everything held in the struct itself. + */ +typedef void * lws_list_ptr; + +/* + * optional sorting callback called by lws_list_ptr_insert() to sort the right + * things inside the opqaue struct being sorted / inserted on the list. + */ +typedef int (*lws_list_ptr_sort_func_t)(lws_list_ptr a, lws_list_ptr b); + +#define lws_list_ptr_advance(_lp) _lp = *((void **)_lp) + +/* sort may be NULL if you don't care about order */ +LWS_VISIBLE LWS_EXTERN void +lws_list_ptr_insert(lws_list_ptr *phead, lws_list_ptr *add, + lws_list_ptr_sort_func_t sort); + + +/** + * lwsac_use - allocate / use some memory from a lwsac + * + * \param head: pointer to the lwsac list object + * \param ensure: the number of bytes we want to use + * \param chunk_size: 0, or the size of the chunk to (over)allocate if + * what we want won't fit in the current tail chunk. If + * 0, the default value of 4000 is used. If ensure is + * larger, it is used instead. + * + * This also serves to init the lwsac if *head is NULL. Basically it does + * whatever is necessary to return you a pointer to ensure bytes of memory + * reserved for the caller. + * + * Returns NULL if OOM. + */ +LWS_VISIBLE LWS_EXTERN void * +lwsac_use(struct lwsac **head, size_t ensure, size_t chunk_size); + +/** + * lwsac_free - deallocate all chunks in the lwsac and set head NULL + * + * \param head: pointer to the lwsac list object + * + * This deallocates all chunks in the lwsac, then sets *head to NULL. All + * lwsac_use() pointers are invalidated in one hit without individual frees. + */ +LWS_VISIBLE LWS_EXTERN void +lwsac_free(struct lwsac **head); + +/* + * Optional helpers useful for where consumers may need to defer destruction + * until all consumers are finished with the lwsac + */ + +/** + * lwsac_detach() - destroy an lwsac unless somebody else is referencing it + * + * \param head: pointer to the lwsac list object + * + * The creator of the lwsac can all this instead of lwsac_free() when it itself + * has finished with the lwsac, but other code may be consuming it. + * + * If there are no other references, the lwsac is destroyed, *head is set to + * NULL and that's the end; however if something else has called + * lwsac_reference() on the lwsac, it simply returns. When lws_unreference() + * is called and no references are left, it will be destroyed then. + */ +LWS_VISIBLE LWS_EXTERN void +lwsac_detach(struct lwsac **head); + +/** + * lwsac_reference() - increase the lwsac reference count + * + * \param head: pointer to the lwsac list object + * + * Increment the reference count on the lwsac to defer destruction. + */ +LWS_VISIBLE LWS_EXTERN void +lwsac_reference(struct lwsac *head); + +/** + * lwsac_reference() - increase the lwsac reference count + * + * \param head: pointer to the lwsac list object + * + * Decrement the reference count on the lwsac... if it reached 0 on a detached + * lwsac then the lwsac is immediately destroyed and *head set to NULL. + */ +LWS_VISIBLE LWS_EXTERN void +lwsac_unreference(struct lwsac **head); + + +/* helpers to keep a file cached in memory */ + +LWS_VISIBLE LWS_EXTERN void +lwsac_use_cached_file_start(lwsac_cached_file_t cache); + +LWS_VISIBLE LWS_EXTERN void +lwsac_use_cached_file_end(lwsac_cached_file_t *cache); + +LWS_VISIBLE LWS_EXTERN void +lwsac_use_cached_file_detach(lwsac_cached_file_t *cache); + +LWS_VISIBLE LWS_EXTERN int +lwsac_cached_file(const char *filepath, lwsac_cached_file_t *cache, + size_t *len); + +/* more advanced helpers */ + +LWS_VISIBLE LWS_EXTERN size_t +lwsac_sizeof(void); + +LWS_VISIBLE LWS_EXTERN size_t +lwsac_get_tail_pos(struct lwsac *lac); + +LWS_VISIBLE LWS_EXTERN struct lwsac * +lwsac_get_next(struct lwsac *lac); + +LWS_VISIBLE LWS_EXTERN size_t +lwsac_align(size_t length); + +LWS_VISIBLE LWS_EXTERN void +lwsac_info(struct lwsac *head); + +LWS_VISIBLE LWS_EXTERN uint64_t +lwsac_total_alloc(struct lwsac *head); + +///@} diff --git a/lib/misc/lwsac/README.md b/lib/misc/lwsac/README.md new file mode 100644 index 000000000..33462a743 --- /dev/null +++ b/lib/misc/lwsac/README.md @@ -0,0 +1,63 @@ +## LWS Allocated Chunks + +![lwsac flow](/doc-assets/lwsac.svg) + +These apis provide a way to manage a linked-list of allocated chunks... + +[ HEAD alloc ] -> [ next alloc ] -> [ next alloc ] -> [ curr alloc ] + +... and sub-allocate trivially inside the chunks. These sub-allocations are +not tracked by lwsac at all, there is a "used" high-water mark for each chunk +that's simply advanced by the amount sub-allocated. If the allocation size +matches the platform pointer alignment, there is zero overhead to sub-allocate +(otherwise the allocation is padded to the next platform pointer alignment +automatically). + +If you have an unknown amount of relatively little things to allocate, including +strings or other unstructured data, lwsac is significantly more efficient than +individual allocations using malloc or so. + +## lwsac_use() api + +When you make an sub-allocation using `lwsac_use()`, you can either +set the `chunk_size` arg to zero, defaulting to 4000, or a specific chunk size. +In the event the requested sub-allocation exceeds the chunk size, the chunk +size is increated to match it automatically for this allocation only. + +Subsequent `lwsac_use()` calls will advance internal pointers to use up the +remaining space inside the current chunk if possible; if not enough remaining +space it is skipped, a new allocation is chained on and the request pointed to +there. + +Lwsac does not store information about sub-allocations. There is really zero +overhead for individual sub-allocations (unless their size is not +pointer-aligned, in which case the actual amount sub-allocated is rounded up to +the next pointer alignment automatically). For structs, which are pointer- +aligned naturally, and a chunk size relatively large for the sub-allocation +size, lwsac is extremely efficient even for huge numbers of small allocations. + +This makes lwsac very effective when the total amount of allocation needed is +not known at the start and may be large... it will simply add on chunks to cope +with whatever happens. + +## lwsac_free() api + +When you are finished with the lwsac, you simply free the chain of allocated +chunks using lwsac_free() on the lwsac head. There's no tracking or individual +destruction of suballocations - the whole chain of chunks the suballocations +live in are freed and invalidated all together. + +If the structs stored in the lwsac allocated things **outside** the lwsac, then the +user must unwind through them and perform the frees. But the idea of lwsac is +things stored in the lwsac also suballocate into the lwsac, and point into the +lwsac if they need to, avoiding any need to visit them during destroy. It's +like clearing up after a kids' party by gathering up a disposable tablecloth: +no matter what was left on the table, it's all gone in one step. + +## lws_list_ptr helpers + +A common pattern needed with sub-allocated structs is they are on one or more +linked-list. To make that simple to do cleanly, lws_list... apis are provided +along with a generic insertion function that can take a sort callback. These +allow a struct to participate on multiple linked-lists simultaneously. + diff --git a/lib/misc/lwsac/cached-file.c b/lib/misc/lwsac/cached-file.c new file mode 100644 index 000000000..f5f64d216 --- /dev/null +++ b/lib/misc/lwsac/cached-file.c @@ -0,0 +1,202 @@ +/* + * libwebsockets - lws alloc chunk live file caching + * + * Copyright (C) 2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "core/private.h" +#include "misc/lwsac/private.h" + +/* + * Helper for caching a file in memory in a lac, but also to check at intervals + * no less than 5s if the file is still fresh. + * + * Set *cache to NULL the first time before calling. + * + * You should call this each time before using the cache... if it's + * + * - less than 5s since the last freshness check, and + * - the file is already in memory + * + * it just returns with *cache left alone; this costs very little. You should + * call `lwsac_use_cached_file_start()` and `lwsac_use_cached_file_end()` to lock + * the cache against deletion while you are using it. + * + * If it's + * + * - at least 5s since the last freshness check, and + * - the file timestamp has changed + * + * then + * + * - the file is reloaded into a new lac and *cache set to that + * + * - the old cache lac, if any, is detached (so it will be freed when its + * reference count reaches zero, or immediately if nobody has it) + * + * Note the call can fail due to OOM or filesystem issue at any time. + * + * + * After the LAC header there is stored a `struct cached_file_info` and then + * the raw file contents. * + * + * [LAC header] + * [struct cached_file_info] + * [file contents] <--- *cache is set to here + * + * The api returns a lwsac_cached_file_t type offset to point to the file + * contents. Helpers for reference counting and freeing are also provided + * that take that type and know how to correct it back to operate on the LAC. + */ + +#define cache_file_to_lac(c) ((struct lwsac *)((char *)c - \ + sizeof(struct cached_file_info) - \ + sizeof(struct lwsac))) + +void +lwsac_use_cached_file_start(lwsac_cached_file_t cache) +{ + struct lwsac *lac = cache_file_to_lac(cache); + + lac->refcount++; + // lwsl_debug("%s: html refcount: %d\n", __func__, lac->refcount); +} + +void +lwsac_use_cached_file_end(lwsac_cached_file_t *cache) +{ + struct lwsac *lac; + + if (!cache || !*cache) + return; + + lac = cache_file_to_lac(*cache); + + if (!lac->refcount) + lwsl_err("%s: html refcount zero on entry\n", __func__); + + if (lac->refcount && !--lac->refcount && lac->detached) { + *cache = NULL; /* not usable any more */ + lwsac_free(&lac); + } +} + +void +lwsac_use_cached_file_detach(lwsac_cached_file_t *cache) +{ + struct lwsac *lac = cache_file_to_lac(*cache); + + lac->detached = 1; + if (lac->refcount) + return; + + *cache = NULL; + lwsac_free(&lac); +} + +int +lwsac_cached_file(const char *filepath, lwsac_cached_file_t *cache, size_t *len) +{ + struct cached_file_info *info = NULL; + lwsac_cached_file_t old = *cache; + struct lwsac *lac = NULL; + time_t t = time(NULL); + unsigned char *a; + struct stat s; + size_t all; + ssize_t rd; + int fd; + + if (old) { /* we already have a cached copy of it */ + + info = (struct cached_file_info *)((*cache) - sizeof(*info)); + + if (t - info->last_confirm < 5) + /* we checked it as fresh less than 5s ago, use old */ + return 0; + } + + /* + * ...it's been 5s, we should check again on the filesystem + * that the file hasn't changed + */ + + fd = open(filepath, O_RDONLY); + if (fd < 0) { + lwsl_err("%s: cannot open %s\n", __func__, filepath); + + return 1; + } + + if (fstat(fd, &s)) { + lwsl_err("%s: cannot stat %s\n", __func__, filepath); + + goto bail; + } + + if (old && s.st_mtime == info->s.st_mtime) { + /* it still seems to be the same as our cached one */ + info->last_confirm = t; + + close(fd); + + return 0; + } + + /* + * we either didn't cache it yet, or it has changed since we cached + * it... reload in a new lac and then detach the old lac. + */ + + all = sizeof(*info) + s.st_size + 1; + + info = lwsac_use(&lac, all, all); + if (!info) + goto bail; + + info->s = s; + info->last_confirm = t; + + a = (unsigned char *)(info + 1); + + *len = s.st_size; + a[s.st_size] = '\0'; + + rd = read(fd, a, s.st_size); + if (rd != s.st_size) { + lwsl_err("%s: cannot read %s (%d)\n", __func__, filepath, + (int)rd); + goto bail1; + } + + close(fd); + + *cache = (lwsac_cached_file_t)a; + if (old) + lwsac_use_cached_file_detach(&old); + + return 0; + +bail1: + lwsac_free(&lac); + +bail: + close(fd); + + return 1; +} diff --git a/lib/misc/lwsac/lwsac.c b/lib/misc/lwsac/lwsac.c new file mode 100644 index 000000000..abde1a832 --- /dev/null +++ b/lib/misc/lwsac/lwsac.c @@ -0,0 +1,182 @@ +/* + * libwebsockets - lws alloc chunk + * + * Copyright (C) 2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "core/private.h" +#include "misc/lwsac/private.h" + +void +lws_list_ptr_insert(lws_list_ptr *head, lws_list_ptr *add, + lws_list_ptr_sort_func_t sort_func) +{ + while (sort_func && *head) { + if (sort_func(add, *head) <= 0) + break; + + head = *head; + } + + *add = *head; + *head = add; +} + +size_t +lwsac_align(size_t length) +{ + size_t align = sizeof(int *); + + if (length & (align - 1)) + length += align - (length & (align - 1)); + + return length; +} + +size_t +lwsac_sizeof(void) +{ + return sizeof(struct lwsac); +} + +size_t +lwsac_get_tail_pos(struct lwsac *lac) +{ + return lac->ofs; +} + +struct lwsac * +lwsac_get_next(struct lwsac *lac) +{ + return lac->next; +} + +void * +lwsac_use(struct lwsac **head, size_t ensure, size_t chunk_size) +{ + struct lwsac *chunk; + size_t ofs, alloc; + + /* ensure there's a chunk and enough space in it for this name */ + + if (!*head || (*head)->curr->alloc_size - (*head)->curr->ofs < ensure) { + + if (!chunk_size) + alloc = LWSAC_CHUNK_SIZE + sizeof(*chunk); + else + alloc = chunk_size + sizeof(*chunk); + + /* + * If we get asked for something outside our expectation, + * allocate to meet it + */ + + if (ensure >= alloc - sizeof(*chunk)) + alloc = ensure + sizeof(*chunk); + + chunk = malloc(alloc); + if (!chunk) { + lwsl_err("%s: OOM trying to alloc %llud\n", __func__, + (unsigned long long)alloc); + return NULL; + } + + if (!*head) { + *head = chunk; + chunk->total_alloc_size = 0; + chunk->total_blocks = 0; + } + else + (*head)->curr->next = chunk; + + (*head)->curr = chunk; + (*head)->curr->head = *head; + + chunk->next = NULL; + chunk->alloc_size = alloc; + chunk->detached = 0; + chunk->refcount = 0; + + (*head)->total_alloc_size += alloc; + (*head)->total_blocks++; + + /* + * belabouring the point... ofs is aligned to the platform's + * generic struct alignment at the start then + */ + (*head)->curr->ofs = sizeof(*chunk); + } + + ofs = (*head)->curr->ofs; + + (*head)->curr->ofs += lwsac_align(ensure); + if ((*head)->curr->ofs >= (*head)->curr->alloc_size) + (*head)->curr->ofs = (*head)->curr->alloc_size; + + return (char *)(*head)->curr + ofs; +} + +void +lwsac_free(struct lwsac **head) +{ + struct lwsac *it = *head; + + while (it) { + struct lwsac *tmp = it->next; + + free(it); + it = tmp; + } + + *head = NULL; +} + +void +lwsac_info(struct lwsac *head) +{ + lwsl_notice("%s: lac %p: %dKiB in %d blocks\n", __func__, head, + (int)(head->total_alloc_size >> 10), head->total_blocks); +} + +uint64_t +lwsac_total_alloc(struct lwsac *head) +{ + return head->total_alloc_size; +} + +void +lwsac_reference(struct lwsac *head) +{ + head->refcount++; +} + +void +lwsac_unreference(struct lwsac **head) +{ + (*head)->refcount--; + if ((*head)->detached && !(*head)->refcount) + lwsac_free(head); +} + +void +lwsac_detach(struct lwsac **head) +{ + (*head)->detached = 1; + if (!(*head)->refcount) + lwsac_free(head); +} diff --git a/lib/misc/lwsac/private.h b/lib/misc/lwsac/private.h new file mode 100644 index 000000000..efefeb85a --- /dev/null +++ b/lib/misc/lwsac/private.h @@ -0,0 +1,48 @@ +/* + * libwebsockets - lws alloc chunk + * + * Copyright (C) 2018 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include + +/* under page size of 4096 to allow overhead */ +#define LWSAC_CHUNK_SIZE 4000 + +/* + * the chunk list members all point back to the head themselves so the list + * can be detached from the formal head and free itself when its reference + * count reaches zero. + */ + +struct lwsac { + struct lwsac *next; + struct lwsac *head; /* pointer back to the first chunk */ + struct lwsac *curr; /* applies to head chunk only */ + size_t total_alloc_size; /* applies to head chunk only */ + size_t alloc_size; + size_t ofs; /* next writeable position inside chunk */ + int refcount; /* applies to head chunk only */ + int total_blocks; /* applies to head chunk only */ + char detached; /* if our refcount gets to zero, free the chunk list */ +}; + +struct cached_file_info { + struct stat s; + time_t last_confirm; +}; diff --git a/minimal-examples/api-tests/README.md b/minimal-examples/api-tests/README.md new file mode 100644 index 000000000..b047af52f --- /dev/null +++ b/minimal-examples/api-tests/README.md @@ -0,0 +1,5 @@ +|name|tests| +---|--- +api-test-lwsac|LWS Allocated Chunks +api-test-lws_tokenize|Generic secure string tokenizer + diff --git a/minimal-examples/api-tests/api-test-lwsac/CMakeLists.txt b/minimal-examples/api-tests/api-test-lwsac/CMakeLists.txt new file mode 100644 index 000000000..a73c6807e --- /dev/null +++ b/minimal-examples/api-tests/api-test-lwsac/CMakeLists.txt @@ -0,0 +1,73 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-api-test-lwsac) +set(SRCS main.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + + + + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() diff --git a/minimal-examples/api-tests/api-test-lwsac/README.md b/minimal-examples/api-tests/api-test-lwsac/README.md new file mode 100644 index 000000000..74034c793 --- /dev/null +++ b/minimal-examples/api-tests/api-test-lwsac/README.md @@ -0,0 +1,22 @@ +# lws api test lwsac + +Demonstrates how to use and performs selftests for lwsac + +## build + +``` + $ cmake . && make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 + +``` + $ ./lws-api-test-lwsac +[2018/10/09 09:14:17:4834] USER: LWS API selftest: lwsac +[2018/10/09 09:14:17:4835] USER: Completed: PASS +``` + diff --git a/minimal-examples/api-tests/api-test-lwsac/main.c b/minimal-examples/api-tests/api-test-lwsac/main.c new file mode 100644 index 000000000..854e0adc2 --- /dev/null +++ b/minimal-examples/api-tests/api-test-lwsac/main.c @@ -0,0 +1,81 @@ +/* + * lws-api-test-lwsac + * + * Copyright (C) 2018 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include + +struct mytest { + int payload; + /* notice doesn't have to be at start of struct */ + lws_list_ptr list_next; + /* a struct can appear on multiple lists too... */ +}; + +/* converts a ptr to struct mytest .list_next to a ptr to struct mytest */ +#define list_to_mytest(p) lws_list_ptr_container(p, struct mytest, list_next) + +int main(int argc, const char **argv) +{ + int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE, acc; + lws_list_ptr list_head = NULL, iter; + struct lwsac *lwsac = NULL; + struct mytest *m; + const char *p; + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS API selftest: lwsac\n"); + + /* + * 1) allocate and create 1000 struct mytest in a linked-list + */ + + for (n = 0; n < 1000; n++) { + m = lwsac_use(&lwsac, sizeof(*m), 0); + m->payload = n; + + lws_list_ptr_insert(&list_head, &m->list_next, NULL); + } + + /* + * 2) report some debug info about the lwsac state... those 1000 + * allocations actually only required 4 mallocs + */ + + lwsac_info(lwsac); + + /* 3) iterate the list, accumulating the payloads */ + + acc = 0; + iter = list_head; + while (iter) { + m = list_to_mytest(iter); + acc += m->payload; + + lws_list_ptr_advance(iter); + } + + if (acc != 499500) { + lwsl_err("%s: FAIL acc %d\n", __func__, acc); + + return 1; + } + + /* + * 4) deallocate everything (lwsac is also set to NULL). It just + * deallocates the 4 mallocs, everything in there is gone accordingly + */ + + lwsac_free(&lwsac); + + lwsl_user("Completed: PASS\n"); + + return 0; +} diff --git a/minimal-examples/api-tests/api-test-lwsac/selftest.sh b/minimal-examples/api-tests/api-test-lwsac/selftest.sh new file mode 100755 index 000000000..16d1e2e8e --- /dev/null +++ b/minimal-examples/api-tests/api-test-lwsac/selftest.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# $1: path to minimal example binaries... +# if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1 +# that will be ./bin from your build dir +# +# $2: path for logs and results. The results will go +# in a subdir named after the directory this script +# is in +# +# $3: offset for test index count +# +# $4: total test count +# +# $5: path to ./minimal-examples dir in lws +# +# Test return code 0: OK, 254: timed out, other: error indication + +. $5/selftests-library.sh + +COUNT_TESTS=1 + +dotest $1 $2 apiselftest +exit $FAILS