diff --git a/Single-Header/dn_single_header.cpp b/Single-Header/dn_single_header.cpp index 36efc65..6065254 100644 --- a/Single-Header/dn_single_header.cpp +++ b/Single-Header/dn_single_header.cpp @@ -1,4 +1,4 @@ -// Generated by the DN single header generator 2025-11-08 01:47:36 +// Generated by the DN single header generator 2025-11-08 17:58:44 #define DN_BASE_INC_CPP @@ -5046,9 +5046,11 @@ DN_API void DN_OS_Init(DN_OSCore *os, DN_OSInitArgs *args) InitializeCriticalSection(&w32->sync_primitive_free_list_mutex); QueryPerformanceFrequency(&w32->qpc_frequency); - HMODULE module = LoadLibraryA("kernel32.dll"); - w32->set_thread_description = DN_Cast(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); - FreeLibrary(module); + HMODULE module = LoadLibraryA("kernel32.dll"); + if (module) { + w32->set_thread_description = DN_Cast(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); + FreeLibrary(module); + } // NOTE: win32 bcrypt wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; @@ -8656,11 +8658,11 @@ DN_API void DN_OS_Exit(int32_t exit_code) DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, char *stdout_buffer, - size_t *stdout_size, + DN_USize *stdout_size, char *stderr_buffer, - size_t *stderr_size, - DN_U32 timeout_ms, - DN_OSErrSink *err) + DN_USize *stderr_size, + DN_U32 timeout_ms, + DN_OSErrSink *err) { DN_OSExecResult result = {}; size_t stdout_buffer_size = 0; @@ -8704,24 +8706,27 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, result.os_error_code = win_error.code; DN_OS_ErrSinkAppendF(err, result.os_error_code, "Executed command failed to terminate: %.*s", DN_Str8PrintFmt(win_error.msg)); } else if (DN_Check(exec_result == WAIT_TIMEOUT || exec_result == WAIT_OBJECT_0)) { - // NOTE: Read stdout from process ////////////////////////////////////////////////////// + // NOTE: Read stdout from process // If the pipes are full, the process will block. We periodically // flush the pipes to make sure this doesn't happen char sink[DN_Kilobytes(8)]; stdout_bytes_available = 0; if (PeekNamedPipe(handle.stdout_read, nullptr, 0, nullptr, &stdout_bytes_available, nullptr)) { if (stdout_bytes_available) { - DWORD bytes_read = 0; - char *dest_buffer = handle.stdout_write && stdout_buffer ? stdout_buffer : sink; - size_t dest_size = handle.stdout_write && stdout_buffer ? stdout_buffer_size : DN_ArrayCountU(sink); - BOOL success = ReadFile(handle.stdout_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL); - (void)success; // TODO: - if (stdout_size) - *stdout_size = bytes_read; + DWORD bytes_read = 0; + char *dest_buffer = handle.stdout_write && stdout_buffer ? stdout_buffer : sink; + DN_USize dest_size = handle.stdout_write && stdout_buffer ? stdout_buffer_size : DN_ArrayCountU(sink); + BOOL success = ReadFile(handle.stdout_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL); + if (success) { + if (stdout_size) + *stdout_size = bytes_read; + } else { + DN_OS_ErrSinkAppendF(err, 1, "Failed to read bytes from stdout"); + } } } - // NOTE: Read stderr from process ////////////////////////////////////////////////////// + // NOTE: Read stderr from process stderr_bytes_available = 0; if (PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr)) { if (stderr_bytes_available) { @@ -8729,9 +8734,12 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, size_t dest_size = handle.stderr_write && stderr_buffer ? stderr_buffer_size : DN_ArrayCountU(sink); DWORD bytes_read = 0; BOOL success = ReadFile(handle.stderr_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL); - (void)success; // TODO: - if (stderr_size) - *stderr_size = bytes_read; + if (success) { + if (stderr_size) + *stderr_size = bytes_read; + } else { + DN_OS_ErrSinkAppendF(err, 1, "Failed to read bytes from stderr"); + } } } } @@ -8750,12 +8758,17 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, DN_Str8PrintFmt(win_error.msg)); } - // NOTE: Cleanup /////////////////////////////////////////////////////////////////////////////// - CloseHandle(handle.stdout_write); - CloseHandle(handle.stderr_write); - CloseHandle(handle.stdout_read); - CloseHandle(handle.stderr_read); - CloseHandle(handle.process); + // NOTE: Cleanup + if (handle.stdout_write) + CloseHandle(handle.stdout_write); + if (handle.stderr_write) + CloseHandle(handle.stderr_write); + if (handle.stdout_read) + CloseHandle(handle.stdout_read); + if (handle.stderr_read) + CloseHandle(handle.stderr_read); + if (handle.process) + CloseHandle(handle.process); } result.stdout_text = DN_Str8FromPtr(stdout_buffer, stdout_size ? *stdout_size : 0); diff --git a/Single-Header/dn_single_header.h b/Single-Header/dn_single_header.h index 4b40570..aeb3211 100644 --- a/Single-Header/dn_single_header.h +++ b/Single-Header/dn_single_header.h @@ -1,4 +1,4 @@ -// Generated by the DN single header generator 2025-11-08 01:47:36 +// Generated by the DN single header generator 2025-11-08 17:58:44 #if !defined(DN_BASE_INC_H) #define DN_BASE_INC_H @@ -3691,44 +3691,86 @@ template struct DN_List #define DN_DLList_ForEach(it, list) \ auto *it = (list)->next; (it) != (list); (it) = (it)->next +#define DN_DoublyLLDetach(head, ptr) \ + do { \ + if ((head) && (head) == (ptr)) \ + (head) = (head)->next; \ + if ((ptr)) { \ + if ((ptr)->next) \ + (ptr)->next->prev = (ptr)->prev; \ + if ((ptr)->prev) \ + (ptr)->prev->next = (ptr)->next; \ + (ptr)->prev = (ptr)->next = 0; \ + } \ + } while (0) -#define DN_ISLList_Detach(list) (decltype(list)) DN_CSLList_Detach((void **)&(list), (void **)&(list)->next) +#define DN_DoublyLLAppend(head, ptr) \ + do { \ + if ((ptr)) { \ + DN_AssertF((ptr)->prev == 0 && (ptr)->next == 0, "Detach the pointer first"); \ + (ptr)->prev = (head); \ + (ptr)->next = 0; \ + if ((head)) { \ + (ptr)->next = (head)->next; \ + (head)->next = (ptr); \ + } else { \ + (head) = (ptr); \ + } \ + } \ + } while (0) -#define DN_LArray_ResizeFromPool(c_array, size, max, pool, new_max) DN_CArray2_ResizeFromPool((void **)&(c_array), size, max, sizeof((c_array)[0]), pool, new_max) -#define DN_LArray_GrowFromPool(c_array, size, max, pool, new_max) DN_CArray2_GrowFromPool((void **)&(c_array), size, max, sizeof((c_array)[0]), pool, new_max) -#define DN_LArray_GrowIfNeededFromPool(c_array, size, max, pool, add_count) DN_CArray2_GrowIfNeededFromPool((void **)(c_array), size, max, sizeof((c_array)[0]), pool, add_count) -#define DN_LArray_MakeArray(c_array, size, max, count, z_mem) (decltype(&(c_array)[0])) DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), count, z_mem) -#define DN_LArray_MakeArrayZ(c_array, size, max, count) (decltype(&(c_array)[0])) DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), count, DN_ZMem_Yes) -#define DN_LArray_Make(c_array, size, max, z_mem) (decltype(&(c_array)[0])) DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), 1, z_mem) -#define DN_LArray_MakeZ(c_array, size, max) (decltype(&(c_array)[0])) DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), 1, DN_ZMem_Yes) -#define DN_LArray_AddArray(c_array, size, max, items, count, add) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), items, count, add) -#define DN_LArray_Add(c_array, size, max, item, add) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), &item, 1, add) -#define DN_LArray_AppendArray(c_array, size, max, items, count) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), items, count, DN_ArrayAdd_Append) -#define DN_LArray_Append(c_array, size, max, item) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), &item, 1, DN_ArrayAdd_Append) -#define DN_LArray_PrependArray(c_array, size, max, items, count) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), items, count, DN_ArrayAdd_Prepend) -#define DN_LArray_Prepend(c_array, size, max, item) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), &item, 1, DN_ArrayAdd_Prepend) -#define DN_LArray_EraseRange(c_array, size, begin_index, count, erase) DN_CArray2_EraseRange(c_array, size, sizeof((c_array)[0]), begin_index, count, erase) -#define DN_LArray_Erase(c_array, size, index, erase) DN_CArray2_EraseRange(c_array, size, sizeof((c_array)[0]), index, 1, erase) -#define DN_LArray_InsertArray(c_array, size, max, index, items, count) (decltype(&(c_array)[0])) DN_CArray2_InsertArray(c_array, size, max, sizeof((c_array)[0]), index, items, count) -#define DN_LArray_Insert(c_array, size, max, index, item) (decltype(&(c_array)[0])) DN_CArray2_InsertArray(c_array, size, max, sizeof((c_array)[0]), index, &item, 1) +#define DN_DoublyLLPrepend(head, ptr) \ + do { \ + if ((ptr)) { \ + DN_AssertF((ptr)->prev == 0 && (ptr)->next == 0, "Detach the pointer first"); \ + (ptr)->prev = nullptr; \ + (ptr)->next = (head); \ + if ((head)) { \ + (ptr)->prev = (head)->prev; \ + (head)->prev = (ptr); \ + } else { \ + (head) = (ptr); \ + } \ + } \ + } while (0) -#define DN_IArray_ResizeFromPool(array, pool, new_max) DN_CArray2_ResizeFromPool((void **)(&(array)->data), &(array)->size, &(array)->max, sizeof((array)->data[0]), pool, new_max) -#define DN_IArray_GrowFromPool(array, pool, new_max) DN_CArray2_GrowFromPool((void **)(&(array)->data), &(array)->size, &(array)->max, sizeof((array)->data[0]), pool, new_max) -#define DN_IArray_GrowIfNeededFromPool(array, pool, add_count) DN_CArray2_GrowIfNeededFromPool((void **)(&(array)->data), (array)->size, &(array)->max, sizeof((array)->data[0]), pool, add_count) -#define DN_IArray_MakeArray(array, count, z_mem) (decltype(&((array)->data)[0])) DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), count, z_mem) -#define DN_IArray_MakeArrayZ(array, count) (decltype(&((array)->data)[0])) DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), count, DN_ZMem_Yes) -#define DN_IArray_Make(array, z_mem) (decltype(&((array)->data)[0])) DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), 1, z_mem) -#define DN_IArray_MakeZ(array) (decltype(&((array)->data)[0])) DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), 1, DN_ZMem_Yes) -#define DN_IArray_AddArray(array, items, count, add) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, add) -#define DN_IArray_Add(array, item, add) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, add) -#define DN_IArray_AppendArray(array, items, count) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, DN_ArrayAdd_Append) -#define DN_IArray_Append(array, item) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, DN_ArrayAdd_Append) -#define DN_IArray_PrependArray(array, items, count) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, DN_ArrayAdd_Prepend) -#define DN_IArray_Prepend(array, item) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, DN_ArrayAdd_Prepend) -#define DN_IArray_EraseRange(array, begin_index, count, erase) DN_CArray2_EraseRange((array)->data, &(array)->size, sizeof(((array)->data)[0]), begin_index, count, erase) -#define DN_IArray_Erase(array, index, erase) DN_CArray2_EraseRange((array)->data, &(array)->size, sizeof(((array)->data)[0]), index, 1, erase) -#define DN_IArray_InsertArray(array, index, items, count) (decltype(&((array)->data)[0])) DN_CArray2_InsertArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), index, items, count) -#define DN_IArray_Insert(array, index, item, count) (decltype(&((array)->data)[0])) DN_CArray2_InsertArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), index, &item, 1) +#define DN_ISLList_Detach(list) (decltype(list))DN_CSLList_Detach((void **)&(list), (void **)&(list)->next) + +#define DN_LArray_ResizeFromPool(c_array, size, max, pool, new_max) DN_CArray2_ResizeFromPool((void **)&(c_array), size, max, sizeof((c_array)[0]), pool, new_max) +#define DN_LArray_GrowFromPool(c_array, size, max, pool, new_max) DN_CArray2_GrowFromPool((void **)&(c_array), size, max, sizeof((c_array)[0]), pool, new_max) +#define DN_LArray_GrowIfNeededFromPool(c_array, size, max, pool, add_count) DN_CArray2_GrowIfNeededFromPool((void **)(c_array), size, max, sizeof((c_array)[0]), pool, add_count) +#define DN_LArray_MakeArray(c_array, size, max, count, z_mem) (decltype(&(c_array)[0]))DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), count, z_mem) +#define DN_LArray_MakeArrayZ(c_array, size, max, count) (decltype(&(c_array)[0]))DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), count, DN_ZMem_Yes) +#define DN_LArray_Make(c_array, size, max, z_mem) (decltype(&(c_array)[0]))DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), 1, z_mem) +#define DN_LArray_MakeZ(c_array, size, max) (decltype(&(c_array)[0]))DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), 1, DN_ZMem_Yes) +#define DN_LArray_AddArray(c_array, size, max, items, count, add) (decltype(&(c_array)[0]))DN_CArray2_AddArray(c_array, size, max, sizeof((c_array)[0]), items, count, add) +#define DN_LArray_Add(c_array, size, max, item, add) (decltype(&(c_array)[0]))DN_CArray2_AddArray(c_array, size, max, sizeof((c_array)[0]), &item, 1, add) +#define DN_LArray_AppendArray(c_array, size, max, items, count) (decltype(&(c_array)[0]))DN_CArray2_AddArray(c_array, size, max, sizeof((c_array)[0]), items, count, DN_ArrayAdd_Append) +#define DN_LArray_Append(c_array, size, max, item) (decltype(&(c_array)[0]))DN_CArray2_AddArray(c_array, size, max, sizeof((c_array)[0]), &item, 1, DN_ArrayAdd_Append) +#define DN_LArray_PrependArray(c_array, size, max, items, count) (decltype(&(c_array)[0]))DN_CArray2_AddArray(c_array, size, max, sizeof((c_array)[0]), items, count, DN_ArrayAdd_Prepend) +#define DN_LArray_Prepend(c_array, size, max, item) (decltype(&(c_array)[0]))DN_CArray2_AddArray(c_array, size, max, sizeof((c_array)[0]), &item, 1, DN_ArrayAdd_Prepend) +#define DN_LArray_EraseRange(c_array, size, begin_index, count, erase) DN_CArray2_EraseRange(c_array, size, sizeof((c_array)[0]), begin_index, count, erase) +#define DN_LArray_Erase(c_array, size, index, erase) DN_CArray2_EraseRange(c_array, size, sizeof((c_array)[0]), index, 1, erase) +#define DN_LArray_InsertArray(c_array, size, max, index, items, count) (decltype(&(c_array)[0]))DN_CArray2_InsertArray(c_array, size, max, sizeof((c_array)[0]), index, items, count) +#define DN_LArray_Insert(c_array, size, max, index, item) (decltype(&(c_array)[0]))DN_CArray2_InsertArray(c_array, size, max, sizeof((c_array)[0]), index, &item, 1) + +#define DN_IArray_ResizeFromPool(array, pool, new_max) DN_CArray2_ResizeFromPool((void **)(&(array)->data), &(array)->size, &(array)->max, sizeof((array)->data[0]), pool, new_max) +#define DN_IArray_GrowFromPool(array, pool, new_max) DN_CArray2_GrowFromPool((void **)(&(array)->data), &(array)->size, &(array)->max, sizeof((array)->data[0]), pool, new_max) +#define DN_IArray_GrowIfNeededFromPool(array, pool, add_count) DN_CArray2_GrowIfNeededFromPool((void **)(&(array)->data), (array)->size, &(array)->max, sizeof((array)->data[0]), pool, add_count) +#define DN_IArray_MakeArray(array, count, z_mem) (decltype(&((array)->data)[0]))DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), count, z_mem) +#define DN_IArray_MakeArrayZ(array, count) (decltype(&((array)->data)[0]))DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), count, DN_ZMem_Yes) +#define DN_IArray_Make(array, z_mem) (decltype(&((array)->data)[0]))DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), 1, z_mem) +#define DN_IArray_MakeZ(array) (decltype(&((array)->data)[0]))DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), 1, DN_ZMem_Yes) +#define DN_IArray_AddArray(array, items, count, add) (decltype(&((array)->data)[0]))DN_CArray2_AddArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, add) +#define DN_IArray_Add(array, item, add) (decltype(&((array)->data)[0]))DN_CArray2_AddArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, add) +#define DN_IArray_AppendArray(array, items, count) (decltype(&((array)->data)[0]))DN_CArray2_AddArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, DN_ArrayAdd_Append) +#define DN_IArray_Append(array, item) (decltype(&((array)->data)[0]))DN_CArray2_AddArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, DN_ArrayAdd_Append) +#define DN_IArray_PrependArray(array, items, count) (decltype(&((array)->data)[0]))DN_CArray2_AddArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, DN_ArrayAdd_Prepend) +#define DN_IArray_Prepend(array, item) (decltype(&((array)->data)[0]))DN_CArray2_AddArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, DN_ArrayAdd_Prepend) +#define DN_IArray_EraseRange(array, begin_index, count, erase) DN_CArray2_EraseRange((array)->data, &(array)->size, sizeof(((array)->data)[0]), begin_index, count, erase) +#define DN_IArray_Erase(array, index, erase) DN_CArray2_EraseRange((array)->data, &(array)->size, sizeof(((array)->data)[0]), index, 1, erase) +#define DN_IArray_InsertArray(array, index, items, count) (decltype(&((array)->data)[0]))DN_CArray2_InsertArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), index, items, count) +#define DN_IArray_Insert(array, index, item, count) (decltype(&((array)->data)[0]))DN_CArray2_InsertArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), index, &item, 1) DN_API DN_ArrayEraseResult DN_CArray2_EraseRange (void *data, DN_USize *size, DN_USize elem_size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); DN_API void *DN_CArray2_MakeArray (void *data, DN_USize *size, DN_USize max, DN_USize data_size, DN_USize make_size, DN_ZMem z_mem); diff --git a/Source/Base/dn_base_containers.h b/Source/Base/dn_base_containers.h index 52a8941..4065bb4 100644 --- a/Source/Base/dn_base_containers.h +++ b/Source/Base/dn_base_containers.h @@ -272,44 +272,86 @@ template struct DN_List #define DN_DLList_ForEach(it, list) \ auto *it = (list)->next; (it) != (list); (it) = (it)->next +#define DN_DoublyLLDetach(head, ptr) \ + do { \ + if ((head) && (head) == (ptr)) \ + (head) = (head)->next; \ + if ((ptr)) { \ + if ((ptr)->next) \ + (ptr)->next->prev = (ptr)->prev; \ + if ((ptr)->prev) \ + (ptr)->prev->next = (ptr)->next; \ + (ptr)->prev = (ptr)->next = 0; \ + } \ + } while (0) -#define DN_ISLList_Detach(list) (decltype(list)) DN_CSLList_Detach((void **)&(list), (void **)&(list)->next) +#define DN_DoublyLLAppend(head, ptr) \ + do { \ + if ((ptr)) { \ + DN_AssertF((ptr)->prev == 0 && (ptr)->next == 0, "Detach the pointer first"); \ + (ptr)->prev = (head); \ + (ptr)->next = 0; \ + if ((head)) { \ + (ptr)->next = (head)->next; \ + (head)->next = (ptr); \ + } else { \ + (head) = (ptr); \ + } \ + } \ + } while (0) -#define DN_LArray_ResizeFromPool(c_array, size, max, pool, new_max) DN_CArray2_ResizeFromPool((void **)&(c_array), size, max, sizeof((c_array)[0]), pool, new_max) -#define DN_LArray_GrowFromPool(c_array, size, max, pool, new_max) DN_CArray2_GrowFromPool((void **)&(c_array), size, max, sizeof((c_array)[0]), pool, new_max) -#define DN_LArray_GrowIfNeededFromPool(c_array, size, max, pool, add_count) DN_CArray2_GrowIfNeededFromPool((void **)(c_array), size, max, sizeof((c_array)[0]), pool, add_count) -#define DN_LArray_MakeArray(c_array, size, max, count, z_mem) (decltype(&(c_array)[0])) DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), count, z_mem) -#define DN_LArray_MakeArrayZ(c_array, size, max, count) (decltype(&(c_array)[0])) DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), count, DN_ZMem_Yes) -#define DN_LArray_Make(c_array, size, max, z_mem) (decltype(&(c_array)[0])) DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), 1, z_mem) -#define DN_LArray_MakeZ(c_array, size, max) (decltype(&(c_array)[0])) DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), 1, DN_ZMem_Yes) -#define DN_LArray_AddArray(c_array, size, max, items, count, add) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), items, count, add) -#define DN_LArray_Add(c_array, size, max, item, add) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), &item, 1, add) -#define DN_LArray_AppendArray(c_array, size, max, items, count) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), items, count, DN_ArrayAdd_Append) -#define DN_LArray_Append(c_array, size, max, item) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), &item, 1, DN_ArrayAdd_Append) -#define DN_LArray_PrependArray(c_array, size, max, items, count) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), items, count, DN_ArrayAdd_Prepend) -#define DN_LArray_Prepend(c_array, size, max, item) (decltype(&(c_array)[0])) DN_CArray2_AddArray (c_array, size, max, sizeof((c_array)[0]), &item, 1, DN_ArrayAdd_Prepend) -#define DN_LArray_EraseRange(c_array, size, begin_index, count, erase) DN_CArray2_EraseRange(c_array, size, sizeof((c_array)[0]), begin_index, count, erase) -#define DN_LArray_Erase(c_array, size, index, erase) DN_CArray2_EraseRange(c_array, size, sizeof((c_array)[0]), index, 1, erase) -#define DN_LArray_InsertArray(c_array, size, max, index, items, count) (decltype(&(c_array)[0])) DN_CArray2_InsertArray(c_array, size, max, sizeof((c_array)[0]), index, items, count) -#define DN_LArray_Insert(c_array, size, max, index, item) (decltype(&(c_array)[0])) DN_CArray2_InsertArray(c_array, size, max, sizeof((c_array)[0]), index, &item, 1) +#define DN_DoublyLLPrepend(head, ptr) \ + do { \ + if ((ptr)) { \ + DN_AssertF((ptr)->prev == 0 && (ptr)->next == 0, "Detach the pointer first"); \ + (ptr)->prev = nullptr; \ + (ptr)->next = (head); \ + if ((head)) { \ + (ptr)->prev = (head)->prev; \ + (head)->prev = (ptr); \ + } else { \ + (head) = (ptr); \ + } \ + } \ + } while (0) -#define DN_IArray_ResizeFromPool(array, pool, new_max) DN_CArray2_ResizeFromPool((void **)(&(array)->data), &(array)->size, &(array)->max, sizeof((array)->data[0]), pool, new_max) -#define DN_IArray_GrowFromPool(array, pool, new_max) DN_CArray2_GrowFromPool((void **)(&(array)->data), &(array)->size, &(array)->max, sizeof((array)->data[0]), pool, new_max) -#define DN_IArray_GrowIfNeededFromPool(array, pool, add_count) DN_CArray2_GrowIfNeededFromPool((void **)(&(array)->data), (array)->size, &(array)->max, sizeof((array)->data[0]), pool, add_count) -#define DN_IArray_MakeArray(array, count, z_mem) (decltype(&((array)->data)[0])) DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), count, z_mem) -#define DN_IArray_MakeArrayZ(array, count) (decltype(&((array)->data)[0])) DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), count, DN_ZMem_Yes) -#define DN_IArray_Make(array, z_mem) (decltype(&((array)->data)[0])) DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), 1, z_mem) -#define DN_IArray_MakeZ(array) (decltype(&((array)->data)[0])) DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), 1, DN_ZMem_Yes) -#define DN_IArray_AddArray(array, items, count, add) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, add) -#define DN_IArray_Add(array, item, add) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, add) -#define DN_IArray_AppendArray(array, items, count) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, DN_ArrayAdd_Append) -#define DN_IArray_Append(array, item) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, DN_ArrayAdd_Append) -#define DN_IArray_PrependArray(array, items, count) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, DN_ArrayAdd_Prepend) -#define DN_IArray_Prepend(array, item) (decltype(&((array)->data)[0])) DN_CArray2_AddArray ((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, DN_ArrayAdd_Prepend) -#define DN_IArray_EraseRange(array, begin_index, count, erase) DN_CArray2_EraseRange((array)->data, &(array)->size, sizeof(((array)->data)[0]), begin_index, count, erase) -#define DN_IArray_Erase(array, index, erase) DN_CArray2_EraseRange((array)->data, &(array)->size, sizeof(((array)->data)[0]), index, 1, erase) -#define DN_IArray_InsertArray(array, index, items, count) (decltype(&((array)->data)[0])) DN_CArray2_InsertArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), index, items, count) -#define DN_IArray_Insert(array, index, item, count) (decltype(&((array)->data)[0])) DN_CArray2_InsertArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), index, &item, 1) +#define DN_ISLList_Detach(list) (decltype(list))DN_CSLList_Detach((void **)&(list), (void **)&(list)->next) + +#define DN_LArray_ResizeFromPool(c_array, size, max, pool, new_max) DN_CArray2_ResizeFromPool((void **)&(c_array), size, max, sizeof((c_array)[0]), pool, new_max) +#define DN_LArray_GrowFromPool(c_array, size, max, pool, new_max) DN_CArray2_GrowFromPool((void **)&(c_array), size, max, sizeof((c_array)[0]), pool, new_max) +#define DN_LArray_GrowIfNeededFromPool(c_array, size, max, pool, add_count) DN_CArray2_GrowIfNeededFromPool((void **)(c_array), size, max, sizeof((c_array)[0]), pool, add_count) +#define DN_LArray_MakeArray(c_array, size, max, count, z_mem) (decltype(&(c_array)[0]))DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), count, z_mem) +#define DN_LArray_MakeArrayZ(c_array, size, max, count) (decltype(&(c_array)[0]))DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), count, DN_ZMem_Yes) +#define DN_LArray_Make(c_array, size, max, z_mem) (decltype(&(c_array)[0]))DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), 1, z_mem) +#define DN_LArray_MakeZ(c_array, size, max) (decltype(&(c_array)[0]))DN_CArray2_MakeArray(c_array, size, max, sizeof((c_array)[0]), 1, DN_ZMem_Yes) +#define DN_LArray_AddArray(c_array, size, max, items, count, add) (decltype(&(c_array)[0]))DN_CArray2_AddArray(c_array, size, max, sizeof((c_array)[0]), items, count, add) +#define DN_LArray_Add(c_array, size, max, item, add) (decltype(&(c_array)[0]))DN_CArray2_AddArray(c_array, size, max, sizeof((c_array)[0]), &item, 1, add) +#define DN_LArray_AppendArray(c_array, size, max, items, count) (decltype(&(c_array)[0]))DN_CArray2_AddArray(c_array, size, max, sizeof((c_array)[0]), items, count, DN_ArrayAdd_Append) +#define DN_LArray_Append(c_array, size, max, item) (decltype(&(c_array)[0]))DN_CArray2_AddArray(c_array, size, max, sizeof((c_array)[0]), &item, 1, DN_ArrayAdd_Append) +#define DN_LArray_PrependArray(c_array, size, max, items, count) (decltype(&(c_array)[0]))DN_CArray2_AddArray(c_array, size, max, sizeof((c_array)[0]), items, count, DN_ArrayAdd_Prepend) +#define DN_LArray_Prepend(c_array, size, max, item) (decltype(&(c_array)[0]))DN_CArray2_AddArray(c_array, size, max, sizeof((c_array)[0]), &item, 1, DN_ArrayAdd_Prepend) +#define DN_LArray_EraseRange(c_array, size, begin_index, count, erase) DN_CArray2_EraseRange(c_array, size, sizeof((c_array)[0]), begin_index, count, erase) +#define DN_LArray_Erase(c_array, size, index, erase) DN_CArray2_EraseRange(c_array, size, sizeof((c_array)[0]), index, 1, erase) +#define DN_LArray_InsertArray(c_array, size, max, index, items, count) (decltype(&(c_array)[0]))DN_CArray2_InsertArray(c_array, size, max, sizeof((c_array)[0]), index, items, count) +#define DN_LArray_Insert(c_array, size, max, index, item) (decltype(&(c_array)[0]))DN_CArray2_InsertArray(c_array, size, max, sizeof((c_array)[0]), index, &item, 1) + +#define DN_IArray_ResizeFromPool(array, pool, new_max) DN_CArray2_ResizeFromPool((void **)(&(array)->data), &(array)->size, &(array)->max, sizeof((array)->data[0]), pool, new_max) +#define DN_IArray_GrowFromPool(array, pool, new_max) DN_CArray2_GrowFromPool((void **)(&(array)->data), &(array)->size, &(array)->max, sizeof((array)->data[0]), pool, new_max) +#define DN_IArray_GrowIfNeededFromPool(array, pool, add_count) DN_CArray2_GrowIfNeededFromPool((void **)(&(array)->data), (array)->size, &(array)->max, sizeof((array)->data[0]), pool, add_count) +#define DN_IArray_MakeArray(array, count, z_mem) (decltype(&((array)->data)[0]))DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), count, z_mem) +#define DN_IArray_MakeArrayZ(array, count) (decltype(&((array)->data)[0]))DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), count, DN_ZMem_Yes) +#define DN_IArray_Make(array, z_mem) (decltype(&((array)->data)[0]))DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), 1, z_mem) +#define DN_IArray_MakeZ(array) (decltype(&((array)->data)[0]))DN_CArray2_MakeArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), 1, DN_ZMem_Yes) +#define DN_IArray_AddArray(array, items, count, add) (decltype(&((array)->data)[0]))DN_CArray2_AddArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, add) +#define DN_IArray_Add(array, item, add) (decltype(&((array)->data)[0]))DN_CArray2_AddArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, add) +#define DN_IArray_AppendArray(array, items, count) (decltype(&((array)->data)[0]))DN_CArray2_AddArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, DN_ArrayAdd_Append) +#define DN_IArray_Append(array, item) (decltype(&((array)->data)[0]))DN_CArray2_AddArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, DN_ArrayAdd_Append) +#define DN_IArray_PrependArray(array, items, count) (decltype(&((array)->data)[0]))DN_CArray2_AddArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), items, count, DN_ArrayAdd_Prepend) +#define DN_IArray_Prepend(array, item) (decltype(&((array)->data)[0]))DN_CArray2_AddArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), &item, 1, DN_ArrayAdd_Prepend) +#define DN_IArray_EraseRange(array, begin_index, count, erase) DN_CArray2_EraseRange((array)->data, &(array)->size, sizeof(((array)->data)[0]), begin_index, count, erase) +#define DN_IArray_Erase(array, index, erase) DN_CArray2_EraseRange((array)->data, &(array)->size, sizeof(((array)->data)[0]), index, 1, erase) +#define DN_IArray_InsertArray(array, index, items, count) (decltype(&((array)->data)[0]))DN_CArray2_InsertArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), index, items, count) +#define DN_IArray_Insert(array, index, item, count) (decltype(&((array)->data)[0]))DN_CArray2_InsertArray((array)->data, &(array)->size, (array)->max, sizeof(((array)->data)[0]), index, &item, 1) DN_API DN_ArrayEraseResult DN_CArray2_EraseRange (void *data, DN_USize *size, DN_USize elem_size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); DN_API void *DN_CArray2_MakeArray (void *data, DN_USize *size, DN_USize max, DN_USize data_size, DN_USize make_size, DN_ZMem z_mem); diff --git a/Source/Extra/dn_net2.cpp b/Source/Extra/dn_net.cpp similarity index 68% rename from Source/Extra/dn_net2.cpp rename to Source/Extra/dn_net.cpp index c5e6f14..a2c48bc 100644 --- a/Source/Extra/dn_net2.cpp +++ b/Source/Extra/dn_net.cpp @@ -1,14 +1,33 @@ -#define DN_NET2_CURL_CPP +#define DN_NET_CURL_CPP #include "../dn_base_inc.h" #include "../dn_os_inc.h" -DN_NET2Response DN_NET2_MakeResponseFromFinishedRequest_(DN_NET2Request request, DN_Arena *arena) +DN_NETRequestInternal *DN_NET_RequestFromHandle(DN_NETRequest request) { - DN_NET2Response result = {}; - DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle; + DN_NETRequestInternal *ptr = DN_Cast(DN_NETRequestInternal *) request.handle; + DN_NETRequestInternal *result = nullptr; + if (ptr && ptr->gen == request.gen) + result = ptr; + return result; +} + +DN_NETRequest DN_NET_HandleFromRequest(DN_NETRequestInternal *request) +{ + DN_NETRequest result = {}; + if (request) { + result.handle = DN_Cast(DN_UPtr) request; + result.gen = request->gen; + } + return result; +} + +DN_NETResponse DN_NET_MakeResponseFromFinishedRequest_(DN_NETRequest request, DN_Arena *arena) +{ + DN_NETResponse result = {}; + DN_NETRequestInternal *request_ptr = DN_Cast(DN_NETRequestInternal *) request.handle; if (request_ptr && request_ptr->gen == request.gen) { - DN_NET2ResponseInternal const *response = &request_ptr->response; + DN_NETResponseInternal const *response = &request_ptr->response; // NOTE: Construct the response from the request result.request = request; @@ -21,7 +40,7 @@ DN_NET2Response DN_NET2_MakeResponseFromFinishedRequest_(DN_NET2Request request, return result; } -void DN_NET2_EndFinishedRequest_(DN_NET2Core *net, DN_NET2RequestInternal *request) +void DN_NET_EndFinishedRequest_(DN_NETRequestInternal *request) { // NOTE: Deallocate the memory used in the request and reset the string builder DN_ArenaPopTo(&request->arena, request->start_response_arena_pos); @@ -31,7 +50,7 @@ void DN_NET2_EndFinishedRequest_(DN_NET2Core *net, DN_NET2RequestInternal *reque DN_Assert(request->next == nullptr); } -void DN_NET2_BaseInit_(DN_NET2Core *net, char *base, DN_U64 base_size) +void DN_NET_BaseInit_(DN_NETCore *net, char *base, DN_U64 base_size) { net->base = base; net->base_size = base_size; @@ -39,11 +58,11 @@ void DN_NET2_BaseInit_(DN_NET2Core *net, char *base, DN_U64 base_size) net->completion_sem = DN_OS_SemaphoreInit(0); } -DN_NET2Request DN_NET2_SetupRequest_(DN_NET2RequestInternal *request, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args, DN_NET2RequestType type) +DN_NETRequest DN_NET_SetupRequest_(DN_NETRequestInternal *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type) { // NOTE: Setup request DN_Assert(request); - DN_NET2Request result = {}; + DN_NETRequest result = {}; if (request) { if (!request->arena.curr) request->arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_ArenaFlags_Nil); @@ -56,7 +75,7 @@ DN_NET2Request DN_NET2_SetupRequest_(DN_NET2RequestInternal *request, DN_Str8 ur request->args.flags = args->flags; request->args.username = DN_Str8FromStr8Arena(&request->arena, args->username); request->args.password = DN_Str8FromStr8Arena(&request->arena, args->password); - if (type == DN_NET2RequestType_HTTP) + if (type == DN_NETRequestType_HTTP) request->args.payload = DN_Str8FromStr8Arena(&request->arena, args->payload); request->args.headers = DN_ArenaNewArray(&request->arena, DN_Str8, args->headers_size, DN_ZMem_No); diff --git a/Source/Extra/dn_net.h b/Source/Extra/dn_net.h new file mode 100644 index 0000000..a5c8b86 --- /dev/null +++ b/Source/Extra/dn_net.h @@ -0,0 +1,136 @@ +#if !defined(DN_NET_H) +#define DN_NET_H + +#include "../dn_base_inc.h" +#include "../dn_os_inc.h" + +enum DN_NETRequestType +{ + DN_NETRequestType_Nil, + DN_NETRequestType_HTTP, + DN_NETRequestType_WS, +}; + +enum DN_NETResponseState +{ + DN_NETResponseState_Nil, + DN_NETResponseState_Error, + DN_NETResponseState_HTTP, + DN_NETResponseState_WSOpen, + DN_NETResponseState_WSText, + DN_NETResponseState_WSBinary, + DN_NETResponseState_WSClose, + DN_NETResponseState_WSPing, + DN_NETResponseState_WSPong, +}; + +enum DN_NETWSSend +{ + DN_NETWSSend_Text, + DN_NETWSSend_Binary, + DN_NETWSSend_Close, + DN_NETWSSend_Ping, + DN_NETWSSend_Pong, +}; + +enum DN_NETDoHTTPFlags +{ + DN_NETDoHTTPFlags_Nil = 0, + DN_NETDoHTTPFlags_BasicAuth = 1 << 0, +}; + +struct DN_NETDoHTTPArgs +{ + // NOTE: WS and HTTP args + DN_NETDoHTTPFlags flags; + DN_Str8 username; + DN_Str8 password; + DN_Str8 *headers; + DN_U16 headers_size; + + // NOTE: HTTP args only + DN_Str8 payload; +}; + +struct DN_NETRequest +{ + DN_UPtr handle; + DN_U64 gen; +}; + +struct DN_NETResponseInternal +{ + DN_NETResponseState state; + bool ws_has_more; + DN_Str8Builder body; + DN_U32 http_status; + DN_Str8 error_str8; +}; + +struct DN_NETResponse +{ + // NOTE: Common to WS and HTTP responses + DN_NETResponseState state; + DN_NETRequest request; + DN_Str8 error_str8; + DN_Str8 body; + + // NOTE: HTTP responses only + DN_U32 http_status; +}; + +struct DN_NETRequestInternal +{ + // NOTE: Initialised in user thread, then read-only and shared to networking thread until reset + DN_Arena arena; + DN_USize start_response_arena_pos; + DN_NETRequestType type; + DN_U64 gen; + DN_Str8 url; + DN_Str8 method; + DN_OSSemaphore completion_sem; + DN_NETDoHTTPArgs args; + DN_NETResponseInternal response; + DN_NETRequestInternal *next; + DN_NETRequestInternal *prev; + DN_U64 context[2]; +}; + +struct DN_NETCore +{ + char *base; + DN_U64 base_size; + DN_Arena arena; + DN_OSSemaphore completion_sem; + void *context; +}; + +typedef void (DN_NETInitFunc) (DN_NETCore *net, char *base, DN_U64 base_size); +typedef void (DN_NETDeinitFunc) (DN_NETCore *net); +typedef DN_NETRequest (DN_NETDoHTTPFunc) (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); +typedef DN_NETRequest (DN_NETDoWSFunc) (DN_NETCore *net, DN_Str8 url); +typedef void (DN_NETDoWSSendFunc) (DN_NETRequest request, DN_Str8 data, DN_NETWSSend send); +typedef DN_NETResponse (DN_NETWaitForResponseFunc) (DN_NETRequest request, DN_Arena *arena, DN_U32 timeout_ms); +typedef DN_NETResponse (DN_NETWaitForAnyResponseFunc)(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); + +struct DN_NETInterface +{ + DN_NETInitFunc* init; + DN_NETDeinitFunc* deinit; + DN_NETDoHTTPFunc* do_http; + DN_NETDoWSFunc* do_ws; + DN_NETDoWSSendFunc* do_ws_send; + DN_NETWaitForResponseFunc* wait_for_response; + DN_NETWaitForAnyResponseFunc* wait_for_any_response; +}; + +DN_NETRequestInternal *DN_NET_RequestFromHandle (DN_NETRequest request); +DN_NETRequest DN_NET_HandleFromRequest (DN_NETRequestInternal *request); + +// NOTE: Internal functions for different networking implementations to use +void DN_NET_BaseInit_ (DN_NETCore *net, char *base, DN_U64 base_size); +DN_NETRequest DN_NET_SetupRequest_ (DN_NETRequestInternal *request, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type); +DN_NETResponse DN_NET_MakeResponseFromFinishedRequest_(DN_NETRequest request, DN_Arena *arena); +void DN_NET_EndFinishedRequest_ (DN_NETRequestInternal *request); + +#endif // DN_NET_H diff --git a/Source/Extra/dn_net2.h b/Source/Extra/dn_net2.h deleted file mode 100644 index 293af61..0000000 --- a/Source/Extra/dn_net2.h +++ /dev/null @@ -1,133 +0,0 @@ -#if !defined(DN_NET2_H) -#define DN_NET2_H - -#include "../dn_base_inc.h" -#include "../dn_os_inc.h" - -enum DN_NET2RequestType -{ - DN_NET2RequestType_Nil, - DN_NET2RequestType_HTTP, - DN_NET2RequestType_WS, -}; - -enum DN_NET2ResponseState -{ - DN_NET2ResponseState_Nil, - DN_NET2ResponseState_Error, - DN_NET2ResponseState_HTTP, - DN_NET2ResponseState_WSOpen, - DN_NET2ResponseState_WSText, - DN_NET2ResponseState_WSBinary, - DN_NET2ResponseState_WSClose, - DN_NET2ResponseState_WSPing, - DN_NET2ResponseState_WSPong, -}; - -enum DN_NET2WSSend -{ - DN_NET2WSSend_Text, - DN_NET2WSSend_Binary, - DN_NET2WSSend_Close, - DN_NET2WSSend_Ping, - DN_NET2WSSend_Pong, -}; - -enum DN_NET2DoHTTPFlags -{ - DN_NET2DoHTTPFlags_Nil = 0, - DN_NET2DoHTTPFlags_BasicAuth = 1 << 0, -}; - -struct DN_NET2DoHTTPArgs -{ - // NOTE: WS and HTTP args - DN_NET2DoHTTPFlags flags; - DN_Str8 username; - DN_Str8 password; - DN_Str8 *headers; - DN_U16 headers_size; - - // NOTE: HTTP args only - DN_Str8 payload; -}; - -struct DN_NET2Request -{ - DN_UPtr handle; - DN_U64 gen; -}; - -struct DN_NET2ResponseInternal -{ - DN_NET2ResponseState state; - bool ws_has_more; - DN_Str8Builder body; - DN_U32 http_status; - DN_Str8 error_str8; -}; - -struct DN_NET2Response -{ - // NOTE: Common to WS and HTTP responses - DN_NET2ResponseState state; - DN_NET2Request request; - DN_Str8 error_str8; - DN_Str8 body; - - // NOTE: HTTP responses only - DN_U32 http_status; -}; - -struct DN_NET2RequestInternal -{ - // NOTE: Initialised in user thread, then read-only and shared to networking thread until reset - DN_Arena arena; - DN_USize start_response_arena_pos; - DN_NET2RequestType type; - DN_U64 gen; - DN_Str8 url; - DN_Str8 method; - DN_OSSemaphore completion_sem; - DN_NET2DoHTTPArgs args; - DN_NET2ResponseInternal response; - DN_NET2RequestInternal *next; - DN_NET2RequestInternal *prev; - DN_U64 context[2]; -}; - -struct DN_NET2Core -{ - char *base; - DN_U64 base_size; - DN_Arena arena; - DN_OSSemaphore completion_sem; - DN_NET2RequestInternal *done_list; - DN_NET2RequestInternal *free_list; - void *context; -}; - -typedef void (DN_NET2InitFunc) (DN_NET2Core *net, char *base, DN_U64 base_size); -typedef DN_NET2Request (DN_NET2DoHTTPFunc) (DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args); -typedef DN_NET2Request (DN_NET2DoWSFunc) (DN_NET2Core *net, DN_Str8 url); -typedef void (DN_NET2DoWSSendFunc) (DN_NET2Request request, DN_Str8 data, DN_NET2WSSend send); -typedef DN_NET2Response (DN_NET2WaitForResponseFunc) (DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms); -typedef DN_NET2Response (DN_NET2WaitForAnyResponseFunc)(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms); - -struct DN_NET2Interface -{ - DN_NET2InitFunc* init; - DN_NET2DoHTTPFunc* do_http; - DN_NET2DoWSFunc* do_ws; - DN_NET2DoWSSendFunc* do_ws_send; - DN_NET2WaitForResponseFunc* wait_for_response; - DN_NET2WaitForAnyResponseFunc* wait_for_any_response; -}; - -// NOTE: Internal functions for different networking implementations to use -void DN_NET2_BaseInit_ (DN_NET2Core *net, char *base, DN_U64 base_size); -DN_NET2Request DN_NET2_SetupRequest_ (DN_NET2RequestInternal *request, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args, DN_NET2RequestType type); -DN_NET2Response DN_NET2_MakeResponseFromFinishedRequest_(DN_NET2Request request, DN_Arena *arena); -void DN_NET2_EndFinishedRequest_ (DN_NET2Core *net, DN_NET2RequestInternal *request); - -#endif // DN_NET2_H diff --git a/Source/Extra/dn_net2_curl.cpp b/Source/Extra/dn_net2_curl.cpp deleted file mode 100644 index cb9045b..0000000 --- a/Source/Extra/dn_net2_curl.cpp +++ /dev/null @@ -1,553 +0,0 @@ -#define DN_NET2_CURL_CPP - -#include "../dn_base_inc.h" -#include "../dn_os_inc.h" -#include "dn_net2_curl.h" - -static void DN_NET2_MarkRequestDone_(DN_NET2Core *net, DN_NET2RequestInternal *request) -{ - for (DN_OS_MutexScope(&net->free_or_done_mutex)) - DN_DLList_Append(net->done_list, request); - DN_OS_SemaphoreIncrement(&net->completion_sem, 1); - DN_OS_SemaphoreIncrement(&request->completion_sem, 1); -} - -static DN_NET2Response DN_NET2_WaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms) -{ - DN_NET2Response result = {}; - DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&net->completion_sem, timeout_ms); - if (wait == DN_OSSemaphoreWaitResult_Success) { - for (DN_OS_MutexScope(&net->free_or_done_mutex)) { - DN_NET2RequestInternal *request = nullptr; - DN_DLList_Dequeue(net->done_list, request); - DN_Assert(request); - - // NOTE: Fill in the result - DN_NET2ResponseInternal const *response = &request->response; - result.request = {DN_Cast(DN_U64) request}; - result.success = response->status != DN_NET2RequestStatus_Error; - result.ws_type = response->ws_type; - result.http_status = response->http_status; - result.body = DN_Str8BuilderBuild(&response->body, arena); - if (response->error.size) - result.error = DN_Str8FromStr8Arena(arena, response->error); - - // NOTE: Deallocate the memory used in the request - DN_ArenaPopTo(&request->arena, request->start_response_arena_pos); - request->response.body = DN_Str8BuilderFromArena(&request->arena); - - // NOTE: For websocket requests, notify the NET thread we've read data from it and it can go - // back to polling the socket for more data - if (request->type == DN_NET2RequestType_WS && request->response.status != DN_NET2RequestStatus_Error) { - DN_NET2RingEvent event = {}; - event.type = DN_NET2RingEventType_ReceivedWSReceipt; - event.request = {DN_Cast(DN_U64)request}; - for (DN_OS_MutexScope(&net->ring_mutex)) - DN_Ring_WriteStruct(&net->ring, &event); - curl_multi_wakeup(net->curlm); - } - } - } - return result; -} - -static DN_NET2Response DN_NET2_WaitForResponse(DN_NET2Core *net, DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms) -{ - DN_NET2Response result = {}; - if (request.handle != 0) { - DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle; - DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&request_ptr->completion_sem, timeout_ms); - if (wait == DN_OSSemaphoreWaitResult_Success) { - // NOTE: Fill in the result - DN_NET2ResponseInternal const *response = &request_ptr->response; - result.request = request; - result.success = response->status != DN_NET2RequestStatus_Error; - result.ws_type = response->ws_type; - result.http_status = response->http_status; - result.body = DN_Str8BuilderBuild(&response->body, arena); - if (response->error.size) - result.error = DN_Str8FromStr8Arena(arena, response->error); - - // NOTE: Deallocate the memory used in the request - DN_ArenaPopTo(&request_ptr->arena, request_ptr->start_response_arena_pos); - request_ptr->response.body = DN_Str8BuilderFromArena(&request_ptr->arena); - - // NOTE: Decrement the global completion tracking semaphore (this is so that if you waited on - // the net object's semaphore, you don't get a phantom wakeup because this function already - // consumed it). - DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/); - DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize)net_wait_result); - - // NOTE: Remove the request from the done list - for (DN_OS_MutexScope(&net->free_or_done_mutex)) { - bool is_in_done_list = false; - for (DN_DLList_ForEach(it, net->done_list)) { - if (it == request_ptr) { - is_in_done_list = true; - break; - } - } - DN_Assert(is_in_done_list); - DN_DLList_Detach(request_ptr); - } - - // NOTE: For websocket requests, notify the NET thread we've read data from it and it can go - // back to polling the socket for more data - if (request_ptr->type == DN_NET2RequestType_WS && request_ptr->response.status != DN_NET2RequestStatus_Error) { - DN_NET2RingEvent event = {}; - event.type = DN_NET2RingEventType_ReceivedWSReceipt; - event.request = request; - for (DN_OS_MutexScope(&net->ring_mutex)) - DN_Ring_WriteStruct(&net->ring, &event); - curl_multi_wakeup(net->curlm); - } - } - } - - return result; -} - -static void DN_NET2_DeinitRequest(DN_NET2Core *net, DN_NET2Request *request) -{ - DN_NET2RingEvent event = {}; - event.type = DN_NET2RingEventType_DeinitRequest; - event.request = *request; - *request = {}; - for (DN_OS_MutexScope(&net->ring_mutex)) - DN_Ring_WriteStruct(&net->ring, &event); -} - -static DN_USize DN_NET2_HTTPCallback_(char *payload, DN_USize size, DN_USize count, void *user_data) -{ - auto *request = DN_Cast(DN_NET2RequestInternal *) user_data; - DN_USize result = 0; - DN_USize payload_size = size * count; - if (DN_Str8BuilderAppendBytesCopy(&request->response.body, payload, payload_size)) - result = payload_size; - return result; -} - -static int32_t DN_NET2_ThreadEntryPoint_(DN_OSThread *thread) -{ - DN_NET2Core *net = DN_Cast(DN_NET2Core *) thread->user_context; - DN_OS_ThreadSetName(DN_Str8FromPtr(net->curl_thread.name.data, net->curl_thread.name.size)); - - for (;;) { - DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); - for (bool dequeue_ring = true; dequeue_ring;) { - // NOTE: Dequeue user request - DN_NET2RingEvent event = {}; - for (DN_OS_MutexScope(&net->ring_mutex)) { - if (DN_Ring_HasData(&net->ring, sizeof(event))) - DN_Ring_Read(&net->ring, &event, sizeof(event)); - } - - switch (event.type) { - case DN_NET2RingEventType_Nil: dequeue_ring = false; break; - - case DN_NET2RingEventType_DoHTTP: { - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *)event.request.handle; - DN_Assert(request->response.status != DN_NET2RequestStatus_Error); - switch (request->type) { - case DN_NET2RequestType_Nil: { - DN_NET2_MarkRequestDone_(net, request); - DN_InvalidCodePath; - } break; - - case DN_NET2RequestType_HTTP: { - DN_Assert(request->response.status == DN_NET2RequestStatus_Nil); - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context; - CURLMcode multi_add = curl_multi_add_handle(net->curlm, conn->curl); - DN_Assert(multi_add == CURLM_OK); - DN_Assert(request->next == nullptr); - DN_Assert(request->prev == nullptr); - DN_DLList_Append(net->http_list, request); - } break; - - case DN_NET2RequestType_WS: { - DN_Assert(request->response.status == DN_NET2RequestStatus_HTTPReceived || - request->response.status == DN_NET2RequestStatus_WSReceived || - request->response.status == DN_NET2RequestStatus_Nil); - - DN_Assert(request->next == nullptr); - DN_Assert(request->prev == nullptr); - if (request->response.status == DN_NET2RequestStatus_Nil) { - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context; - CURLMcode multi_add = curl_multi_add_handle(net->curlm, conn->curl); - DN_Assert(multi_add == CURLM_OK); - DN_DLList_Append(net->http_list, request); // Open the WS connection - } else { - DN_DLList_Append(net->ws_list, request); // Ready to send data through the WS connection - } - } break; - } - } break; - - case DN_NET2RingEventType_SendWS: { - DN_Str8 payload = {}; - for (DN_OS_MutexScope(&net->ring_mutex)) { - DN_Assert(DN_Ring_HasData(&net->ring, event.send_ws_payload_size)); - payload = DN_Str8FromArena(tmem.arena, event.send_ws_payload_size, DN_ZMem_No); - DN_Ring_Read(&net->ring, payload.data, payload.size); - } - - DN_U32 curlws_flag = 0; - switch (event.send_ws_type) { - case DN_NET2WSType_Nil: DN_InvalidCodePath; break; - case DN_NET2WSType_Text: curlws_flag = CURLWS_TEXT; break; - case DN_NET2WSType_Binary: curlws_flag = CURLWS_BINARY; break; - case DN_NET2WSType_Close: curlws_flag = CURLWS_CLOSE; break; - case DN_NET2WSType_Ping: curlws_flag = CURLWS_PING; break; - case DN_NET2WSType_Pong: curlws_flag = CURLWS_PONG; break; - } - - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle; - DN_Assert(request->type == DN_NET2RequestType_WS); - DN_Assert(request->response.status == DN_NET2RequestStatus_HTTPReceived || request->response.status == DN_NET2RequestStatus_WSReceived); - - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context; - DN_USize sent = 0; - CURLcode send_result = curl_ws_send(conn->curl, payload.data, payload.size, &sent, 0, curlws_flag); - DN_AssertF(send_result == CURLE_OK, "Failed to send: %s", curl_easy_strerror(send_result)); - DN_AssertF(sent == payload.size, "Failed to send all bytes (%zu vs %zu)", sent, payload.size); - } break; - - case DN_NET2RingEventType_ReceivedWSReceipt: { - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle; - DN_Assert(request->type == DN_NET2RequestType_WS); - DN_Assert(request->response.status == DN_NET2RequestStatus_WSReceived || - request->response.status == DN_NET2RequestStatus_HTTPReceived); - DN_Assert(request->next == nullptr); - DN_Assert(request->prev == nullptr); - DN_DLList_Append(net->ws_list, request); - } break; - - case DN_NET2RingEventType_DeinitRequest: { - if (event.request.handle != 0) { - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle; - request->response = {}; - - DN_ArenaClear(&request->arena); - DN_OS_SemaphoreDeinit(&request->completion_sem); - - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context; - curl_multi_remove_handle(net->curlm, conn->curl); - curl_easy_reset(conn->curl); - curl_slist_free_all(conn->curl_slist); - - for (DN_OS_MutexScope(&net->free_or_done_mutex)) - DN_DLList_Append(net->free_list, request); - } - } break; - } - } - - // NOTE: Pump handles - int running_handles = 0; - CURLMcode perform_result = curl_multi_perform(net->curlm, &running_handles); - if (perform_result != CURLM_OK) - DN_InvalidCodePath; - - // NOTE: Check pump result - for (;;) { - int msgs_in_queue = 0; - CURLMsg *msg = curl_multi_info_read(net->curlm, &msgs_in_queue); - if (msg) { - // NOTE: Get request handle - DN_NET2RequestInternal *request = nullptr; - curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, DN_Cast(void **) & request); - DN_Assert(request); - - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *)request->context; - DN_Assert(conn->curl == msg->easy_handle); - - if (msg->data.result == CURLE_OK) { - // NOTE: Get HTTP response code - CURLcode get_result = curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &request->response.http_status); - if (get_result == CURLE_OK) { - request->response.status = DN_NET2RequestStatus_HTTPReceived; - } else { - request->response.error = DN_Str8FromFmtArena(&request->arena, "Failed to get HTTP response status (CURL %d): %s", msg->data.result, curl_easy_strerror(get_result)); - request->response.status = DN_NET2RequestStatus_Error; - } - } else { - request->response.status = DN_NET2RequestStatus_Error; - request->response.error = DN_Str8FromFmtArena(&request->arena, - "Net request to '%.*s' failed (CURL %d): %s", - DN_Str8PrintFmt(request->url), - msg->data.result, - curl_easy_strerror(msg->data.result)); - } - - if (request->type == DN_NET2RequestType_HTTP || request->response.status == DN_NET2RequestStatus_Error) { - // NOTE: Remove the request from the multi handle if we're a HTTP request - // because it typically terminates the connection. In websockets the - // connection remains in the multi-handle to allow you to send and - // receive WS data from it. - // - // If there's an error (either websocket or HTTP) we will also remove the - // connection from the multi handle as it failed. One a connection has - // failed, curl will not poll that connection so there's no point keeping - // it attached to the multi handle. - curl_multi_remove_handle(net->curlm, msg->easy_handle); - } - - DN_NET2_MarkRequestDone_(net, request); - } - - if (msgs_in_queue == 0) - break; - } - - // NOTE: Check websockets - DN_I32 sleep_time_ms = DN_DLList_HasItems(net->ws_list) ? 100 : INT32_MAX; - for (DN_DLList_ForEach(request, net->ws_list)) { - DN_Assert(request->response.status == DN_NET2RequestStatus_HTTPReceived || - request->response.status == DN_NET2RequestStatus_WSReceived); - - CURLcode receive_result = CURLE_OK; - const curl_ws_frame *meta = nullptr; - size_t bytes_read_this_frame = {}; - - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context; - for (;;) { - // NOTE: Determine WS payload size received - DN_USize bytes_read = 0; - receive_result = curl_ws_recv(conn->curl, nullptr, 0, &bytes_read, &meta); - if (receive_result != CURLE_OK || meta->bytesleft == 0) - break; - - // NOTE: Allocate and read - DN_Str8 buffer = DN_Str8FromArena(&request->arena, meta->bytesleft, DN_ZMem_No); - receive_result = curl_ws_recv(conn->curl, buffer.data, buffer.size, &buffer.size, &meta); - bytes_read_this_frame += buffer.size; - DN_Str8BuilderAppendRef(&request->response.body, buffer); - - if (meta->flags & CURLWS_TEXT) - request->response.ws_type = DN_NET2WSType_Text; - - if (meta->flags & CURLWS_BINARY) - request->response.ws_type = DN_NET2WSType_Binary; - - if (meta->flags & CURLWS_PING) - request->response.ws_type = DN_NET2WSType_Ping; - - if (meta->flags & CURLWS_PONG) - request->response.ws_type = DN_NET2WSType_Pong; - - if (meta->flags & CURLWS_CLOSE) - request->response.ws_type = DN_NET2WSType_Close; - - request->response.ws_has_more = meta->flags & CURLWS_CONT || meta->bytesleft > 0; - if (request->response.ws_has_more) { - if (meta->flags & CURLWS_CONT) { - bool is_text_or_binary = request->response.ws_type == DN_NET2WSType_Binary || - request->response.ws_type == DN_NET2WSType_Text; - DN_Assert(is_text_or_binary); - } - } - - if (receive_result != CURLE_OK || bytes_read_this_frame == meta->len) - break; - } - - // NOTE: We read all the possible bytes that CURL has received for this - // socket, but, there are more bytes left that we will receive on subsequent - // calls. We will continue to the next request and return back to this one - // when PumpRequests is called again where hopefully that data has arrived. - if (request->response.ws_has_more || receive_result == CURLE_AGAIN) { - if (receive_result == CURLE_AGAIN) - sleep_time_ms = 100; - continue; - } - - request->response.status = DN_NET2RequestStatus_WSReceived; - if (receive_result != CURLE_OK) { - request->response.status = DN_NET2RequestStatus_Error; - request->response.error = DN_Str8FromFmtArena(&request->arena, - "Websocket failed to receive data for '%.*s' (CURL %d): %s", - DN_Str8PrintFmt(request->url), - receive_result, - curl_easy_strerror(receive_result)); - } - - DN_NET2RequestInternal *request_copy = request; - request = request->prev; - DN_NET2_MarkRequestDone_(net, request_copy); - } - - curl_multi_poll(net->curlm, nullptr, 0, sleep_time_ms, nullptr); - } - return 0; -} - -static void DN_NET2_Init(DN_NET2Core *net, char *ring_base, DN_USize ring_size, char *base, DN_U64 base_size) -{ - net->base = base; - net->base_size = base_size; - net->arena = DN_ArenaFromBuffer(net->base, net->base_size, DN_ArenaFlags_Nil); - net->ring.base = ring_base; - net->ring.size = ring_size; - net->ring_mutex = DN_OS_MutexInit(); - net->completion_sem = DN_OS_SemaphoreInit(0); - net->free_or_done_mutex = DN_OS_MutexInit(); - net->curlm = DN_Cast(CURLM *)curl_multi_init(); - DN_DLList_InitArena(net->free_list, DN_NET2RequestInternal, &net->arena); - DN_DLList_InitArena(net->http_list, DN_NET2RequestInternal, &net->arena); - DN_DLList_InitArena(net->ws_list, DN_NET2RequestInternal, &net->arena); - DN_DLList_InitArena(net->done_list, DN_NET2RequestInternal, &net->arena); - - DN_FmtAppend(net->curl_thread.name.data, &net->curl_thread.name.size, sizeof(net->curl_thread.name.data), "NET (CURL)"); - DN_OS_ThreadInit(&net->curl_thread, DN_NET2_ThreadEntryPoint_, net); -} - -static void DN_NET2_SetupCurlRequest_(DN_NET2RequestInternal *request) -{ - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context; - CURL *curl = conn->curl; - curl_easy_setopt(curl, CURLOPT_PRIVATE, request); - - // NOTE: Perform request and read all response headers before handing - // control back to app. - curl_easy_setopt(curl, CURLOPT_URL, request->url.data); - curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); - - // NOTE: Setup response handler - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DN_NET2_HTTPCallback_); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, request); - - // NOTE: Assign HTTP headers - for (DN_ForItSize(it, DN_Str8, request->args.headers, request->args.headers_size)) - conn->curl_slist = curl_slist_append(conn->curl_slist, it.data->data); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, conn->curl_slist); - - // NOTE: Setup handle for protocol ///////////////////////////////////////////////////////// - switch (request->type) { - case DN_NET2RequestType_Nil: DN_InvalidCodePath; break; - - case DN_NET2RequestType_WS: { - curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); - } break; - - case DN_NET2RequestType_HTTP: { - request->method = DN_Str8TrimWhitespaceAround(request->method); - DN_Str8 const GET = DN_Str8Lit("GET"); - DN_Str8 const POST = DN_Str8Lit("POST"); - - if (DN_Str8EqInsensitive(request->method, GET)) { - curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); - } else if (DN_Str8EqInsensitive(request->method, POST)) { - curl_easy_setopt(curl, CURLOPT_POST, 1); - if (request->args.payload.size > DN_Gigabytes(2)) - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, request->args.payload.size); - else - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request->args.payload.size); - curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request->args.payload.data); - } else { - DN_InvalidCodePathF("Unimplemented"); - } - } break; - } - - // NOTE: Handle basic auth - if (request->args.flags & DN_NET2DoHTTPFlags_BasicAuth) { - if (request->args.username.size && request->args.password.size) { - DN_Assert(request->args.username.data[request->args.username.size] == 0); - DN_Assert(request->args.password.data[request->args.password.size] == 0); - curl_easy_setopt(curl, CURLOPT_USERNAME, request->args.username.data); - curl_easy_setopt(curl, CURLOPT_PASSWORD, request->args.password.data); - } - } -} - -static DN_NET2Request DN_NET2_DoRequest_(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args, DN_NET2RequestType type) -{ - DN_Assert(net); - - // NOTE: Allocate request - DN_NET2RequestInternal *request = nullptr; - for (DN_OS_MutexScope(&net->free_or_done_mutex)) - DN_DLList_Dequeue(net->free_list, request); - - if (!request) { - request = DN_ArenaNew(&net->arena, DN_NET2RequestInternal, DN_ZMem_Yes); - DN_NET2CurlConn *conn = new (request->context) DN_NET2CurlConn; - conn->curl = DN_Cast(CURL *)curl_easy_init(); - } - - // NOTE: Setup request - DN_NET2Request result = {}; - if (request) { - result.handle = DN_Cast(DN_U64) request; - if (!request->arena.curr) - request->arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_ArenaFlags_Nil); - - request->type = type; - request->gen = DN_Max(request->gen + 1, 1); - request->url = DN_Str8FromStr8Arena(&request->arena, url); - request->method = DN_Str8FromStr8Arena(&request->arena, method); - - if (args) { - request->args.flags = args->flags; - request->args.username = DN_Str8FromStr8Arena(&request->arena, args->username); - request->args.password = DN_Str8FromStr8Arena(&request->arena, args->password); - if (type == DN_NET2RequestType_HTTP) - request->args.payload = DN_Str8FromStr8Arena(&request->arena, args->payload); - - request->args.headers = DN_ArenaNewArray(&request->arena, DN_Str8, args->headers_size, DN_ZMem_No); - DN_Assert(request->args.headers); - if (request->args.headers) { - for (DN_ForItSize(it, DN_Str8, args->headers, args->headers_size)) - request->args.headers[it.index] = DN_Str8FromStr8Arena(&request->arena, *it.data); - request->args.headers_size = args->headers_size; - } - } - - request->response.body = DN_Str8BuilderFromArena(&request->arena); - request->completion_sem = DN_OS_SemaphoreInit(0); - request->start_response_arena_pos = DN_ArenaPos(&request->arena); - - DN_NET2_SetupCurlRequest_(request); - - // NOTE: Submit request to the networking thread's queue - DN_NET2RingEvent event = {}; - event.type = DN_NET2RingEventType_DoHTTP; - event.request = result; - for (DN_OS_MutexScope(&net->ring_mutex)) - DN_Ring_WriteStruct(&net->ring, &event); - - curl_multi_wakeup(net->curlm); - } - - return result; -} - -static DN_NET2Request DN_NET2_DoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args) -{ - DN_NET2Request result = DN_NET2_DoRequest_(net, url, method, args, DN_NET2RequestType_HTTP); - return result; -} - -static DN_NET2Request DN_NET2_OpenWS(DN_NET2Core *net, DN_Str8 url, DN_NET2DoHTTPArgs const *args) -{ - DN_NET2Request result = DN_NET2_DoRequest_(net, url, DN_Str8Lit(""), args, DN_NET2RequestType_WS); - return result; -} - -static void DN_NET2_SendWS(DN_NET2Core *net, DN_NET2Request request, DN_Str8 payload, DN_NET2WSType type) -{ - DN_Assert(type != DN_NET2WSType_Nil); - DN_NET2RingEvent event = {}; - event.type = DN_NET2RingEventType_SendWS; - event.request = request; - event.send_ws_payload_size = payload.size; - event.send_ws_type = type; - - for (DN_OS_MutexScope(&net->ring_mutex)) { - DN_Assert(DN_Ring_HasSpace(&net->ring, payload.size)); - DN_Ring_WriteStruct(&net->ring, &event); - DN_Ring_Write(&net->ring, payload.data, payload.size); - } - curl_multi_wakeup(net->curlm); -} - diff --git a/Source/Extra/dn_net2_curl.h b/Source/Extra/dn_net2_curl.h deleted file mode 100644 index f988518..0000000 --- a/Source/Extra/dn_net2_curl.h +++ /dev/null @@ -1,151 +0,0 @@ -#if !defined(DN_NET2_CURL_H) -#define DN_NET2_CURL_H - -#include "../dn_base_inc.h" -#include "../dn_os_inc.h" - -enum DN_NET2RequestType -{ - DN_NET2RequestType_Nil, - DN_NET2RequestType_HTTP, - DN_NET2RequestType_WS, -}; - -enum DN_NET2RequestStatus -{ - DN_NET2RequestStatus_Nil, - DN_NET2RequestStatus_Error, - DN_NET2RequestStatus_HTTPReceived, - DN_NET2RequestStatus_WSReceived, -}; - -enum DN_NET2WSType -{ - DN_NET2WSType_Nil, - DN_NET2WSType_Text, - DN_NET2WSType_Binary, - DN_NET2WSType_Close, - DN_NET2WSType_Ping, - DN_NET2WSType_Pong, -}; - -struct DN_NET2CurlConn -{ - void *curl; - struct curl_slist *curl_slist; -}; - -enum DN_NET2DoHTTPFlags -{ - DN_NET2DoHTTPFlags_Nil = 0, - DN_NET2DoHTTPFlags_BasicAuth = 1 << 0, -}; - -struct DN_NET2DoHTTPArgs -{ - // NOTE: WS and HTTP args - DN_NET2DoHTTPFlags flags; - DN_Str8 username; - DN_Str8 password; - DN_Str8 *headers; - DN_U16 headers_size; - - // NOTE: HTTP args only - DN_Str8 payload; -}; - -struct DN_NET2ResponseInternal -{ - DN_NET2RequestStatus status; - DN_NET2WSType ws_type; - bool ws_has_more; - DN_Str8Builder body; - DN_U32 http_status; - DN_Str8 error; -}; - -struct DN_NET2Request -{ - DN_U64 handle; - DN_U64 gen; -}; - -struct DN_NET2Response -{ - DN_NET2Request request; - bool success; - DN_NET2WSType ws_type; - DN_U32 http_status; - DN_Str8 error; - DN_Str8 body; -}; - -struct DN_NET2RequestInternal -{ - // NOTE: Initialised in user thread, then read-only and shared to networking thread until reset - DN_Arena arena; - DN_USize start_response_arena_pos; - DN_NET2RequestType type; - DN_U64 gen; - DN_Str8 url; - DN_Str8 method; - DN_OSSemaphore completion_sem; - DN_NET2DoHTTPArgs args; - DN_NET2ResponseInternal response; - DN_NET2RequestInternal *next; - DN_NET2RequestInternal *prev; - - // NOTE: Networking thread only - char context[sizeof(void *) * 2]; -}; - -enum DN_NET2RingEventType -{ - DN_NET2RingEventType_Nil, - DN_NET2RingEventType_DoHTTP, - DN_NET2RingEventType_SendWS, - DN_NET2RingEventType_ReceivedWSReceipt, - DN_NET2RingEventType_DeinitRequest, -}; - -struct DN_NET2RingEvent -{ - DN_NET2RingEventType type; - DN_NET2Request request; - DN_USize send_ws_payload_size; - DN_NET2WSType send_ws_type; -}; - -struct DN_NET2Core -{ - // NOTE: User thread only - char *base; // Backing memory for arena - DN_U64 base_size; - DN_Arena arena; // Allocates DN_NET2Request for appending to the user submission queue - - // NOTE: Shared w/ user and networking thread - DN_Ring ring; - DN_OSMutex ring_mutex; - - DN_OSSemaphore completion_sem; - DN_OSMutex free_or_done_mutex; // Lock for free list and done list - DN_NET2RequestInternal *done_list; - DN_NET2RequestInternal *free_list; - - DN_OSThread curl_thread; - - // NOTE: Networking thread only - void *curlm; - DN_NET2RequestInternal *http_list; - DN_NET2RequestInternal *ws_list; -}; - -static DN_NET2Response DN_NET2_WaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms); -static DN_NET2Response DN_NET2_WaitForResponse (DN_NET2Core *net, DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms); -static void DN_NET2_DeinitRequest (DN_NET2Core *net, DN_NET2Request *request); -static void DN_NET2_Init (DN_NET2Core *net, char *ring_base, DN_USize ring_size, char *base, DN_U64 base_size); -static DN_NET2Request DN_NET2_DoHTTP (DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args); -static DN_NET2Request DN_NET2_OpenWS (DN_NET2Core *net, DN_Str8 url, DN_NET2DoHTTPArgs const *args); -static void DN_NET2_SendWS (DN_NET2Core *net, DN_NET2Request request, DN_Str8 payload, DN_NET2WSType type); - -#endif // DN_NET2_CURL_H diff --git a/Source/Extra/dn_net_curl.cpp b/Source/Extra/dn_net_curl.cpp index dbcd18d..995bc39 100644 --- a/Source/Extra/dn_net_curl.cpp +++ b/Source/Extra/dn_net_curl.cpp @@ -1,59 +1,62 @@ -#include "dn_net2.h" +#include "dn_net.h" #include "dn_net_curl.h" -struct DN_NET2CurlConn +struct DN_NETCurlConn { void *curl; struct curl_slist *curl_slist; char error[CURL_ERROR_SIZE]; }; -enum DN_NET2CurlRingEventType +enum DN_NETCurlRingEventType { - DN_NET2CurlRingEventType_Nil, - DN_NET2CurlRingEventType_DoRequest, - DN_NET2CurlRingEventType_SendWS, - DN_NET2CurlRingEventType_ReceivedWSReceipt, - DN_NET2CurlRingEventType_DeinitRequest, + DN_NETCurlRingEventType_Nil, + DN_NETCurlRingEventType_DoRequest, + DN_NETCurlRingEventType_SendWS, + DN_NETCurlRingEventType_ReceivedWSReceipt, + DN_NETCurlRingEventType_DeinitRequest, }; -struct DN_NET2CurlRingEvent +struct DN_NETCurlRingEvent { - DN_NET2CurlRingEventType type; - DN_NET2Request request; - DN_USize ws_send_size; - DN_NET2WSSend ws_send; + DN_NETCurlRingEventType type; + DN_NETRequest request; + DN_USize ws_send_size; + DN_NETWSSend ws_send; }; -struct DN_NET2CurlCore +struct DN_NETCurlCore { // NOTE: Shared w/ user and networking thread - DN_Ring ring; - DN_OSMutex ring_mutex; + DN_Ring ring; + DN_OSMutex ring_mutex; + bool kill_thread; - DN_OSMutex free_or_done_mutex; // Lock for free list and done list - DN_OSThread thread; + DN_OSMutex list_mutex; // Lock for request, response, deinit, free list + DN_NETRequestInternal *request_list; // Current requests submitted by the user thread awaiting to move into the thread request list + DN_NETRequestInternal *thread_request_list; // Current requests being executed by the CURL thread. + // This list is exclusively owned by the CURL thread so no locking is needed + DN_NETRequestInternal *response_list; // Finished requests that are to be deqeued by the user via wait for response + DN_NETRequestInternal *deinit_list; // Requests that are finished and are awaiting to be de-initialised by the CURL thread + DN_NETRequestInternal *free_list; // Request pool that new requests will use before allocating // NOTE: Networking thread only - void *curlm; - DN_NET2RequestInternal *http_list; - DN_NET2RequestInternal *ws_list; + DN_OSThread thread; + void *thread_curlm; }; -DN_NET2Interface DN_NET2_CurlInterface() +static bool DN_NET_CurlRequestIsInList(DN_NETRequestInternal const *first, DN_NETRequestInternal const *find) { - DN_NET2Interface result = {}; - result.init = DN_NET2_CurlInit; - result.do_http = DN_NET2_CurlDoHTTP; - result.do_ws = DN_NET2_CurlDoWS; - result.do_ws_send = DN_NET2_CurlDoWSSend; - result.wait_for_response = DN_NET2_CurlWaitForResponse; - result.wait_for_any_response = DN_NET2_CurlWaitForAnyResponse; + bool result = false; + for (DN_NETRequestInternal const *it = first; !result && it; it = it->next) + result = find == it; return result; } -static void DN_NET2_CurlMarkRequestDone_(DN_NET2Core *net, DN_NET2RequestInternal *request) +static void DN_NET_CurlMarkRequestDone_(DN_NETCore *net, DN_NETRequestInternal *request) { + DN_Assert(request); + DN_Assert(net); // NOTE: The done list in CURL is also used as a place to put websocket requests after removing it // from the 'ws_list'. By doing this we are stopping the CURL thread from receiving more data on // the socket as that thread ticks the list of 'ws_list' sockets for data. @@ -62,16 +65,19 @@ static void DN_NET2_CurlMarkRequestDone_(DN_NET2Core *net, DN_NET2RequestInterna // into the 'ws_list' which then lets the CURL thread start receiving more data for that socket. // // Since CURL uses a background thread, we do this behind a mutex - DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *)net->context; - for (DN_OS_MutexScope(&curl->free_or_done_mutex)) - DN_DLList_Append(net->done_list, request); + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *)net->context; + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, request)); + DN_DoublyLLDetach(curl->thread_request_list, request); + DN_DoublyLLAppend(curl->response_list, request); + } DN_OS_SemaphoreIncrement(&net->completion_sem, 1); DN_OS_SemaphoreIncrement(&request->completion_sem, 1); } -static DN_USize DN_NET2_CurlHTTPCallback_(char *payload, DN_USize size, DN_USize count, void *user_data) +static DN_USize DN_NET_CurlHTTPCallback_(char *payload, DN_USize size, DN_USize count, void *user_data) { - auto *request = DN_Cast(DN_NET2RequestInternal *) user_data; + auto *request = DN_Cast(DN_NETRequestInternal *) user_data; DN_USize result = 0; DN_USize payload_size = size * count; if (DN_Str8BuilderAppendBytesCopy(&request->response.body, payload, payload_size)) @@ -79,61 +85,46 @@ static DN_USize DN_NET2_CurlHTTPCallback_(char *payload, DN_USize size, DN_USize return result; } -static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread) +static int32_t DN_NET_CurlThreadEntryPoint_(DN_OSThread *thread) { - DN_NET2Core *net = DN_Cast(DN_NET2Core *) thread->user_context; - DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *) net->context; + DN_NETCore *net = DN_Cast(DN_NETCore *) thread->user_context; + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; DN_OS_ThreadSetName(DN_Str8FromPtr(curl->thread.name.data, curl->thread.name.size)); - for (;;) { + while (!curl->kill_thread) { DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + + // NOTE: Handle events sitting in the ring queue for (bool dequeue_ring = true; dequeue_ring;) { - // NOTE: Dequeue user request - DN_NET2CurlRingEvent event = {}; + DN_NETCurlRingEvent event = {}; for (DN_OS_MutexScope(&curl->ring_mutex)) { if (DN_Ring_HasData(&curl->ring, sizeof(event))) DN_Ring_Read(&curl->ring, &event, sizeof(event)); } switch (event.type) { - case DN_NET2CurlRingEventType_Nil: dequeue_ring = false; break; + case DN_NETCurlRingEventType_Nil: dequeue_ring = false; break; - case DN_NET2CurlRingEventType_DoRequest: { - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *)event.request.handle; - DN_Assert(request->response.state != DN_NET2ResponseState_Error); - switch (request->type) { - case DN_NET2RequestType_Nil: { - DN_NET2_CurlMarkRequestDone_(net, request); - DN_InvalidCodePath; - } break; + case DN_NETCurlRingEventType_DoRequest: { + DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *)event.request.handle; + DN_Assert(request->response.state == DN_NETResponseState_Nil); + DN_Assert(request->type != DN_NETRequestType_Nil); - case DN_NET2RequestType_HTTP: { - DN_Assert(request->response.state == DN_NET2ResponseState_Nil); - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0]; - CURLMcode multi_add = curl_multi_add_handle(curl->curlm, conn->curl); - DN_Assert(multi_add == CURLM_OK); - DN_Assert(request->next == nullptr); - DN_Assert(request->prev == nullptr); - DN_DLList_Append(curl->http_list, request); - } break; - - case DN_NET2RequestType_WS: { - DN_Assert(request->response.state == DN_NET2ResponseState_Nil); - DN_Assert(request->next == nullptr); - DN_Assert(request->prev == nullptr); - if (request->response.state == DN_NET2ResponseState_Nil) { - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0]; - CURLMcode multi_add = curl_multi_add_handle(curl->curlm, conn->curl); - DN_Assert(multi_add == CURLM_OK); - DN_DLList_Append(curl->http_list, request); // Open the WS connection - } else { - DN_DLList_Append(curl->ws_list, request); // Ready to send data through the WS connection - } - } break; + // NOTE: Attach it to the CURL thread's request list + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(DN_NET_CurlRequestIsInList(curl->request_list, request)); + DN_DoublyLLDetach(curl->request_list, request); } + DN_DoublyLLAppend(curl->thread_request_list, request); + + // NOTE: Add the connection to CURLM and start ticking it once we finish handling all the + // ring events + DN_NETCurlConn *conn = DN_Cast(DN_NETCurlConn *) request->context[0]; + CURLMcode multi_add = curl_multi_add_handle(curl->thread_curlm, conn->curl); + DN_Assert(multi_add == CURLM_OK); } break; - case DN_NET2CurlRingEventType_SendWS: { + case DN_NETCurlRingEventType_SendWS: { DN_Str8 payload = {}; for (DN_OS_MutexScope(&curl->ring_mutex)) { DN_Assert(DN_Ring_HasData(&curl->ring, event.ws_send_size)); @@ -143,56 +134,63 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread) DN_U32 curlws_flag = 0; switch (event.ws_send) { - case DN_NET2WSSend_Text: curlws_flag = CURLWS_TEXT; break; - case DN_NET2WSSend_Binary: curlws_flag = CURLWS_BINARY; break; - case DN_NET2WSSend_Close: curlws_flag = CURLWS_CLOSE; break; - case DN_NET2WSSend_Ping: curlws_flag = CURLWS_PING; break; - case DN_NET2WSSend_Pong: curlws_flag = CURLWS_PONG; break; + case DN_NETWSSend_Text: curlws_flag = CURLWS_TEXT; break; + case DN_NETWSSend_Binary: curlws_flag = CURLWS_BINARY; break; + case DN_NETWSSend_Close: curlws_flag = CURLWS_CLOSE; break; + case DN_NETWSSend_Ping: curlws_flag = CURLWS_PING; break; + case DN_NETWSSend_Pong: curlws_flag = CURLWS_PONG; break; } - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle; - DN_Assert(request->type == DN_NET2RequestType_WS); - DN_Assert(request->response.state == DN_NET2ResponseState_WSOpen); + DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) event.request.handle; + DN_Assert(request->type == DN_NETRequestType_WS); + DN_Assert(request->response.state == DN_NETResponseState_WSOpen); + DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, request)); - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0]; - DN_USize sent = 0; - CURLcode send_result = curl_ws_send(conn->curl, payload.data, payload.size, &sent, 0, curlws_flag); + DN_NETCurlConn *conn = DN_Cast(DN_NETCurlConn *) request->context[0]; + DN_USize sent = 0; + CURLcode send_result = curl_ws_send(conn->curl, payload.data, payload.size, &sent, 0, curlws_flag); DN_AssertF(send_result == CURLE_OK, "Failed to send: %s", curl_easy_strerror(send_result)); DN_AssertF(sent == payload.size, "Failed to send all bytes (%zu vs %zu)", sent, payload.size); } break; - case DN_NET2CurlRingEventType_ReceivedWSReceipt: { - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle; - DN_Assert(request->type == DN_NET2RequestType_WS); - DN_Assert(request->response.state >= DN_NET2ResponseState_WSOpen && request->response.state <= DN_NET2ResponseState_WSPong); - DN_Assert(request->next == nullptr); - DN_Assert(request->prev == nullptr); - request->response.state = DN_NET2ResponseState_WSOpen; - DN_DLList_Append(curl->ws_list, request); + case DN_NETCurlRingEventType_ReceivedWSReceipt: { + DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) event.request.handle; + DN_Assert(request->type == DN_NETRequestType_WS); + DN_Assert(request->response.state >= DN_NETResponseState_WSOpen && request->response.state <= DN_NETResponseState_WSPong); + request->response.state = DN_NETResponseState_WSOpen; + + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(DN_NET_CurlRequestIsInList(curl->request_list, request)); + DN_DoublyLLDetach(curl->request_list, request); + } + DN_DoublyLLAppend(curl->thread_request_list, request); } break; - case DN_NET2CurlRingEventType_DeinitRequest: { - if (event.request.handle != 0) { - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) event.request.handle; + case DN_NETCurlRingEventType_DeinitRequest: { + DN_Assert(event.request.handle != 0); + DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) event.request.handle; - // NOTE: Release resources - DN_ArenaClear(&request->arena); - DN_OS_SemaphoreDeinit(&request->completion_sem); + // NOTE: Release resources + DN_ArenaClear(&request->arena); + DN_OS_SemaphoreDeinit(&request->completion_sem); - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0]; - curl_multi_remove_handle(curl->curlm, conn->curl); - curl_easy_reset(conn->curl); - curl_slist_free_all(conn->curl_slist); + DN_NETCurlConn *conn = DN_Cast(DN_NETCurlConn *) request->context[0]; + curl_multi_remove_handle(curl->thread_curlm, conn->curl); + curl_easy_reset(conn->curl); + curl_slist_free_all(conn->curl_slist); - // NOTE: Zero the struct preserving just the data we need to retain - DN_NET2RequestInternal resetter = {}; - resetter.arena = request->arena; - resetter.gen = request->gen; - DN_Memcpy(resetter.context, request->context, sizeof(resetter.context)); - *request = resetter; + // NOTE: Zero the struct preserving just the data we need to retain + DN_NETRequestInternal resetter = {}; + resetter.arena = request->arena; + resetter.gen = request->gen; + DN_Memcpy(resetter.context, request->context, sizeof(resetter.context)); + *request = resetter; - for (DN_OS_MutexScope(&curl->free_or_done_mutex)) - DN_DLList_Append(net->free_list, request); + // NOTE: Add it to the free list + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(DN_NET_CurlRequestIsInList(curl->deinit_list, request)); + DN_DoublyLLDetach(curl->deinit_list, request); + DN_DoublyLLAppend(curl->free_list, request); } } break; } @@ -200,40 +198,41 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread) // NOTE: Pump handles int running_handles = 0; - CURLMcode perform_result = curl_multi_perform(curl->curlm, &running_handles); + CURLMcode perform_result = curl_multi_perform(curl->thread_curlm, &running_handles); if (perform_result != CURLM_OK) DN_InvalidCodePath; // NOTE: Check pump result for (;;) { int msgs_in_queue = 0; - CURLMsg *msg = curl_multi_info_read(curl->curlm, &msgs_in_queue); + CURLMsg *msg = curl_multi_info_read(curl->thread_curlm, &msgs_in_queue); if (msg) { // NOTE: Get request handle - DN_NET2RequestInternal *request = nullptr; + DN_NETRequestInternal *request = nullptr; curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, DN_Cast(void **) & request); DN_Assert(request); + DN_Assert(DN_NET_CurlRequestIsInList(curl->thread_request_list, request)); - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *)request->context[0]; + DN_NETCurlConn *conn = DN_Cast(DN_NETCurlConn *)request->context[0]; DN_Assert(conn->curl == msg->easy_handle); if (msg->data.result == CURLE_OK) { // NOTE: Get HTTP response code CURLcode get_result = curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &request->response.http_status); if (get_result == CURLE_OK) { - if (request->type == DN_NET2RequestType_HTTP) { - request->response.state = DN_NET2ResponseState_HTTP; + if (request->type == DN_NETRequestType_HTTP) { + request->response.state = DN_NETResponseState_HTTP; } else { - DN_Assert(request->type == DN_NET2RequestType_WS); - request->response.state = DN_NET2ResponseState_WSOpen; + DN_Assert(request->type == DN_NETRequestType_WS); + request->response.state = DN_NETResponseState_WSOpen; } } else { request->response.error_str8 = DN_Str8FromFmtArena(&request->arena, "Failed to get HTTP response status (CURL %d): %s", msg->data.result, curl_easy_strerror(get_result)); - request->response.state = DN_NET2ResponseState_Error; + request->response.state = DN_NETResponseState_Error; } } else { DN_USize curl_extended_error_size = DN_CStr8Size(conn->error); - request->response.state = DN_NET2ResponseState_Error; + request->response.state = DN_NETResponseState_Error; request->response.error_str8 = DN_Str8FromFmtArena(&request->arena, "HTTP request '%.*s' failed (CURL %d): %s%s%s%s", DN_Str8PrintFmt(request->url), @@ -244,7 +243,7 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread) curl_extended_error_size ? ")" : ""); } - if (request->type == DN_NET2RequestType_HTTP || request->response.state == DN_NET2ResponseState_Error) { + if (request->type == DN_NETRequestType_HTTP || request->response.state == DN_NETResponseState_Error) { // NOTE: Remove the request from the multi handle if we're a HTTP request // because it typically terminates the connection. In websockets the // connection remains in the multi-handle to allow you to send and @@ -254,10 +253,10 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread) // connection from the multi handle as it failed. One a connection has // failed, curl will not poll that connection so there's no point keeping // it attached to the multi handle. - curl_multi_remove_handle(curl->curlm, msg->easy_handle); + curl_multi_remove_handle(curl->thread_curlm, msg->easy_handle); } - DN_NET2_CurlMarkRequestDone_(net, request); + DN_NET_CurlMarkRequestDone_(net, request); } if (msgs_in_queue == 0) @@ -265,41 +264,43 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread) } // NOTE: Check websockets - DN_I32 sleep_time_ms = DN_DLList_HasItems(curl->ws_list) ? 100 : INT32_MAX; - for (DN_DLList_ForEach(request, curl->ws_list)) { - DN_Assert(request->type == DN_NET2RequestType_WS); - DN_Assert(request->response.state == DN_NET2ResponseState_WSOpen); - CURLcode receive_result = CURLE_OK; + DN_USize ws_count = 0; + for (DN_NETRequestInternal *request = curl->thread_request_list; request; request = request->next) { + DN_Assert(request->type == DN_NETRequestType_WS || request->type == DN_NETRequestType_HTTP); + if (request->type != DN_NETRequestType_WS || !(request->response.state >= DN_NETResponseState_WSOpen && request->response.state <= DN_NETResponseState_WSPong)) + continue; + ws_count++; const curl_ws_frame *meta = nullptr; - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0]; - for (;;) { + DN_NETCurlConn *conn = DN_Cast(DN_NETCurlConn *) request->context[0]; + CURLcode receive_result = CURLE_OK; + while (receive_result == CURLE_OK) { // NOTE: Determine WS payload size received. Note that since we pass in a null pointer CURL // will set meta->len to 0 and say that there's meta->bytesleft in the next chunk. DN_USize bytes_read = 0; receive_result = curl_ws_recv(conn->curl, nullptr, 0, &bytes_read, &meta); if (receive_result != CURLE_OK) - break; + continue; DN_Assert(meta->len == 0); if (meta->flags & CURLWS_TEXT) - request->response.state = DN_NET2ResponseState_WSText; + request->response.state = DN_NETResponseState_WSText; if (meta->flags & CURLWS_BINARY) - request->response.state = DN_NET2ResponseState_WSBinary; + request->response.state = DN_NETResponseState_WSBinary; if (meta->flags & CURLWS_PING) - request->response.state = DN_NET2ResponseState_WSPing; + request->response.state = DN_NETResponseState_WSPing; if (meta->flags & CURLWS_PONG) - request->response.state = DN_NET2ResponseState_WSPong; + request->response.state = DN_NETResponseState_WSPong; if (meta->flags & CURLWS_CLOSE) - request->response.state = DN_NET2ResponseState_WSClose; + request->response.state = DN_NETResponseState_WSClose; request->response.ws_has_more = meta->flags & CURLWS_CONT; if (request->response.ws_has_more) { - bool is_text_or_binary = request->response.state == DN_NET2ResponseState_WSText || - request->response.state == DN_NET2ResponseState_WSBinary; + bool is_text_or_binary = request->response.state == DN_NETResponseState_WSText || + request->response.state == DN_NETResponseState_WSBinary; DN_Assert(is_text_or_binary); } @@ -318,47 +319,83 @@ static int32_t DN_NET2_CurlThreadEntryPoint_(DN_OSThread *thread) // > If this is not a complete fragment, the bytesleft field informs about how many // additional bytes are expected to arrive before this fragment is complete. request->response.ws_has_more |= meta && meta->bytesleft > 0; - break; } - // NOTE: We read all the possible bytes that CURL has received for this - // socket, but, there are more bytes left that we will receive on subsequent - // calls. We will continue to the next request and return back to this one - // when PumpRequests is called again where hopefully that data has arrived. - if (request->response.ws_has_more || receive_result == CURLE_AGAIN) { - if (receive_result == CURLE_AGAIN) - sleep_time_ms = 100; + // NOTE: curl_ws_recv returns CURLE_GOT_NOTHING if the associated connection is closed. + if (receive_result == CURLE_GOT_NOTHING) + request->response.ws_has_more = false; + + // NOTE: We read all the possible bytes that CURL has received for this message, but, there are + // more bytes left that we will receive on subsequent calls. We will continue to the next + // request and return back to this one when PumpRequests is called again where hopefully that + // data has arrived. + if (request->response.ws_has_more) continue; + + // For CURLE_AGAIN + // + // > Instead of blocking, the function returns CURLE_AGAIN. The correct behavior is then to + // > wait for the socket to signal readability before calling this function again. + // + // In which case we continue ticking the other sockets and eventually exit once all ticked. + // Right after this we wait on the CURLM instance which will wake us up again when there's + // data to be read. + // + // if we received data, e.g. state was set to Text, Binary ... e.t.c we bypass this and + // report it to the user first. When the user waits for the response, they consume the data + // and then that will reinsert it into request list for CURL to read from the socket again. + bool received_data = (request->response.state >= DN_NETResponseState_WSText && request->response.state <= DN_NETResponseState_WSPong); + if (receive_result == CURLE_AGAIN && !received_data) + continue; + + if (!received_data) { + if (receive_result == CURLE_GOT_NOTHING) { + request->response.state = DN_NETResponseState_WSClose; + } else if (receive_result != CURLE_OK) { + DN_USize curl_extended_error_size = DN_CStr8Size(conn->error); + request->response.state = DN_NETResponseState_Error; + request->response.error_str8 = DN_Str8FromFmtArena(&request->arena, + "Websocket receive '%.*s' failed (CURL %d): %s%s%s%s", + DN_Str8PrintFmt(request->url), + receive_result, + curl_easy_strerror(receive_result), + curl_extended_error_size ? " (" : "", + curl_extended_error_size ? conn->error : "", + curl_extended_error_size ? ")" : ""); + } } - if (receive_result != CURLE_OK) { - DN_USize curl_extended_error_size = DN_CStr8Size(conn->error); - request->response.state = DN_NET2ResponseState_Error; - request->response.error_str8 = DN_Str8FromFmtArena(&request->arena, - "Websocket receive '%.*s' failed (CURL %d): %s%s%s%s", - DN_Str8PrintFmt(request->url), - receive_result, - curl_easy_strerror(receive_result), - curl_extended_error_size ? " (" : "", - curl_extended_error_size ? conn->error : "", - curl_extended_error_size ? ")" : ""); - } - - DN_NET2RequestInternal *request_copy = request; - request = request->prev; - DN_NET2_CurlMarkRequestDone_(net, request_copy); + DN_NETRequestInternal *request_copy = request; + request = request->prev; + DN_NET_CurlMarkRequestDone_(net, request_copy); + if (!request) + break; } - curl_multi_poll(curl->curlm, nullptr, 0, sleep_time_ms, nullptr); + DN_I32 sleep_time_ms = ws_count > 0 ? 16 : INT32_MAX; + curl_multi_poll(curl->thread_curlm, nullptr, 0, sleep_time_ms, nullptr); } + return 0; } - -void DN_NET2_CurlInit(DN_NET2Core *net, char *base, DN_U64 base_size) +DN_NETInterface DN_NET_CurlInterface() { - DN_NET2_BaseInit_(net, base, base_size); - DN_NET2CurlCore *curl = DN_ArenaNew(&net->arena, DN_NET2CurlCore, DN_ZMem_Yes); + DN_NETInterface result = {}; + result.init = DN_NET_CurlInit; + result.deinit = DN_NET_CurlDeinit; + result.do_http = DN_NET_CurlDoHTTP; + result.do_ws = DN_NET_CurlDoWS; + result.do_ws_send = DN_NET_CurlDoWSSend; + result.wait_for_response = DN_NET_CurlWaitForResponse; + result.wait_for_any_response = DN_NET_CurlWaitForAnyResponse; + return result; +} + +void DN_NET_CurlInit(DN_NETCore *net, char *base, DN_U64 base_size) +{ + DN_NET_BaseInit_(net, base, base_size); + DN_NETCurlCore *curl = DN_ArenaNew(&net->arena, DN_NETCurlCore, DN_ZMem_Yes); net->context = curl; DN_USize arena_bytes_avail = (net->arena.curr->reserve - net->arena.curr->used); @@ -366,37 +403,41 @@ void DN_NET2_CurlInit(DN_NET2Core *net, char *base, DN_U64 base_size) curl->ring.base = DN_Cast(char *) DN_ArenaAlloc(&net->arena, curl->ring.size, /*align*/ 1, DN_ZMem_Yes); DN_Assert(curl->ring.base); - curl->ring_mutex = DN_OS_MutexInit(); - curl->free_or_done_mutex = DN_OS_MutexInit(); - curl->curlm = DN_Cast(CURLM *) curl_multi_init(); - - // TODO: Free list should be initialised in the base - DN_DLList_InitArena(net->free_list, DN_NET2RequestInternal, &net->arena); - DN_DLList_InitArena(net->done_list, DN_NET2RequestInternal, &net->arena); - DN_DLList_InitArena(curl->http_list, DN_NET2RequestInternal, &net->arena); - DN_DLList_InitArena(curl->ws_list, DN_NET2RequestInternal, &net->arena); + curl->ring_mutex = DN_OS_MutexInit(); + curl->list_mutex = DN_OS_MutexInit(); + curl->thread_curlm = DN_Cast(CURLM *) curl_multi_init(); DN_FmtAppend(curl->thread.name.data, &curl->thread.name.size, sizeof(curl->thread.name.data), "NET (CURL)"); - DN_OS_ThreadInit(&curl->thread, DN_NET2_CurlThreadEntryPoint_, net); + DN_OS_ThreadInit(&curl->thread, DN_NET_CurlThreadEntryPoint_, net); } -static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args, DN_NET2RequestType type) +void DN_NET_CurlDeinit(DN_NETCore *net) +{ + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; + curl->kill_thread = true; + curl_multi_wakeup(curl->thread_curlm); + DN_OS_ThreadDeinit(&curl->thread); +} + +static DN_NETRequest DN_NET_CurlDoRequest_(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args, DN_NETRequestType type) { // NOTE: Allocate the request - DN_NET2CurlCore *curl_core = DN_Cast(DN_NET2CurlCore *) net->context; - DN_NET2RequestInternal *request = nullptr; - DN_NET2Request result = {}; + DN_NETCurlCore *curl_core = DN_Cast(DN_NETCurlCore *) net->context; + DN_NETRequestInternal *request = nullptr; + DN_NETRequest result = {}; { // NOTE: The free list is modified by both the calling thread and the CURLM thread (which ticks // all the requests in the background for us) - for (DN_OS_MutexScope(&curl_core->free_or_done_mutex)) - DN_DLList_Dequeue(net->free_list, request); + for (DN_OS_MutexScope(&curl_core->list_mutex)) { + request = curl_core->free_list; + DN_DoublyLLDetach(curl_core->free_list, request); + } - // NOTE None if the free list so allocate one + // NOTE None in the free list so allocate one if (!request) { - DN_U64 arena_pos = DN_ArenaPos(&net->arena); - request = DN_ArenaNew(&net->arena, DN_NET2RequestInternal, DN_ZMem_Yes); - DN_NET2CurlConn *conn = DN_ArenaNew(&net->arena, DN_NET2CurlConn, DN_ZMem_Yes); + DN_U64 arena_pos = DN_ArenaPos(&net->arena); + request = DN_ArenaNew(&net->arena, DN_NETRequestInternal, DN_ZMem_Yes); + DN_NETCurlConn *conn = DN_ArenaNew(&net->arena, DN_NETCurlConn, DN_ZMem_Yes); if (!request || !conn) { DN_ArenaPopTo(&net->arena, arena_pos); return result; @@ -408,12 +449,12 @@ static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_S } // NOTE: Setup the request - result = DN_NET2_SetupRequest_(request, url, method, args, type); + result = DN_NET_SetupRequest_(request, url, method, args, type); request->context[1] = DN_Cast(DN_UPtr) net; // NOTE: Setup the request for curl { - DN_NET2CurlConn *conn = DN_Cast(DN_NET2CurlConn *) request->context[0]; + DN_NETCurlConn *conn = DN_Cast(DN_NETCurlConn *) request->context[0]; CURL *curl = conn->curl; curl_easy_setopt(curl, CURLOPT_PRIVATE, request); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, conn->error); @@ -424,7 +465,7 @@ static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_S curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); // NOTE: Setup response handler - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DN_NET2_CurlHTTPCallback_); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DN_NET_CurlHTTPCallback_); curl_easy_setopt(curl, CURLOPT_WRITEDATA, request); // NOTE: Assign HTTP headers @@ -434,13 +475,13 @@ static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_S // NOTE: Setup handle for protocol switch (request->type) { - case DN_NET2RequestType_Nil: DN_InvalidCodePath; break; + case DN_NETRequestType_Nil: DN_InvalidCodePath; break; - case DN_NET2RequestType_WS: { + case DN_NETRequestType_WS: { curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); } break; - case DN_NET2RequestType_HTTP: { + case DN_NETRequestType_HTTP: { DN_Str8 const GET = DN_Str8Lit("GET"); DN_Str8 const POST = DN_Str8Lit("POST"); @@ -460,7 +501,7 @@ static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_S } // NOTE: Handle basic auth - if (request->args.flags & DN_NET2DoHTTPFlags_BasicAuth) { + if (request->args.flags & DN_NETDoHTTPFlags_BasicAuth) { if (request->args.username.size && request->args.password.size) { DN_Assert(request->args.username.data[request->args.username.size] == 0); DN_Assert(request->args.password.data[request->args.password.size] == 0); @@ -470,50 +511,59 @@ static DN_NET2Request DN_NET2_CurlDoRequest_(DN_NET2Core *net, DN_Str8 url, DN_S } } - // NOTE: Dispatch the request to the CURL thread. It will append to the CURLM - // instance and tick it in the background for us + // NOTE: Dispatch the request to the CURL thread { - DN_NET2CurlRingEvent event = {}; - event.type = DN_NET2CurlRingEventType_DoRequest; - event.request = result; + // NOTE: Immediately add the request to the request list so it happens "atomically" in the + // calling thread. If the calling thread deinitialises this layer before the CURL thread can be + // pre-empted, we can lose track of this request. + for (DN_OS_MutexScope(&curl_core->list_mutex)) + DN_DoublyLLAppend(curl_core->request_list, request); + + // NOTE: Enqueue request to go into CURL's ring queue. The CURL thread will sleep and wait for + // bytes to come in for the request and then dump the response into the done list to be consumed + // via wait for response + DN_NETCurlRingEvent event = {}; + event.type = DN_NETCurlRingEventType_DoRequest; + event.request = result; for (DN_OS_MutexScope(&curl_core->ring_mutex)) DN_Ring_WriteStruct(&curl_core->ring, &event); - curl_multi_wakeup(curl_core->curlm); + + curl_multi_wakeup(curl_core->thread_curlm); } return result; } -DN_NET2Request DN_NET2_CurlDoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args) +DN_NETRequest DN_NET_CurlDoHTTP(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args) { - DN_NET2Request result = DN_NET2_CurlDoRequest_(net, url, method, args, DN_NET2RequestType_HTTP); + DN_NETRequest result = DN_NET_CurlDoRequest_(net, url, method, args, DN_NETRequestType_HTTP); return result; } -DN_NET2Request DN_NET2_CurlDoWSArgs(DN_NET2Core *net, DN_Str8 url, DN_NET2DoHTTPArgs const *args) +DN_NETRequest DN_NET_CurlDoWSArgs(DN_NETCore *net, DN_Str8 url, DN_NETDoHTTPArgs const *args) { - DN_NET2Request result = DN_NET2_CurlDoRequest_(net, url, DN_Str8Lit(""), args, DN_NET2RequestType_WS); + DN_NETRequest result = DN_NET_CurlDoRequest_(net, url, DN_Str8Lit(""), args, DN_NETRequestType_WS); return result; } -DN_NET2Request DN_NET2_CurlDoWS(DN_NET2Core *net, DN_Str8 url) +DN_NETRequest DN_NET_CurlDoWS(DN_NETCore *net, DN_Str8 url) { - DN_NET2Request result = DN_NET2_CurlDoWSArgs(net, url, nullptr); + DN_NETRequest result = DN_NET_CurlDoWSArgs(net, url, nullptr); return result; } -void DN_NET2_CurlDoWSSend(DN_NET2Request request, DN_Str8 payload, DN_NET2WSSend send) +void DN_NET_CurlDoWSSend(DN_NETRequest request, DN_Str8 payload, DN_NETWSSend send) { - DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle; - if (!request_ptr || request_ptr->gen != request.gen) + DN_NETRequestInternal *request_ptr = DN_NET_RequestFromHandle(request); + if (!request_ptr) return; - DN_NET2Core *net = DN_Cast(DN_NET2Core *) request_ptr->context[1]; - DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *) net->context; + DN_NETCore *net = DN_Cast(DN_NETCore *) request_ptr->context[1]; + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; DN_Assert(curl); - DN_NET2CurlRingEvent event = {}; - event.type = DN_NET2CurlRingEventType_SendWS; + DN_NETCurlRingEvent event = {}; + event.type = DN_NETCurlRingEventType_SendWS; event.request = request; event.ws_send_size = payload.size; event.ws_send = send; @@ -523,116 +573,107 @@ void DN_NET2_CurlDoWSSend(DN_NET2Request request, DN_Str8 payload, DN_NET2WSSend DN_Ring_WriteStruct(&curl->ring, &event); DN_Ring_Write(&curl->ring, payload.data, payload.size); } - curl_multi_wakeup(curl->curlm); + curl_multi_wakeup(curl->thread_curlm); } -static DN_NET2Response DN_NET2_CurlHandleFinishedRequest_(DN_NET2Core *net, DN_NET2CurlCore *curl, DN_NET2Request request, DN_NET2RequestInternal *request_ptr, DN_Arena *arena) +static DN_NETResponse DN_NET_CurlHandleFinishedRequest_(DN_NETCurlCore *curl, DN_NETRequest request, DN_NETRequestInternal *request_ptr, DN_Arena *arena) { // NOTE: Process the response - DN_NET2Response result = DN_NET2_MakeResponseFromFinishedRequest_(request, arena); - DN_NET2_EndFinishedRequest_(net, request_ptr); + DN_NETResponse result = DN_NET_MakeResponseFromFinishedRequest_(request, arena); + DN_NET_EndFinishedRequest_(request_ptr); - // NOTE: For websocket requests, notify the CURL thread we've read data from it and it can go - // back to polling the socket for more data. Over on the CURL thread when it receives the event - // it will append the request onto the 'ws_list' which it iterates to poll for data. - // - // The request was _just_ sitting in the done list (see above) so it was not being polled but - // it will be polled after this step. - // - // TODO: How do we unsticky the error? Right now we just deallocate/close the request entirely and move on - bool end_the_request = true; - if (request_ptr->type == DN_NET2RequestType_WS && - request_ptr->response.state != DN_NET2ResponseState_Error && - request_ptr->response.state != DN_NET2ResponseState_WSClose) { - DN_NET2CurlRingEvent event = {}; - event.type = DN_NET2CurlRingEventType_ReceivedWSReceipt; - event.request = request; - for (DN_OS_MutexScope(&curl->ring_mutex)) - DN_Ring_WriteStruct(&curl->ring, &event); - curl_multi_wakeup(curl->curlm); - end_the_request = false; + bool continue_ws_request = false; + if (request_ptr->type == DN_NETRequestType_WS && + request_ptr->response.state != DN_NETResponseState_Error && + request_ptr->response.state != DN_NETResponseState_WSClose) { + continue_ws_request = true; } - if (end_the_request) { - // NOTE: This _has_ to be sent to our CURL thread because we need to remove the CURL handle from - // the CURLM instance and the CURL thread uses the CURLM instance (e.g. not thread safe to do - // here) - DN_NET2CurlRingEvent event = {}; - event.type = DN_NET2CurlRingEventType_DeinitRequest; - event.request = request; - for (DN_OS_MutexScope(&curl->ring_mutex)) - DN_Ring_WriteStruct(&curl->ring, &event); + // NOTE: Put the request into the requisite list + for (DN_OS_MutexScope(&curl->list_mutex)) { + // NOTE: Dequeue the request, it _must_ have been in the response list at this point for it to + // have ben waitable in the first place. + DN_AssertF(DN_NET_CurlRequestIsInList(curl->response_list, request_ptr), + "A completed response should only signal the completion semaphore when it's in the response list"); + DN_DoublyLLDetach(curl->response_list, request_ptr); + + // NOTE: A websocket that is continuing to get data should go back into the request list because + // there's more data to be received. All other requests need to go into the deinit list (so that + // we keep track of it in the time inbetween it takes for the CURL thread to be scheduled and + // release the CURL handle from CURLM and release resources e.t.c.) + if (continue_ws_request) + DN_DoublyLLAppend(curl->request_list, request_ptr); + else + DN_DoublyLLAppend(curl->deinit_list, request_ptr); } + // NOTE: Submit the post-request event to the CURL thread + DN_NETCurlRingEvent event = {}; + event.request = request; + if (continue_ws_request) { + event.type = DN_NETCurlRingEventType_ReceivedWSReceipt; + } else { + // NOTE: Deinit _has_ to be sent to the CURL thread because we need to remove the CURL handle + // from the CURLM instance and the CURL thread uses the CURLM instance (e.g. CURLM is not thread + // safe) + event.type = DN_NETCurlRingEventType_DeinitRequest; + } + + for (DN_OS_MutexScope(&curl->ring_mutex)) + DN_Ring_WriteStruct(&curl->ring, &event); + curl_multi_wakeup(curl->thread_curlm); + return result; } -DN_NET2Response DN_NET2_CurlWaitForResponse(DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms) +DN_NETResponse DN_NET_CurlWaitForResponse(DN_NETRequest request, DN_Arena *arena, DN_U32 timeout_ms) { - DN_NET2Response result = {}; - DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle; - if (!request_ptr || request_ptr->gen != request.gen) + DN_NETResponse result = {}; + DN_NETRequestInternal *request_ptr = DN_NET_RequestFromHandle(request); + if (!request_ptr) return result; - DN_NET2Core *net = DN_Cast(DN_NET2Core *) request_ptr->context[1]; - DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *) net->context; + DN_NETCore *net = DN_Cast(DN_NETCore *) request_ptr->context[1]; + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; DN_Assert(curl); DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&request_ptr->completion_sem, timeout_ms); if (wait != DN_OSSemaphoreWaitResult_Success) return result; - // NOTE: Remove the request from the done list. Note it _has_ to be in the done list, anything - // sitting in the done list is guaranteed to not be modified by the CURL thread. - for (DN_OS_MutexScope(&curl->free_or_done_mutex)) { - bool is_in_done_list = false; - for (DN_DLList_ForEach(it, net->done_list)) { - if (it == request_ptr) { - is_in_done_list = true; - break; - } - } - DN_AssertF(is_in_done_list, "Any request that has signalled completion should be inserted into the done list"); - DN_DLList_Detach(request_ptr); - } - - // NOTE: Finish handling the response - result = DN_NET2_CurlHandleFinishedRequest_(net, curl, request, request_ptr, arena); - // NOTE: Decrement the global 'request done' completion semaphore since the user consumed the // request individually. DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&net->completion_sem, 0 /*timeout_ms*/); DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result); + // NOTE: Finish handling the response + result = DN_NET_CurlHandleFinishedRequest_(curl, request, request_ptr, arena); return result; } -DN_NET2Response DN_NET2_CurlWaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms) +DN_NETResponse DN_NET_CurlWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms) { - DN_NET2CurlCore *curl = DN_Cast(DN_NET2CurlCore *) net->context; + DN_NETCurlCore *curl = DN_Cast(DN_NETCurlCore *) net->context; DN_Assert(curl); - DN_NET2Response result = {}; + DN_NETResponse result = {}; DN_OSSemaphoreWaitResult wait = DN_OS_SemaphoreWait(&net->completion_sem, timeout_ms); if (wait != DN_OSSemaphoreWaitResult_Success) - return result; + return result; - // NOTE: Dequeue the request that is done from the done list - DN_NET2RequestInternal *request_ptr = nullptr; - for (DN_OS_MutexScope(&curl->free_or_done_mutex)) - DN_DLList_Dequeue(net->done_list, request_ptr); - DN_Assert(request_ptr); + // NOTE: Just grab the handle, handle finished request will dequeue for us + DN_NETRequest request = {}; + for (DN_OS_MutexScope(&curl->list_mutex)) { + DN_Assert(curl->response_list); + request = DN_NET_HandleFromRequest(curl->response_list); + } // NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore + DN_NETRequestInternal *request_ptr = DN_NET_RequestFromHandle(request); DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&request_ptr->completion_sem, 0 /*timeout_ms*/); DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result); // NOTE: Finish handling the response - DN_NET2Request request = {}; - request.handle = DN_Cast(DN_UPtr) request_ptr; - request.gen = request_ptr->gen; - result = DN_NET2_CurlHandleFinishedRequest_(net, curl, request, request_ptr, arena); - + result = DN_NET_CurlHandleFinishedRequest_(curl, request, request_ptr, arena); return result; } - diff --git a/Source/Extra/dn_net_curl.h b/Source/Extra/dn_net_curl.h index 8d08e17..95ac44b 100644 --- a/Source/Extra/dn_net_curl.h +++ b/Source/Extra/dn_net_curl.h @@ -1,15 +1,16 @@ #if !defined(DN_NET_CURL_H) #define DN_NET_CURL_H -#include "dn_net2.h" +#include "dn_net.h" -DN_NET2Interface DN_NET2_CurlInterface(); -void DN_NET2_CurlInit (DN_NET2Core *net, char *base, DN_U64 base_size); -DN_NET2Request DN_NET2_CurlDoHTTP (DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args); -DN_NET2Request DN_NET2_CurlDoWSArgs (DN_NET2Core *net, DN_Str8 url, DN_NET2DoHTTPArgs const *args); -DN_NET2Request DN_NET2_CurlDoWS (DN_NET2Core *net, DN_Str8 url); -void DN_NET2_CurlDoWSSend (DN_NET2Request request, DN_Str8 payload, DN_NET2WSSend send); -DN_NET2Response DN_NET2_CurlWaitForResponse (DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms); -DN_NET2Response DN_NET2_CurlWaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms); +DN_NETInterface DN_NET_CurlInterface(); +void DN_NET_CurlInit (DN_NETCore *net, char *base, DN_U64 base_size); +void DN_NET_CurlDeinit (DN_NETCore *net); +DN_NETRequest DN_NET_CurlDoHTTP (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); +DN_NETRequest DN_NET_CurlDoWSArgs (DN_NETCore *net, DN_Str8 url, DN_NETDoHTTPArgs const *args); +DN_NETRequest DN_NET_CurlDoWS (DN_NETCore *net, DN_Str8 url); +void DN_NET_CurlDoWSSend (DN_NETRequest request, DN_Str8 payload, DN_NETWSSend send); +DN_NETResponse DN_NET_CurlWaitForResponse (DN_NETRequest request, DN_Arena *arena, DN_U32 timeout_ms); +DN_NETResponse DN_NET_CurlWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); #endif // !defined(DN_NET_CURL_H) diff --git a/Source/Extra/dn_net_emscripten.cpp b/Source/Extra/dn_net_emscripten.cpp index d969a5f..ab5325a 100644 --- a/Source/Extra/dn_net_emscripten.cpp +++ b/Source/Extra/dn_net_emscripten.cpp @@ -2,45 +2,49 @@ #include #include -#include "dn_net2.h" +#include "dn_net.h" #include "dn_net_emscripten.h" -struct DN_NET2EmcWSEvent +struct DN_NETEmcWSEvent { - DN_NET2ResponseState state; - DN_Str8 payload; - DN_NET2EmcWSEvent *next; + DN_NETResponseState state; + DN_Str8 payload; + DN_NETEmcWSEvent *next; }; -struct DN_NET2EmcCore +struct DN_NETEmcCore { - DN_Pool pool; + DN_Pool pool; + DN_NETRequestInternal *request_list; // Current requests being executed + DN_NETRequestInternal *response_list; // Responses received that are to be deqeued via wait for response + DN_NETRequestInternal *free_list; // Request pool that new requests will use before allocating }; -struct DN_NET2EmcRequest +struct DN_NETEmcRequest { - int socket; - DN_NET2EmcWSEvent *first_event; - DN_NET2EmcWSEvent *last_event; + int socket; + DN_NETEmcWSEvent *first_event; + DN_NETEmcWSEvent *last_event; }; -DN_NET2Interface DN_NET2_EmcInterface() +DN_NETInterface DN_NET_EmcInterface() { - DN_NET2Interface result = {}; - result.init = DN_NET2_EmcInit; - result.do_http = DN_NET2_EmcDoHTTP; - result.do_ws = DN_NET2_EmcDoWS; - result.do_ws_send = DN_NET2_EmcDoWSSend; - result.wait_for_response = DN_NET2_EmcWaitForResponse; - result.wait_for_any_response = DN_NET2_EmcWaitForAnyResponse; + DN_NETInterface result = {}; + result.init = DN_NET_EmcInit; + result.deinit = DN_NET_EmcDeinit; + result.do_http = DN_NET_EmcDoHTTP; + result.do_ws = DN_NET_EmcDoWS; + result.do_ws_send = DN_NET_EmcDoWSSend; + result.wait_for_response = DN_NET_EmcWaitForResponse; + result.wait_for_any_response = DN_NET_EmcWaitForAnyResponse; return result; } -static DN_NET2EmcWSEvent *DN_NET2_EmcAllocWSEvent_(DN_NET2RequestInternal *request) +static DN_NETEmcWSEvent *DN_NET_EmcAllocWSEvent_(DN_NETRequestInternal *request) { // NOTE: Allocate the event and attach to the request - DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request->context[1]; - DN_NET2EmcWSEvent *result = DN_ArenaNew(&request->arena, DN_NET2EmcWSEvent, DN_ZMem_Yes); + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request->context[1]; + DN_NETEmcWSEvent *result = DN_ArenaNew(&request->arena, DN_NETEmcWSEvent, DN_ZMem_Yes); if (result) { if (!emc_request->first_event) emc_request->first_event = result; @@ -51,110 +55,116 @@ static DN_NET2EmcWSEvent *DN_NET2_EmcAllocWSEvent_(DN_NET2RequestInternal *reque return result; } -static void DN_NET2_EmcOnRequestDone_(DN_NET2Core *net, DN_NET2RequestInternal *request) +static void DN_NET_EmcOnRequestDone_(DN_NETCore *net, DN_NETRequestInternal *request) { // NOTE: This may be call multiple times if we get multiple responses when we yield to the javascript event loop if (!request->next) { - request->next = net->done_list; - net->done_list = request; + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + request->next = emc->response_list; + emc->response_list = request; } DN_OS_SemaphoreIncrement(&net->completion_sem, 1); DN_OS_SemaphoreIncrement(&request->completion_sem, 1); } // TODO: Need to enqueue the results since they can accumulate when you yield to the javascript event loop -static bool DN_NET2_EmcWSOnOpen(int eventType, EmscriptenWebSocketOpenEvent const *event, void *user_data) +static bool DN_NET_EmcWSOnOpen(int eventType, EmscriptenWebSocketOpenEvent const *event, void *user_data) { - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) user_data; - DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0]; - DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context; - DN_NET2EmcWSEvent *net_event = DN_NET2_EmcAllocWSEvent_(request); - net_event->state = DN_NET2ResponseState_WSOpen; - DN_NET2_EmcOnRequestDone_(net, request); + DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) user_data; + DN_NETCore *net = DN_Cast(DN_NETCore *) request->context[0]; + DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(request); + net_event->state = DN_NETResponseState_WSOpen; + DN_NET_EmcOnRequestDone_(net, request); return true; } -static bool DN_NET2_EmcWSOnMessage(int eventType, const EmscriptenWebSocketMessageEvent *event, void *user_data) +static bool DN_NET_EmcWSOnMessage(int eventType, const EmscriptenWebSocketMessageEvent *event, void *user_data) { - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) user_data; - DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0]; - DN_NET2EmcWSEvent *net_event = DN_NET2_EmcAllocWSEvent_(request); - net_event->state = event->isText ? DN_NET2ResponseState_WSText : DN_NET2ResponseState_WSBinary; + DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) user_data; + DN_NETCore *net = DN_Cast(DN_NETCore *) request->context[0]; + DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(request); + net_event->state = event->isText ? DN_NETResponseState_WSText : DN_NETResponseState_WSBinary; if (event->numBytes > 0) { - DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context; + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; net_event->payload = DN_Str8FromPtrPool(&emc->pool, event->data, event->numBytes); } - DN_NET2_EmcOnRequestDone_(net, request); + DN_NET_EmcOnRequestDone_(net, request); return true; } -static bool DN_NET2_EmcWSOnError(int eventType, EmscriptenWebSocketErrorEvent const *event, void *user_data) +static bool DN_NET_EmcWSOnError(int eventType, EmscriptenWebSocketErrorEvent const *event, void *user_data) { - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) user_data; - DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0]; - DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context; - DN_NET2EmcWSEvent *net_event = DN_NET2_EmcAllocWSEvent_(request); - net_event->state = DN_NET2ResponseState_Error; - DN_NET2_EmcOnRequestDone_(net, request); + DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) user_data; + DN_NETCore *net = DN_Cast(DN_NETCore *) request->context[0]; + DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(request); + net_event->state = DN_NETResponseState_Error; + DN_NET_EmcOnRequestDone_(net, request); return true; } -static bool DN_NET2_EmcWSOnClose(int eventType, EmscriptenWebSocketCloseEvent const *event, void *user_data) +static bool DN_NET_EmcWSOnClose(int eventType, EmscriptenWebSocketCloseEvent const *event, void *user_data) { - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) user_data; - DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0]; - DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context; - DN_NET2EmcWSEvent *net_event = DN_NET2_EmcAllocWSEvent_(request); - net_event->state = DN_NET2ResponseState_WSClose; + DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) user_data; + DN_NETCore *net = DN_Cast(DN_NETCore *) request->context[0]; + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + DN_NETEmcWSEvent *net_event = DN_NET_EmcAllocWSEvent_(request); + net_event->state = DN_NETResponseState_WSClose; net_event->payload = DN_Str8FromFmtPool(&emc->pool, "Websocket closed '%.*s': (%u) %s (was %s close)", DN_Str8PrintFmt(request->url), event->code, event->reason, event->wasClean ? "clean" : "unclean"); - DN_NET2_EmcOnRequestDone_(net, request); + DN_NET_EmcOnRequestDone_(net, request); return true; } -static void DN_NET2_EmcHTTPSuccessCallback(emscripten_fetch_t *fetch) +static void DN_NET_EmcHTTPSuccessCallback(emscripten_fetch_t *fetch) { - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) fetch->userData; - DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0]; + DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) fetch->userData; + DN_NETCore *net = DN_Cast(DN_NETCore *) request->context[0]; request->response.http_status = fetch->status; - request->response.state = DN_NET2ResponseState_HTTP; + request->response.state = DN_NETResponseState_HTTP; DN_Str8BuilderAppendCopy(&request->response.body, DN_Str8FromPtr(fetch->data, fetch->numBytes - 1)); - DN_NET2_EmcOnRequestDone_(net, request); + DN_NET_EmcOnRequestDone_(net, request); } -static void DN_NET2_EmcHTTPFailCallback(emscripten_fetch_t *fetch) +static void DN_NET_EmcHTTPFailCallback(emscripten_fetch_t *fetch) { - DN_NET2RequestInternal *request = DN_Cast(DN_NET2RequestInternal *) fetch->userData; - DN_NET2Core *net = DN_Cast(DN_NET2Core *) request->context[0]; + DN_NETRequestInternal *request = DN_Cast(DN_NETRequestInternal *) fetch->userData; + DN_NETCore *net = DN_Cast(DN_NETCore *) request->context[0]; request->response.http_status = fetch->status; - request->response.state = DN_NET2ResponseState_Error; - DN_NET2_EmcOnRequestDone_(net, request); + request->response.state = DN_NETResponseState_Error; + DN_NET_EmcOnRequestDone_(net, request); } -static void DN_NET2_EmcHTTPProgressCallback(emscripten_fetch_t *fetch) +static void DN_NET_EmcHTTPProgressCallback(emscripten_fetch_t *fetch) { } -void DN_NET2_EmcInit(DN_NET2Core *net, char *base, DN_U64 base_size) +void DN_NET_EmcInit(DN_NETCore *net, char *base, DN_U64 base_size) { - DN_NET2_BaseInit_(net, base, base_size); - DN_NET2EmcCore *emc = DN_ArenaNew(&net->arena, DN_NET2EmcCore, DN_ZMem_Yes); + DN_NET_BaseInit_(net, base, base_size); + DN_NETEmcCore *emc = DN_ArenaNew(&net->arena, DN_NETEmcCore, DN_ZMem_Yes); emc->pool = DN_PoolFromArena(&net->arena, 0); net->context = emc; } -DN_NET2Request DN_NET2_EmcDoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args) +void DN_NET_EmcDeinit(DN_NETCore *net) +{ + (void)net; + // TODO: Track all the request handles and clean it up +} + +DN_NETRequest DN_NET_EmcDoHTTP(DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args) { // NOTE: Allocate request - DN_NET2RequestInternal *request = net->free_list; + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + DN_NETRequestInternal *request = emc->free_list; if (request) { - net->free_list = net->free_list->next; + emc->free_list = emc->free_list->next; request->next = nullptr; } else { - request = DN_ArenaNew(&net->arena, DN_NET2RequestInternal, DN_ZMem_Yes); + request = DN_ArenaNew(&net->arena, DN_NETRequestInternal, DN_ZMem_Yes); } - DN_NET2Request result = DN_NET2_SetupRequest_(request, url, method, args, DN_NET2RequestType_HTTP); + DN_NETRequest result = DN_NET_SetupRequest_(request, url, method, args, DN_NETRequestType_HTTP); // NOTE: Setup some emscripten specific data into our request context request->context[0] = DN_Cast(DN_UPtr) net; @@ -185,7 +195,7 @@ DN_NET2Request DN_NET2_EmcDoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, } // NOTE: Handle basic auth - if (request->args.flags & DN_NET2DoHTTPFlags_BasicAuth) { + if (request->args.flags & DN_NETDoHTTPFlags_BasicAuth) { if (request->args.username.size && request->args.password.size) { DN_Assert(request->args.username.data[request->args.username.size] == 0); DN_Assert(request->args.password.data[request->args.password.size] == 0); @@ -204,9 +214,9 @@ DN_NET2Request DN_NET2_EmcDoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, // > be called, but without data bytes. Note: Firefox only as it depends on // > 'moz-chunked-arraybuffer'. fetch_attribs.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; - fetch_attribs.onsuccess = DN_NET2_EmcHTTPSuccessCallback; - fetch_attribs.onerror = DN_NET2_EmcHTTPFailCallback; - fetch_attribs.onprogress = DN_NET2_EmcHTTPProgressCallback; + fetch_attribs.onsuccess = DN_NET_EmcHTTPSuccessCallback; + fetch_attribs.onerror = DN_NET_EmcHTTPFailCallback; + fetch_attribs.onprogress = DN_NET_EmcHTTPProgressCallback; fetch_attribs.userData = request; } @@ -218,27 +228,27 @@ DN_NET2Request DN_NET2_EmcDoHTTP(DN_NET2Core *net, DN_Str8 url, DN_Str8 method, return result; } -DN_NET2Request DN_NET2_EmcDoWS(DN_NET2Core *net, DN_Str8 url) +DN_NETRequest DN_NET_EmcDoWS(DN_NETCore *net, DN_Str8 url) { DN_Assert(emscripten_websocket_is_supported()); // NOTE: Allocate request - DN_NET2RequestInternal *request = net->free_list; + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + DN_NETRequestInternal *request = emc->free_list; if (request) { - net->free_list = net->free_list->next; + emc->free_list = emc->free_list->next; request->next = nullptr; } else { - request = DN_ArenaNew(&net->arena, DN_NET2RequestInternal, DN_ZMem_Yes); + request = DN_ArenaNew(&net->arena, DN_NETRequestInternal, DN_ZMem_Yes); } - DN_NET2Request result = DN_NET2_SetupRequest_(request, url, /*method=*/ DN_Str8Lit(""), /*args=*/nullptr, DN_NET2RequestType_WS); + DN_NETRequest result = DN_NET_SetupRequest_(request, url, /*method=*/ DN_Str8Lit(""), /*args=*/nullptr, DN_NETRequestType_WS); if (!request) return result; // NOTE: Setup some emscripten specific data into our request context - DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context; request->context[0] = DN_Cast(DN_UPtr) net; - request->context[1] = DN_Cast(DN_UPtr) DN_ArenaNew(&request->arena, DN_NET2EmcRequest, DN_ZMem_Yes); + request->context[1] = DN_Cast(DN_UPtr) DN_ArenaNew(&request->arena, DN_NETEmcRequest, DN_ZMem_Yes); request->start_response_arena_pos = DN_ArenaPos(&request->arena); // NOTE: Create the websocket request and dispatch it via emscripten @@ -246,62 +256,63 @@ DN_NET2Request DN_NET2_EmcDoWS(DN_NET2Core *net, DN_Str8 url) emscripten_websocket_init_create_attributes(&attr); attr.url = request->url.data; - DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request->context[1]; + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request->context[1]; emc_request->socket = emscripten_websocket_new(&attr); DN_Assert(emc_request->socket > 0); - emscripten_websocket_set_onopen_callback(emc_request->socket, /*userData=*/ request, DN_NET2_EmcWSOnOpen); - emscripten_websocket_set_onmessage_callback(emc_request->socket, /*userData=*/ request, DN_NET2_EmcWSOnMessage); - emscripten_websocket_set_onerror_callback(emc_request->socket, /*userData=*/ request, DN_NET2_EmcWSOnError); - emscripten_websocket_set_onclose_callback(emc_request->socket, /*userData=*/ request, DN_NET2_EmcWSOnClose); + emscripten_websocket_set_onopen_callback(emc_request->socket, /*userData=*/ request, DN_NET_EmcWSOnOpen); + emscripten_websocket_set_onmessage_callback(emc_request->socket, /*userData=*/ request, DN_NET_EmcWSOnMessage); + emscripten_websocket_set_onerror_callback(emc_request->socket, /*userData=*/ request, DN_NET_EmcWSOnError); + emscripten_websocket_set_onclose_callback(emc_request->socket, /*userData=*/ request, DN_NET_EmcWSOnClose); return result; } -void DN_NET2_EmcDoWSSend(DN_NET2Request request, DN_Str8 data, DN_NET2WSSend send) +void DN_NET_EmcDoWSSend(DN_NETRequest request, DN_Str8 data, DN_NETWSSend send) { - DN_AssertF(send == DN_NET2WSSend_Binary || send == DN_NET2WSSend_Text || send == DN_NET2WSSend_Close, + DN_AssertF(send == DN_NETWSSend_Binary || send == DN_NETWSSend_Text || send == DN_NETWSSend_Close, "Unimplemented, Emscripten only supports some of the available operations"); - bool result = false; - DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle; + int result = 0; + DN_NETRequestInternal *request_ptr = DN_Cast(DN_NETRequestInternal *) request.handle; if (request_ptr && request_ptr->gen == request.gen) { - DN_Assert(request_ptr->type == DN_NET2RequestType_WS); - DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request_ptr->context[1]; + DN_Assert(request_ptr->type == DN_NETRequestType_WS); + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request_ptr->context[1]; switch (send) { default: DN_InvalidCodePath; break; - case DN_NET2WSSend_Text: { + case DN_NETWSSend_Text: { DN_U64 pos = DN_ArenaPos(&request_ptr->arena); DN_Str8 data_null_terminated = DN_Str8FromStr8Arena(&request_ptr->arena, data); result = emscripten_websocket_send_utf8_text(emc_request->socket, data_null_terminated.data); DN_ArenaPopTo(&request_ptr->arena, pos); } break; - case DN_NET2WSSend_Binary: { + case DN_NETWSSend_Binary: { result = emscripten_websocket_send_binary(emc_request->socket, data.data, data.size); } break; - case DN_NET2WSSend_Close: { + case DN_NETWSSend_Close: { result = emscripten_websocket_close(emc_request->socket, 0, nullptr); } break; } } - // TODO: Handle result + // TODO: Handle result, the header file doesn't really elucidate what this result value is + (void)result; } -static DN_NET2Response DN_NET2_EmcHandleFinishedRequest_(DN_NET2Core *net, DN_NET2EmcCore *emc, DN_NET2Request request, DN_NET2RequestInternal *request_ptr, DN_Arena *arena) +static DN_NETResponse DN_NET_EmcHandleFinishedRequest_(DN_NETCore *net, DN_NETEmcCore *emc, DN_NETRequest request, DN_NETRequestInternal *request_ptr, DN_Arena *arena) { - DN_NET2Response result = {}; + DN_NETResponse result = {}; bool end_request = true; - if (request_ptr->type == DN_NET2RequestType_HTTP) { - result = DN_NET2_MakeResponseFromFinishedRequest_(request, arena); + if (request_ptr->type == DN_NETRequestType_HTTP) { + result = DN_NET_MakeResponseFromFinishedRequest_(request, arena); } else { // NOTE: Get emscripten contexts - DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request_ptr->context[1]; - DN_NET2EmcWSEvent *emc_event = emc_request->first_event; + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request_ptr->context[1]; + DN_NETEmcWSEvent *emc_event = emc_request->first_event; emc_request->first_event = emc_event->next; // Advance the list pointer DN_Assert(emc_event); - DN_Assert((emc_event->state >= DN_NET2ResponseState_WSOpen && emc_event->state <= DN_NET2ResponseState_WSPong) || - emc_event->state == DN_NET2ResponseState_Error); + DN_Assert((emc_event->state >= DN_NETResponseState_WSOpen && emc_event->state <= DN_NETResponseState_WSPong) || + emc_event->state == DN_NETResponseState_Error); // NOTE: Build the result result.state = emc_event->state; @@ -315,7 +326,7 @@ static DN_NET2Response DN_NET2_EmcHandleFinishedRequest_(DN_NET2Core *net, DN_NE emc_event->payload = {}; } - if (result.state != DN_NET2ResponseState_WSClose) + if (result.state != DN_NETResponseState_WSClose) end_request = false; } @@ -324,18 +335,19 @@ static DN_NET2Response DN_NET2_EmcHandleFinishedRequest_(DN_NET2Core *net, DN_NE request_ptr->response.body = DN_Str8BuilderFromArena(&request_ptr->arena); if (end_request) { - DN_NET2_EndFinishedRequest_(net, request_ptr); - DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context; - DN_NET2EmcRequest *emc_request = DN_Cast(DN_NET2EmcRequest *) request_ptr->context[1]; + DN_NET_EndFinishedRequest_(request_ptr); + DN_NETEmcRequest *emc_request = DN_Cast(DN_NETEmcRequest *) request_ptr->context[1]; emscripten_websocket_delete(emc_request->socket); - request_ptr->next = net->free_list; - net->free_list = request_ptr; + + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; + request_ptr->next = emc->free_list; + emc->free_list = request_ptr; } return result; } -static DN_OSSemaphoreWaitResult DN_NET2_EmcSemaphoreWait_(DN_OSSemaphore *sem, DN_U32 timeout_ms) +static DN_OSSemaphoreWaitResult DN_NET_EmcSemaphoreWait_(DN_OSSemaphore *sem, DN_U32 timeout_ms) { // NOTE: In emscripten you can't just block on the semaphore with 'timeout_ms' because it needs // to yield to the javascript's event loop otherwise the fetching step cannot progress. Instead @@ -363,22 +375,22 @@ static DN_OSSemaphoreWaitResult DN_NET2_EmcSemaphoreWait_(DN_OSSemaphore *sem, D return result; } -DN_NET2Response DN_NET2_EmcWaitForResponse(DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms) +DN_NETResponse DN_NET_EmcWaitForResponse(DN_NETRequest request, DN_Arena *arena, DN_U32 timeout_ms) { - DN_NET2Response result = {}; - DN_NET2RequestInternal *request_ptr = DN_Cast(DN_NET2RequestInternal *) request.handle; + DN_NETResponse result = {}; + DN_NETRequestInternal *request_ptr = DN_Cast(DN_NETRequestInternal *) request.handle; if (request_ptr && request_ptr->gen == request.gen) { - DN_NET2Core *net = DN_Cast(DN_NET2Core *) request_ptr->context[0]; - DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context; + DN_NETCore *net = DN_Cast(DN_NETCore *) request_ptr->context[0]; + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; DN_Assert(emc); - DN_OSSemaphoreWaitResult wait = DN_NET2_EmcSemaphoreWait_(&request_ptr->completion_sem, timeout_ms); + DN_OSSemaphoreWaitResult wait = DN_NET_EmcSemaphoreWait_(&request_ptr->completion_sem, timeout_ms); if (wait != DN_OSSemaphoreWaitResult_Success) return result; // NOTE: Remove request from the done list - request_ptr->next = nullptr; - net->done_list = net->done_list->next; - result = DN_NET2_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena); + request_ptr->next = nullptr; + emc->response_list = emc->response_list->next; + result = DN_NET_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena); // NOTE: Decrement the global 'request done' completion semaphore since the user consumed the // request individually. @@ -388,33 +400,33 @@ DN_NET2Response DN_NET2_EmcWaitForResponse(DN_NET2Request request, DN_Arena *are return result; } -DN_NET2Response DN_NET2_EmcWaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms) +DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms) { - DN_NET2EmcCore *emc = DN_Cast(DN_NET2EmcCore *) net->context; + DN_NETEmcCore *emc = DN_Cast(DN_NETEmcCore *) net->context; DN_Assert(emc); - DN_NET2Response result = {}; - DN_OSSemaphoreWaitResult wait = DN_NET2_EmcSemaphoreWait_(&net->completion_sem, timeout_ms); + DN_NETResponse result = {}; + DN_OSSemaphoreWaitResult wait = DN_NET_EmcSemaphoreWait_(&net->completion_sem, timeout_ms); if (wait != DN_OSSemaphoreWaitResult_Success) return result; // NOTE: Dequeue the request that is done from the done list - DN_AssertF(net->done_list, + DN_AssertF(emc->response_list, "This should be set otherwise we bumped the completion sem without queueing into the " "done list or we forgot to wait on the global semaphore after a request finished"); - DN_NET2RequestInternal *request_ptr = net->done_list; - DN_Assert(request_ptr == net->done_list); + DN_NETRequestInternal *request_ptr = emc->response_list; + DN_Assert(request_ptr == emc->response_list); request_ptr->next = nullptr; - net->done_list = net->done_list->next; + emc->response_list = emc->response_list->next; // NOTE: Decrement the request's completion semaphore since the user consumed the global semaphore DN_OSSemaphoreWaitResult net_wait_result = DN_OS_SemaphoreWait(&request_ptr->completion_sem, 0 /*timeout_ms*/); DN_AssertF(net_wait_result == DN_OSSemaphoreWaitResult_Success, "Wait result was: %zu", DN_Cast(DN_USize) net_wait_result); - DN_NET2Request request = {}; + DN_NETRequest request = {}; request.handle = DN_Cast(DN_UPtr) request_ptr; request.gen = request_ptr->gen; - result = DN_NET2_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena); + result = DN_NET_EmcHandleFinishedRequest_(net, emc, request, request_ptr, arena); return result; } diff --git a/Source/Extra/dn_net_emscripten.h b/Source/Extra/dn_net_emscripten.h index 34a19df..be00805 100644 --- a/Source/Extra/dn_net_emscripten.h +++ b/Source/Extra/dn_net_emscripten.h @@ -1,14 +1,15 @@ #if !defined(DN_NET_EMSCRIPTEN_H) #define DN_NET_EMSCRIPTEN_H -#include "dn_net2.h" +#include "dn_net.h" -DN_NET2Interface DN_NET2_EmcInterface(); -void DN_NET2_EmcInit (DN_NET2Core *net, char *base, DN_U64 base_size); -DN_NET2Request DN_NET2_EmcDoHTTP (DN_NET2Core *net, DN_Str8 url, DN_Str8 method, DN_NET2DoHTTPArgs const *args); -DN_NET2Request DN_NET2_EmcDoWS (DN_NET2Core *net, DN_Str8 url); -void DN_NET2_EmcDoWSSend (DN_NET2Request request, DN_Str8 data, DN_NET2WSSend send); -DN_NET2Response DN_NET2_EmcWaitForResponse (DN_NET2Request request, DN_Arena *arena, DN_U32 timeout_ms); -DN_NET2Response DN_NET2_EmcWaitForAnyResponse(DN_NET2Core *net, DN_Arena *arena, DN_U32 timeout_ms); +DN_NETInterface DN_NET_EmcInterface(); +void DN_NET_EmcInit (DN_NETCore *net, char *base, DN_U64 base_size); +void DN_NET_EmcDeinit (DN_NETCore *net); +DN_NETRequest DN_NET_EmcDoHTTP (DN_NETCore *net, DN_Str8 url, DN_Str8 method, DN_NETDoHTTPArgs const *args); +DN_NETRequest DN_NET_EmcDoWS (DN_NETCore *net, DN_Str8 url); +void DN_NET_EmcDoWSSend (DN_NETRequest request, DN_Str8 data, DN_NETWSSend send); +DN_NETResponse DN_NET_EmcWaitForResponse (DN_NETRequest request, DN_Arena *arena, DN_U32 timeout_ms); +DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *arena, DN_U32 timeout_ms); #endif // DN_NET_EMSCRIPTEN_H diff --git a/Source/Extra/dn_tests.cpp b/Source/Extra/dn_tests.cpp index 97e22ca..1e17d14 100644 --- a/Source/Extra/dn_tests.cpp +++ b/Source/Extra/dn_tests.cpp @@ -2488,13 +2488,13 @@ static DN_UTCore DN_Tests_Win() static DN_UTCore DN_Tests_Net() { - DN_Str8 label = {}; - DN_NET2Interface net_interface = {}; + DN_Str8 label = {}; + DN_NETInterface net_interface = {}; #if defined(DN_PLATFORM_EMSCRIPTEN) - net_interface = DN_NET2_EmcInterface(); + net_interface = DN_NET_EmcInterface(); label = DN_Str8Lit("Emscripten"); #elif defined(DN_UNIT_TESTS_WITH_CURL) - net_interface = DN_NET2_CurlInterface(); + net_interface = DN_NET_CurlInterface(); label = DN_Str8Lit("CURL"); #endif @@ -2506,59 +2506,66 @@ static DN_UTCore DN_Tests_Net() DN_Str8 remote_ws_server_url = DN_Str8Lit("wss://echo.websocket.org"); DN_Str8 remote_http_server_url = DN_Str8Lit("https://google.com"); - char net_base[DN_Kilobytes(16)] = {}; - DN_NET2Core net = {}; - net_interface.init(&net, net_base, sizeof(net_base)); + DN_USize net_base_size = DN_Megabytes(1); + char *net_base = DN_ArenaNewArray(&arena, char, net_base_size, DN_ZMem_Yes); + DN_NETCore net = {}; + net_interface.init(&net, net_base, net_base_size); + DN_U64 arena_reset_p = DN_ArenaPos(&arena); for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP GET request", DN_Str8PrintFmt(label))) { - DN_NET2Request request = net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("GET"), nullptr); - DN_NET2Response response = net_interface.wait_for_response(request, &arena, UINT32_MAX); + DN_NETRequest request = net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("GET"), nullptr); + DN_NETResponse response = net_interface.wait_for_response(request, &arena, UINT32_MAX); DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status); - DN_UT_AssertF(&result, response.state == DN_NET2ResponseState_HTTP, "state=%u", response.state); + DN_UT_AssertF(&result, response.state == DN_NETResponseState_HTTP, "state=%u", response.state); DN_UT_AssertF(&result, response.error_str8.size == 0, "%.*s", DN_Str8PrintFmt(response.error_str8)); DN_UT_Assert(&result, response.body.size); } for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP POST request", DN_Str8PrintFmt(label))) { - DN_NET2Request request = net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("POST"), nullptr); - DN_NET2Response response = net_interface.wait_for_any_response(&net, &arena, UINT32_MAX); + net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("POST"), nullptr); + DN_NETResponse response = net_interface.wait_for_any_response(&net, &arena, UINT32_MAX); DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status); - DN_UT_AssertF(&result, response.state == DN_NET2ResponseState_HTTP, "state=%u", response.state); + DN_UT_AssertF(&result, response.state == DN_NETResponseState_HTTP, "state=%u", response.state); DN_UT_AssertF(&result, response.error_str8.size == 0, "error=%.*s", DN_Str8PrintFmt(response.error_str8)); DN_UT_Assert(&result, response.body.size); } for (DN_UT_Test(&result, "%.*s WaitForResponse WS request", DN_Str8PrintFmt(label))) { - DN_NET2Request request = net_interface.do_ws(&net, remote_ws_server_url); - DN_USize const WS_TIMEOUT_MS = 2000; + DN_NETRequest request = net_interface.do_ws(&net, remote_ws_server_url); + DN_USize const WS_TIMEOUT_MS = 16; // NOTE: Wait for WS connection to open - for (bool done = false; !done; DN_ArenaPopTo(&arena, 0)) { - DN_NET2Response response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS); - if (response.state == DN_NET2ResponseState_Nil) // NOTE: Timeout + for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_ArenaPopTo(&arena, arena_reset_p)) { + DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS); + if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout continue; - DN_UT_Assert(&result, response.state == DN_NET2ResponseState_WSOpen); + if (response.state == DN_NETResponseState_Error) + DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); + DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSOpen, "state=%d", response.state); done = true; } // NOTE: Receive the initial text from the echo server - for (bool done = false; !done; DN_ArenaPopTo(&arena, 0)) { - DN_NET2Response response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS); - if (response.state == DN_NET2ResponseState_Nil) // NOTE: Timeout + for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_ArenaPopTo(&arena, arena_reset_p)) { + DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS); + if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout continue; - DN_UT_Assert(&result, response.state == DN_NET2ResponseState_WSText); - + if (response.state == DN_NETResponseState_Error) + DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); + DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSText, "state=%d", response.state); // NOTE: Send the close signal - net_interface.do_ws_send(request, DN_Str8Lit(""), DN_NET2WSSend_Close); + net_interface.do_ws_send(request, DN_Str8Lit(""), DN_NETWSSend_Close); done = true; } // NOTE: Expect to hear the close - for (bool done = false; !done; DN_ArenaPopTo(&arena, 0)) { - DN_NET2Response response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS); - if (response.state == DN_NET2ResponseState_Nil) // NOTE: Timeout + for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_ArenaPopTo(&arena, arena_reset_p)) { + DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS); + if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout continue; - DN_UT_Assert(&result, response.state == DN_NET2ResponseState_WSClose); + if (response.state == DN_NETResponseState_Error) + DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); + DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSClose, "state=%d"); done = true; } } diff --git a/Source/Extra/dn_tests_main.cpp b/Source/Extra/dn_tests_main.cpp index 1a7c1d2..b0bbf2a 100644 --- a/Source/Extra/dn_tests_main.cpp +++ b/Source/Extra/dn_tests_main.cpp @@ -16,8 +16,8 @@ #include "../Extra/dn_math.cpp" #include "../Extra/dn_helpers.cpp" -#include "../Extra/dn_net2.h" -#include "../Extra/dn_net2.cpp" +#include "../Extra/dn_net.h" +#include "../Extra/dn_net.cpp" #if defined(DN_UNIT_TESTS_WITH_CURL) #define CURL_STATICLIB diff --git a/Source/OS/dn_os.cpp b/Source/OS/dn_os.cpp index ce8b9ee..ff2b0fa 100644 --- a/Source/OS/dn_os.cpp +++ b/Source/OS/dn_os.cpp @@ -130,9 +130,11 @@ DN_API void DN_OS_Init(DN_OSCore *os, DN_OSInitArgs *args) InitializeCriticalSection(&w32->sync_primitive_free_list_mutex); QueryPerformanceFrequency(&w32->qpc_frequency); - HMODULE module = LoadLibraryA("kernel32.dll"); - w32->set_thread_description = DN_Cast(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); - FreeLibrary(module); + HMODULE module = LoadLibraryA("kernel32.dll"); + if (module) { + w32->set_thread_description = DN_Cast(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); + FreeLibrary(module); + } // NOTE: win32 bcrypt wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; diff --git a/Source/OS/dn_os_w32.cpp b/Source/OS/dn_os_w32.cpp index aa6a936..12246ed 100644 --- a/Source/OS/dn_os_w32.cpp +++ b/Source/OS/dn_os_w32.cpp @@ -688,11 +688,11 @@ DN_API void DN_OS_Exit(int32_t exit_code) DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, char *stdout_buffer, - size_t *stdout_size, + DN_USize *stdout_size, char *stderr_buffer, - size_t *stderr_size, - DN_U32 timeout_ms, - DN_OSErrSink *err) + DN_USize *stderr_size, + DN_U32 timeout_ms, + DN_OSErrSink *err) { DN_OSExecResult result = {}; size_t stdout_buffer_size = 0; @@ -736,24 +736,27 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, result.os_error_code = win_error.code; DN_OS_ErrSinkAppendF(err, result.os_error_code, "Executed command failed to terminate: %.*s", DN_Str8PrintFmt(win_error.msg)); } else if (DN_Check(exec_result == WAIT_TIMEOUT || exec_result == WAIT_OBJECT_0)) { - // NOTE: Read stdout from process ////////////////////////////////////////////////////// + // NOTE: Read stdout from process // If the pipes are full, the process will block. We periodically // flush the pipes to make sure this doesn't happen char sink[DN_Kilobytes(8)]; stdout_bytes_available = 0; if (PeekNamedPipe(handle.stdout_read, nullptr, 0, nullptr, &stdout_bytes_available, nullptr)) { if (stdout_bytes_available) { - DWORD bytes_read = 0; - char *dest_buffer = handle.stdout_write && stdout_buffer ? stdout_buffer : sink; - size_t dest_size = handle.stdout_write && stdout_buffer ? stdout_buffer_size : DN_ArrayCountU(sink); - BOOL success = ReadFile(handle.stdout_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL); - (void)success; // TODO: - if (stdout_size) - *stdout_size = bytes_read; + DWORD bytes_read = 0; + char *dest_buffer = handle.stdout_write && stdout_buffer ? stdout_buffer : sink; + DN_USize dest_size = handle.stdout_write && stdout_buffer ? stdout_buffer_size : DN_ArrayCountU(sink); + BOOL success = ReadFile(handle.stdout_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL); + if (success) { + if (stdout_size) + *stdout_size = bytes_read; + } else { + DN_OS_ErrSinkAppendF(err, 1, "Failed to read bytes from stdout"); + } } } - // NOTE: Read stderr from process ////////////////////////////////////////////////////// + // NOTE: Read stderr from process stderr_bytes_available = 0; if (PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr)) { if (stderr_bytes_available) { @@ -761,9 +764,12 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, size_t dest_size = handle.stderr_write && stderr_buffer ? stderr_buffer_size : DN_ArrayCountU(sink); DWORD bytes_read = 0; BOOL success = ReadFile(handle.stderr_read, dest_buffer, DN_Cast(DWORD) dest_size, &bytes_read, NULL); - (void)success; // TODO: - if (stderr_size) - *stderr_size = bytes_read; + if (success) { + if (stderr_size) + *stderr_size = bytes_read; + } else { + DN_OS_ErrSinkAppendF(err, 1, "Failed to read bytes from stderr"); + } } } } @@ -782,12 +788,17 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, DN_Str8PrintFmt(win_error.msg)); } - // NOTE: Cleanup /////////////////////////////////////////////////////////////////////////////// - CloseHandle(handle.stdout_write); - CloseHandle(handle.stderr_write); - CloseHandle(handle.stdout_read); - CloseHandle(handle.stderr_read); - CloseHandle(handle.process); + // NOTE: Cleanup + if (handle.stdout_write) + CloseHandle(handle.stdout_write); + if (handle.stderr_write) + CloseHandle(handle.stderr_write); + if (handle.stdout_read) + CloseHandle(handle.stdout_read); + if (handle.stderr_read) + CloseHandle(handle.stderr_read); + if (handle.process) + CloseHandle(handle.process); } result.stdout_text = DN_Str8FromPtr(stdout_buffer, stdout_size ? *stdout_size : 0); diff --git a/project.rdbg b/project.rdbg new file mode 100644 index 0000000..4654071 --- /dev/null +++ b/project.rdbg @@ -0,0 +1,2 @@ +// raddbg 0.9.25 project file +