From 8c12a578d0c472ac1e566eea4223f83392c0cf60 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Wed, 1 Mar 2023 08:38:43 -0800 Subject: [PATCH 01/85] WIP: A skeleton microservice object --- examples/js-microservice.c | 107 +++++++++++++++++++++++ src/micro.c | 170 +++++++++++++++++++++++++++++++++++++ src/micro.h | 19 +++++ src/nats.h | 79 +++++++++++++++++ src/natsp.h | 23 +++++ 5 files changed, 398 insertions(+) create mode 100644 examples/js-microservice.c create mode 100644 src/micro.c create mode 100644 src/micro.h diff --git a/examples/js-microservice.c b/examples/js-microservice.c new file mode 100644 index 000000000..423d152e4 --- /dev/null +++ b/examples/js-microservice.c @@ -0,0 +1,107 @@ +// Copyright 2021 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "examples.h" + +static void +onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +{ + if (print) + printf("Received msg: %s - %.*s\n", + natsMsg_GetSubject(msg), + natsMsg_GetDataLength(msg), + natsMsg_GetData(msg)); + + if (start == 0) + start = nats_Now(); + + // We should be using a mutex to protect those variables since + // they are used from the subscription's delivery and the main + // threads. For demo purposes, this is fine. + if (++count == total) + elapsed = nats_Now() - start; + + // Since this is auto-ack callback, we don't need to ack here. + natsMsg_Destroy(msg); +} + +static void +asyncCb(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) +{ + printf("Async error: %u - %s\n", err, natsStatus_GetText(err)); + + natsSubscription_GetDropped(sub, (int64_t *)&dropped); +} + +int main(int argc, char **argv) +{ + natsConnection *conn = NULL; + natsOptions *opts = NULL; + jsMicroservice *m = NULL; + jsCtx *js = NULL; + jsErrCode jerr = 0; + jsOptions jsOpts; + jsSubOptions so; + natsStatus s; + bool delStream = false; + jsMicroserviceConfig cfg = { + .description = "Example JetStream microservice", + .name = "example", + .version = "1.0.0", + }; + + opts = parseArgs(argc, argv, ""); + + s = natsOptions_SetErrorHandler(opts, asyncCb, NULL); + if (s == NATS_OK) + { + s = natsConnection_Connect(&conn, opts); + } + if (s == NATS_OK) + { + s = jsOptions_Init(&jsOpts); + } + if (s == NATS_OK) + { + s = jsSubOptions_Init(&so); + } + if (s == NATS_OK) + { + s = natsConnection_JetStream(&js, conn, &jsOpts); + } + if (s == NATS_OK) + { + s = js_AddMicroservice(&m, js, &cfg, &jerr); + } + if (s == NATS_OK) + { + s = js_RunMicroservice(m, &jerr); + } + if (s == NATS_OK) + { + // Destroy all our objects to avoid report of memory leak + jsMicroservice_Destroy(m); + jsCtx_Destroy(js); + natsConnection_Destroy(conn); + natsOptions_Destroy(opts); + + // To silence reports of memory still in used with valgrind + nats_Close(); + + return 0; + } + + printf("Error: %u - %s - jerr=%u\n", s, natsStatus_GetText(s), jerr); + nats_PrintLastErrorStack(stderr); + return 1; +} diff --git a/src/micro.c b/src/micro.c new file mode 100644 index 000000000..2b69d609a --- /dev/null +++ b/src/micro.c @@ -0,0 +1,170 @@ +// Copyright 2021-2023 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "natsp.h" +#include "micro.h" +#include "mem.h" +#include "js.h" +// #include "conn.h" +// #include "sub.h" + +////////////////////////////////////////////////////////////////////////////// +// microservice management APIs +////////////////////////////////////////////////////////////////////////////// + +static void +_freeMicroservice(jsMicroservice *m) +{ + jsCtx *js = NULL; + + if (m == NULL) + return; + + js = m->js; + natsMutex_Destroy(m->mu); + NATS_FREE(m->identity.id); + NATS_FREE(m); + js_release(js); +} + +static void +_retainMicroservice(jsMicroservice *m) +{ + natsMutex_Lock(m->mu); + m->refs++; + natsMutex_Unlock(m->mu); +} + +static void +_releaseMicroservice(jsMicroservice *m) +{ + bool doFree; + + if (m == NULL) + return; + + natsMutex_Lock(m->mu); + doFree = (--(m->refs) == 0); + natsMutex_Unlock(m->mu); + + if (doFree) + _freeMicroservice(m); +} + +void jsMicroservice_Destroy(jsMicroservice *m) +{ + _releaseMicroservice(m); +} + +static natsStatus +_createMicroservice(jsMicroservice **new_microservice, jsCtx *js, jsMicroserviceConfig *cfg) +{ + natsStatus s = NATS_OK; + jsMicroservice *m = NULL; + natsMutex *mu = NULL; + + m = (jsMicroservice *)NATS_CALLOC(1, sizeof(jsMicroservice)); + if (m == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + + s = natsMutex_Create(&mu); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + m->mu = mu; + + char tmpNUID[NUID_BUFFER_LEN + 1]; + s = natsNUID_Next(tmpNUID, NUID_BUFFER_LEN + 1); + IF_OK_DUP_STRING(s, m->identity.id, tmpNUID); + if (s == NATS_OK) + { + m->identity.name = cfg->name; + m->identity.version = cfg->version; + *new_microservice = m; + return NATS_OK; + } +} + +natsStatus +js_AddMicroservice(jsMicroservice **new_m, jsCtx *js, jsMicroserviceConfig *cfg, jsErrCode *errCode) +{ + natsStatus s; + + if ((new_m == NULL) || (js == NULL) || (cfg == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + + s = _createMicroservice(new_m, js, cfg); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + + if (errCode != NULL) + { + *errCode = 0; + } + return NATS_OK; +} + +natsStatus +js_StopMicroservice(jsMicroservice *m, jsErrCode *errCode) +{ + if ((m == NULL) || (m->mu == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + + natsMutex_Lock(m->mu); + + if (m->stopped) + goto OK; + + m->stopped = true; + + // if s.DoneHandler != nil { + // s.asyncDispatcher.push(func() { s.DoneHandler(s) }) + // s.asyncDispatcher.close() + // } + +OK: + natsMutex_Unlock(m->mu); + if (errCode != NULL) + { + *errCode = 0; + } + return NATS_OK; +} + +bool js_IsMicroserviceStopped(jsMicroservice *m) +{ + bool stopped; + + if ((m == NULL) || (m->mu == NULL)) + return true; + + natsMutex_Lock(m->mu); + stopped = m->stopped; + natsMutex_Unlock(m->mu); + return stopped; +} + +// TODO: eliminate sleep +natsStatus +js_RunMicroservice(jsMicroservice *m, jsErrCode *errCode) +{ + if ((m == NULL) || (m->mu == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + + for (; !js_IsMicroserviceStopped(m);) + { + nats_Sleep(50); + } + return NATS_OK; +} diff --git a/src/micro.h b/src/micro.h new file mode 100644 index 000000000..bc0ede1a4 --- /dev/null +++ b/src/micro.h @@ -0,0 +1,19 @@ +// Copyright 2015-2018 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MICRO_H_ +#define MICRO_H_ + +#include "natsp.h" + +#endif /* MICRO_H_ */ diff --git a/src/nats.h b/src/nats.h index 5b53a29a5..cf7acb6d2 100644 --- a/src/nats.h +++ b/src/nats.h @@ -1158,6 +1158,21 @@ typedef struct jsOptions } jsOptions; +/** + * The Microservice object. Initialize with #js_AddMicroservice. + */ +typedef struct __microservice jsMicroservice; + +/** + * The Microservice configuration object. Initialize with #jsMicroserviceConfig_Init. + */ +typedef struct jsMicroserviceConfig +{ + const char *name; + const char *version; + const char *description; +} jsMicroserviceConfig; + /** * The KeyValue store object. */ @@ -6472,6 +6487,70 @@ natsMsg_GetTime(natsMsg *msg); * @{ */ +/** \defgroup microserviceGroup Microservice support + * + * A simple JetStream-based microservice implementation framework. + * + * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without + * necessarily bumping the major version of the library. + * + * @{ + */ + +// /** \brief Initializes a Microservice configuration structure. +// * +// * Use this before setting specific #jsMicroserviceConfig options and passing it +// * to #js_AddMicroservice. +// * +// * @see js_AddMicroservice +// * +// * @param cfg the pointer to the stack variable #jsMicroserviceConfig to +// * initialize. +// */ +// NATS_EXTERN natsStatus +// jsMicroserviceConfig_Init(jsMicroserviceConfig *cfg); + +/** \brief Adds a microservice with a given configuration. + * + * Adds a microservice with a given configuration. + * + * \note The return #jsMicroservice object needs to be destroyed using + * #jsMicroservice_Destroy when no longer needed to free allocated memory. + * + * @param new_microservice the location where to store the newly created + * #jsMicroservice object. + * @param js the pointer to the #jsCtx object. + * @param cfg the pointer to the #jsMicroserviceConfig configuration information + * used to create the #jsMicroservice object. + */ +NATS_EXTERN natsStatus +js_AddMicroservice(jsMicroservice **new_microservice, jsCtx *js, jsMicroserviceConfig *cfg, jsErrCode *errCode); + +/** \brief Waits for the microservice to stop. + */ +NATS_EXTERN natsStatus +js_RunMicroservice(jsMicroservice *m, jsErrCode *errCode); + +/** \brief Stops a running microservice. + */ +NATS_EXTERN natsStatus +js_StopMicroservice(jsMicroservice *m, jsErrCode *errCode); + +/** \brief Checks if a microservice is stopped. + */ +NATS_EXTERN bool +js_IsMicroserviceStopped(jsMicroservice *m); + +/** \brief Destroys a microservice object. + * + * Destroys a microservice object; frees all memory. The service must be stopped + * first, this function does not check if it is. + */ +NATS_EXTERN void +jsMicroservice_Destroy(jsMicroservice *m); + +/** @} */ // end of microserviceGroup + /** \defgroup kvGroupMgt KeyValue store management * * These functions allow to create, get or delete a KeyValue store. diff --git a/src/natsp.h b/src/natsp.h index c98bbb283..1a11cae79 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -432,6 +432,29 @@ typedef struct __jsSub } jsSub; + +struct __microserviceIdentity +{ + const char *name; + const char *version; + char *id; +}; + +struct __microservice +{ + natsMutex *mu; + jsCtx *js; + int refs; + struct __microserviceIdentity identity; + bool stopped; + // verbSubs map[string]*nats.Subscription + // started time.Time + // nc *nats.Conn + // natsHandlers handlers + + // asyncDispatcher asyncCallbacksHandler +}; + struct __kvStore { natsMutex *mu; From 8721da1962ccd96ef68c138672879ca6ba2d8612 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Fri, 3 Mar 2023 05:56:39 -0800 Subject: [PATCH 02/85] Responds to a PING --- src/micro.c | 339 ++++++++++++++++++++++++++++++++++++++++++---------- src/micro.h | 46 ++++++- src/nats.h | 3 + src/natsp.h | 13 ++ 4 files changed, 340 insertions(+), 61 deletions(-) diff --git a/src/micro.c b/src/micro.c index 2b69d609a..0dc317d24 100644 --- a/src/micro.c +++ b/src/micro.c @@ -12,6 +12,7 @@ // limitations under the License. #include +#include #include "natsp.h" #include "micro.h" @@ -20,10 +21,96 @@ // #include "conn.h" // #include "sub.h" +static void _freeMicroservice(jsMicroservice *m); +static void _retainMicroservice(jsMicroservice *m); +static void _releaseMicroservice(jsMicroservice *m); +static natsStatus _createMicroservice(jsMicroservice **new_microservice, jsCtx *js, jsMicroserviceConfig *cfg); +static bool _isEmpty(const char *s); +static natsStatus _newDottedSubject(char **new_subject, int count, ...); +static natsStatus _newControlSubject(char **newSubject, jsMicroserviceVerb verb, const char *name, const char *id); +static natsStatus _addInternalHandler(jsMicroservice *m, jsMicroserviceVerb verb, const char *kind, const char *id, const char *name, natsMsgHandler handler, jsErrCode *errCode); +static natsStatus _addVerbHandlers(jsMicroservice *m, jsMicroserviceVerb verb, natsMsgHandler handler); +static natsStatus _respond(jsMicroservice *m, jsMicroserviceRequest *r, const char *data, int len, jsErrCode *errCode); +static natsStatus _marshalPing(natsBuffer **new_buf, jsMicroservice *m); +static void _handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); + ////////////////////////////////////////////////////////////////////////////// // microservice management APIs ////////////////////////////////////////////////////////////////////////////// +natsStatus +js_AddMicroservice(jsMicroservice **new_m, jsCtx *js, jsMicroserviceConfig *cfg, jsErrCode *errCode) +{ + natsStatus s; + + if ((new_m == NULL) || (js == NULL) || (cfg == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + + s = _createMicroservice(new_m, js, cfg); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + + if (errCode != NULL) + { + *errCode = 0; + } + return NATS_OK; +} + +natsStatus +js_StopMicroservice(jsMicroservice *m, jsErrCode *errCode) +{ + if ((m == NULL) || (m->mu == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + + natsMutex_Lock(m->mu); + + if (m->stopped) + goto OK; + + m->stopped = true; + + // if s.DoneHandler != nil { + // s.asyncDispatcher.push(func() { s.DoneHandler(s) }) + // s.asyncDispatcher.close() + // } + +OK: + natsMutex_Unlock(m->mu); + if (errCode != NULL) + { + *errCode = 0; + } + return NATS_OK; +} + +bool js_IsMicroserviceStopped(jsMicroservice *m) +{ + bool stopped; + + if ((m == NULL) || (m->mu == NULL)) + return true; + + natsMutex_Lock(m->mu); + stopped = m->stopped; + natsMutex_Unlock(m->mu); + return stopped; +} + +// TODO: eliminate sleep +natsStatus +js_RunMicroservice(jsMicroservice *m, jsErrCode *errCode) +{ + if ((m == NULL) || (m->mu == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + + for (; !js_IsMicroserviceStopped(m);) + { + nats_Sleep(50); + } + return NATS_OK; +} + static void _freeMicroservice(jsMicroservice *m) { @@ -68,103 +155,235 @@ void jsMicroservice_Destroy(jsMicroservice *m) _releaseMicroservice(m); } +static bool +_isEmpty(const char *s) +{ + return (s == NULL || *s == '\0'); +} + static natsStatus -_createMicroservice(jsMicroservice **new_microservice, jsCtx *js, jsMicroserviceConfig *cfg) +_newDottedSubject(char **new_subject, int count, ...) { - natsStatus s = NATS_OK; - jsMicroservice *m = NULL; - natsMutex *mu = NULL; + va_list args1, args2; + int i, len, n; + char *result, *p; - m = (jsMicroservice *)NATS_CALLOC(1, sizeof(jsMicroservice)); - if (m == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); + va_start(args1, count); + va_copy(args2, args1); - s = natsMutex_Create(&mu); + len = 0; + for (i = 0; i < count; i++) + { + if (i > 0) + { + len++; /* for the dot */ + } + len += strlen(va_arg(args1, char *)); + } + va_end(args1); + + result = NATS_MALLOC(len + 1); + if (result == NULL) + { + va_end(args2); + return NATS_NO_MEMORY; + } + + len = 0; + for (i = 0; i < count; i++) + { + if (i > 0) + { + result[len++] = '.'; + } + p = va_arg(args2, char *); + n = strlen(p); + memcpy(result + len, p, n); + len += n; + } + va_end(args2); + + *new_subject = result; + return NATS_OK; +} + +static natsStatus +_newControlSubject(char **newSubject, jsMicroserviceVerb verb, const char *name, const char *id) +{ + natsStatus s = NATS_OK; + const char *verbStr; + + s = jsMicroserviceVerb_String(&verbStr, verb); if (s != NATS_OK) + { return NATS_UPDATE_ERR_STACK(s); - m->mu = mu; + } - char tmpNUID[NUID_BUFFER_LEN + 1]; - s = natsNUID_Next(tmpNUID, NUID_BUFFER_LEN + 1); - IF_OK_DUP_STRING(s, m->identity.id, tmpNUID); - if (s == NATS_OK) + if (_isEmpty(name) && !_isEmpty(id)) { - m->identity.name = cfg->name; - m->identity.version = cfg->version; - *new_microservice = m; - return NATS_OK; + NATS_UPDATE_ERR_TXT("service name is required when id is provided: %s", id); + return NATS_UPDATE_ERR_STACK(NATS_INVALID_ARG); } + + else if (_isEmpty(name) && _isEmpty(id)) + return _newDottedSubject(newSubject, 2, jsMicroserviceAPIPrefix, verbStr); + else if (_isEmpty(id)) + return _newDottedSubject(newSubject, 3, jsMicroserviceAPIPrefix, verbStr, name); + else + return _newDottedSubject(newSubject, 4, jsMicroserviceAPIPrefix, verbStr, name, id); } -natsStatus -js_AddMicroservice(jsMicroservice **new_m, jsCtx *js, jsMicroserviceConfig *cfg, jsErrCode *errCode) +static natsStatus +_addInternalHandler(jsMicroservice *m, jsMicroserviceVerb verb, const char *kind, + const char *id, const char *name, natsMsgHandler handler, jsErrCode *errCode) { - natsStatus s; - if ((new_m == NULL) || (js == NULL) || (cfg == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); + natsStatus s = NATS_OK; + natsSubscription *sub = NULL; + char *subj = NULL; - s = _createMicroservice(new_m, js, cfg); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); + s = _newControlSubject(&subj, verb, kind, id); + if (s == NATS_OK) + { + s = natsConnection_Subscribe(&sub, m->js->nc, subj, handler, m); + } + if (s == NATS_OK) + { + return NATS_OK; + } - if (errCode != NULL) + js_StopMicroservice(m, errCode); + return NATS_UPDATE_ERR_STACK(s); +} + +// __verbHandlers generates control handlers for a specific verb. Each request +// generates 3 subscriptions, one for the general verb affecting all services +// written with the framework, one that handles all services of a particular +// kind, and finally a specific service instance. +static natsStatus +_addVerbHandlers(jsMicroservice *m, jsMicroserviceVerb verb, natsMsgHandler handler) +{ + natsStatus s = NATS_OK; + char name[256]; + const char *verbStr; + + s = jsMicroserviceVerb_String(&verbStr, verb); + if (s == NATS_OK) { - *errCode = 0; + snprintf(name, sizeof(name), "%s-all", verbStr); + s = _addInternalHandler(m, verb, "", "", name, handler, NULL); } - return NATS_OK; + if (s == NATS_OK) + { + snprintf(name, sizeof(name), "%s-kind", verbStr); + s = _addInternalHandler(m, verb, m->identity.name, "", name, handler, NULL); + } + if (s == NATS_OK) + { + s = _addInternalHandler(m, verb, m->identity.name, m->identity.id, verbStr, handler, NULL); + } + return NATS_UPDATE_ERR_STACK(s); } -natsStatus -js_StopMicroservice(jsMicroservice *m, jsErrCode *errCode) +static natsStatus +_respond(jsMicroservice *m, jsMicroserviceRequest *r, const char *data, int len, jsErrCode *errCode) { + natsStatus s = NATS_OK; + natsMsg *respMsg = NULL; + if ((m == NULL) || (m->mu == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); - natsMutex_Lock(m->mu); + s = natsConnection_Publish(m->js->nc, natsMsg_GetReply(r->msg), data, len); + return NATS_UPDATE_ERR_STACK(s); +} - if (m->stopped) - goto OK; +static natsStatus +_marshalPing(natsBuffer **new_buf, jsMicroservice *m) +{ + natsBuffer *buf = NULL; + natsStatus s; - m->stopped = true; + s = natsBuf_Create(&buf, 1024); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); - // if s.DoneHandler != nil { - // s.asyncDispatcher.push(func() { s.DoneHandler(s) }) - // s.asyncDispatcher.close() - // } +// name and sep must be a string literal +#define IFOK_attr(_name, _value, _sep) \ + IFOK(s, natsBuf_Append(buf, "\"" _name "\":\"", -1)); \ + IFOK(s, natsBuf_Append(buf, _value, -1)); \ + IFOK(s, natsBuf_Append(buf, "\"" _sep, -1)); -OK: - natsMutex_Unlock(m->mu); - if (errCode != NULL) + s = natsBuf_Append(buf, "{", -1); + IFOK_attr("name", m->identity.name, ","); + IFOK_attr("version", m->identity.version, ","); + IFOK_attr("id", m->identity.id, ","); + IFOK_attr("type", jsMicroservicePingResponseType, ""); + IFOK(s, natsBuf_AppendByte(buf, '}')); + + if (s == NATS_OK) { - *errCode = 0; + *new_buf = buf; + return NATS_OK; } - return NATS_OK; + + natsBuf_Destroy(buf); + return NATS_UPDATE_ERR_STACK(s); } -bool js_IsMicroserviceStopped(jsMicroservice *m) +static void +_handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { - bool stopped; - - if ((m == NULL) || (m->mu == NULL)) - return true; + natsStatus s = NATS_OK; + jsMicroservice *m = (jsMicroservice *)closure; + jsMicroserviceRequest req = { + .msg = msg, + }; + natsBuffer *buf = NULL; - natsMutex_Lock(m->mu); - stopped = m->stopped; - natsMutex_Unlock(m->mu); - return stopped; + s = _marshalPing(&buf, m); + if (s == NATS_OK) + { + s = _respond(m, &req, natsBuf_Data(buf), natsBuf_Len(buf), NULL); + } + if (buf != NULL) + { + natsBuf_Destroy(buf); + } } -// TODO: eliminate sleep -natsStatus -js_RunMicroservice(jsMicroservice *m, jsErrCode *errCode) +static natsStatus +_createMicroservice(jsMicroservice **new_microservice, jsCtx *js, jsMicroserviceConfig *cfg) { - if ((m == NULL) || (m->mu == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); + natsStatus s = NATS_OK; + jsMicroservice *m = NULL; + natsMutex *mu = NULL; - for (; !js_IsMicroserviceStopped(m);) + m = (jsMicroservice *)NATS_CALLOC(1, sizeof(jsMicroservice)); + if (m == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + m->js = js; + + s = natsMutex_Create(&mu); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + m->mu = mu; + + char tmpNUID[NUID_BUFFER_LEN + 1]; + s = natsNUID_Next(tmpNUID, NUID_BUFFER_LEN + 1); + IF_OK_DUP_STRING(s, m->identity.id, tmpNUID); + if (s == NATS_OK) { - nats_Sleep(50); + m->identity.name = cfg->name; + m->identity.version = cfg->version; } - return NATS_OK; + if (s == NATS_OK) + { + s = _addVerbHandlers(m, jsMicroserviceVerbPing, _handleMicroservicePing); + } + if (s == NATS_OK) + { + *new_microservice = m; + } + return NATS_UPDATE_ERR_STACK(s); } diff --git a/src/micro.h b/src/micro.h index bc0ede1a4..7a245fe00 100644 --- a/src/micro.h +++ b/src/micro.h @@ -14,6 +14,50 @@ #ifndef MICRO_H_ #define MICRO_H_ -#include "natsp.h" +#define jsMicroserviceAPIPrefix "$SRV" + +enum jsMicroserviceVerb +{ + jsMicroserviceVerbPing = 0, + jsMicroserviceVerbStats, + jsMicroserviceVerbInfo, + jsMicroserviceVerbSchema, + jsMicroserviceVerbMax +}; + +typedef enum jsMicroserviceVerb jsMicroserviceVerb; + +#define jsMicroserviceInfoResponseType "io.nats.micro.v1.info_response" +#define jsMicroservicePingResponseType "io.nats.micro.v1.ping_response" +#define jsMicroserviceStatsResponseType "io.nats.micro.v1.stats_response" +#define jsMicroserviceSchemaResponseType "io.nats.micro.v1.schema_response" + +static natsStatus +jsMicroserviceVerb_String(const char **new_subject, jsMicroserviceVerb verb) +{ + if (new_subject == NULL) + return nats_setDefaultError(NATS_INVALID_ARG); + + switch (verb) + { + case jsMicroserviceVerbPing: + *new_subject = "PING"; + return NATS_OK; + + case jsMicroserviceVerbStats: + *new_subject = "STATS"; + return NATS_OK; + + case jsMicroserviceVerbInfo: + *new_subject = "INFO"; + return NATS_OK; + + case jsMicroserviceVerbSchema: + *new_subject = "SCHEMA"; + return NATS_OK; + default: + return nats_setError(NATS_INVALID_ARG, "Invalid microservice verb %d", verb); + } +} #endif /* MICRO_H_ */ diff --git a/src/nats.h b/src/nats.h index cf7acb6d2..6fa10e982 100644 --- a/src/nats.h +++ b/src/nats.h @@ -1163,6 +1163,9 @@ typedef struct jsOptions */ typedef struct __microservice jsMicroservice; +typedef struct __microserviceRequest jsMicroserviceRequest; + + /** * The Microservice configuration object. Initialize with #jsMicroserviceConfig_Init. */ diff --git a/src/natsp.h b/src/natsp.h index 1a11cae79..d5a229388 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -433,6 +433,13 @@ typedef struct __jsSub } jsSub; +struct __microserviceConfig +{ + const char *name; + const char *version; + const char *description; +}; + struct __microserviceIdentity { const char *name; @@ -455,6 +462,12 @@ struct __microservice // asyncDispatcher asyncCallbacksHandler }; +struct __microserviceRequest { + natsMsg *msg; + // respondError error +}; + + struct __kvStore { natsMutex *mu; From a07aa710e046b026aae82adc5648d3fa89ba66d9 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Fri, 3 Mar 2023 06:49:55 -0800 Subject: [PATCH 03/85] Clarified - this is NOT a JS thing! --- .../{js-microservice.c => microservice.c} | 29 +-- src/micro.c | 244 +++++++++--------- src/micro.h | 34 +-- src/nats.h | 46 ++-- src/natsp.h | 8 +- 5 files changed, 169 insertions(+), 192 deletions(-) rename examples/{js-microservice.c => microservice.c} (79%) diff --git a/examples/js-microservice.c b/examples/microservice.c similarity index 79% rename from examples/js-microservice.c rename to examples/microservice.c index 423d152e4..f4e0fadb1 100644 --- a/examples/js-microservice.c +++ b/examples/microservice.c @@ -47,14 +47,10 @@ int main(int argc, char **argv) { natsConnection *conn = NULL; natsOptions *opts = NULL; - jsMicroservice *m = NULL; - jsCtx *js = NULL; - jsErrCode jerr = 0; - jsOptions jsOpts; - jsSubOptions so; + natsMicroservice *m = NULL; natsStatus s; bool delStream = false; - jsMicroserviceConfig cfg = { + natsMicroserviceConfig cfg = { .description = "Example JetStream microservice", .name = "example", .version = "1.0.0", @@ -69,29 +65,16 @@ int main(int argc, char **argv) } if (s == NATS_OK) { - s = jsOptions_Init(&jsOpts); + s = nats_AddMicroservice(&m, conn, &cfg); } if (s == NATS_OK) { - s = jsSubOptions_Init(&so); - } - if (s == NATS_OK) - { - s = natsConnection_JetStream(&js, conn, &jsOpts); - } - if (s == NATS_OK) - { - s = js_AddMicroservice(&m, js, &cfg, &jerr); - } - if (s == NATS_OK) - { - s = js_RunMicroservice(m, &jerr); + s = natsMicroservice_Run(m); } if (s == NATS_OK) { // Destroy all our objects to avoid report of memory leak - jsMicroservice_Destroy(m); - jsCtx_Destroy(js); + natsMicroservice_Destroy(m); natsConnection_Destroy(conn); natsOptions_Destroy(opts); @@ -101,7 +84,7 @@ int main(int argc, char **argv) return 0; } - printf("Error: %u - %s - jerr=%u\n", s, natsStatus_GetText(s), jerr); + printf("Error: %u - %s\n", s, natsStatus_GetText(s)); nats_PrintLastErrorStack(stderr); return 1; } diff --git a/src/micro.c b/src/micro.c index 0dc317d24..76318d3ae 100644 --- a/src/micro.c +++ b/src/micro.c @@ -17,48 +17,60 @@ #include "natsp.h" #include "micro.h" #include "mem.h" -#include "js.h" -// #include "conn.h" -// #include "sub.h" - -static void _freeMicroservice(jsMicroservice *m); -static void _retainMicroservice(jsMicroservice *m); -static void _releaseMicroservice(jsMicroservice *m); -static natsStatus _createMicroservice(jsMicroservice **new_microservice, jsCtx *js, jsMicroserviceConfig *cfg); -static bool _isEmpty(const char *s); +#include "conn.h" + +static natsStatus +_createMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg); +static void _freeMicroservice(natsMicroservice *m); +static void _retainMicroservice(natsMicroservice *m); +static void _releaseMicroservice(natsMicroservice *m); + +static void +_handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); + +static natsStatus +_newControlSubject(char **newSubject, natsMicroserviceVerb verb, const char *name, const char *id); + +static natsStatus +_addInternalHandler(natsMicroservice *m, natsMicroserviceVerb verb, const char *kind, const char *id, + const char *name, natsMsgHandler handler); +static natsStatus +_addVerbHandlers(natsMicroservice *m, natsMicroserviceVerb verb, natsMsgHandler handler); + +static natsStatus +_respond(natsMicroservice *m, natsMicroserviceRequest *r, const char *data, int len); + +static natsStatus _marshalPing(natsBuffer **new_buf, natsMicroservice *m); + static natsStatus _newDottedSubject(char **new_subject, int count, ...); -static natsStatus _newControlSubject(char **newSubject, jsMicroserviceVerb verb, const char *name, const char *id); -static natsStatus _addInternalHandler(jsMicroservice *m, jsMicroserviceVerb verb, const char *kind, const char *id, const char *name, natsMsgHandler handler, jsErrCode *errCode); -static natsStatus _addVerbHandlers(jsMicroservice *m, jsMicroserviceVerb verb, natsMsgHandler handler); -static natsStatus _respond(jsMicroservice *m, jsMicroserviceRequest *r, const char *data, int len, jsErrCode *errCode); -static natsStatus _marshalPing(natsBuffer **new_buf, jsMicroservice *m); -static void _handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); +static bool _isEmpty(const char *s); ////////////////////////////////////////////////////////////////////////////// // microservice management APIs ////////////////////////////////////////////////////////////////////////////// natsStatus -js_AddMicroservice(jsMicroservice **new_m, jsCtx *js, jsMicroserviceConfig *cfg, jsErrCode *errCode) +nats_AddMicroservice(natsMicroservice **new_m, natsConnection *nc, natsMicroserviceConfig *cfg) { natsStatus s; - if ((new_m == NULL) || (js == NULL) || (cfg == NULL)) + if ((new_m == NULL) || (nc == NULL) || (cfg == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); - s = _createMicroservice(new_m, js, cfg); + s = _createMicroservice(new_m, nc, cfg); if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); - if (errCode != NULL) - { - *errCode = 0; - } return NATS_OK; } +void natsMicroservice_Destroy(natsMicroservice *m) +{ + _releaseMicroservice(m); +} + natsStatus -js_StopMicroservice(jsMicroservice *m, jsErrCode *errCode) +natsMicroservice_Stop(natsMicroservice *m) { if ((m == NULL) || (m->mu == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); @@ -70,21 +82,12 @@ js_StopMicroservice(jsMicroservice *m, jsErrCode *errCode) m->stopped = true; - // if s.DoneHandler != nil { - // s.asyncDispatcher.push(func() { s.DoneHandler(s) }) - // s.asyncDispatcher.close() - // } - OK: natsMutex_Unlock(m->mu); - if (errCode != NULL) - { - *errCode = 0; - } return NATS_OK; } -bool js_IsMicroserviceStopped(jsMicroservice *m) +bool natsMicroservice_IsStopped(natsMicroservice *m) { bool stopped; @@ -99,35 +102,95 @@ bool js_IsMicroserviceStopped(jsMicroservice *m) // TODO: eliminate sleep natsStatus -js_RunMicroservice(jsMicroservice *m, jsErrCode *errCode) +natsMicroservice_Run(natsMicroservice *m) { if ((m == NULL) || (m->mu == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); - for (; !js_IsMicroserviceStopped(m);) + for (; !natsMicroservice_IsStopped(m);) { nats_Sleep(50); } return NATS_OK; } +// Message handlers. + static void -_freeMicroservice(jsMicroservice *m) +_handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +{ + natsStatus s = NATS_OK; + natsMicroservice *m = (natsMicroservice *)closure; + natsMicroserviceRequest req = { + .msg = msg, + }; + natsBuffer *buf = NULL; + + s = _marshalPing(&buf, m); + if (s == NATS_OK) + { + s = _respond(m, &req, natsBuf_Data(buf), natsBuf_Len(buf)); + } + if (buf != NULL) + { + natsBuf_Destroy(buf); + } +} + +// Implementation functions. + +static natsStatus +_createMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg) { - jsCtx *js = NULL; + natsStatus s = NATS_OK; + natsMicroservice *m = NULL; + natsMutex *mu = NULL; + + m = (natsMicroservice *)NATS_CALLOC(1, sizeof(natsMicroservice)); + if (m == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + + natsConn_retain(nc); + m->nc = nc; + + s = natsMutex_Create(&mu); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + m->mu = mu; + + char tmpNUID[NUID_BUFFER_LEN + 1]; + s = natsNUID_Next(tmpNUID, NUID_BUFFER_LEN + 1); + IF_OK_DUP_STRING(s, m->identity.id, tmpNUID); + if (s == NATS_OK) + { + m->identity.name = cfg->name; + m->identity.version = cfg->version; + } + if (s == NATS_OK) + { + s = _addVerbHandlers(m, natsMicroserviceVerbPing, _handleMicroservicePing); + } + if (s == NATS_OK) + { + *new_microservice = m; + } + return NATS_UPDATE_ERR_STACK(s); +} +static void +_freeMicroservice(natsMicroservice *m) +{ if (m == NULL) return; - js = m->js; + natsConn_release(m->nc); natsMutex_Destroy(m->mu); NATS_FREE(m->identity.id); NATS_FREE(m); - js_release(js); } static void -_retainMicroservice(jsMicroservice *m) +_retainMicroservice(natsMicroservice *m) { natsMutex_Lock(m->mu); m->refs++; @@ -135,7 +198,7 @@ _retainMicroservice(jsMicroservice *m) } static void -_releaseMicroservice(jsMicroservice *m) +_releaseMicroservice(natsMicroservice *m) { bool doFree; @@ -150,11 +213,6 @@ _releaseMicroservice(jsMicroservice *m) _freeMicroservice(m); } -void jsMicroservice_Destroy(jsMicroservice *m) -{ - _releaseMicroservice(m); -} - static bool _isEmpty(const char *s) { @@ -208,12 +266,12 @@ _newDottedSubject(char **new_subject, int count, ...) } static natsStatus -_newControlSubject(char **newSubject, jsMicroserviceVerb verb, const char *name, const char *id) +_newControlSubject(char **newSubject, natsMicroserviceVerb verb, const char *name, const char *id) { natsStatus s = NATS_OK; const char *verbStr; - s = jsMicroserviceVerb_String(&verbStr, verb); + s = natsMicroserviceVerb_String(&verbStr, verb); if (s != NATS_OK) { return NATS_UPDATE_ERR_STACK(s); @@ -226,16 +284,16 @@ _newControlSubject(char **newSubject, jsMicroserviceVerb verb, const char *name, } else if (_isEmpty(name) && _isEmpty(id)) - return _newDottedSubject(newSubject, 2, jsMicroserviceAPIPrefix, verbStr); + return _newDottedSubject(newSubject, 2, natsMicroserviceAPIPrefix, verbStr); else if (_isEmpty(id)) - return _newDottedSubject(newSubject, 3, jsMicroserviceAPIPrefix, verbStr, name); + return _newDottedSubject(newSubject, 3, natsMicroserviceAPIPrefix, verbStr, name); else - return _newDottedSubject(newSubject, 4, jsMicroserviceAPIPrefix, verbStr, name, id); + return _newDottedSubject(newSubject, 4, natsMicroserviceAPIPrefix, verbStr, name, id); } static natsStatus -_addInternalHandler(jsMicroservice *m, jsMicroserviceVerb verb, const char *kind, - const char *id, const char *name, natsMsgHandler handler, jsErrCode *errCode) +_addInternalHandler(natsMicroservice *m, natsMicroserviceVerb verb, const char *kind, + const char *id, const char *name, natsMsgHandler handler) { natsStatus s = NATS_OK; @@ -245,14 +303,14 @@ _addInternalHandler(jsMicroservice *m, jsMicroserviceVerb verb, const char *kind s = _newControlSubject(&subj, verb, kind, id); if (s == NATS_OK) { - s = natsConnection_Subscribe(&sub, m->js->nc, subj, handler, m); + s = natsConnection_Subscribe(&sub, m->nc, subj, handler, m); } if (s == NATS_OK) { return NATS_OK; } - js_StopMicroservice(m, errCode); + natsMicroservice_Stop(m); return NATS_UPDATE_ERR_STACK(s); } @@ -261,45 +319,44 @@ _addInternalHandler(jsMicroservice *m, jsMicroserviceVerb verb, const char *kind // written with the framework, one that handles all services of a particular // kind, and finally a specific service instance. static natsStatus -_addVerbHandlers(jsMicroservice *m, jsMicroserviceVerb verb, natsMsgHandler handler) +_addVerbHandlers(natsMicroservice *m, natsMicroserviceVerb verb, natsMsgHandler handler) { natsStatus s = NATS_OK; char name[256]; const char *verbStr; - s = jsMicroserviceVerb_String(&verbStr, verb); + s = natsMicroserviceVerb_String(&verbStr, verb); if (s == NATS_OK) { snprintf(name, sizeof(name), "%s-all", verbStr); - s = _addInternalHandler(m, verb, "", "", name, handler, NULL); + s = _addInternalHandler(m, verb, "", "", name, handler); } if (s == NATS_OK) { snprintf(name, sizeof(name), "%s-kind", verbStr); - s = _addInternalHandler(m, verb, m->identity.name, "", name, handler, NULL); + s = _addInternalHandler(m, verb, m->identity.name, "", name, handler); } if (s == NATS_OK) { - s = _addInternalHandler(m, verb, m->identity.name, m->identity.id, verbStr, handler, NULL); + s = _addInternalHandler(m, verb, m->identity.name, m->identity.id, verbStr, handler); } return NATS_UPDATE_ERR_STACK(s); } static natsStatus -_respond(jsMicroservice *m, jsMicroserviceRequest *r, const char *data, int len, jsErrCode *errCode) +_respond(natsMicroservice *m, natsMicroserviceRequest *r, const char *data, int len) { natsStatus s = NATS_OK; - natsMsg *respMsg = NULL; if ((m == NULL) || (m->mu == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); - s = natsConnection_Publish(m->js->nc, natsMsg_GetReply(r->msg), data, len); + s = natsConnection_Publish(m->nc, natsMsg_GetReply(r->msg), data, len); return NATS_UPDATE_ERR_STACK(s); } static natsStatus -_marshalPing(natsBuffer **new_buf, jsMicroservice *m) +_marshalPing(natsBuffer **new_buf, natsMicroservice *m) { natsBuffer *buf = NULL; natsStatus s; @@ -318,7 +375,7 @@ _marshalPing(natsBuffer **new_buf, jsMicroservice *m) IFOK_attr("name", m->identity.name, ","); IFOK_attr("version", m->identity.version, ","); IFOK_attr("id", m->identity.id, ","); - IFOK_attr("type", jsMicroservicePingResponseType, ""); + IFOK_attr("type", natsMicroservicePingResponseType, ""); IFOK(s, natsBuf_AppendByte(buf, '}')); if (s == NATS_OK) @@ -330,60 +387,3 @@ _marshalPing(natsBuffer **new_buf, jsMicroservice *m) natsBuf_Destroy(buf); return NATS_UPDATE_ERR_STACK(s); } - -static void -_handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - natsStatus s = NATS_OK; - jsMicroservice *m = (jsMicroservice *)closure; - jsMicroserviceRequest req = { - .msg = msg, - }; - natsBuffer *buf = NULL; - - s = _marshalPing(&buf, m); - if (s == NATS_OK) - { - s = _respond(m, &req, natsBuf_Data(buf), natsBuf_Len(buf), NULL); - } - if (buf != NULL) - { - natsBuf_Destroy(buf); - } -} - -static natsStatus -_createMicroservice(jsMicroservice **new_microservice, jsCtx *js, jsMicroserviceConfig *cfg) -{ - natsStatus s = NATS_OK; - jsMicroservice *m = NULL; - natsMutex *mu = NULL; - - m = (jsMicroservice *)NATS_CALLOC(1, sizeof(jsMicroservice)); - if (m == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - m->js = js; - - s = natsMutex_Create(&mu); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - m->mu = mu; - - char tmpNUID[NUID_BUFFER_LEN + 1]; - s = natsNUID_Next(tmpNUID, NUID_BUFFER_LEN + 1); - IF_OK_DUP_STRING(s, m->identity.id, tmpNUID); - if (s == NATS_OK) - { - m->identity.name = cfg->name; - m->identity.version = cfg->version; - } - if (s == NATS_OK) - { - s = _addVerbHandlers(m, jsMicroserviceVerbPing, _handleMicroservicePing); - } - if (s == NATS_OK) - { - *new_microservice = m; - } - return NATS_UPDATE_ERR_STACK(s); -} diff --git a/src/micro.h b/src/micro.h index 7a245fe00..b9f66ee35 100644 --- a/src/micro.h +++ b/src/micro.h @@ -14,45 +14,45 @@ #ifndef MICRO_H_ #define MICRO_H_ -#define jsMicroserviceAPIPrefix "$SRV" +#define natsMicroserviceAPIPrefix "$SRV" -enum jsMicroserviceVerb +enum natsMicroserviceVerb { - jsMicroserviceVerbPing = 0, - jsMicroserviceVerbStats, - jsMicroserviceVerbInfo, - jsMicroserviceVerbSchema, - jsMicroserviceVerbMax + natsMicroserviceVerbPing = 0, + natsMicroserviceVerbStats, + natsMicroserviceVerbInfo, + natsMicroserviceVerbSchema, + natsMicroserviceVerbMax }; -typedef enum jsMicroserviceVerb jsMicroserviceVerb; +typedef enum natsMicroserviceVerb natsMicroserviceVerb; -#define jsMicroserviceInfoResponseType "io.nats.micro.v1.info_response" -#define jsMicroservicePingResponseType "io.nats.micro.v1.ping_response" -#define jsMicroserviceStatsResponseType "io.nats.micro.v1.stats_response" -#define jsMicroserviceSchemaResponseType "io.nats.micro.v1.schema_response" +#define natsMicroserviceInfoResponseType "io.nats.micro.v1.info_response" +#define natsMicroservicePingResponseType "io.nats.micro.v1.ping_response" +#define natsMicroserviceStatsResponseType "io.nats.micro.v1.stats_response" +#define natsMicroserviceSchemaResponseType "io.nats.micro.v1.schema_response" static natsStatus -jsMicroserviceVerb_String(const char **new_subject, jsMicroserviceVerb verb) +natsMicroserviceVerb_String(const char **new_subject, natsMicroserviceVerb verb) { if (new_subject == NULL) return nats_setDefaultError(NATS_INVALID_ARG); switch (verb) { - case jsMicroserviceVerbPing: + case natsMicroserviceVerbPing: *new_subject = "PING"; return NATS_OK; - case jsMicroserviceVerbStats: + case natsMicroserviceVerbStats: *new_subject = "STATS"; return NATS_OK; - case jsMicroserviceVerbInfo: + case natsMicroserviceVerbInfo: *new_subject = "INFO"; return NATS_OK; - case jsMicroserviceVerbSchema: + case natsMicroserviceVerbSchema: *new_subject = "SCHEMA"; return NATS_OK; default: diff --git a/src/nats.h b/src/nats.h index 6fa10e982..e122b58f7 100644 --- a/src/nats.h +++ b/src/nats.h @@ -1159,22 +1159,22 @@ typedef struct jsOptions } jsOptions; /** - * The Microservice object. Initialize with #js_AddMicroservice. + * The Microservice object. Initialize with #nats_AddMicroservice. */ -typedef struct __microservice jsMicroservice; +typedef struct __microservice natsMicroservice; -typedef struct __microserviceRequest jsMicroserviceRequest; +typedef struct __microserviceRequest natsMicroserviceRequest; /** - * The Microservice configuration object. Initialize with #jsMicroserviceConfig_Init. + * The Microservice configuration object. Initialize with #natsMicroserviceConfig_Init. */ -typedef struct jsMicroserviceConfig +typedef struct natsMicroserviceConfig { const char *name; const char *version; const char *description; -} jsMicroserviceConfig; +} natsMicroserviceConfig; /** * The KeyValue store object. @@ -6492,7 +6492,7 @@ natsMsg_GetTime(natsMsg *msg); /** \defgroup microserviceGroup Microservice support * - * A simple JetStream-based microservice implementation framework. + * A simple NATS-based microservice implementation framework. * * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without * necessarily bumping the major version of the library. @@ -6502,47 +6502,47 @@ natsMsg_GetTime(natsMsg *msg); // /** \brief Initializes a Microservice configuration structure. // * -// * Use this before setting specific #jsMicroserviceConfig options and passing it -// * to #js_AddMicroservice. +// * Use this before setting specific #natsMicroserviceConfig options and passing it +// * to #nats_AddMicroservice. // * -// * @see js_AddMicroservice +// * @see nats_AddMicroservice // * -// * @param cfg the pointer to the stack variable #jsMicroserviceConfig to +// * @param cfg the pointer to the stack variable #natsMicroserviceConfig to // * initialize. // */ // NATS_EXTERN natsStatus -// jsMicroserviceConfig_Init(jsMicroserviceConfig *cfg); +// natsMicroserviceConfig_Init(natsMicroserviceConfig *cfg); /** \brief Adds a microservice with a given configuration. * * Adds a microservice with a given configuration. * - * \note The return #jsMicroservice object needs to be destroyed using - * #jsMicroservice_Destroy when no longer needed to free allocated memory. + * \note The return #natsMicroservice object needs to be destroyed using + * #natsMicroservice_Destroy when no longer needed to free allocated memory. * * @param new_microservice the location where to store the newly created - * #jsMicroservice object. - * @param js the pointer to the #jsCtx object. - * @param cfg the pointer to the #jsMicroserviceConfig configuration information - * used to create the #jsMicroservice object. + * #natsMicroservice object. + * @param nc the pointer to the #natsCOnnection object on which the service will listen on. + * @param cfg the pointer to the #natsMicroserviceConfig configuration + * information used to create the #natsMicroservice object. */ NATS_EXTERN natsStatus -js_AddMicroservice(jsMicroservice **new_microservice, jsCtx *js, jsMicroserviceConfig *cfg, jsErrCode *errCode); +nats_AddMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg); /** \brief Waits for the microservice to stop. */ NATS_EXTERN natsStatus -js_RunMicroservice(jsMicroservice *m, jsErrCode *errCode); +natsMicroservice_Run(natsMicroservice *m); /** \brief Stops a running microservice. */ NATS_EXTERN natsStatus -js_StopMicroservice(jsMicroservice *m, jsErrCode *errCode); +natsMicroservice_Stop(natsMicroservice *m); /** \brief Checks if a microservice is stopped. */ NATS_EXTERN bool -js_IsMicroserviceStopped(jsMicroservice *m); +natsMicroservice_IsStopped(natsMicroservice *m); /** \brief Destroys a microservice object. * @@ -6550,7 +6550,7 @@ js_IsMicroserviceStopped(jsMicroservice *m); * first, this function does not check if it is. */ NATS_EXTERN void -jsMicroservice_Destroy(jsMicroservice *m); +natsMicroservice_Destroy(natsMicroservice *m); /** @} */ // end of microserviceGroup diff --git a/src/natsp.h b/src/natsp.h index d5a229388..3261db02a 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -450,16 +450,10 @@ struct __microserviceIdentity struct __microservice { natsMutex *mu; - jsCtx *js; + natsConnection *nc; int refs; struct __microserviceIdentity identity; bool stopped; - // verbSubs map[string]*nats.Subscription - // started time.Time - // nc *nats.Conn - // natsHandlers handlers - - // asyncDispatcher asyncCallbacksHandler }; struct __microserviceRequest { From 3d4cf0da65cef667071daff7e8797dd96e351f53 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sun, 5 Mar 2023 06:17:23 -0800 Subject: [PATCH 04/85] Renamed the example microservice --- examples/microservice.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/microservice.c b/examples/microservice.c index f4e0fadb1..180761571 100644 --- a/examples/microservice.c +++ b/examples/microservice.c @@ -51,8 +51,8 @@ int main(int argc, char **argv) natsStatus s; bool delStream = false; natsMicroserviceConfig cfg = { - .description = "Example JetStream microservice", - .name = "example", + .description = "NATS microservice example in C", + .name = "c-example-microservice", .version = "1.0.0", }; From 624b542dc65ee702af0d498a44f42355fc9203ad Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 7 Mar 2023 09:39:23 -0800 Subject: [PATCH 05/85] First implementation of the default endpoint --- examples/microservice.c | 36 ++--- src/micro.c | 277 +++++++----------------------------- src/micro.h | 5 - src/micro_endpoint.c | 304 ++++++++++++++++++++++++++++++++++++++++ src/micro_monitoring.c | 225 +++++++++++++++++++++++++++++ src/microp.h | 38 +++++ src/nats.h | 231 +++++++++++++++++++++--------- src/natsp.h | 19 ++- 8 files changed, 817 insertions(+), 318 deletions(-) create mode 100644 src/micro_endpoint.c create mode 100644 src/micro_monitoring.c create mode 100644 src/microp.h diff --git a/examples/microservice.c b/examples/microservice.c index 180761571..9be7c1f03 100644 --- a/examples/microservice.c +++ b/examples/microservice.c @@ -11,28 +11,22 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include "examples.h" static void -onMsg(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +onMsg(natsMicroservice *m, natsMicroserviceRequest *req, void *closure) { - if (print) - printf("Received msg: %s - %.*s\n", - natsMsg_GetSubject(msg), - natsMsg_GetDataLength(msg), - natsMsg_GetData(msg)); - - if (start == 0) - start = nats_Now(); + char buf[1024]; + snprintf(buf, sizeof(buf), "c-example-microservice: OK: %.*s", + natsMicroserviceRequest_GetDataLength(req), + natsMicroserviceRequest_GetData(req)); - // We should be using a mutex to protect those variables since - // they are used from the subscription's delivery and the main - // threads. For demo purposes, this is fine. - if (++count == total) - elapsed = nats_Now() - start; + if (print) + printf("%s\n", buf); - // Since this is auto-ack callback, we don't need to ack here. - natsMsg_Destroy(msg); + natsMicroservice_Respond(m, req, buf, strlen(buf)); } static void @@ -49,11 +43,19 @@ int main(int argc, char **argv) natsOptions *opts = NULL; natsMicroservice *m = NULL; natsStatus s; - bool delStream = false; + int fakeClosure = 0; + natsMicroserviceEndpointConfig default_endpoint_cfg = { + .subject = "c-test", + .handler = onMsg, + .closure = &fakeClosure, + .schema = NULL, + }; + natsMicroserviceConfig cfg = { .description = "NATS microservice example in C", .name = "c-example-microservice", .version = "1.0.0", + .endpoint = &default_endpoint_cfg, }; opts = parseArgs(argc, argv, ""); diff --git a/src/micro.c b/src/micro.c index 76318d3ae..c8039aacc 100644 --- a/src/micro.c +++ b/src/micro.c @@ -11,39 +11,22 @@ // See the License for the specific language governing permissions and // limitations under the License. +// TODO review includes #include #include -#include "natsp.h" -#include "micro.h" +#include "microp.h" #include "mem.h" #include "conn.h" static natsStatus _createMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg); -static void _freeMicroservice(natsMicroservice *m); -static void _retainMicroservice(natsMicroservice *m); -static void _releaseMicroservice(natsMicroservice *m); - static void -_handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); - -static natsStatus -_newControlSubject(char **newSubject, natsMicroserviceVerb verb, const char *name, const char *id); - -static natsStatus -_addInternalHandler(natsMicroservice *m, natsMicroserviceVerb verb, const char *kind, const char *id, - const char *name, natsMsgHandler handler); -static natsStatus -_addVerbHandlers(natsMicroservice *m, natsMicroserviceVerb verb, natsMsgHandler handler); - -static natsStatus -_respond(natsMicroservice *m, natsMicroserviceRequest *r, const char *data, int len); - -static natsStatus _marshalPing(natsBuffer **new_buf, natsMicroservice *m); - -static natsStatus _newDottedSubject(char **new_subject, int count, ...); -static bool _isEmpty(const char *s); +_freeMicroservice(natsMicroservice *m); +static void +_retainMicroservice(natsMicroservice *m); +static void +_releaseMicroservice(natsMicroservice *m); ////////////////////////////////////////////////////////////////////////////// // microservice management APIs @@ -69,6 +52,7 @@ void natsMicroservice_Destroy(natsMicroservice *m) _releaseMicroservice(m); } +// TODO update from Go natsStatus natsMicroservice_Stop(natsMicroservice *m) { @@ -114,27 +98,16 @@ natsMicroservice_Run(natsMicroservice *m) return NATS_OK; } -// Message handlers. - -static void -_handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +natsStatus +natsMicroservice_Respond(natsMicroservice *m, natsMicroserviceRequest *r, const char *data, int len) { natsStatus s = NATS_OK; - natsMicroservice *m = (natsMicroservice *)closure; - natsMicroserviceRequest req = { - .msg = msg, - }; - natsBuffer *buf = NULL; - s = _marshalPing(&buf, m); - if (s == NATS_OK) - { - s = _respond(m, &req, natsBuf_Data(buf), natsBuf_Len(buf)); - } - if (buf != NULL) - { - natsBuf_Destroy(buf); - } + if ((m == NULL) || (m->mu == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + + s = natsConnection_Publish(m->nc, natsMsg_GetReply(r->msg), data, len); + return NATS_UPDATE_ERR_STACK(s); } // Implementation functions. @@ -145,34 +118,48 @@ _createMicroservice(natsMicroservice **new_microservice, natsConnection *nc, nat natsStatus s = NATS_OK; natsMicroservice *m = NULL; natsMutex *mu = NULL; + char tmpNUID[NUID_BUFFER_LEN + 1]; + // Make a microservice object, with a reference to a natsConnection. m = (natsMicroservice *)NATS_CALLOC(1, sizeof(natsMicroservice)); if (m == NULL) return nats_setDefaultError(NATS_NO_MEMORY); - natsConn_retain(nc); m->nc = nc; + // Need a mutex for concurrent operations. s = natsMutex_Create(&mu); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - m->mu = mu; + if (s == NATS_OK) + { + m->mu = mu; + } - char tmpNUID[NUID_BUFFER_LEN + 1]; - s = natsNUID_Next(tmpNUID, NUID_BUFFER_LEN + 1); - IF_OK_DUP_STRING(s, m->identity.id, tmpNUID); + // Generate a unique ID for this microservice. + IFOK(s, natsNUID_Next(tmpNUID, NUID_BUFFER_LEN + 1)); + IF_OK_DUP_STRING(s, m->identity.id, tmpNUID); + + // Copy the config data. if (s == NATS_OK) { m->identity.name = cfg->name; m->identity.version = cfg->version; + m->cfg = *cfg; } - if (s == NATS_OK) + + // Set up the default endpoint. + if (s == NATS_OK && cfg->endpoint != NULL) { - s = _addVerbHandlers(m, natsMicroserviceVerbPing, _handleMicroservicePing); + s = natsMicroservice_AddEndpoint(m, natsMicroserviceDefaultEndpointName, cfg->endpoint); } + + // Set up monitoring (PING, STATS, etc.) responders. + IFOK(s, _initMicroserviceMonitoring(m)); + if (s == NATS_OK) - { *new_microservice = m; + else + { + _freeMicroservice(m); } return NATS_UPDATE_ERR_STACK(s); } @@ -180,9 +167,21 @@ _createMicroservice(natsMicroservice **new_microservice, natsConnection *nc, nat static void _freeMicroservice(natsMicroservice *m) { + int i; + natsStatus s; + if (m == NULL) return; + for (i = 0; i < m->num_endpoints; i++) + { + s = _stopMicroserviceEndpoint(m->endpoints[i]); + + if(s == NATS_OK) + _freeMicroserviceEndpoint(m->endpoints[i]); + } + NATS_FREE(m->endpoints); + natsConn_release(m->nc); natsMutex_Destroy(m->mu); NATS_FREE(m->identity.id); @@ -213,177 +212,3 @@ _releaseMicroservice(natsMicroservice *m) _freeMicroservice(m); } -static bool -_isEmpty(const char *s) -{ - return (s == NULL || *s == '\0'); -} - -static natsStatus -_newDottedSubject(char **new_subject, int count, ...) -{ - va_list args1, args2; - int i, len, n; - char *result, *p; - - va_start(args1, count); - va_copy(args2, args1); - - len = 0; - for (i = 0; i < count; i++) - { - if (i > 0) - { - len++; /* for the dot */ - } - len += strlen(va_arg(args1, char *)); - } - va_end(args1); - - result = NATS_MALLOC(len + 1); - if (result == NULL) - { - va_end(args2); - return NATS_NO_MEMORY; - } - - len = 0; - for (i = 0; i < count; i++) - { - if (i > 0) - { - result[len++] = '.'; - } - p = va_arg(args2, char *); - n = strlen(p); - memcpy(result + len, p, n); - len += n; - } - va_end(args2); - - *new_subject = result; - return NATS_OK; -} - -static natsStatus -_newControlSubject(char **newSubject, natsMicroserviceVerb verb, const char *name, const char *id) -{ - natsStatus s = NATS_OK; - const char *verbStr; - - s = natsMicroserviceVerb_String(&verbStr, verb); - if (s != NATS_OK) - { - return NATS_UPDATE_ERR_STACK(s); - } - - if (_isEmpty(name) && !_isEmpty(id)) - { - NATS_UPDATE_ERR_TXT("service name is required when id is provided: %s", id); - return NATS_UPDATE_ERR_STACK(NATS_INVALID_ARG); - } - - else if (_isEmpty(name) && _isEmpty(id)) - return _newDottedSubject(newSubject, 2, natsMicroserviceAPIPrefix, verbStr); - else if (_isEmpty(id)) - return _newDottedSubject(newSubject, 3, natsMicroserviceAPIPrefix, verbStr, name); - else - return _newDottedSubject(newSubject, 4, natsMicroserviceAPIPrefix, verbStr, name, id); -} - -static natsStatus -_addInternalHandler(natsMicroservice *m, natsMicroserviceVerb verb, const char *kind, - const char *id, const char *name, natsMsgHandler handler) -{ - - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; - char *subj = NULL; - - s = _newControlSubject(&subj, verb, kind, id); - if (s == NATS_OK) - { - s = natsConnection_Subscribe(&sub, m->nc, subj, handler, m); - } - if (s == NATS_OK) - { - return NATS_OK; - } - - natsMicroservice_Stop(m); - return NATS_UPDATE_ERR_STACK(s); -} - -// __verbHandlers generates control handlers for a specific verb. Each request -// generates 3 subscriptions, one for the general verb affecting all services -// written with the framework, one that handles all services of a particular -// kind, and finally a specific service instance. -static natsStatus -_addVerbHandlers(natsMicroservice *m, natsMicroserviceVerb verb, natsMsgHandler handler) -{ - natsStatus s = NATS_OK; - char name[256]; - const char *verbStr; - - s = natsMicroserviceVerb_String(&verbStr, verb); - if (s == NATS_OK) - { - snprintf(name, sizeof(name), "%s-all", verbStr); - s = _addInternalHandler(m, verb, "", "", name, handler); - } - if (s == NATS_OK) - { - snprintf(name, sizeof(name), "%s-kind", verbStr); - s = _addInternalHandler(m, verb, m->identity.name, "", name, handler); - } - if (s == NATS_OK) - { - s = _addInternalHandler(m, verb, m->identity.name, m->identity.id, verbStr, handler); - } - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_respond(natsMicroservice *m, natsMicroserviceRequest *r, const char *data, int len) -{ - natsStatus s = NATS_OK; - - if ((m == NULL) || (m->mu == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = natsConnection_Publish(m->nc, natsMsg_GetReply(r->msg), data, len); - return NATS_UPDATE_ERR_STACK(s); -} - -static natsStatus -_marshalPing(natsBuffer **new_buf, natsMicroservice *m) -{ - natsBuffer *buf = NULL; - natsStatus s; - - s = natsBuf_Create(&buf, 1024); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - -// name and sep must be a string literal -#define IFOK_attr(_name, _value, _sep) \ - IFOK(s, natsBuf_Append(buf, "\"" _name "\":\"", -1)); \ - IFOK(s, natsBuf_Append(buf, _value, -1)); \ - IFOK(s, natsBuf_Append(buf, "\"" _sep, -1)); - - s = natsBuf_Append(buf, "{", -1); - IFOK_attr("name", m->identity.name, ","); - IFOK_attr("version", m->identity.version, ","); - IFOK_attr("id", m->identity.id, ","); - IFOK_attr("type", natsMicroservicePingResponseType, ""); - IFOK(s, natsBuf_AppendByte(buf, '}')); - - if (s == NATS_OK) - { - *new_buf = buf; - return NATS_OK; - } - - natsBuf_Destroy(buf); - return NATS_UPDATE_ERR_STACK(s); -} diff --git a/src/micro.h b/src/micro.h index b9f66ee35..4807325f6 100644 --- a/src/micro.h +++ b/src/micro.h @@ -27,11 +27,6 @@ enum natsMicroserviceVerb typedef enum natsMicroserviceVerb natsMicroserviceVerb; -#define natsMicroserviceInfoResponseType "io.nats.micro.v1.info_response" -#define natsMicroservicePingResponseType "io.nats.micro.v1.ping_response" -#define natsMicroserviceStatsResponseType "io.nats.micro.v1.stats_response" -#define natsMicroserviceSchemaResponseType "io.nats.micro.v1.schema_response" - static natsStatus natsMicroserviceVerb_String(const char **new_subject, natsMicroserviceVerb verb) { diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c new file mode 100644 index 000000000..6c0f9e720 --- /dev/null +++ b/src/micro_endpoint.c @@ -0,0 +1,304 @@ +// Copyright 2021-2023 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// TODO review includes +#include +#include + +#include "microp.h" +#include "mem.h" +#include "conn.h" + +static bool +_isValidName(const char *name); +static bool +_isValidSubject(const char *subject); + +static natsStatus +_newEndpoint(natsMicroserviceEndpoint **__new_endpoint, natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg); +static natsStatus +_startEndpoint(natsMicroserviceEndpoint *endpoint); + +static natsStatus +_newRequest(natsMicroserviceRequest **new_request, natsMsg *msg); +static void +_freeRequest(natsMicroserviceRequest *req); +static void +_handleEndpointRequest(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); + +natsStatus +natsMicroservice_AddEndpoint(natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg) +{ + natsStatus s = NATS_OK; + int i; + int add = -1; + natsMicroserviceEndpoint **new_endpoints = m->endpoints; + int new_num_endpoints = m->num_endpoints; + natsMicroserviceEndpoint *new_endpoint = NULL; + + // TODO return more comprehensive errors + if ((m == NULL) || (m->mu == NULL) || (cfg == NULL) || (cfg->subject == NULL) || + (!_isValidName(name)) || (!_isValidSubject(cfg->subject))) + { + return nats_setDefaultError(NATS_INVALID_ARG); + } + + // This is a rare call, usually happens at the initialization of the + // microservice, so it's ok to lock for the duration of the function, may + // not be necessary at all but will not hurt. + natsMutex_Lock(m->mu); + + for (i = 0; i < m->num_endpoints; i++) + { + if (strcmp(m->endpoints[i]->config.subject, cfg->subject) == 0) + { + add = i; + break; + } + } + if (add == -1) + { + new_endpoints = (natsMicroserviceEndpoint **) + NATS_REALLOC(m->endpoints, sizeof(natsMicroserviceEndpoint *) * (m->num_endpoints + 1)); + s = (new_endpoints != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); + if (s == NATS_OK) + { + add = m->num_endpoints; + new_num_endpoints = m->num_endpoints + 1; + } + } + + IFOK(s, _newEndpoint(&new_endpoint, m, name, cfg)); + IFOK(s, _startEndpoint(new_endpoint)); + + if (s == NATS_OK) + { + new_endpoints[add] = new_endpoint; + m->endpoints = new_endpoints; + m->num_endpoints = new_num_endpoints; + } + else + { + _freeMicroserviceEndpoint(new_endpoint); + } + + natsMutex_Unlock(m->mu); + return NATS_UPDATE_ERR_STACK(s); +} + +static bool +_isValidName(const char *name) +{ + int i; + int len = (int)strlen(name); + + if (len == 0) + return false; + + for (i = 0; i < len; i++) + { + if (!isalnum(name[i]) && (name[i] != '_') && (name[i] != '-')) + return false; + } + return true; +} + +static bool +_isValidSubject(const char *subject) +{ + int i; + int len = (int)strlen(subject); + + if (len == 0) + return false; + + for (i = 0; i < len - 1; i++) + { + if ((subject[i] == ' ') || (subject[i] == '>')) + return false; + } + + if ((subject[i] == ' ')) + return false; + + return true; +} + +static natsStatus +_newEndpoint(natsMicroserviceEndpoint **__new_endpoint, natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg) +{ + natsStatus s = NATS_OK; + natsMicroserviceEndpoint *ep = NULL; + char *dup_name = NULL; + char *dup_subject = NULL; + + ep = (natsMicroserviceEndpoint *)NATS_CALLOC(1, sizeof(natsMicroserviceEndpoint)); + s = (ep != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); + if (s == NATS_OK) + { + dup_name = NATS_STRDUP(name); + s = (dup_name != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); + } + if (s == NATS_OK) + { + dup_subject = NATS_STRDUP(cfg->subject); + s = (dup_subject != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); + } + if (s == NATS_OK) + { + ep->m = m; + ep->stats.name = dup_name; + ep->stats.subject = dup_subject; + ep->config = *cfg; + ep->config.subject = dup_subject; + } + if (s == NATS_OK) + { + *__new_endpoint = ep; + return NATS_OK; + } + + NATS_FREE(dup_name); + NATS_FREE(dup_subject); + return NATS_UPDATE_ERR_STACK(s); +} + +static natsStatus +_startEndpoint(natsMicroserviceEndpoint *endpoint) +{ + if (endpoint->config.handler == NULL) + // nothing to do + return NATS_OK; + + return natsConnection_QueueSubscribe(&endpoint->sub, endpoint->m->nc, endpoint->config.subject, + natsMicroserviceQueueGroup, _handleEndpointRequest, endpoint); +} + +// TODO COPY FROM GO +natsStatus +_stopMicroserviceEndpoint(natsMicroserviceEndpoint *endpoint) +{ + natsStatus s = NATS_OK; + + // TODO review locking for modifying endpoints, may not be necessary or + // endpoint may need its own lock (stats). + + // This is a rare call, usually happens at the initialization of the + // microservice, so it's ok to lock for the duration of the function, may + // not be necessary at all but will not hurt. + natsMutex_Lock(endpoint->m->mu); + + if (endpoint->sub != NULL) + { + s = natsSubscription_Drain(endpoint->sub); + } + if (s == NATS_OK) + { + endpoint->sub = NULL; + endpoint->stopped = true; + endpoint->stats.stopped = true; + } + + natsMutex_Unlock(endpoint->m->mu); + return NATS_UPDATE_ERR_STACK(s); +} + +void _freeMicroserviceEndpoint(natsMicroserviceEndpoint *endpoint) +{ + if (endpoint == NULL) + return; + + // The struct fields are declared as const char * for the external API, but + // we know that the strings were duplicated and need to be freed. + // endpoint->config.name is the same as endpoint->stats.name, no need to + // free it. + NATS_FREE((char *)endpoint->stats.name); + NATS_FREE((char *)endpoint->stats.subject); + + NATS_FREE(endpoint); +} + +static void +_handleEndpointRequest(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +{ + natsStatus s = NATS_OK; + natsMicroserviceEndpoint *endpoint = (natsMicroserviceEndpoint *)closure; + // natsMicroserviceEndpointStats *stats = &endpoint->stats; + natsMicroserviceEndpointConfig *cfg = &endpoint->config; + natsMicroserviceRequest *req = NULL; + natsMicroserviceRequestHandler handler = cfg->handler; + void *handlerClosure = cfg->closure; + // const char *errorString = ""; + + if (handler == NULL) + { + // This is a bug, we should not have received a message on this + // subscription. + natsMsg_Destroy(msg); + return; + } + + s = _newRequest(&req, msg); + if (s == NATS_OK) + { + handler(endpoint->m, req, handlerClosure); + // errorString = req->err; + } + else + { + // errorString = natsStatus_GetText(s); + } + + // Update stats + // natsMutex_Lock(endpoint->mu); + // stats->numRequests++; + // natsMutex_Unlock(endpoint->mu); + + _freeRequest(req); + natsMsg_Destroy(msg); +} + +static natsStatus +_newRequest(natsMicroserviceRequest **new_request, natsMsg *msg) +{ + natsStatus s = NATS_OK; + natsMicroserviceRequest *req = NULL; + + req = (natsMicroserviceRequest *)NATS_CALLOC(1, sizeof(natsMicroserviceRequest)); + s = (req != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); + if (s == NATS_OK) + { + req->msg = msg; + *new_request = req; + return NATS_OK; + } + + _freeRequest(req); + return NATS_UPDATE_ERR_STACK(s); +} + +static void +_freeRequest(natsMicroserviceRequest *req) +{ + if (req == NULL) + return; + + NATS_FREE(req->err); + NATS_FREE(req); +} + +natsMsg* +natsMicroserviceRequest_GetMsg(natsMicroserviceRequest *req) +{ + return req != NULL ? req->msg : NULL; +} diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c new file mode 100644 index 000000000..395f9eae8 --- /dev/null +++ b/src/micro_monitoring.c @@ -0,0 +1,225 @@ +// Copyright 2021-2023 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "natsp.h" +#include "mem.h" +#include "microp.h" + +static void +_handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); + +static natsStatus _marshalPing(natsBuffer **new_buf, natsMicroservice *m); + +static natsStatus +_addInternalHandler(natsMicroservice *m, natsMicroserviceVerb verb, const char *kind, const char *id, + const char *name, natsMsgHandler handler); +static natsStatus +_addVerbHandlers(natsMicroservice *m, natsMicroserviceVerb verb, natsMsgHandler handler); + +static natsStatus _newControlSubject(char **newSubject, natsMicroserviceVerb verb, const char *name, const char *id); +static natsStatus _newDottedSubject(char **new_subject, int count, ...); +static bool _isEmpty(const char *s); + +natsStatus _initMicroserviceMonitoring(natsMicroservice *m) { + natsStatus s = NATS_OK; + + IFOK(s, _addVerbHandlers(m, natsMicroserviceVerbPing, _handleMicroservicePing)); + + return NATS_UPDATE_ERR_STACK(s); +} + +static void +_handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +{ + natsStatus s = NATS_OK; + natsMicroservice *m = (natsMicroservice *)closure; + natsMicroserviceRequest req = { + .msg = msg, + }; + natsBuffer *buf = NULL; + + s = _marshalPing(&buf, m); + if (s == NATS_OK) + { + s = natsMicroservice_Respond(m, &req, natsBuf_Data(buf), natsBuf_Len(buf)); + } + if (buf != NULL) + { + natsBuf_Destroy(buf); + } +} + +static bool +_isEmpty(const char *s) +{ + return (s == NULL || *s == '\0'); +} + +static natsStatus +_newDottedSubject(char **new_subject, int count, ...) +{ + va_list args1, args2; + int i, len, n; + char *result, *p; + + va_start(args1, count); + va_copy(args2, args1); + + len = 0; + for (i = 0; i < count; i++) + { + if (i > 0) + { + len++; /* for the dot */ + } + len += strlen(va_arg(args1, char *)); + } + va_end(args1); + + result = NATS_MALLOC(len + 1); + if (result == NULL) + { + va_end(args2); + return NATS_NO_MEMORY; + } + + len = 0; + for (i = 0; i < count; i++) + { + if (i > 0) + { + result[len++] = '.'; + } + p = va_arg(args2, char *); + n = strlen(p); + memcpy(result + len, p, n); + len += n; + } + va_end(args2); + + *new_subject = result; + return NATS_OK; +} + +static natsStatus +_newControlSubject(char **newSubject, natsMicroserviceVerb verb, const char *name, const char *id) +{ + natsStatus s = NATS_OK; + const char *verbStr; + + s = natsMicroserviceVerb_String(&verbStr, verb); + if (s != NATS_OK) + { + return NATS_UPDATE_ERR_STACK(s); + } + + if (_isEmpty(name) && !_isEmpty(id)) + { + NATS_UPDATE_ERR_TXT("service name is required when id is provided: %s", id); + return NATS_UPDATE_ERR_STACK(NATS_INVALID_ARG); + } + + else if (_isEmpty(name) && _isEmpty(id)) + return _newDottedSubject(newSubject, 2, natsMicroserviceAPIPrefix, verbStr); + else if (_isEmpty(id)) + return _newDottedSubject(newSubject, 3, natsMicroserviceAPIPrefix, verbStr, name); + else + return _newDottedSubject(newSubject, 4, natsMicroserviceAPIPrefix, verbStr, name, id); +} + +static natsStatus +_addInternalHandler(natsMicroservice *m, natsMicroserviceVerb verb, const char *kind, + const char *id, const char *name, natsMsgHandler handler) +{ + + natsStatus s = NATS_OK; + natsSubscription *sub = NULL; + char *subj = NULL; + + s = _newControlSubject(&subj, verb, kind, id); + if (s == NATS_OK) + { + s = natsConnection_Subscribe(&sub, m->nc, subj, handler, m); + } + if (s == NATS_OK) + { + return NATS_OK; + } + + natsMicroservice_Stop(m); + return NATS_UPDATE_ERR_STACK(s); +} + +// __verbHandlers generates control handlers for a specific verb. Each request +// generates 3 subscriptions, one for the general verb affecting all services +// written with the framework, one that handles all services of a particular +// kind, and finally a specific service instance. +static natsStatus +_addVerbHandlers(natsMicroservice *m, natsMicroserviceVerb verb, natsMsgHandler handler) +{ + natsStatus s = NATS_OK; + char name[256]; + const char *verbStr; + + s = natsMicroserviceVerb_String(&verbStr, verb); + if (s == NATS_OK) + { + snprintf(name, sizeof(name), "%s-all", verbStr); + s = _addInternalHandler(m, verb, "", "", name, handler); + } + if (s == NATS_OK) + { + snprintf(name, sizeof(name), "%s-kind", verbStr); + s = _addInternalHandler(m, verb, m->identity.name, "", name, handler); + } + if (s == NATS_OK) + { + s = _addInternalHandler(m, verb, m->identity.name, m->identity.id, verbStr, handler); + } + return NATS_UPDATE_ERR_STACK(s); +} + +static natsStatus +_marshalPing(natsBuffer **new_buf, natsMicroservice *m) +{ + natsBuffer *buf = NULL; + natsStatus s; + + s = natsBuf_Create(&buf, 1024); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + +// name and sep must be a string literal +#define IFOK_attr(_name, _value, _sep) \ + IFOK(s, natsBuf_Append(buf, "\"" _name "\":\"", -1)); \ + IFOK(s, natsBuf_Append(buf, _value, -1)); \ + IFOK(s, natsBuf_Append(buf, "\"" _sep, -1)); + + s = natsBuf_Append(buf, "{", -1); + IFOK_attr("name", m->identity.name, ","); + IFOK_attr("version", m->identity.version, ","); + IFOK_attr("id", m->identity.id, ","); + IFOK_attr("type", natsMicroservicePingResponseType, ""); + IFOK(s, natsBuf_AppendByte(buf, '}')); + + if (s == NATS_OK) + { + *new_buf = buf; + return NATS_OK; + } + + natsBuf_Destroy(buf); + return NATS_UPDATE_ERR_STACK(s); +} diff --git a/src/microp.h b/src/microp.h new file mode 100644 index 000000000..b6a02868f --- /dev/null +++ b/src/microp.h @@ -0,0 +1,38 @@ +// Copyright 2015-2018 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MICROP_H_ +#define MICROP_H_ + +#include "natsp.h" +#include "micro.h" + +#define natsMicroserviceInfoResponseType "io.nats.micro.v1.info_response" +#define natsMicroservicePingResponseType "io.nats.micro.v1.ping_response" +#define natsMicroserviceStatsResponseType "io.nats.micro.v1.stats_response" +#define natsMicroserviceSchemaResponseType "io.nats.micro.v1.schema_response" + +#define natsMicroserviceQueueGroup "q" + +#define natsMicroserviceDefaultEndpointName "default" + +extern natsStatus +_initMicroserviceMonitoring(natsMicroservice *m); + +extern natsStatus +_stopMicroserviceEndpoint(natsMicroserviceEndpoint *endpoint); +extern void +_freeMicroserviceEndpoint(natsMicroserviceEndpoint *endpoint); + + +#endif /* MICROP_H_ */ diff --git a/src/nats.h b/src/nats.h index e122b58f7..b2ec5290b 100644 --- a/src/nats.h +++ b/src/nats.h @@ -1160,11 +1160,67 @@ typedef struct jsOptions /** * The Microservice object. Initialize with #nats_AddMicroservice. + * TODO document the interface. */ -typedef struct __microservice natsMicroservice; +typedef struct __microservice natsMicroservice; -typedef struct __microserviceRequest natsMicroserviceRequest; +/** + * The Microservice request object. + * TODO document the interface. + */ +typedef struct __microserviceRequest natsMicroserviceRequest; + +/** + * The Microservice endpoint object. + * TODO document the interface. + */ +typedef struct __microserviceEndpoint natsMicroserviceEndpoint; + +/** \brief Callback used to deliver messages to a microservice. + * + * This is the callback that one provides when creating a microservice endpoint. + * The library will invoke this callback for each message arriving to the + * specified subject. + * + * @see natsMicroservice_AddEndpoint() + * @see natsMicroserviceEndpoint_AddEndpoint() + */ +typedef void (*natsMicroserviceRequestHandler)(natsMicroservice *m, natsMicroserviceRequest *req, void *closure); + +/** + * The Microservice schema object. + */ +typedef struct natsMicroserviceSchema +{ + const char *request; + const char *response; +} natsMicroserviceSchema; + +/** + * The Microservice endpoint configuration object. + */ +typedef struct natsMicroserviceEndpointConfig +{ + const char *subject; + natsMicroserviceRequestHandler handler; + void *closure; + natsMicroserviceSchema *schema; +} natsMicroserviceEndpointConfig; +/** + * The Microservice endpoint stats struct. + */ +typedef struct natsMicroserviceEndpointStats +{ + char *name; + char *subject; + int num_requests; + int num_errors; + char *last_error; + int processing_time_ms; + int average_processing_time_ms; + bool stopped; +} natsMicroserviceEndpointStats; /** * The Microservice configuration object. Initialize with #natsMicroserviceConfig_Init. @@ -1174,6 +1230,7 @@ typedef struct natsMicroserviceConfig const char *name; const char *version; const char *description; + natsMicroserviceEndpointConfig *endpoint; } natsMicroserviceConfig; /** @@ -6490,70 +6547,6 @@ natsMsg_GetTime(natsMsg *msg); * @{ */ -/** \defgroup microserviceGroup Microservice support - * - * A simple NATS-based microservice implementation framework. - * - * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without - * necessarily bumping the major version of the library. - * - * @{ - */ - -// /** \brief Initializes a Microservice configuration structure. -// * -// * Use this before setting specific #natsMicroserviceConfig options and passing it -// * to #nats_AddMicroservice. -// * -// * @see nats_AddMicroservice -// * -// * @param cfg the pointer to the stack variable #natsMicroserviceConfig to -// * initialize. -// */ -// NATS_EXTERN natsStatus -// natsMicroserviceConfig_Init(natsMicroserviceConfig *cfg); - -/** \brief Adds a microservice with a given configuration. - * - * Adds a microservice with a given configuration. - * - * \note The return #natsMicroservice object needs to be destroyed using - * #natsMicroservice_Destroy when no longer needed to free allocated memory. - * - * @param new_microservice the location where to store the newly created - * #natsMicroservice object. - * @param nc the pointer to the #natsCOnnection object on which the service will listen on. - * @param cfg the pointer to the #natsMicroserviceConfig configuration - * information used to create the #natsMicroservice object. - */ -NATS_EXTERN natsStatus -nats_AddMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg); - -/** \brief Waits for the microservice to stop. - */ -NATS_EXTERN natsStatus -natsMicroservice_Run(natsMicroservice *m); - -/** \brief Stops a running microservice. - */ -NATS_EXTERN natsStatus -natsMicroservice_Stop(natsMicroservice *m); - -/** \brief Checks if a microservice is stopped. - */ -NATS_EXTERN bool -natsMicroservice_IsStopped(natsMicroservice *m); - -/** \brief Destroys a microservice object. - * - * Destroys a microservice object; frees all memory. The service must be stopped - * first, this function does not check if it is. - */ -NATS_EXTERN void -natsMicroservice_Destroy(natsMicroservice *m); - -/** @} */ // end of microserviceGroup - /** \defgroup kvGroupMgt KeyValue store management * * These functions allow to create, get or delete a KeyValue store. @@ -7184,6 +7177,112 @@ kvStatus_Destroy(kvStatus *sts); /** @} */ // end of kvGroup +/** \defgroup microserviceGroup Microservice support + * + * A simple NATS-based microservice implementation framework. + * + * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without + * necessarily bumping the major version of the library. + * + * @{ + */ + +// /** \brief Initializes a Microservice configuration structure. +// * +// * Use this before setting specific #natsMicroserviceConfig options and passing it +// * to #nats_AddMicroservice. +// * +// * @see nats_AddMicroservice +// * +// * @param cfg the pointer to the stack variable #natsMicroserviceConfig to +// * initialize. +// */ +// NATS_EXTERN natsStatus +// natsMicroserviceConfig_Init(natsMicroserviceConfig *cfg); + +/** \brief Adds a microservice with a given configuration. + * + * Adds a microservice with a given configuration. + * + * \note The return #natsMicroservice object needs to be destroyed using + * #natsMicroservice_Destroy when no longer needed to free allocated memory. + * + * @param new_microservice the location where to store the newly created + * #natsMicroservice object. + * @param nc the pointer to the #natsCOnnection object on which the service will listen on. + * @param cfg the pointer to the #natsMicroserviceConfig configuration + * information used to create the #natsMicroservice object. + */ +NATS_EXTERN natsStatus +nats_AddMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg); + +/** \brief Waits for the microservice to stop. + */ +NATS_EXTERN natsStatus +natsMicroservice_Run(natsMicroservice *m); + +/** \brief Stops a running microservice. + */ +NATS_EXTERN natsStatus +natsMicroservice_Stop(natsMicroservice *m); + +/** \brief Checks if a microservice is stopped. + */ +NATS_EXTERN bool +natsMicroservice_IsStopped(natsMicroservice *m); + +/** \brief Destroys a microservice object. + * + * Destroys a microservice object; frees all memory. The service must be stopped + * first, this function does not check if it is. + */ +NATS_EXTERN void +natsMicroservice_Destroy(natsMicroservice *m); + +/** \brief Adds (and starts) a microservice endpoint. + */ +NATS_EXTERN natsStatus +natsMicroservice_AddEndpoint(natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg); + +/** \brief Adds (and starts) a microservice endpoint. + */ +NATS_EXTERN natsStatus +natsMicroservice_Respond(natsMicroservice *m, natsMicroserviceRequest *r, const char *data, int len); + +/** \brief Returns the original NATS message underlying the request. + */ +NATS_EXTERN natsMsg* +natsMicroserviceRequest_GetMsg(natsMicroserviceRequest *req); + +#define natsMicroserviceRequestHeader_Set(req, key, value) \ + natsMsgHeader_Set(natsMicroserviceRequest_GetMsg(req), (key), (value)) +#define natsMicroserviceRequestHeader_Add(req, key, value) \ + natsMsgHeader_Add(natsMicroserviceRequest_GetMsg(req), (key), (value)) +#define natsMicroserviceRequestHeader_Get(req, key, value) \ + natsMsgHeader_Get(natsMicroserviceRequest_GetMsg(req), (key), (value)) +#define natsMicroserviceRequestHeader_Values(req, key, values, count) \ + natsMsgHeader_Values(natsMicroserviceRequest_GetMsg(req), (key), (values), (count)) +#define natsMicroserviceRequestHeader_Keys(req, key, keys, count) \ + natsMsgHeader_Keys(natsMicroserviceRequest_GetMsg(req), (key), (keys), (count)) +#define natsMicroserviceRequestHeader_Delete(req, key) \ + natsMsgHeader_Delete(natsMicroserviceRequest_GetMsg(req), (key)) + +#define natsMicroserviceRequest_GetSubject(req) \ + natsMsg_GetSubject(natsMicroserviceRequest_GetMsg(req)) +#define natsMicroserviceRequest_GetReply(req) \ + natsMsg_GetReply(natsMicroserviceRequest_GetMsg(req)) +#define natsMicroserviceRequest_GetData(req) \ + natsMsg_GetData(natsMicroserviceRequest_GetMsg(req)) +#define natsMicroserviceRequest_GetDataLength(req) \ + natsMsg_GetDataLength(natsMicroserviceRequest_GetMsg(req)) +#define natsMicroserviceRequest_GetSequence(req) \ + natsMsg_GetSequence(natsMicroserviceRequest_GetMsg(req)) +#define natsMicroserviceRequest_GetTime(req) \ + natsMsg_GetTime(natsMicroserviceRequest_GetMsg(req)) + +/** @} */ // end of microserviceGroup + + /** @} */ // end of funcGroup /** \defgroup wildcardsGroup Wildcards diff --git a/src/natsp.h b/src/natsp.h index 3261db02a..acef7ecf7 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -432,7 +432,6 @@ typedef struct __jsSub } jsSub; - struct __microserviceConfig { const char *name; @@ -453,14 +452,26 @@ struct __microservice natsConnection *nc; int refs; struct __microserviceIdentity identity; + struct natsMicroserviceConfig cfg; bool stopped; + struct __microserviceEndpoint **endpoints; + int num_endpoints; }; -struct __microserviceRequest { - natsMsg *msg; - // respondError error +struct __microserviceRequest +{ + natsMsg *msg; + char *err; }; +struct __microserviceEndpoint +{ + natsMicroservice *m; + bool stopped; + natsMicroserviceEndpointConfig config; + natsMicroserviceEndpointStats stats; + natsSubscription *sub; +}; struct __kvStore { From 1961a5ff074178342191984cdf94e026538d02cc Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Fri, 10 Mar 2023 05:41:01 -0800 Subject: [PATCH 06/85] Sequence calculator service - a working example of multiple endpoints. --- examples/examples.h | 1 + examples/microservice.c | 576 +++++++++++++++++++++++++++++++++++--- src/mem.h | 8 + src/micro.c | 196 +++++++------ src/micro.h | 297 ++++++++++++++++++-- src/micro_args_parser.c | 369 ++++++++++++++++++++++++ src/micro_client.c | 67 +++++ src/micro_endpoint.c | 320 +++++++++++---------- src/micro_endpoint_list.c | 163 +++++++++++ src/micro_error.c | 130 +++++++++ src/micro_monitoring.c | 43 ++- src/micro_request.c | 131 +++++++++ src/microp.h | 124 +++++++- src/nats.h | 181 ------------ src/natsp.h | 43 +-- 15 files changed, 2107 insertions(+), 542 deletions(-) create mode 100644 src/micro_args_parser.c create mode 100644 src/micro_client.c create mode 100644 src/micro_endpoint_list.c create mode 100644 src/micro_error.c create mode 100644 src/micro_request.c diff --git a/examples/examples.h b/examples/examples.h index 83670b5f3..4a765830c 100644 --- a/examples/examples.h +++ b/examples/examples.h @@ -15,6 +15,7 @@ #define EXAMPLES_H_ #include +#include #include #include diff --git a/examples/microservice.c b/examples/microservice.c index 9be7c1f03..0617b6b14 100644 --- a/examples/microservice.c +++ b/examples/microservice.c @@ -11,82 +11,570 @@ // See the License for the specific language governing permissions and // limitations under the License. +#ifdef _WIN32 +#error "This example does not work on Windows" +#endif + #include +#include #include "examples.h" -static void -onMsg(natsMicroservice *m, natsMicroserviceRequest *req, void *closure) +static int fakeClosure = 0; + +// The "main" functions for the services, each is run as a pthread. +static void *run_arithmetics(void *closure); +static void *run_functions(void *closure); +static void *run_sequence(void *closure); + +// a generic service runner, used by the services' "main" functions. +static void *run_service(natsConnection *conn, natsMicroserviceConfig *svc, + natsMicroserviceEndpointConfig **endpoints, const char **ep_names, int len_endpoints); + +// Callers and handlers for operations (2 floating point args, and a single int arg). +static natsMicroserviceError * + call_arithmetics(long double *result, natsConnection *nc, const char *subject, long double a1, long double a2); +typedef natsMicroserviceError *(*arithmeticsOP)( + long double *result, natsConnection *conn, long double a1, long double a2); +static void handle_arithmetics_op(natsMicroserviceRequest *req, arithmeticsOP op); + +static natsMicroserviceError * + call_function(long double *result, natsConnection *nc, const char *subject, int n); +typedef natsMicroserviceError *(*functionOP)(long double *result, natsConnection *conn, int n); +static void handle_function_op(natsMicroserviceRequest *req, functionOP op); + +// Stop handler is the same for all services. +static void handle_stop(natsMicroservice *m, natsMicroserviceRequest *req); + +// Handler for "sequence", the main endpoint of the sequence service. +static void handle_sequence(natsMicroservice *m, natsMicroserviceRequest *req); + +// Math operations, wrapped as handlers. +static natsMicroserviceError *add(long double *result, natsConnection *nc, long double a1, long double a2); +static natsMicroserviceError *divide(long double *result, natsConnection *nc, long double a1, long double a2); +static natsMicroserviceError *multiply(long double *result, natsConnection *nc, long double a1, long double a2); + +static void handle_add(natsMicroservice *m, natsMicroserviceRequest *req) { handle_arithmetics_op(req, add); } +static void handle_divide(natsMicroservice *m, natsMicroserviceRequest *req) { handle_arithmetics_op(req, divide); } +static void handle_multiply(natsMicroservice *m, natsMicroserviceRequest *req) { handle_arithmetics_op(req, multiply); } + +static natsMicroserviceError *factorial(long double *result, natsConnection *nc, int n); +static natsMicroserviceError *fibonacci(long double *result, natsConnection *nc, int n); +static natsMicroserviceError *power2(long double *result, natsConnection *nc, int n); +static void handle_factorial(natsMicroservice *m, natsMicroserviceRequest *req) { handle_function_op(req, factorial); } +static void handle_fibonacci(natsMicroservice *m, natsMicroserviceRequest *req) { handle_function_op(req, fibonacci); } +static void handle_power2(natsMicroservice *m, natsMicroserviceRequest *req) { handle_function_op(req, power2); } + +static natsMicroserviceEndpointConfig stop_cfg = { + .subject = "stop", + .handler = handle_stop, + .closure = &fakeClosure, + .schema = NULL, +}; + +int main(int argc, char **argv) { - char buf[1024]; - snprintf(buf, sizeof(buf), "c-example-microservice: OK: %.*s", - natsMicroserviceRequest_GetDataLength(req), - natsMicroserviceRequest_GetData(req)); + natsStatus s = NATS_OK; + natsConnection *conn = NULL; + natsOptions *opts = NULL; + pthread_t arithmetics_th; + void *a_val = NULL; + pthread_t functions_th; + void *f_val = NULL; + pthread_t sequence_th; + void *s_val = NULL; + int errno; - if (print) - printf("%s\n", buf); + // Connect and start the services + opts = parseArgs(argc, argv, ""); + s = natsConnection_Connect(&conn, opts); + if (s == NATS_OK) + { + errno = pthread_create(&arithmetics_th, NULL, run_arithmetics, conn); + if (errno != 0) + { + printf("Error creating arithmetics thread: %d: %s\n", errno, strerror(errno)); + return 1; + } + + errno = pthread_create(&functions_th, NULL, run_functions, conn); + if (errno != 0) + { + printf("Error creating functions thread: %d: %s\n", errno, strerror(errno)); + return 1; + } + + errno = pthread_create(&sequence_th, NULL, run_sequence, conn); + if (errno != 0) + { + printf("Error creating sequence thread: %d: %s\n", errno, strerror(errno)); + return 1; + } + } + + // Wait for the services to stop and self-destruct. + if (s == NATS_OK) + { + pthread_join(arithmetics_th, &a_val); + s = (natsStatus)(uintptr_t)a_val; + } + if (s == NATS_OK) + { + pthread_join(functions_th, &f_val); + s = (natsStatus)(uintptr_t)f_val; + } + if (s == NATS_OK) + { + pthread_join(sequence_th, &s_val); + s = (natsStatus)(uintptr_t)s_val; + } - natsMicroservice_Respond(m, req, buf, strlen(buf)); + if (s == NATS_OK) + { + return 0; + } + else + { + printf("Error: %u - %s\n", s, natsStatus_GetText(s)); + nats_PrintLastErrorStack(stderr); + return 1; + } } -static void -asyncCb(natsConnection *nc, natsSubscription *sub, natsStatus err, void *closure) +static void *run_sequence(void *closure) { - printf("Async error: %u - %s\n", err, natsStatus_GetText(err)); + natsConnection *conn = (natsConnection *)closure; + natsMicroserviceConfig cfg = { + .description = "Sequence adder - NATS microservice example in C", + .name = "c-sequence", + .version = "1.0.0", + }; + natsMicroserviceEndpointConfig sequence_cfg = { + .subject = "sequence", + .handler = handle_sequence, + .closure = &fakeClosure, + .schema = NULL, + }; + natsMicroserviceEndpointConfig *endpoints[] = {&sequence_cfg}; + const char *names[] = {"sequence"}; - natsSubscription_GetDropped(sub, (int64_t *)&dropped); + return run_service(conn, &cfg, endpoints, names, 1); } -int main(int argc, char **argv) +// calculates the sum of X/f(1) + X/f(2)... up to N (included). The inputs are X +// (float), f name (string), and N (int). E.g.: '10.0 "power2" 10' will +// calculate 10/2 + 10/4 + 10/8 + 10/16 + 10/32 + 10/64 + 10/128 + 10/256 + +// 10/512 + 10/1024 = 20.998046875 +static void handle_sequence(natsMicroservice *m, natsMicroserviceRequest *req) { - natsConnection *conn = NULL; - natsOptions *opts = NULL; - natsMicroservice *m = NULL; - natsStatus s; - int fakeClosure = 0; - natsMicroserviceEndpointConfig default_endpoint_cfg = { - .subject = "c-test", - .handler = onMsg, + natsMicroserviceError *err = NULL; + natsConnection *nc = natsMicroservice_GetConnection(m); + natsArgs *args = NULL; + int n = 0; + int i; + const char *function; + long double initialValue = 1.0; + long double value = 1.0; + long double denominator = 0; + char result[64]; + + err = natsParseAsArgs(&args, natsMicroserviceRequest_GetData(req), natsMicroserviceRequest_GetDataLength(req)); + if ((err == NULL) && + (natsArgs_Count(args) != 2)) + { + err = nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "Invalid number of arguments"); + } + + if (err == NULL) + { + err = natsArgs_GetString(&function, args, 0); + } + if (err == NULL) + { + err = natsArgs_GetInt(&n, args, 1); + } + if ((err == NULL) && + (strcmp(function, "factorial") != 0) && + (strcmp(function, "power2") != 0) && + (strcmp(function, "fibonacci") != 0)) + { + err = nats_NewMicroserviceError( + NATS_INVALID_ARG, 400, + "Invalid function name, must be 'factorial', 'power2', or 'fibonacci'"); + } + if ((err == NULL) && + (n < 1)) + { + err = nats_NewMicroserviceError( + NATS_INVALID_ARG, 400, + "Invalid number of iterations, must be at least 1"); + } + + for (i = 1; (err == NULL) && (i <= n); i++) + { + err = call_function(&denominator, nc, function, i); + if (err == NULL && denominator == 0) + { + err = nats_NewMicroserviceError(0, 500, "division by zero"); + } + if (err == NULL) + { + value = value + initialValue / denominator; + } + } + + if (err == NULL) + { + snprintf(result, sizeof(result), "%Lf", value); + err = natsMicroserviceRequest_Respond(req, result, strlen(result)); + } + + if (err != NULL) + { + natsMicroserviceRequest_Error(req, &err); + } + natsArgs_Destroy(args); +} + +static void *run_arithmetics(void *closure) +{ + natsConnection *conn = (natsConnection *)closure; + natsMicroserviceConfig cfg = { + .description = "Arithmetic operations - NATS microservice example in C", + .name = "c-arithmetics", + .version = "1.0.0", + }; + natsMicroserviceEndpointConfig add_cfg = { + .subject = "add", + .handler = handle_add, + .closure = &fakeClosure, + .schema = NULL, + }; + natsMicroserviceEndpointConfig divide_cfg = { + .subject = "divide", + .handler = handle_divide, .closure = &fakeClosure, .schema = NULL, }; + natsMicroserviceEndpointConfig multiply_cfg = { + .subject = "multiply", + .handler = handle_multiply, + .closure = &fakeClosure, + .schema = NULL, + }; + natsMicroserviceEndpointConfig *endpoints[] = + {&add_cfg, ÷_cfg, &multiply_cfg}; + const char *names[] = + {"add", "divide", "multiply"}; + + return run_service(conn, &cfg, endpoints, names, 3); +} +static void *run_functions(void *closure) +{ + natsConnection *conn = (natsConnection *)closure; natsMicroserviceConfig cfg = { - .description = "NATS microservice example in C", - .name = "c-example-microservice", + .description = "Functions - NATS microservice example in C", + .name = "c-functions", .version = "1.0.0", - .endpoint = &default_endpoint_cfg, }; + natsMicroserviceEndpointConfig factorial_cfg = { + .subject = "factorial", + .handler = handle_factorial, + .closure = &fakeClosure, + .schema = NULL, + }; + natsMicroserviceEndpointConfig fibonacci_cfg = { + .subject = "fibonacci", + .handler = handle_fibonacci, + .closure = &fakeClosure, + .schema = NULL, + }; + natsMicroserviceEndpointConfig power2_cfg = { + .subject = "power2", + .handler = handle_power2, + .closure = &fakeClosure, + .schema = NULL, + }; + natsMicroserviceEndpointConfig *endpoints[] = + {&factorial_cfg, &fibonacci_cfg, &power2_cfg}; + const char *names[] = + {"factorial", "fibonacci", "power2"}; - opts = parseArgs(argc, argv, ""); + return run_service(conn, &cfg, endpoints, names, 3); +} - s = natsOptions_SetErrorHandler(opts, asyncCb, NULL); - if (s == NATS_OK) +static void +handle_arithmetics_op(natsMicroserviceRequest *req, arithmeticsOP op) +{ + natsMicroserviceError *err = NULL; + natsArgs *args = NULL; + long double a1, a2, result; + char buf[1024]; + int len; + + err = natsParseAsArgs(&args, natsMicroserviceRequest_GetData(req), natsMicroserviceRequest_GetDataLength(req)); + if ((err == NULL) && (natsArgs_Count(args) != 2)) { - s = natsConnection_Connect(&conn, opts); + err = nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "Invalid number of arguments, must be 2"); } - if (s == NATS_OK) + if (err == NULL) { - s = nats_AddMicroservice(&m, conn, &cfg); + err = natsArgs_GetFloat(&a1, args, 0); } - if (s == NATS_OK) + if (err == NULL) { - s = natsMicroservice_Run(m); + err = natsArgs_GetFloat(&a2, args, 1); } - if (s == NATS_OK) + if (err == NULL) + { + err = op(&result, natsMicroserviceRequest_GetConnection(req), a1, a2); + } + if (err == NULL) { - // Destroy all our objects to avoid report of memory leak - natsMicroservice_Destroy(m); - natsConnection_Destroy(conn); - natsOptions_Destroy(opts); + len = snprintf(buf, sizeof(buf), "%Lf", result); + err = natsMicroserviceRequest_Respond(req, buf, len); + } - // To silence reports of memory still in used with valgrind - nats_Close(); + if (err != NULL) + { + natsMicroserviceRequest_Error(req, &err); + } + natsArgs_Destroy(args); +} - return 0; +static void +handle_function_op(natsMicroserviceRequest *req, functionOP op) +{ + natsMicroserviceError *err = NULL; + natsArgs *args = NULL; + int n; + long double result; + char buf[1024]; + int len; + + err = natsParseAsArgs(&args, natsMicroserviceRequest_GetData(req), natsMicroserviceRequest_GetDataLength(req)); + if ((err == NULL) && (natsArgs_Count(args) != 1)) + { + err = nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "Invalid number of arguments, must be 1"); + } + if (err == NULL) + { + err = natsArgs_GetInt(&n, args, 0); + } + if (err == NULL) + { + err = op(&result, natsMicroserviceRequest_GetConnection(req), n); + } + if (err == NULL) + { + len = snprintf(buf, sizeof(buf), "%Lf", result); + err = natsMicroserviceRequest_Respond(req, buf, len); + } + + if (err != NULL) + { + natsMicroserviceRequest_Error(req, &err); + } + natsArgs_Destroy(args); +} + +static natsMicroserviceError * +call_arithmetics(long double *result, natsConnection *nc, const char *subject, long double a1, long double a2) +{ + natsMicroserviceError *err = NULL; + natsMicroserviceClient *client = NULL; + natsMsg *response = NULL; + natsArgs *args = NULL; + char buf[1024]; + int len; + + err = nats_NewMicroserviceClient(&client, nc, NULL); + if (err == NULL) + { + len = snprintf(buf, sizeof(buf), "%Lf %Lf", a1, a2); + err = natsMicroserviceClient_DoRequest(client, &response, subject, buf, len); + } + if (err == NULL) + { + err = natsParseAsArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response)); + } + if (err == NULL) + { + err = natsArgs_GetFloat(result, args, 0); + } + + natsMicroserviceClient_Destroy(client); + natsMsg_Destroy(response); + return err; +} + +static natsMicroserviceError * +call_function(long double *result, natsConnection *nc, const char *subject, int n) +{ + natsMicroserviceError *err = NULL; + natsMicroserviceClient *client = NULL; + natsMsg *response = NULL; + natsArgs *args = NULL; + char buf[1024]; + int len; + + err = nats_NewMicroserviceClient(&client, nc, NULL); + if (err == NULL) + { + len = snprintf(buf, sizeof(buf), "%d", n); + err = natsMicroserviceClient_DoRequest(client, &response, subject, buf, len); + } + if (err == NULL) + { + err = natsParseAsArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response)); + } + if (err == NULL) + { + err = natsArgs_GetFloat(result, args, 0); + } + + natsMicroserviceClient_Destroy(client); + natsMsg_Destroy(response); + return err; +} + +static void handle_stop(natsMicroservice *m, natsMicroserviceRequest *req) +{ + natsMicroserviceError *err; + + err = natsMicroservice_Stop(m); + if (err == NULL) + { + err = natsMicroserviceRequest_Respond(req, "OK", 2); } - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); - return 1; + if (err == NULL) + { + pthread_exit((void *)(NATS_OK)); + } + else + { + natsMicroserviceRequest_Error(req, &err); + pthread_exit((void *)(err->status)); + } +} + +static void *run_service(natsConnection *conn, natsMicroserviceConfig *svc, + natsMicroserviceEndpointConfig **endpoints, const char **ep_names, int len_endpoints) +{ + natsMicroserviceError *err = NULL; + natsMicroservice *m = NULL; + char errbuf[1024]; + int i; + + err = nats_AddMicroservice(&m, conn, svc); + for (i = 0; (err == NULL) && (i < len_endpoints); i++) + { + err = natsMicroservice_AddEndpoint(NULL, m, ep_names[i], endpoints[i]); + if (err != NULL) + { + break; + } + } + if (err == NULL) + { + err = natsMicroservice_AddEndpoint(NULL, m, "stop", &stop_cfg); + } + if (err == NULL) + { + err = natsMicroservice_Run(m); + } + + natsMicroservice_Destroy(m); + if (err != NULL) + { + printf("Error: %s\n", err->String(err, errbuf, sizeof(errbuf))); + return (void *)(err->status); + } + return (void *)NATS_OK; +} + +static natsMicroserviceError * +add(long double *result, natsConnection *nc, long double a1, long double a2) +{ + *result = a1 + a2; + return NULL; +} + +static natsMicroserviceError * +divide(long double *result, natsConnection *nc, long double a1, long double a2) +{ + *result = a1 / a2; + return NULL; +} + +static natsMicroserviceError *multiply(long double *result, natsConnection *nc, long double a1, long double a2) +{ + *result = a1 * a2; + return NULL; +} + +static natsMicroserviceError * +factorial(long double *result, natsConnection *nc, int n) +{ + natsMicroserviceError *err = NULL; + int i; + + if (n < 1) + return nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "n must be greater than 0"); + + *result = 1; + for (i = 1; i <= n; i++) + { + err = call_arithmetics(result, nc, "multiply", *result, i); + if (err != NULL) + return err; + } + return NULL; +} + +static natsMicroserviceError * +fibonacci(long double *result, natsConnection *nc, int n) +{ + natsMicroserviceError *err = NULL; + int i; + long double n1, n2; + + if (n < 0) + return nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "n must be greater than 0"); + + if (n < 2) + { + *result = n; + return NULL; + } + + for (i = 1, n1 = 0, n2 = 1; i <= n; i++) + { + err = call_arithmetics(result, nc, "add", n1, n2); + if (err != NULL) + return err; + n1 = n2; + n2 = *result; + } + return NULL; +} + +static natsMicroserviceError *power2(long double *result, natsConnection *nc, int n) +{ + natsMicroserviceError *err = NULL; + int i; + + if (n < 1) + return nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "n must be greater than 0"); + + *result = 1; + for (i = 1; i <= n; i++) + { + err = call_arithmetics(result, nc, "multiply", *result, 2); + if (err != NULL) + return err; + } + return NULL; } diff --git a/src/mem.h b/src/mem.h index a73f43070..6ef6e2e66 100644 --- a/src/mem.h +++ b/src/mem.h @@ -27,6 +27,14 @@ #endif #define NATS_FREE(p) free((p)) +#define NATS_CALLOCS(dupe, count, size) ( \ + *(dupe) = NATS_CALLOC((count), (size)), \ + *(dupe) != NULL ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY)) + +#define NATS_STRDUPS(dupe, str) ( \ + *(dupe) = NATS_STRDUP(str), \ + *(dupe) != NULL ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY)) + // GNU C Library version 2.25 or later. #if defined(__GLIBC__) && \ (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25)) diff --git a/src/micro.c b/src/micro.c index c8039aacc..2259c4aa4 100644 --- a/src/micro.c +++ b/src/micro.c @@ -11,64 +11,57 @@ // See the License for the specific language governing permissions and // limitations under the License. -// TODO review includes -#include -#include - +// TODO <>/<> review includes +#include "micro.h" #include "microp.h" -#include "mem.h" #include "conn.h" +#include "mem.h" -static natsStatus +static natsMicroserviceError * _createMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg); static void -_freeMicroservice(natsMicroservice *m); -static void _retainMicroservice(natsMicroservice *m); -static void +static natsMicroserviceError * _releaseMicroservice(natsMicroservice *m); +static void +_markMicroserviceStopped(natsMicroservice *m, bool stopped); -////////////////////////////////////////////////////////////////////////////// -// microservice management APIs -////////////////////////////////////////////////////////////////////////////// - -natsStatus +natsMicroserviceError * nats_AddMicroservice(natsMicroservice **new_m, natsConnection *nc, natsMicroserviceConfig *cfg) { - natsStatus s; - if ((new_m == NULL) || (nc == NULL) || (cfg == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - s = _createMicroservice(new_m, nc, cfg); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); + return natsMicroserviceErrorInvalidArg; - return NATS_OK; + return _createMicroservice(new_m, nc, cfg); } -void natsMicroservice_Destroy(natsMicroservice *m) +natsMicroserviceError * +natsMicroservice_Destroy(natsMicroservice *m) { - _releaseMicroservice(m); + return _releaseMicroservice(m); } -// TODO update from Go -natsStatus +// TODO <>/<> update from Go +natsMicroserviceError * natsMicroservice_Stop(natsMicroservice *m) { - if ((m == NULL) || (m->mu == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsMutex_Lock(m->mu); - - if (m->stopped) - goto OK; + natsStatus s = NATS_OK; - m->stopped = true; + if (m == NULL) + return natsMicroserviceErrorInvalidArg; + if (natsMicroservice_IsStopped(m)) + return NULL; -OK: - natsMutex_Unlock(m->mu); - return NATS_OK; + s = natsMicroserviceEndpoint_Stop(m->root); + if (s == NATS_OK) + { + _markMicroserviceStopped(m, true); + return NULL; + } + else + { + return nats_NewMicroserviceError(NATS_UPDATE_ERR_STACK(s), 500, "failed to stop microservice"); + } } bool natsMicroservice_IsStopped(natsMicroservice *m) @@ -84,38 +77,71 @@ bool natsMicroservice_IsStopped(natsMicroservice *m) return stopped; } -// TODO: eliminate sleep -natsStatus +// TODO: <>/<> eliminate sleep +natsMicroserviceError * natsMicroservice_Run(natsMicroservice *m) { if ((m == NULL) || (m->mu == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); + return natsMicroserviceErrorInvalidArg; for (; !natsMicroservice_IsStopped(m);) { nats_Sleep(50); } - return NATS_OK; + + return NULL; } -natsStatus -natsMicroservice_Respond(natsMicroservice *m, natsMicroserviceRequest *r, const char *data, int len) +NATS_EXTERN natsConnection * +natsMicroservice_GetConnection(natsMicroservice *m) { - natsStatus s = NATS_OK; + if (m == NULL) + return NULL; + return m->nc; +} - if ((m == NULL) || (m->mu == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); +natsMicroserviceError * +natsMicroservice_AddEndpoint(natsMicroserviceEndpoint **new_ep, natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg) +{ + natsStatus s = NATS_OK; + if ((m == NULL) || (m->root == NULL)) + { + return natsMicroserviceErrorInvalidArg; + } + s = natsMicroserviceEndpoint_AddEndpoint(new_ep, m->root, name, cfg); - s = natsConnection_Publish(m->nc, natsMsg_GetReply(r->msg), data, len); - return NATS_UPDATE_ERR_STACK(s); + if (s == NATS_OK) + return NULL; + else + return nats_NewMicroserviceError(NATS_UPDATE_ERR_STACK(s), 500, "failed to add endpoint"); } -// Implementation functions. +static natsMicroserviceError * +_destroyMicroservice(natsMicroservice *m) +{ + natsStatus s = NATS_OK; -static natsStatus + if ((m == NULL) || (m->root == NULL)) + return NULL; + + s = natsMicroserviceEndpoint_Destroy(m->root); + if (s != NATS_OK) + { + return nats_NewMicroserviceError(NATS_UPDATE_ERR_STACK(s), 500, "failed to destroy microservice"); + } + + natsConn_release(m->nc); + natsMutex_Destroy(m->mu); + NATS_FREE(m->identity.id); + NATS_FREE(m); + return NULL; +} + +static natsMicroserviceError * _createMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg) { natsStatus s = NATS_OK; + natsMicroserviceError *err = NULL; natsMicroservice *m = NULL; natsMutex *mu = NULL; char tmpNUID[NUID_BUFFER_LEN + 1]; @@ -123,9 +149,12 @@ _createMicroservice(natsMicroservice **new_microservice, natsConnection *nc, nat // Make a microservice object, with a reference to a natsConnection. m = (natsMicroservice *)NATS_CALLOC(1, sizeof(natsMicroservice)); if (m == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); + return natsMicroserviceErrorOutOfMemory; + + // TODO: <>/<> separate PR, make a natsConn_retain return a natsConnection* natsConn_retain(nc); m->nc = nc; + m->refs = 1; // Need a mutex for concurrent operations. s = natsMutex_Create(&mu); @@ -136,56 +165,46 @@ _createMicroservice(natsMicroservice **new_microservice, natsConnection *nc, nat // Generate a unique ID for this microservice. IFOK(s, natsNUID_Next(tmpNUID, NUID_BUFFER_LEN + 1)); - IF_OK_DUP_STRING(s, m->identity.id, tmpNUID); + IF_OK_DUP_STRING(s, m->identity.id, tmpNUID); // Copy the config data. if (s == NATS_OK) { m->identity.name = cfg->name; m->identity.version = cfg->version; - m->cfg = *cfg; + m->cfg = cfg; + } + + // Setup the root endpoint. + if (s == NATS_OK) + { + s = _newMicroserviceEndpoint(&m->root, m, "", NULL); } // Set up the default endpoint. if (s == NATS_OK && cfg->endpoint != NULL) { - s = natsMicroservice_AddEndpoint(m, natsMicroserviceDefaultEndpointName, cfg->endpoint); + err = natsMicroservice_AddEndpoint(NULL, m, natsMicroserviceDefaultEndpointName, cfg->endpoint); + if (err != NULL) + { + // status is always set in AddEndpoint errors. + s = err->status; + } } // Set up monitoring (PING, STATS, etc.) responders. IFOK(s, _initMicroserviceMonitoring(m)); if (s == NATS_OK) - *new_microservice = m; - else { - _freeMicroservice(m); + *new_microservice = m; + return NULL; } - return NATS_UPDATE_ERR_STACK(s); -} - -static void -_freeMicroservice(natsMicroservice *m) -{ - int i; - natsStatus s; - - if (m == NULL) - return; - - for (i = 0; i < m->num_endpoints; i++) + else { - s = _stopMicroserviceEndpoint(m->endpoints[i]); - - if(s == NATS_OK) - _freeMicroserviceEndpoint(m->endpoints[i]); + _destroyMicroservice(m); + return nats_NewMicroserviceError(NATS_UPDATE_ERR_STACK(s), 500, "failed to create microservice"); } - NATS_FREE(m->endpoints); - - natsConn_release(m->nc); - natsMutex_Destroy(m->mu); - NATS_FREE(m->identity.id); - NATS_FREE(m); } static void @@ -196,19 +215,28 @@ _retainMicroservice(natsMicroservice *m) natsMutex_Unlock(m->mu); } -static void +static natsMicroserviceError * _releaseMicroservice(natsMicroservice *m) { bool doFree; if (m == NULL) - return; + return NULL; natsMutex_Lock(m->mu); doFree = (--(m->refs) == 0); natsMutex_Unlock(m->mu); - if (doFree) - _freeMicroservice(m); + if (!doFree) + return NULL; + + return _destroyMicroservice(m); } +static void +_markMicroserviceStopped(natsMicroservice *m, bool stopped) +{ + natsMutex_Lock(m->mu); + m->stopped = stopped; + natsMutex_Unlock(m->mu); +} diff --git a/src/micro.h b/src/micro.h index 4807325f6..26d715aff 100644 --- a/src/micro.h +++ b/src/micro.h @@ -14,8 +14,14 @@ #ifndef MICRO_H_ #define MICRO_H_ +#include "nats.h" + #define natsMicroserviceAPIPrefix "$SRV" +#define NATS_MICROSERVICE_STATUS_HDR "Nats-Status" +#define NATS_MICROSERVICE_ERROR_HDR "Nats-Service-Error" +#define NATS_MICROSERVICE_ERROR_CODE_HDR "Nats-Service-Error-Code" + enum natsMicroserviceVerb { natsMicroserviceVerbPing = 0, @@ -27,32 +33,271 @@ enum natsMicroserviceVerb typedef enum natsMicroserviceVerb natsMicroserviceVerb; -static natsStatus -natsMicroserviceVerb_String(const char **new_subject, natsMicroserviceVerb verb) +/** + * The Microservice error. Note that code and description do not have a + * well-defined lifespan and should be copied if needed to be used later. + */ +typedef struct natsMicroserviceError +{ + natsStatus status; + int code; + const char *description; + + const char *(*String)(struct natsMicroserviceError *err, char *buf, int size); +} natsMicroserviceError; + +/** + * The Microservice request object. + */ +typedef struct __microserviceRequest natsMicroserviceRequest; + +/** + * The Microservice object. Create and start with #nats_AddMicroservice. + */ +typedef struct __microservice natsMicroservice; + +/** + * The Microservice configuration object. + */ +typedef struct natsMicroserviceConfig +{ + const char *name; + const char *version; + const char *description; + struct natsMicroserviceEndpointConfig *endpoint; +} natsMicroserviceConfig; + +/** + * The Microservice endpoint object. + * TODO document the interface. + */ +typedef struct __microserviceEndpoint natsMicroserviceEndpoint; + +/** \brief Callback used to deliver messages to a microservice. + * + * This is the callback that one provides when creating a microservice endpoint. + * The library will invoke this callback for each message arriving to the + * specified subject. + * + * @see natsMicroservice_AddEndpoint() + */ +typedef void (*natsMicroserviceRequestHandler)(natsMicroservice *m, natsMicroserviceRequest *req); + +/** + * The Microservice endpoint configuration object. + */ +typedef struct natsMicroserviceEndpointConfig +{ + const char *subject; + natsMicroserviceRequestHandler handler; + void *closure; + struct natsMicroserviceSchema *schema; +} natsMicroserviceEndpointConfig; + +/** + * The Microservice endpoint stats struct. + */ +typedef struct natsMicroserviceEndpointStats { - if (new_subject == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); - - switch (verb) - { - case natsMicroserviceVerbPing: - *new_subject = "PING"; - return NATS_OK; - - case natsMicroserviceVerbStats: - *new_subject = "STATS"; - return NATS_OK; - - case natsMicroserviceVerbInfo: - *new_subject = "INFO"; - return NATS_OK; - - case natsMicroserviceVerbSchema: - *new_subject = "SCHEMA"; - return NATS_OK; - default: - return nats_setError(NATS_INVALID_ARG, "Invalid microservice verb %d", verb); - } -} + const char *name; + const char *subject; + int num_requests; + int num_errors; + char *last_error; + int processing_time_ms; + int average_processing_time_ms; + bool stopped; +} natsMicroserviceEndpointStats; + +/** + * The Microservice endpoint schema object. + */ +typedef struct natsMicroserviceSchema +{ + const char *request; + const char *response; +} natsMicroserviceSchema; + +/** + * Request unmarshaled as "arguments", a space-separated list of numbers and strings. + * TODO document the interface. + */ +typedef struct __args_t natsArgs; + +/** + * The Microservice client. Initialize with #nats_NewMicroserviceClient. + */ +typedef struct __microserviceClient natsMicroserviceClient; + +/** + * The Microservice configuration object. Initialize with #natsMicroserviceConfig_Init. + */ +typedef struct natsMicroserviceClientConfig +{ + // TBD in the future. + int dummy; +} natsMicroserviceClientConfig; + +/** \defgroup microserviceGroup Microservice support + * + * A simple NATS-based microservice implementation framework. + * + * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without + * necessarily bumping the major version of the library. + * + * @{ + */ + +/** \brief Makes a new error. + * + * @param new_microservice the location where to store the newly created + * #natsMicroservice object. + * @param nc the pointer to the #natsCOnnection object on which the service will listen on. + * @param cfg the pointer to the #natsMicroserviceConfig configuration + * information used to create the #natsMicroservice object. + */ +NATS_EXTERN natsMicroserviceError * +nats_NewMicroserviceError(natsStatus s, int code, const char *desc); + +NATS_EXTERN void +natsMicroserviceError_Destroy(natsMicroserviceError *err); + +/** \brief Adds a microservice with a given configuration. + * + * Adds a microservice with a given configuration. + * + * \note The return #natsMicroservice object needs to be destroyed using + * #natsMicroservice_Destroy when no longer needed to free allocated memory. + * + * @param new_microservice the location where to store the newly created + * #natsMicroservice object. + * @param nc the pointer to the #natsCOnnection object on which the service will listen on. + * @param cfg the pointer to the #natsMicroserviceConfig configuration + * information used to create the #natsMicroservice object. + */ +NATS_EXTERN natsMicroserviceError * +nats_AddMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg); + +/** \brief Waits for the microservice to stop. + */ +NATS_EXTERN natsMicroserviceError * +natsMicroservice_Run(natsMicroservice *m); + +/** \brief Stops a running microservice. + */ +NATS_EXTERN natsMicroserviceError * +natsMicroservice_Stop(natsMicroservice *m); + +/** \brief Checks if a microservice is stopped. + */ +NATS_EXTERN bool +natsMicroservice_IsStopped(natsMicroservice *m); + +/** \brief Destroys a microservice object. + * + * Destroys a microservice object; frees all memory. The service must be stopped + * first, this function does not check if it is. + */ +NATS_EXTERN natsMicroserviceError * +natsMicroservice_Destroy(natsMicroservice *m); + +/** \brief Adds (and starts) a microservice endpoint. + */ +NATS_EXTERN natsMicroserviceError * +natsMicroservice_AddEndpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg); + +/** \brief Returns the microservice's NATS connection. + */ +NATS_EXTERN natsConnection * +natsMicroservice_GetConnection(natsMicroservice *m); + +/** \brief Responds to a microservice request. + */ +NATS_EXTERN natsMicroserviceError * +natsMicroserviceRequest_Respond(natsMicroserviceRequest *req, const char *data, int len); + +/** \brief Responds to a microservice request with an error, does NOT free the + * error. + */ +NATS_EXTERN natsMicroserviceError * +natsMicroserviceRequest_RespondError(natsMicroserviceRequest *req, natsMicroserviceError *err, const char *data, int len); + +/** \brief A convenience method to respond to a microservice request with a + * simple error (no data), and free the error. + */ +NATS_EXTERN void +natsMicroserviceRequest_Error(natsMicroserviceRequest *req, natsMicroserviceError **err); + +/** \brief Returns the original NATS message underlying the request. + */ +NATS_EXTERN natsMsg * +natsMicroserviceRequest_GetMsg(natsMicroserviceRequest *req); + +#define natsMicroserviceRequestHeader_Set(req, key, value) \ + natsMsgHeader_Set(natsMicroserviceRequest_GetMsg(req), (key), (value)) +#define natsMicroserviceRequestHeader_Add(req, key, value) \ + natsMsgHeader_Add(natsMicroserviceRequest_GetMsg(req), (key), (value)) +#define natsMicroserviceRequestHeader_Get(req, key, value) \ + natsMsgHeader_Get(natsMicroserviceRequest_GetMsg(req), (key), (value)) +#define natsMicroserviceRequestHeader_Values(req, key, values, count) \ + natsMsgHeader_Values(natsMicroserviceRequest_GetMsg(req), (key), (values), (count)) +#define natsMicroserviceRequestHeader_Keys(req, key, keys, count) \ + natsMsgHeader_Keys(natsMicroserviceRequest_GetMsg(req), (key), (keys), (count)) +#define natsMicroserviceRequestHeader_Delete(req, key) \ + natsMsgHeader_Delete(natsMicroserviceRequest_GetMsg(req), (key)) + +#define natsMicroserviceRequest_GetSubject(req) \ + natsMsg_GetSubject(natsMicroserviceRequest_GetMsg(req)) +#define natsMicroserviceRequest_GetReply(req) \ + natsMsg_GetReply(natsMicroserviceRequest_GetMsg(req)) +#define natsMicroserviceRequest_GetData(req) \ + natsMsg_GetData(natsMicroserviceRequest_GetMsg(req)) +#define natsMicroserviceRequest_GetDataLength(req) \ + natsMsg_GetDataLength(natsMicroserviceRequest_GetMsg(req)) +#define natsMicroserviceRequest_GetSequence(req) \ + natsMsg_GetSequence(natsMicroserviceRequest_GetMsg(req)) +#define natsMicroserviceRequest_GetTime(req) \ + natsMsg_GetTime(natsMicroserviceRequest_GetMsg(req)) + +NATS_EXTERN natsMicroserviceEndpoint * +natsMicroserviceRequest_GetEndpoint(natsMicroserviceRequest *req); + +NATS_EXTERN natsMicroservice * +natsMicroserviceRequest_GetMicroservice(natsMicroserviceRequest *req); + +NATS_EXTERN natsConnection * +natsMicroserviceRequest_GetConnection(natsMicroserviceRequest *req); + +NATS_EXTERN natsMicroserviceError * +nats_MicroserviceErrorFromMsg(natsStatus s, natsMsg *msg); + +NATS_EXTERN natsMicroserviceError * +natsParseAsArgs(natsArgs **args, const char *data, int data_len); + +NATS_EXTERN int +natsArgs_Count(natsArgs *args); + +NATS_EXTERN natsMicroserviceError * +natsArgs_GetInt(int *val, natsArgs *args, int index); + +NATS_EXTERN natsMicroserviceError * +natsArgs_GetFloat(long double *val, natsArgs *args, int index); + +NATS_EXTERN natsMicroserviceError * +natsArgs_GetString(const char **val, natsArgs *args, int index); + +NATS_EXTERN void +natsArgs_Destroy(natsArgs *args); + +natsMicroserviceError * +nats_NewMicroserviceClient(natsMicroserviceClient **new_client, natsConnection *nc, natsMicroserviceClientConfig *cfg); + +void +natsMicroserviceClient_Destroy(natsMicroserviceClient *client); + +natsMicroserviceError * +natsMicroserviceClient_DoRequest(natsMicroserviceClient *client, natsMsg **reply, const char *subject, const char *data, int data_len); + +/** @} */ // end of microserviceGroup #endif /* MICRO_H_ */ diff --git a/src/micro_args_parser.c b/src/micro_args_parser.c new file mode 100644 index 000000000..f3743087e --- /dev/null +++ b/src/micro_args_parser.c @@ -0,0 +1,369 @@ +// Copyright 2021-2023 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "micro.h" +#include "microp.h" +#include "mem.h" + +static natsMicroserviceError *parse(void **args, int *args_len, int *i, const char *data, int data_len); + +natsMicroserviceError * +natsParseAsArgs(natsArgs **new_args, const char *data, int data_len) +{ + natsMicroserviceError *err = NULL; + int n; + int i = 0; + natsArgs *args = NULL; + + if ((new_args == NULL) || (data == NULL) || (data_len < 0)) + return nats_NewMicroserviceError(NATS_INVALID_ARG, 500, "Invalid function argument"); + + // parse the number of arguments without allocating. + err = parse(NULL, &n, &i, data, data_len); + if (err == NULL) + { + args = NATS_CALLOC(1, sizeof(natsArgs)); + if (args == NULL) + err = natsMicroserviceErrorOutOfMemory; + } + if (err == NULL) + { + args->args = NATS_CALLOC(n, sizeof(void *)); + if (args == NULL) + err = natsMicroserviceErrorOutOfMemory; + else + args->count = n; + } + if (err == NULL) + { + i = 0; + err = parse(args->args, &n, &i, data, data_len); + } + + if (err == NULL) + { + *new_args = args; + } + else + { + natsArgs_Destroy(args); + } + + return err; +} + +void natsArgs_Destroy(natsArgs *args) +{ + int i; + + if (args == NULL) + return; + + for (i = 0; i < args->count; i++) + { + NATS_FREE(args->args[i]); + } + NATS_FREE(args->args); + NATS_FREE(args); +} + +int natsArgs_Count(natsArgs *args) +{ + if (args == NULL) + return 0; + + return args->count; +} + +natsMicroserviceError * +natsArgs_GetInt(int *val, natsArgs *args, int index) +{ + if ((args == NULL) || (index < 0) || (index >= args->count) || (val == NULL)) + return natsMicroserviceErrorInvalidArg; + + *val = *((int *)args->args[index]); + return NULL; +} + +natsMicroserviceError * +natsArgs_GetFloat(long double *val, natsArgs *args, int index) +{ + if ((args == NULL) || (index < 0) || (index >= args->count) || (val == NULL)) + return natsMicroserviceErrorInvalidArg; + + *val = *((long double *)args->args[index]); + return NULL; +} + +natsMicroserviceError * +natsArgs_GetString(const char **val, natsArgs *args, int index) +{ + if ((args == NULL) || (index < 0) || (index >= args->count) || (val == NULL)) + return natsMicroserviceErrorInvalidArg; + + *val = (const char *)args->args[index]; + return NULL; +} + +/// @brief decodes the rest of a string into a pre-allocated buffer of +/// sufficient length, or just calculates the needed buffer size. The opening +/// quote must have been already processed by the caller (parse). +/// +/// @param dup receives the parsed string, must be freed by the caller. pass NULL to just get the length. +/// @param data raw message data +/// @param data_len length of data +/// @return error in case the string is not properly terminated. +static natsMicroserviceError * +decode_rest_of_string(char *dup, int *decoded_len, int *i, const char *data, int data_len) +{ + char c; + int len = 0; + bool terminated = false; + bool escape = false; + + for (; !terminated && *i < data_len; (*i)++) + { + c = data[*i]; + switch (c) + { + case '"': + if (escape) + { + // include the escaped quote. + if (dup != NULL) + { + dup[len] = c; + } + len++; + escape = false; + } + else + { + // end of quoted string. + terminated = true; + } + break; + + case '\\': + if (!escape) + { + escape = true; + } + else + { + // include the escaped backslash. + if (dup != NULL) + { + dup[len] = c; + } + len++; + escape = false; + } + break; + + default: + if (dup != NULL) + { + dup[len] = c; + } + len++; + escape = false; + break; + } + } + if (!terminated) + { + nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "a quoted string is not properly terminated"); + } + + *decoded_len = len; + return NULL; +} + +/// @brief decodes and duplicates the rest of a string, as the name says. +/// @param dup receives the parsed string, must be freed by the caller. pass +/// NULL to just get the length. +/// @param data raw message data +/// @param data_len length of data +/// @return error. +static natsMicroserviceError * +decode_and_dupe_rest_of_string(char **dup, int *i, const char *data, int data_len) +{ + natsMicroserviceError *err = NULL; + int start = *i; + int decoded_len; + + err = decode_rest_of_string(NULL, &decoded_len, i, data, data_len); + if (err != NULL) + { + return err; + } + if (dup == NULL) + { + // nothing else to do - the string has been scanned and validated. + return NULL; + } + + *i = start; + + *dup = NATS_CALLOC(decoded_len + 1, sizeof(char)); + if (*dup == NULL) + { + return natsMicroserviceErrorOutOfMemory; + } + + // no need to check for error the 2nd time, we already know the string is + // valid. + decode_rest_of_string(*dup, &decoded_len, i, data, data_len); + (*dup)[decoded_len] = 0; + return NULL; +} + +typedef enum parserState +{ + NewArg = 0, + NumberArg, +} parserState; + +static natsMicroserviceError * +parse(void **args, int *args_len, int *i, const char *data, int data_len) +{ + natsMicroserviceError *err = NULL; + char c; + int n = 0; + parserState state = NewArg; + char errbuf[1024]; + char numbuf[64]; + int num_len = 0; + bool is_float = false; + +#define EOS 0 + for (; *i < data_len + 1;) + { + c = (*i < data_len) ? data[*i] : EOS; + + switch (state) + { + case NewArg: + switch (c) + { + case EOS: + case ' ': + (*i)++; + break; + + case '"': + (*i)++; // consume the opening quote. + err = decode_and_dupe_rest_of_string((char **)(&args[n]), i, data, data_len); + if (err != NULL) + { + return err; + } + n++; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + case '+': + case '.': + state = NumberArg; + num_len = 0; + numbuf[num_len++] = c; + is_float = (c == '.'); + (*i)++; + break; + + default: + snprintf(errbuf, sizeof(errbuf), "unexpected '%c', an argument must be a number or a quoted string", c); + return nats_NewMicroserviceError(NATS_ERR, 400, errbuf); + } + break; + + case NumberArg: + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + case '+': + case '.': + case 'e': + case 'E': + case ',': + numbuf[num_len] = c; + num_len++; + is_float = is_float || (c == '.') || (c == 'e') || (c == 'E'); + (*i)++; + break; + + case EOS: + case ' ': + if (args != NULL) + { + numbuf[num_len] = 0; + if (is_float) + { + args[n] = NATS_CALLOC(1, sizeof(long double)); + if (args[n] == NULL) + { + return natsMicroserviceErrorOutOfMemory; + } + *(long double *)args[n] = strtold(numbuf, NULL); + } + else + { + args[n] = NATS_CALLOC(1, sizeof(int)); + if (args[n] == NULL) + { + return natsMicroserviceErrorOutOfMemory; + } + *(int *)args[n] = atoi(numbuf); + } + } + n++; + (*i)++; + state = NewArg; + break; + + default: + snprintf(errbuf, sizeof(errbuf), "unexpected '%c', a number must be followed by a space", c); + return nats_NewMicroserviceError(NATS_ERR, 400, errbuf); + } + break; + + default: + snprintf(errbuf, sizeof(errbuf), "unreachable: wrong state for a ' ', expected NewArg or NumberArg, got %d", state); + return nats_NewMicroserviceError(NATS_ERR, 500, errbuf); + } + } + + *args_len = n; + return NULL; +} diff --git a/src/micro_client.c b/src/micro_client.c new file mode 100644 index 000000000..61cd2170c --- /dev/null +++ b/src/micro_client.c @@ -0,0 +1,67 @@ +// Copyright 2015-2018 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "micro.h" +#include "microp.h" +#include "mem.h" +#include "conn.h" + +natsMicroserviceError * +nats_NewMicroserviceClient(natsMicroserviceClient **new_client, natsConnection *nc, natsMicroserviceClientConfig *cfg) +{ + natsMicroserviceClient *client = NULL; + + if (new_client == NULL) + return natsMicroserviceErrorInvalidArg; + + client = NATS_CALLOC(1, sizeof(struct __microserviceClient)); + if (client == NULL) + return natsMicroserviceErrorOutOfMemory; + + natsConn_retain(nc); + client->nc = nc; + *new_client = client; + return NULL; +} + +void +natsMicroserviceClient_Destroy(natsMicroserviceClient *client) +{ + if (client == NULL) + return; + + natsConn_release(client->nc); + NATS_FREE(client); +} + +natsMicroserviceError * +natsMicroserviceClient_DoRequest(natsMicroserviceClient *client, natsMsg **reply, const char *subject, const char *data, int data_len) +{ + natsStatus s = NATS_OK; + natsMicroserviceError *err = NULL; + natsMsg *msg = NULL; + + if ((client == NULL ) || (reply == NULL)) + return natsMicroserviceErrorInvalidArg; + + s = natsConnection_Request(&msg, client->nc, subject, data, data_len, 5000); + if (s != NATS_OK) + return nats_NewMicroserviceError(s, 500, "request failed"); + + err = nats_MicroserviceErrorFromMsg(s, msg); + if (err == NULL) + { + *reply = msg; + } + return err; +} diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index 6c0f9e720..6d0234e6a 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -11,88 +11,132 @@ // See the License for the specific language governing permissions and // limitations under the License. -// TODO review includes #include -#include +#include "micro.h" #include "microp.h" #include "mem.h" -#include "conn.h" + +static natsStatus +_newStats(natsMicroserviceEndpointStats **new_stats, const char *name, const char *subject); +static natsStatus +_setLastError(natsMicroserviceEndpointStats *stats, const char *error); +static void +_freeStats(natsMicroserviceEndpointStats *stats); static bool _isValidName(const char *name); static bool _isValidSubject(const char *subject); - -static natsStatus -_newEndpoint(natsMicroserviceEndpoint **__new_endpoint, natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg); static natsStatus -_startEndpoint(natsMicroserviceEndpoint *endpoint); - +_lazyInitChildren(natsMicroserviceEndpoint *ep); static natsStatus -_newRequest(natsMicroserviceRequest **new_request, natsMsg *msg); -static void -_freeRequest(natsMicroserviceRequest *req); +_findAndRemovePreviousChild(int *found_index, natsMicroserviceEndpoint *ep, const char *name); static void _handleEndpointRequest(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); natsStatus -natsMicroservice_AddEndpoint(natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg) +natsMicroserviceEndpoint_AddEndpoint(natsMicroserviceEndpoint **new_ep, natsMicroserviceEndpoint *parent, const char *name, natsMicroserviceEndpointConfig *cfg) { natsStatus s = NATS_OK; - int i; - int add = -1; - natsMicroserviceEndpoint **new_endpoints = m->endpoints; - int new_num_endpoints = m->num_endpoints; - natsMicroserviceEndpoint *new_endpoint = NULL; - - // TODO return more comprehensive errors - if ((m == NULL) || (m->mu == NULL) || (cfg == NULL) || (cfg->subject == NULL) || - (!_isValidName(name)) || (!_isValidSubject(cfg->subject))) - { - return nats_setDefaultError(NATS_INVALID_ARG); - } - - // This is a rare call, usually happens at the initialization of the - // microservice, so it's ok to lock for the duration of the function, may - // not be necessary at all but will not hurt. - natsMutex_Lock(m->mu); + int index = -1; + natsMicroserviceEndpoint *ep = NULL; - for (i = 0; i < m->num_endpoints; i++) + // TODO <>/<> return more comprehensive errors + if (parent == NULL) { - if (strcmp(m->endpoints[i]->config.subject, cfg->subject) == 0) - { - add = i; - break; - } + return nats_setDefaultError(NATS_INVALID_ARG); } - if (add == -1) + if (cfg != NULL) { - new_endpoints = (natsMicroserviceEndpoint **) - NATS_REALLOC(m->endpoints, sizeof(natsMicroserviceEndpoint *) * (m->num_endpoints + 1)); - s = (new_endpoints != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); - if (s == NATS_OK) + if ((cfg->subject == NULL) || (!_isValidName(name)) || (!_isValidSubject(cfg->subject))) { - add = m->num_endpoints; - new_num_endpoints = m->num_endpoints + 1; + return nats_setDefaultError(NATS_INVALID_ARG); } } - IFOK(s, _newEndpoint(&new_endpoint, m, name, cfg)); - IFOK(s, _startEndpoint(new_endpoint)); + IFOK(s, _lazyInitChildren(parent)); + IFOK(s, _newMicroserviceEndpoint(&ep, parent->m, name, cfg)); + IFOK(s, _findAndRemovePreviousChild(&index, parent, name)); + IFOK(s, _microserviceEndpointList_Put(parent->children, index, ep)); + IFOK(s, natsMicroserviceEndpoint_Start(ep)); if (s == NATS_OK) { - new_endpoints[add] = new_endpoint; - m->endpoints = new_endpoints; - m->num_endpoints = new_num_endpoints; + if (new_ep != NULL) + *new_ep = ep; } else { - _freeMicroserviceEndpoint(new_endpoint); + natsMicroserviceEndpoint_Destroy(ep); + } + return NATS_UPDATE_ERR_STACK(s); +} + +natsStatus +natsMicroserviceEndpoint_Start(natsMicroserviceEndpoint *ep) +{ + if ((ep->subject == NULL) || (ep->config == NULL) || (ep->config->handler == NULL)) + // nothing to do + return NATS_OK; + + return natsConnection_QueueSubscribe(&ep->sub, ep->m->nc, ep->subject, + natsMicroserviceQueueGroup, _handleEndpointRequest, ep); +} + +// TODO <>/<> COPY FROM GO +natsStatus +natsMicroserviceEndpoint_Stop(natsMicroserviceEndpoint *ep) +{ + natsStatus s = NATS_OK; + + // TODO <>/<> review locking for modifying endpoints, may not be necessary + // or ep may need its own lock (stats). + + // This is a rare call, usually happens at the initialization of the + // microservice, so it's ok to lock for the duration of the function, may + // not be necessary at all but will not hurt. + natsMutex_Lock(ep->m->mu); + + if (ep->sub != NULL) + { + s = natsSubscription_Drain(ep->sub); } + if (s == NATS_OK) + { + ep->sub = NULL; + ep->stopped = true; + // TODO <>/<> unsafe + ep->stats->stopped = true; + } + + natsMutex_Unlock(ep->m->mu); + return NATS_UPDATE_ERR_STACK(s); +} + +static natsStatus +_freeMicroserviceEndpoint(natsMicroserviceEndpoint *ep) +{ + // ignore ep->children, must be taken care of by the caller. + _freeStats(ep->stats); + NATS_FREE(ep->name); + NATS_FREE(ep->subject); + NATS_FREE(ep); + return NATS_OK; +} + +natsStatus +natsMicroserviceEndpoint_Destroy(natsMicroserviceEndpoint *ep) +{ + natsStatus s = NATS_OK; + + if (ep == NULL) + return NATS_OK; + + IFOK(s, _destroyMicroserviceEndpointList(ep->children)); + IFOK(s, natsMicroserviceEndpoint_Stop(ep)); + IFOK(s, _freeMicroserviceEndpoint(ep)); - natsMutex_Unlock(m->mu); return NATS_UPDATE_ERR_STACK(s); } @@ -135,110 +179,68 @@ _isValidSubject(const char *subject) } static natsStatus -_newEndpoint(natsMicroserviceEndpoint **__new_endpoint, natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg) +_lazyInitChildren(natsMicroserviceEndpoint *ep) { - natsStatus s = NATS_OK; - natsMicroserviceEndpoint *ep = NULL; - char *dup_name = NULL; - char *dup_subject = NULL; - - ep = (natsMicroserviceEndpoint *)NATS_CALLOC(1, sizeof(natsMicroserviceEndpoint)); - s = (ep != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); - if (s == NATS_OK) + if (ep->children == NULL) { - dup_name = NATS_STRDUP(name); - s = (dup_name != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); - } - if (s == NATS_OK) - { - dup_subject = NATS_STRDUP(cfg->subject); - s = (dup_subject != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); - } - if (s == NATS_OK) - { - ep->m = m; - ep->stats.name = dup_name; - ep->stats.subject = dup_subject; - ep->config = *cfg; - ep->config.subject = dup_subject; - } - if (s == NATS_OK) - { - *__new_endpoint = ep; - return NATS_OK; + return _newMicroserviceEndpointList(&ep->children); } - - NATS_FREE(dup_name); - NATS_FREE(dup_subject); - return NATS_UPDATE_ERR_STACK(s); + return NATS_OK; } static natsStatus -_startEndpoint(natsMicroserviceEndpoint *endpoint) +_findAndRemovePreviousChild(int *found_index, natsMicroserviceEndpoint *ep, const char *name) { - if (endpoint->config.handler == NULL) - // nothing to do - return NATS_OK; - - return natsConnection_QueueSubscribe(&endpoint->sub, endpoint->m->nc, endpoint->config.subject, - natsMicroserviceQueueGroup, _handleEndpointRequest, endpoint); + natsMicroserviceEndpoint *found = _microserviceEndpointList_Find(ep->children, name, found_index); + if (found != NULL) + { + return natsMicroserviceEndpoint_Destroy(found); + } + return NATS_OK; } -// TODO COPY FROM GO natsStatus -_stopMicroserviceEndpoint(natsMicroserviceEndpoint *endpoint) +_newMicroserviceEndpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg) { natsStatus s = NATS_OK; + natsMicroserviceEndpoint *ep = NULL; + // TODO <>/<> do I really need to duplicate name, subject? + char *dup_name = NULL; + char *dup_subject = NULL; - // TODO review locking for modifying endpoints, may not be necessary or - // endpoint may need its own lock (stats). - - // This is a rare call, usually happens at the initialization of the - // microservice, so it's ok to lock for the duration of the function, may - // not be necessary at all but will not hurt. - natsMutex_Lock(endpoint->m->mu); + IFOK(s, NATS_CALLOCS(&ep, 1, sizeof(natsMicroserviceEndpoint))); + IFOK(s, NATS_STRDUPS(&dup_name, name)); + if ((cfg != NULL) && (cfg->subject != NULL)) + IFOK(s, NATS_STRDUPS(&dup_subject, cfg->subject)); + IFOK(s, _newStats(&ep->stats, dup_name, dup_subject)); - if (endpoint->sub != NULL) + if (s == NATS_OK) { - s = natsSubscription_Drain(endpoint->sub); + ep->m = m; + ep->name = dup_name; + ep->subject = dup_subject; + ep->config = cfg; + *new_endpoint = ep; } - if (s == NATS_OK) + else { - endpoint->sub = NULL; - endpoint->stopped = true; - endpoint->stats.stopped = true; + _freeStats(ep->stats); + NATS_FREE(ep); + NATS_FREE(dup_name); + NATS_FREE(dup_subject); } - - natsMutex_Unlock(endpoint->m->mu); return NATS_UPDATE_ERR_STACK(s); } -void _freeMicroserviceEndpoint(natsMicroserviceEndpoint *endpoint) -{ - if (endpoint == NULL) - return; - - // The struct fields are declared as const char * for the external API, but - // we know that the strings were duplicated and need to be freed. - // endpoint->config.name is the same as endpoint->stats.name, no need to - // free it. - NATS_FREE((char *)endpoint->stats.name); - NATS_FREE((char *)endpoint->stats.subject); - - NATS_FREE(endpoint); -} - static void _handleEndpointRequest(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { natsStatus s = NATS_OK; - natsMicroserviceEndpoint *endpoint = (natsMicroserviceEndpoint *)closure; - // natsMicroserviceEndpointStats *stats = &endpoint->stats; - natsMicroserviceEndpointConfig *cfg = &endpoint->config; + natsMicroserviceEndpoint *ep = (natsMicroserviceEndpoint *)closure; + // natsMicroserviceEndpointStats *stats = &ep->stats; + natsMicroserviceEndpointConfig *cfg = ep->config; natsMicroserviceRequest *req = NULL; natsMicroserviceRequestHandler handler = cfg->handler; - void *handlerClosure = cfg->closure; - // const char *errorString = ""; if (handler == NULL) { @@ -248,57 +250,71 @@ _handleEndpointRequest(natsConnection *nc, natsSubscription *sub, natsMsg *msg, return; } - s = _newRequest(&req, msg); + s = _newMicroserviceRequest(&req, msg); if (s == NATS_OK) { - handler(endpoint->m, req, handlerClosure); - // errorString = req->err; - } - else - { - // errorString = natsStatus_GetText(s); + req->ep = ep; + handler(ep->m, req); } // Update stats - // natsMutex_Lock(endpoint->mu); + // natsMutex_Lock(ep->mu); // stats->numRequests++; - // natsMutex_Unlock(endpoint->mu); + // natsMutex_Unlock(ep->mu); - _freeRequest(req); + _freeMicroserviceRequest(req); natsMsg_Destroy(msg); } static natsStatus -_newRequest(natsMicroserviceRequest **new_request, natsMsg *msg) +_newStats(natsMicroserviceEndpointStats **new_stats, const char *name, const char *subject) { natsStatus s = NATS_OK; - natsMicroserviceRequest *req = NULL; + natsMicroserviceEndpointStats *stats = NULL; - req = (natsMicroserviceRequest *)NATS_CALLOC(1, sizeof(natsMicroserviceRequest)); - s = (req != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); + stats = (natsMicroserviceEndpointStats *)NATS_CALLOC(1, sizeof(natsMicroserviceEndpointStats)); + s = (stats != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); if (s == NATS_OK) { - req->msg = msg; - *new_request = req; + stats->name = name; + stats->subject = subject; + *new_stats = stats; return NATS_OK; } - _freeRequest(req); + NATS_FREE(stats); return NATS_UPDATE_ERR_STACK(s); } -static void -_freeRequest(natsMicroserviceRequest *req) +// All locking is to be done by the caller. +static natsStatus +_setLastError(natsMicroserviceEndpointStats *stats, const char *error) { - if (req == NULL) - return; + natsStatus s = NATS_OK; + + if (stats->last_error != NULL) + NATS_FREE(stats->last_error); + + if (nats_IsStringEmpty(error)) + { + stats->last_error = NULL; + return NATS_OK; + } + + stats->last_error = NATS_STRDUP(error); + s = (stats->last_error != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); - NATS_FREE(req->err); - NATS_FREE(req); + return NATS_UPDATE_ERR_STACK(s); } -natsMsg* -natsMicroserviceRequest_GetMsg(natsMicroserviceRequest *req) +static void +_freeStats(natsMicroserviceEndpointStats *stats) { - return req != NULL ? req->msg : NULL; + if (stats == NULL) + return; + + if (stats->last_error != NULL) + NATS_FREE(stats->last_error); + + NATS_FREE(stats); } diff --git a/src/micro_endpoint_list.c b/src/micro_endpoint_list.c new file mode 100644 index 000000000..191e5a721 --- /dev/null +++ b/src/micro_endpoint_list.c @@ -0,0 +1,163 @@ +// Copyright 2021-2023 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "micro.h" +#include "microp.h" +#include "mem.h" + + +natsStatus + _destroyMicroserviceEndpointList(__microserviceEndpointList *list) +{ + natsStatus s = NATS_OK; + if (list == NULL) + return NATS_OK; + + for (int i = 0; i < list->len; i++) + { + s = natsMicroserviceEndpoint_Destroy(list->endpoints[i]); + if (s != NATS_OK) + { + return NATS_UPDATE_ERR_STACK(s); + } + } + + natsMutex_Destroy(list->mu); + NATS_FREE(list->endpoints); + NATS_FREE(list); + return NATS_OK; +} + +natsStatus +_newMicroserviceEndpointList(__microserviceEndpointList **new_list) +{ + natsStatus s = NATS_OK; + __microserviceEndpointList *list = NULL; + + list = NATS_CALLOC(1, sizeof(__microserviceEndpointList)); + if (list == NULL) + { + s = nats_setDefaultError(NATS_NO_MEMORY); + } + IFOK(s, natsMutex_Create(&list->mu)); + + if (s == NATS_OK) + { + *new_list = list; + } + else + { + _destroyMicroserviceEndpointList(list); + } + return NATS_UPDATE_ERR_STACK(s); +} + +natsMicroserviceEndpoint * +_microserviceEndpointList_Find(__microserviceEndpointList *list, const char *name, int *index) +{ + natsMicroserviceEndpoint *ep = NULL; + int i; + + natsMutex_Lock(list->mu); + for (i = 0; i < list->len; i++) + { + if (strcmp(list->endpoints[i]->name, name) == 0) + { + if (index != NULL) + { + *index = i; + } + ep = list->endpoints[i]; + break; + } + } + natsMutex_Unlock(list->mu); + return ep; +} + +natsMicroserviceEndpoint * +_microserviceEndpointList_Get(__microserviceEndpointList *list, const char *name, int *index) +{ + natsMicroserviceEndpoint *ep = NULL; + int i; + + natsMutex_Lock(list->mu); + for (i = 0; i < list->len; i++) + { + if (strcmp(list->endpoints[i]->name, name) == 0) + { + if (index != NULL) + { + *index = i; + } + ep = list->endpoints[i]; + break; + } + } + natsMutex_Unlock(list->mu); + return ep; +} + + +natsStatus +_microserviceEndpointList_Put(__microserviceEndpointList *list, int index, natsMicroserviceEndpoint *ep) +{ + natsStatus s = NATS_OK; + + natsMutex_Lock(list->mu); + + if (index >= list->len) + { + return nats_setDefaultError(NATS_INVALID_ARG); + } + else if (index == -1) + { + list->endpoints = (natsMicroserviceEndpoint **) + NATS_REALLOC(list->endpoints, sizeof(natsMicroserviceEndpoint *) * (list->len + 1)); + if (list->endpoints == NULL) + { + s = nats_setDefaultError(NATS_NO_MEMORY); + } + if (s == NATS_OK) + { + list->endpoints[list->len] = ep; + list->len++; + } + } + else + { + list->endpoints[index] = ep; + } + + natsMutex_Unlock(list->mu); + return NATS_UPDATE_ERR_STACK(s); +} + +natsStatus +_microserviceEndpointList_Remove(__microserviceEndpointList *list, int index) +{ + natsMutex_Lock(list->mu); + + if (index >= list->len || index < 0) + { + return nats_setDefaultError(NATS_INVALID_ARG); + } + if (index < list->len - 1) + { + memmove(&list->endpoints[index], &list->endpoints[index + 1], (list->len - index - 1) * sizeof(natsMicroserviceEndpoint *)); + } + list->len--; + + natsMutex_Unlock(list->mu); + return NATS_OK; +} diff --git a/src/micro_error.c b/src/micro_error.c new file mode 100644 index 000000000..865c15091 --- /dev/null +++ b/src/micro_error.c @@ -0,0 +1,130 @@ +// Copyright 2015-2018 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "micro.h" +#include "microp.h" +#include "mem.h" + +static natsMicroserviceError _errorOutOfMemory = { + .status = NATS_NO_MEMORY, + .code = 500, + .description = "Out of memory", +}; + +static natsMicroserviceError _errorInvalidArg = { + .status = NATS_INVALID_ARG, + .code = 400, + .description = "Invalid function argument", +}; + +static natsMicroserviceError *knownErrors[] = { + &_errorOutOfMemory, + &_errorInvalidArg, + NULL, +}; + +natsMicroserviceError *natsMicroserviceErrorOutOfMemory = &_errorOutOfMemory; +natsMicroserviceError *natsMicroserviceErrorInvalidArg = &_errorInvalidArg; + +static const char * +_string(natsMicroserviceError *err, char *buf, int size) { + if (err == NULL || buf == NULL) + return ""; + if (err->status == NATS_OK) + snprintf(buf, size, "%d: %s", err->code, err->description); + else + snprintf(buf, size, "%d:%d: %s", err->status, err->code, err->description); + return buf; +} + +natsMicroserviceError * +nats_NewMicroserviceError(natsStatus s, int code, const char *description) +{ + natsMicroserviceError *err = NULL; + + if (s == NATS_OK) + return NULL; + + err = NATS_CALLOC(1, sizeof(natsMicroserviceError)); + if (err == NULL) + return &_errorOutOfMemory; + + err->status = s; + err->code = code; + err->description = NATS_STRDUP(description); // it's ok if NULL + err->String = _string; + return err; +} + +void natsMicroserviceError_Destroy(natsMicroserviceError *err) +{ + int i; + + if (err == NULL) + return; + + for (i = 0; knownErrors[i] != NULL; i++) + { + if (err == knownErrors[i]) + return; + } + + // description is declared const for the users, but is strdup-ed on + // creation. + NATS_FREE((void *)err->description); + NATS_FREE(err); +} + +natsMicroserviceError * +nats_MicroserviceErrorFromMsg(natsStatus status, natsMsg *msg) +{ + natsMicroserviceError *err = NULL; + const char *c = NULL, *d = NULL; + bool is_error; + int code = 0; + + if (msg == NULL) + return NULL; + + natsMsgHeader_Get(msg, NATS_MICROSERVICE_ERROR_CODE_HDR, &c); + natsMsgHeader_Get(msg, NATS_MICROSERVICE_ERROR_HDR, &d); + + is_error = (status != NATS_OK) || !nats_IsStringEmpty(c) || !nats_IsStringEmpty(d); + if (!is_error) + return NULL; + + err = nats_NewMicroserviceError(status, 0, ""); + if (err == NULL) + { + // This is not 100% correct - returning an OOM error that was not in the + // message, but since it is usually a fatal condition, it is ok. + return &_errorOutOfMemory; + } + + if (nats_IsStringEmpty(d) && (status != NATS_OK)) + { + d = natsStatus_GetText(status); + if (d == NULL) + d = ""; + } + + if (!nats_IsStringEmpty(c)) + { + code = atoi(c); + } + err->status = status; + err->code = code; + err->description = d; + + return err; +} diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 395f9eae8..fd8d2f50b 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -13,9 +13,9 @@ #include -#include "natsp.h" -#include "mem.h" +#include "micro.h" #include "microp.h" +#include "mem.h" static void _handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); @@ -32,7 +32,37 @@ static natsStatus _newControlSubject(char **newSubject, natsMicroserviceVerb ver static natsStatus _newDottedSubject(char **new_subject, int count, ...); static bool _isEmpty(const char *s); -natsStatus _initMicroserviceMonitoring(natsMicroservice *m) { +static natsStatus +natsMicroserviceVerb_String(const char **new_subject, natsMicroserviceVerb verb) +{ + if (new_subject == NULL) + return nats_setDefaultError(NATS_INVALID_ARG); + + switch (verb) + { + case natsMicroserviceVerbPing: + *new_subject = "PING"; + return NATS_OK; + + case natsMicroserviceVerbStats: + *new_subject = "STATS"; + return NATS_OK; + + case natsMicroserviceVerbInfo: + *new_subject = "INFO"; + return NATS_OK; + + case natsMicroserviceVerbSchema: + *new_subject = "SCHEMA"; + return NATS_OK; + default: + return nats_setError(NATS_INVALID_ARG, "Invalid microservice verb %d", verb); + } +} + +natsStatus +_initMicroserviceMonitoring(natsMicroservice *m) +{ natsStatus s = NATS_OK; IFOK(s, _addVerbHandlers(m, natsMicroserviceVerbPing, _handleMicroservicePing)); @@ -53,12 +83,9 @@ _handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, s = _marshalPing(&buf, m); if (s == NATS_OK) { - s = natsMicroservice_Respond(m, &req, natsBuf_Data(buf), natsBuf_Len(buf)); - } - if (buf != NULL) - { - natsBuf_Destroy(buf); + natsMicroserviceRequest_Respond(&req, natsBuf_Data(buf), natsBuf_Len(buf)); } + natsBuf_Destroy(buf); } static bool diff --git a/src/micro_request.c b/src/micro_request.c new file mode 100644 index 000000000..c3b38fdd6 --- /dev/null +++ b/src/micro_request.c @@ -0,0 +1,131 @@ +// Copyright 2015-2018 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "micro.h" +#include "microp.h" +#include "mem.h" + +natsMsg * +natsMicroserviceRequest_GetMsg(natsMicroserviceRequest *req) +{ + return req != NULL ? req->msg : NULL; +} + +natsMicroserviceEndpoint * +natsMicroserviceRequest_GetEndpoint(natsMicroserviceRequest *req) +{ + return (req != NULL) ? req->ep : NULL; +} + +natsMicroservice * +natsMicroserviceRequest_GetMicroservice(natsMicroserviceRequest *req) +{ + return ((req != NULL) && (req->ep != NULL)) ? req->ep->m : NULL; +} + +natsConnection * +natsMicroserviceRequest_GetConnection(natsMicroserviceRequest *req) +{ + return ((req != NULL) && (req->ep != NULL) && (req->ep->m != NULL)) ? req->ep->m->nc : NULL; +} + +natsMicroserviceError * +natsMicroserviceRequest_Respond(natsMicroserviceRequest *req, const char *data, int len) +{ + natsStatus s = NATS_OK; + + if ((req == NULL) || (req->msg == NULL) || (req->msg->sub == NULL) || (req->msg->sub->conn == NULL)) + return natsMicroserviceErrorInvalidArg; + + s = natsConnection_Publish(req->msg->sub->conn, natsMsg_GetReply(req->msg), data, len); + if (s == NATS_OK) + return NULL; + else + return nats_NewMicroserviceError(s, 500, "failed to respond to message"); +} + +natsMicroserviceError * +natsMicroserviceRequest_RespondError(natsMicroserviceRequest *req, natsMicroserviceError *err, const char *data, int len) +{ + natsMsg *msg = NULL; + natsStatus s = NATS_OK; + char buf[64]; + + if ((req == NULL) || (req->msg == NULL) || (req->msg->sub == NULL) || (req->msg->sub->conn == NULL) || (err == NULL)) + { + return natsMicroserviceErrorInvalidArg; + } + + IFOK(s, natsMsg_Create(&msg, natsMsg_GetReply(req->msg), NULL, data, len)); + + if ((s == NATS_OK) && (err->status != NATS_OK)) + { + s = natsMsgHeader_Set(msg, NATS_MICROSERVICE_STATUS_HDR, natsStatus_GetText(err->status)); + } + if (s == NATS_OK) + { + s = natsMsgHeader_Set(msg, NATS_MICROSERVICE_ERROR_HDR, err->description); + } + if (s == NATS_OK) + { + snprintf(buf, sizeof(buf), "%d", err->code); + s = natsMsgHeader_Set(msg, NATS_MICROSERVICE_ERROR_CODE_HDR, buf); + } + IFOK(s, natsConnection_PublishMsg(req->msg->sub->conn, msg)); + + natsMsg_Destroy(msg); + + if (s == NATS_OK) + return NULL; + else + return nats_NewMicroserviceError(s, 500, "failed to respond to message"); +} + +void +natsMicroserviceRequest_Error(natsMicroserviceRequest *req, natsMicroserviceError **err) +{ + if (err == NULL) + return; + natsMicroserviceRequest_RespondError(req, *err, NULL, 0); + natsMicroserviceError_Destroy(*err); + *err = NULL; +} + + +void _freeMicroserviceRequest(natsMicroserviceRequest *req) +{ + if (req == NULL) + return; + + NATS_FREE(req->err); + NATS_FREE(req); +} + +natsStatus +_newMicroserviceRequest(natsMicroserviceRequest **new_request, natsMsg *msg) +{ + natsStatus s = NATS_OK; + natsMicroserviceRequest *req = NULL; + + req = (natsMicroserviceRequest *)NATS_CALLOC(1, sizeof(natsMicroserviceRequest)); + s = (req != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); + if (s == NATS_OK) + { + req->msg = msg; + *new_request = req; + return NATS_OK; + } + + _freeMicroserviceRequest(req); + return NATS_UPDATE_ERR_STACK(s); +} diff --git a/src/microp.h b/src/microp.h index b6a02868f..80987bd48 100644 --- a/src/microp.h +++ b/src/microp.h @@ -15,7 +15,6 @@ #define MICROP_H_ #include "natsp.h" -#include "micro.h" #define natsMicroserviceInfoResponseType "io.nats.micro.v1.info_response" #define natsMicroservicePingResponseType "io.nats.micro.v1.ping_response" @@ -26,13 +25,126 @@ #define natsMicroserviceDefaultEndpointName "default" -extern natsStatus +struct __microserviceClient +{ + natsConnection *nc; +}; + +struct __microserviceConfig +{ + const char *name; + const char *version; + const char *description; +}; + +struct __microserviceIdentity +{ + const char *name; + const char *version; + char *id; +}; + +typedef struct __microserviceEndpointList +{ + natsMutex *mu; + int len; + struct __microserviceEndpoint **endpoints; +} __microserviceEndpointList; + +struct __microserviceEndpoint +{ + // The name of the endpoint, uniquely identifies the endpoint. The name "" + // is reserved for the top level endpoint of the service. + char *name; + + // Indicates if the endpoint is stopped, or is actively subscribed to a + // subject. + bool stopped; + + // The subject that the endpoint is listening on. The subject is also used + // as the prefix for the children endpoints. + char *subject; + natsSubscription *sub; + + // Children endpoints. Their subscriptions are prefixed with the parent's + // subject. Their stats are summarized in the parent's stats when requested. + __microserviceEndpointList *children; + int len_children; + + // Endpoint stats. These are initialized only for running endpoints, and are + // cleared if the endpoint is stopped. + natsMutex *stats_mu; + natsMicroserviceEndpointStats *stats; + + // References to other entities. + natsMicroservice *m; + natsMicroserviceEndpointConfig *config; +}; + +struct __microservice +{ + natsConnection *nc; + int refs; + natsMutex *mu; + struct __microserviceIdentity identity; + struct natsMicroserviceConfig *cfg; + bool stopped; + struct __microserviceEndpoint *root; +}; + +struct __microserviceRequest +{ + natsMsg *msg; + struct __microserviceEndpoint *ep; + char *err; + void *closure; +}; + +typedef struct __args_t +{ + void **args; + int count; +} __args_t; + +extern natsMicroserviceError *natsMicroserviceErrorOutOfMemory; +extern natsMicroserviceError *natsMicroserviceErrorInvalidArg; + +natsStatus _initMicroserviceMonitoring(natsMicroservice *m); -extern natsStatus -_stopMicroserviceEndpoint(natsMicroserviceEndpoint *endpoint); -extern void -_freeMicroserviceEndpoint(natsMicroserviceEndpoint *endpoint); +natsStatus +_newMicroserviceEndpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg); + +natsStatus +natsMicroserviceEndpoint_AddEndpoint(natsMicroserviceEndpoint **new_ep, natsMicroserviceEndpoint *parent, const char *name, natsMicroserviceEndpointConfig *cfg); + +natsStatus +natsMicroserviceEndpoint_Start(natsMicroserviceEndpoint *ep); + +natsStatus +natsMicroserviceEndpoint_Stop(natsMicroserviceEndpoint *ep); + +natsStatus +natsMicroserviceEndpoint_Destroy(natsMicroserviceEndpoint *ep); + +natsStatus +_destroyMicroserviceEndpointList(__microserviceEndpointList *list); + +natsStatus +_newMicroserviceEndpointList(__microserviceEndpointList **new_list); + +natsMicroserviceEndpoint * +_microserviceEndpointList_Find(__microserviceEndpointList *list, const char *name, int *index); + +natsStatus +_microserviceEndpointList_Put(__microserviceEndpointList *list, int index, natsMicroserviceEndpoint *ep); + +natsStatus +_microserviceEndpointList_Remove(__microserviceEndpointList *list, int index); + +natsStatus +_newMicroserviceRequest(natsMicroserviceRequest **new_request, natsMsg *msg); +void _freeMicroserviceRequest(natsMicroserviceRequest *req); #endif /* MICROP_H_ */ diff --git a/src/nats.h b/src/nats.h index b2ec5290b..5b53a29a5 100644 --- a/src/nats.h +++ b/src/nats.h @@ -1158,81 +1158,6 @@ typedef struct jsOptions } jsOptions; -/** - * The Microservice object. Initialize with #nats_AddMicroservice. - * TODO document the interface. - */ -typedef struct __microservice natsMicroservice; - -/** - * The Microservice request object. - * TODO document the interface. - */ -typedef struct __microserviceRequest natsMicroserviceRequest; - -/** - * The Microservice endpoint object. - * TODO document the interface. - */ -typedef struct __microserviceEndpoint natsMicroserviceEndpoint; - -/** \brief Callback used to deliver messages to a microservice. - * - * This is the callback that one provides when creating a microservice endpoint. - * The library will invoke this callback for each message arriving to the - * specified subject. - * - * @see natsMicroservice_AddEndpoint() - * @see natsMicroserviceEndpoint_AddEndpoint() - */ -typedef void (*natsMicroserviceRequestHandler)(natsMicroservice *m, natsMicroserviceRequest *req, void *closure); - -/** - * The Microservice schema object. - */ -typedef struct natsMicroserviceSchema -{ - const char *request; - const char *response; -} natsMicroserviceSchema; - -/** - * The Microservice endpoint configuration object. - */ -typedef struct natsMicroserviceEndpointConfig -{ - const char *subject; - natsMicroserviceRequestHandler handler; - void *closure; - natsMicroserviceSchema *schema; -} natsMicroserviceEndpointConfig; - -/** - * The Microservice endpoint stats struct. - */ -typedef struct natsMicroserviceEndpointStats -{ - char *name; - char *subject; - int num_requests; - int num_errors; - char *last_error; - int processing_time_ms; - int average_processing_time_ms; - bool stopped; -} natsMicroserviceEndpointStats; - -/** - * The Microservice configuration object. Initialize with #natsMicroserviceConfig_Init. - */ -typedef struct natsMicroserviceConfig -{ - const char *name; - const char *version; - const char *description; - natsMicroserviceEndpointConfig *endpoint; -} natsMicroserviceConfig; - /** * The KeyValue store object. */ @@ -7177,112 +7102,6 @@ kvStatus_Destroy(kvStatus *sts); /** @} */ // end of kvGroup -/** \defgroup microserviceGroup Microservice support - * - * A simple NATS-based microservice implementation framework. - * - * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without - * necessarily bumping the major version of the library. - * - * @{ - */ - -// /** \brief Initializes a Microservice configuration structure. -// * -// * Use this before setting specific #natsMicroserviceConfig options and passing it -// * to #nats_AddMicroservice. -// * -// * @see nats_AddMicroservice -// * -// * @param cfg the pointer to the stack variable #natsMicroserviceConfig to -// * initialize. -// */ -// NATS_EXTERN natsStatus -// natsMicroserviceConfig_Init(natsMicroserviceConfig *cfg); - -/** \brief Adds a microservice with a given configuration. - * - * Adds a microservice with a given configuration. - * - * \note The return #natsMicroservice object needs to be destroyed using - * #natsMicroservice_Destroy when no longer needed to free allocated memory. - * - * @param new_microservice the location where to store the newly created - * #natsMicroservice object. - * @param nc the pointer to the #natsCOnnection object on which the service will listen on. - * @param cfg the pointer to the #natsMicroserviceConfig configuration - * information used to create the #natsMicroservice object. - */ -NATS_EXTERN natsStatus -nats_AddMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg); - -/** \brief Waits for the microservice to stop. - */ -NATS_EXTERN natsStatus -natsMicroservice_Run(natsMicroservice *m); - -/** \brief Stops a running microservice. - */ -NATS_EXTERN natsStatus -natsMicroservice_Stop(natsMicroservice *m); - -/** \brief Checks if a microservice is stopped. - */ -NATS_EXTERN bool -natsMicroservice_IsStopped(natsMicroservice *m); - -/** \brief Destroys a microservice object. - * - * Destroys a microservice object; frees all memory. The service must be stopped - * first, this function does not check if it is. - */ -NATS_EXTERN void -natsMicroservice_Destroy(natsMicroservice *m); - -/** \brief Adds (and starts) a microservice endpoint. - */ -NATS_EXTERN natsStatus -natsMicroservice_AddEndpoint(natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg); - -/** \brief Adds (and starts) a microservice endpoint. - */ -NATS_EXTERN natsStatus -natsMicroservice_Respond(natsMicroservice *m, natsMicroserviceRequest *r, const char *data, int len); - -/** \brief Returns the original NATS message underlying the request. - */ -NATS_EXTERN natsMsg* -natsMicroserviceRequest_GetMsg(natsMicroserviceRequest *req); - -#define natsMicroserviceRequestHeader_Set(req, key, value) \ - natsMsgHeader_Set(natsMicroserviceRequest_GetMsg(req), (key), (value)) -#define natsMicroserviceRequestHeader_Add(req, key, value) \ - natsMsgHeader_Add(natsMicroserviceRequest_GetMsg(req), (key), (value)) -#define natsMicroserviceRequestHeader_Get(req, key, value) \ - natsMsgHeader_Get(natsMicroserviceRequest_GetMsg(req), (key), (value)) -#define natsMicroserviceRequestHeader_Values(req, key, values, count) \ - natsMsgHeader_Values(natsMicroserviceRequest_GetMsg(req), (key), (values), (count)) -#define natsMicroserviceRequestHeader_Keys(req, key, keys, count) \ - natsMsgHeader_Keys(natsMicroserviceRequest_GetMsg(req), (key), (keys), (count)) -#define natsMicroserviceRequestHeader_Delete(req, key) \ - natsMsgHeader_Delete(natsMicroserviceRequest_GetMsg(req), (key)) - -#define natsMicroserviceRequest_GetSubject(req) \ - natsMsg_GetSubject(natsMicroserviceRequest_GetMsg(req)) -#define natsMicroserviceRequest_GetReply(req) \ - natsMsg_GetReply(natsMicroserviceRequest_GetMsg(req)) -#define natsMicroserviceRequest_GetData(req) \ - natsMsg_GetData(natsMicroserviceRequest_GetMsg(req)) -#define natsMicroserviceRequest_GetDataLength(req) \ - natsMsg_GetDataLength(natsMicroserviceRequest_GetMsg(req)) -#define natsMicroserviceRequest_GetSequence(req) \ - natsMsg_GetSequence(natsMicroserviceRequest_GetMsg(req)) -#define natsMicroserviceRequest_GetTime(req) \ - natsMsg_GetTime(natsMicroserviceRequest_GetMsg(req)) - -/** @} */ // end of microserviceGroup - - /** @} */ // end of funcGroup /** \defgroup wildcardsGroup Wildcards diff --git a/src/natsp.h b/src/natsp.h index acef7ecf7..21c71e8a4 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -132,6 +132,8 @@ #endif #define IFOK(s, c) if (s == NATS_OK) { s = (c); } +// TODO <>/<> do I really need it? +#define IFOK_DO(s, c) if (s == NATS_OK) { c; } #define NATS_MILLIS_TO_NANOS(d) (((int64_t)d)*(int64_t)1E6) #define NATS_SECONDS_TO_NANOS(d) (((int64_t)d)*(int64_t)1E9) @@ -432,47 +434,6 @@ typedef struct __jsSub } jsSub; -struct __microserviceConfig -{ - const char *name; - const char *version; - const char *description; -}; - -struct __microserviceIdentity -{ - const char *name; - const char *version; - char *id; -}; - -struct __microservice -{ - natsMutex *mu; - natsConnection *nc; - int refs; - struct __microserviceIdentity identity; - struct natsMicroserviceConfig cfg; - bool stopped; - struct __microserviceEndpoint **endpoints; - int num_endpoints; -}; - -struct __microserviceRequest -{ - natsMsg *msg; - char *err; -}; - -struct __microserviceEndpoint -{ - natsMicroservice *m; - bool stopped; - natsMicroserviceEndpointConfig config; - natsMicroserviceEndpointStats stats; - natsSubscription *sub; -}; - struct __kvStore { natsMutex *mu; From a443b1ef1be80a52cdfe79c5e2dc8ca1df6c9a99 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 20 Mar 2023 05:45:20 -0700 Subject: [PATCH 07/85] Added support for info, stats, natsError --- examples/examples.h | 1 + examples/microservice.c | 191 +++++---- src/micro.c | 421 ++++++++++++++------ src/micro.h | 266 +++++-------- src/{micro_args_parser.c => micro_args.c} | 63 +-- src/micro_args.h | 45 +++ src/micro_client.c | 17 +- src/micro_endpoint.c | 317 +++++++-------- src/micro_endpoint_list.c | 163 -------- src/micro_error.c | 209 +++++++--- src/micro_monitoring.c | 459 +++++++++++++++++----- src/micro_request.c | 43 +- src/microp.h | 130 +++--- src/natsp.h | 2 - 14 files changed, 1326 insertions(+), 1001 deletions(-) rename src/{micro_args_parser.c => micro_args.c} (82%) create mode 100644 src/micro_args.h delete mode 100644 src/micro_endpoint_list.c diff --git a/examples/examples.h b/examples/examples.h index 4a765830c..d321bb465 100644 --- a/examples/examples.h +++ b/examples/examples.h @@ -16,6 +16,7 @@ #include #include +#include #include #include diff --git a/examples/microservice.c b/examples/microservice.c index 0617b6b14..994d4e9b4 100644 --- a/examples/microservice.c +++ b/examples/microservice.c @@ -29,18 +29,18 @@ static void *run_sequence(void *closure); // a generic service runner, used by the services' "main" functions. static void *run_service(natsConnection *conn, natsMicroserviceConfig *svc, - natsMicroserviceEndpointConfig **endpoints, const char **ep_names, int len_endpoints); + natsMicroserviceEndpointConfig **endpoints, int len_endpoints); // Callers and handlers for operations (2 floating point args, and a single int arg). -static natsMicroserviceError * - call_arithmetics(long double *result, natsConnection *nc, const char *subject, long double a1, long double a2); -typedef natsMicroserviceError *(*arithmeticsOP)( +static natsError * +call_arithmetics(long double *result, natsConnection *nc, const char *subject, long double a1, long double a2); +typedef natsError *(*arithmeticsOP)( long double *result, natsConnection *conn, long double a1, long double a2); static void handle_arithmetics_op(natsMicroserviceRequest *req, arithmeticsOP op); -static natsMicroserviceError * - call_function(long double *result, natsConnection *nc, const char *subject, int n); -typedef natsMicroserviceError *(*functionOP)(long double *result, natsConnection *conn, int n); +static natsError * +call_function(long double *result, natsConnection *nc, const char *subject, int n); +typedef natsError *(*functionOP)(long double *result, natsConnection *conn, int n); static void handle_function_op(natsMicroserviceRequest *req, functionOP op); // Stop handler is the same for all services. @@ -50,23 +50,23 @@ static void handle_stop(natsMicroservice *m, natsMicroserviceRequest *req); static void handle_sequence(natsMicroservice *m, natsMicroserviceRequest *req); // Math operations, wrapped as handlers. -static natsMicroserviceError *add(long double *result, natsConnection *nc, long double a1, long double a2); -static natsMicroserviceError *divide(long double *result, natsConnection *nc, long double a1, long double a2); -static natsMicroserviceError *multiply(long double *result, natsConnection *nc, long double a1, long double a2); +static natsError *add(long double *result, natsConnection *nc, long double a1, long double a2); +static natsError *divide(long double *result, natsConnection *nc, long double a1, long double a2); +static natsError *multiply(long double *result, natsConnection *nc, long double a1, long double a2); static void handle_add(natsMicroservice *m, natsMicroserviceRequest *req) { handle_arithmetics_op(req, add); } static void handle_divide(natsMicroservice *m, natsMicroserviceRequest *req) { handle_arithmetics_op(req, divide); } static void handle_multiply(natsMicroservice *m, natsMicroserviceRequest *req) { handle_arithmetics_op(req, multiply); } -static natsMicroserviceError *factorial(long double *result, natsConnection *nc, int n); -static natsMicroserviceError *fibonacci(long double *result, natsConnection *nc, int n); -static natsMicroserviceError *power2(long double *result, natsConnection *nc, int n); +static natsError *factorial(long double *result, natsConnection *nc, int n); +static natsError *fibonacci(long double *result, natsConnection *nc, int n); +static natsError *power2(long double *result, natsConnection *nc, int n); static void handle_factorial(natsMicroservice *m, natsMicroserviceRequest *req) { handle_function_op(req, factorial); } static void handle_fibonacci(natsMicroservice *m, natsMicroserviceRequest *req) { handle_function_op(req, fibonacci); } static void handle_power2(natsMicroservice *m, natsMicroserviceRequest *req) { handle_function_op(req, power2); } static natsMicroserviceEndpointConfig stop_cfg = { - .subject = "stop", + .name = "stop", .handler = handle_stop, .closure = &fakeClosure, .schema = NULL, @@ -151,14 +151,14 @@ static void *run_sequence(void *closure) }; natsMicroserviceEndpointConfig sequence_cfg = { .subject = "sequence", + .name = "sequence-service", .handler = handle_sequence, .closure = &fakeClosure, .schema = NULL, }; natsMicroserviceEndpointConfig *endpoints[] = {&sequence_cfg}; - const char *names[] = {"sequence"}; - return run_service(conn, &cfg, endpoints, names, 1); + return run_service(conn, &cfg, endpoints, 1); } // calculates the sum of X/f(1) + X/f(2)... up to N (included). The inputs are X @@ -167,9 +167,9 @@ static void *run_sequence(void *closure) // 10/512 + 10/1024 = 20.998046875 static void handle_sequence(natsMicroservice *m, natsMicroserviceRequest *req) { - natsMicroserviceError *err = NULL; + natsError *err = NULL; natsConnection *nc = natsMicroservice_GetConnection(m); - natsArgs *args = NULL; + natsMicroserviceArgs *args = NULL; int n = 0; int i; const char *function; @@ -178,36 +178,32 @@ static void handle_sequence(natsMicroservice *m, natsMicroserviceRequest *req) long double denominator = 0; char result[64]; - err = natsParseAsArgs(&args, natsMicroserviceRequest_GetData(req), natsMicroserviceRequest_GetDataLength(req)); + err = nats_ParseMicroserviceArgs(&args, natsMicroserviceRequest_GetData(req), natsMicroserviceRequest_GetDataLength(req)); if ((err == NULL) && - (natsArgs_Count(args) != 2)) + (natsMicroserviceArgs_Count(args) != 2)) { - err = nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "Invalid number of arguments"); + err = nats_Errorf(400, "Invalid number of arguments, expected 2 got %d", natsMicroserviceArgs_Count(args)); } if (err == NULL) { - err = natsArgs_GetString(&function, args, 0); + err = natsMicroserviceArgs_GetString(&function, args, 0); } if (err == NULL) { - err = natsArgs_GetInt(&n, args, 1); + err = natsMicroserviceArgs_GetInt(&n, args, 1); } if ((err == NULL) && (strcmp(function, "factorial") != 0) && (strcmp(function, "power2") != 0) && (strcmp(function, "fibonacci") != 0)) { - err = nats_NewMicroserviceError( - NATS_INVALID_ARG, 400, - "Invalid function name, must be 'factorial', 'power2', or 'fibonacci'"); + err = nats_Errorf(400, "Invalid function name '%s', must be 'factorial', 'power2', or 'fibonacci'", function); } if ((err == NULL) && (n < 1)) { - err = nats_NewMicroserviceError( - NATS_INVALID_ARG, 400, - "Invalid number of iterations, must be at least 1"); + err = nats_Errorf(400, "Invalid number of iterations %d, must be at least 1", n); } for (i = 1; (err == NULL) && (i <= n); i++) @@ -215,7 +211,7 @@ static void handle_sequence(natsMicroservice *m, natsMicroserviceRequest *req) err = call_function(&denominator, nc, function, i); if (err == NULL && denominator == 0) { - err = nats_NewMicroserviceError(0, 500, "division by zero"); + err = nats_Errorf(500, "division by zero at step %d", i); } if (err == NULL) { @@ -227,13 +223,12 @@ static void handle_sequence(natsMicroservice *m, natsMicroserviceRequest *req) { snprintf(result, sizeof(result), "%Lf", value); err = natsMicroserviceRequest_Respond(req, result, strlen(result)); - } - - if (err != NULL) + } + else { - natsMicroserviceRequest_Error(req, &err); + natsMicroserviceRequest_Error(req, err); } - natsArgs_Destroy(args); + natsMicroserviceArgs_Destroy(args); } static void *run_arithmetics(void *closure) @@ -245,29 +240,27 @@ static void *run_arithmetics(void *closure) .version = "1.0.0", }; natsMicroserviceEndpointConfig add_cfg = { - .subject = "add", + .name = "add", .handler = handle_add, .closure = &fakeClosure, .schema = NULL, }; natsMicroserviceEndpointConfig divide_cfg = { - .subject = "divide", + .name = "divide", .handler = handle_divide, .closure = &fakeClosure, .schema = NULL, }; natsMicroserviceEndpointConfig multiply_cfg = { - .subject = "multiply", + .name = "multiply", .handler = handle_multiply, .closure = &fakeClosure, .schema = NULL, }; natsMicroserviceEndpointConfig *endpoints[] = {&add_cfg, ÷_cfg, &multiply_cfg}; - const char *names[] = - {"add", "divide", "multiply"}; - return run_service(conn, &cfg, endpoints, names, 3); + return run_service(conn, &cfg, endpoints, 3); } static void *run_functions(void *closure) @@ -279,52 +272,50 @@ static void *run_functions(void *closure) .version = "1.0.0", }; natsMicroserviceEndpointConfig factorial_cfg = { - .subject = "factorial", + .name = "factorial", .handler = handle_factorial, .closure = &fakeClosure, .schema = NULL, }; natsMicroserviceEndpointConfig fibonacci_cfg = { - .subject = "fibonacci", + .name = "fibonacci", .handler = handle_fibonacci, .closure = &fakeClosure, .schema = NULL, }; natsMicroserviceEndpointConfig power2_cfg = { - .subject = "power2", + .name = "power2", .handler = handle_power2, .closure = &fakeClosure, .schema = NULL, }; natsMicroserviceEndpointConfig *endpoints[] = {&factorial_cfg, &fibonacci_cfg, &power2_cfg}; - const char *names[] = - {"factorial", "fibonacci", "power2"}; - return run_service(conn, &cfg, endpoints, names, 3); + return run_service(conn, &cfg, endpoints, 3); } static void handle_arithmetics_op(natsMicroserviceRequest *req, arithmeticsOP op) { - natsMicroserviceError *err = NULL; - natsArgs *args = NULL; + natsError *err = NULL; + natsMicroserviceArgs *args = NULL; long double a1, a2, result; char buf[1024]; int len; - err = natsParseAsArgs(&args, natsMicroserviceRequest_GetData(req), natsMicroserviceRequest_GetDataLength(req)); - if ((err == NULL) && (natsArgs_Count(args) != 2)) + err = nats_ParseMicroserviceArgs(&args, natsMicroserviceRequest_GetData(req), natsMicroserviceRequest_GetDataLength(req)); + if ((err == NULL) && (natsMicroserviceArgs_Count(args) != 2)) { - err = nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "Invalid number of arguments, must be 2"); + err = nats_Errorf(400, "Invalid number of arguments, expected 2 got %d", natsMicroserviceArgs_Count(args)); } if (err == NULL) { - err = natsArgs_GetFloat(&a1, args, 0); + err = natsMicroserviceArgs_GetFloat(&a1, args, 0); } if (err == NULL) { - err = natsArgs_GetFloat(&a2, args, 1); + err = natsMicroserviceArgs_GetFloat(&a2, args, 1); } if (err == NULL) { @@ -335,32 +326,31 @@ handle_arithmetics_op(natsMicroserviceRequest *req, arithmeticsOP op) len = snprintf(buf, sizeof(buf), "%Lf", result); err = natsMicroserviceRequest_Respond(req, buf, len); } - - if (err != NULL) + else { - natsMicroserviceRequest_Error(req, &err); + natsMicroserviceRequest_Error(req, err); } - natsArgs_Destroy(args); + natsMicroserviceArgs_Destroy(args); } static void handle_function_op(natsMicroserviceRequest *req, functionOP op) { - natsMicroserviceError *err = NULL; - natsArgs *args = NULL; + natsError *err = NULL; + natsMicroserviceArgs *args = NULL; int n; long double result; char buf[1024]; int len; - err = natsParseAsArgs(&args, natsMicroserviceRequest_GetData(req), natsMicroserviceRequest_GetDataLength(req)); - if ((err == NULL) && (natsArgs_Count(args) != 1)) + err = nats_ParseMicroserviceArgs(&args, natsMicroserviceRequest_GetData(req), natsMicroserviceRequest_GetDataLength(req)); + if ((err == NULL) && (natsMicroserviceArgs_Count(args) != 1)) { - err = nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "Invalid number of arguments, must be 1"); + err = nats_Errorf(400, "Invalid number of arguments, expected 1 got %d", natsMicroserviceArgs_Count(args)); } if (err == NULL) { - err = natsArgs_GetInt(&n, args, 0); + err = natsMicroserviceArgs_GetInt(&n, args, 0); } if (err == NULL) { @@ -371,21 +361,20 @@ handle_function_op(natsMicroserviceRequest *req, functionOP op) len = snprintf(buf, sizeof(buf), "%Lf", result); err = natsMicroserviceRequest_Respond(req, buf, len); } - - if (err != NULL) + else { - natsMicroserviceRequest_Error(req, &err); + natsMicroserviceRequest_Error(req, err); } - natsArgs_Destroy(args); + natsMicroserviceArgs_Destroy(args); } -static natsMicroserviceError * +static natsError * call_arithmetics(long double *result, natsConnection *nc, const char *subject, long double a1, long double a2) { - natsMicroserviceError *err = NULL; + natsError *err = NULL; natsMicroserviceClient *client = NULL; natsMsg *response = NULL; - natsArgs *args = NULL; + natsMicroserviceArgs *args = NULL; char buf[1024]; int len; @@ -397,11 +386,11 @@ call_arithmetics(long double *result, natsConnection *nc, const char *subject, l } if (err == NULL) { - err = natsParseAsArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response)); + err = nats_ParseMicroserviceArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response)); } if (err == NULL) { - err = natsArgs_GetFloat(result, args, 0); + err = natsMicroserviceArgs_GetFloat(result, args, 0); } natsMicroserviceClient_Destroy(client); @@ -409,13 +398,13 @@ call_arithmetics(long double *result, natsConnection *nc, const char *subject, l return err; } -static natsMicroserviceError * +static natsError * call_function(long double *result, natsConnection *nc, const char *subject, int n) { - natsMicroserviceError *err = NULL; + natsError *err = NULL; natsMicroserviceClient *client = NULL; natsMsg *response = NULL; - natsArgs *args = NULL; + natsMicroserviceArgs *args = NULL; char buf[1024]; int len; @@ -427,11 +416,11 @@ call_function(long double *result, natsConnection *nc, const char *subject, int } if (err == NULL) { - err = natsParseAsArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response)); + err = nats_ParseMicroserviceArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response)); } if (err == NULL) { - err = natsArgs_GetFloat(result, args, 0); + err = natsMicroserviceArgs_GetFloat(result, args, 0); } natsMicroserviceClient_Destroy(client); @@ -441,7 +430,7 @@ call_function(long double *result, natsConnection *nc, const char *subject, int static void handle_stop(natsMicroservice *m, natsMicroserviceRequest *req) { - natsMicroserviceError *err; + natsError *err; err = natsMicroservice_Stop(m); if (err == NULL) @@ -455,15 +444,15 @@ static void handle_stop(natsMicroservice *m, natsMicroserviceRequest *req) } else { - natsMicroserviceRequest_Error(req, &err); - pthread_exit((void *)(err->status)); + natsMicroserviceRequest_Error(req, err); + pthread_exit((void *)(natsError_StatusCode(err))); } } static void *run_service(natsConnection *conn, natsMicroserviceConfig *svc, - natsMicroserviceEndpointConfig **endpoints, const char **ep_names, int len_endpoints) + natsMicroserviceEndpointConfig **endpoints, int len_endpoints) { - natsMicroserviceError *err = NULL; + natsError *err = NULL; natsMicroservice *m = NULL; char errbuf[1024]; int i; @@ -471,7 +460,7 @@ static void *run_service(natsConnection *conn, natsMicroserviceConfig *svc, err = nats_AddMicroservice(&m, conn, svc); for (i = 0; (err == NULL) && (i < len_endpoints); i++) { - err = natsMicroservice_AddEndpoint(NULL, m, ep_names[i], endpoints[i]); + err = natsMicroservice_AddEndpoint(NULL, m, endpoints[i]); if (err != NULL) { break; @@ -479,50 +468,52 @@ static void *run_service(natsConnection *conn, natsMicroserviceConfig *svc, } if (err == NULL) { - err = natsMicroservice_AddEndpoint(NULL, m, "stop", &stop_cfg); + // TODO <>/<>: add a way to subscribe to broadcast messages, this one is + // queued. + err = natsMicroservice_AddEndpoint(NULL, m, &stop_cfg); } if (err == NULL) { err = natsMicroservice_Run(m); } - natsMicroservice_Destroy(m); + natsMicroservice_Release(m); if (err != NULL) { - printf("Error: %s\n", err->String(err, errbuf, sizeof(errbuf))); - return (void *)(err->status); + printf("Error: %s\n", natsError_String(err, errbuf, sizeof(errbuf))); + return (void *)(natsError_StatusCode(err)); } return (void *)NATS_OK; } -static natsMicroserviceError * +static natsError * add(long double *result, natsConnection *nc, long double a1, long double a2) { *result = a1 + a2; return NULL; } -static natsMicroserviceError * +static natsError * divide(long double *result, natsConnection *nc, long double a1, long double a2) { *result = a1 / a2; return NULL; } -static natsMicroserviceError *multiply(long double *result, natsConnection *nc, long double a1, long double a2) +static natsError *multiply(long double *result, natsConnection *nc, long double a1, long double a2) { *result = a1 * a2; return NULL; } -static natsMicroserviceError * +static natsError * factorial(long double *result, natsConnection *nc, int n) { - natsMicroserviceError *err = NULL; + natsError *err = NULL; int i; if (n < 1) - return nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "n must be greater than 0"); + err = nats_Errorf(400, "n=%d. must be greater than 0", n); *result = 1; for (i = 1; i <= n; i++) @@ -534,15 +525,15 @@ factorial(long double *result, natsConnection *nc, int n) return NULL; } -static natsMicroserviceError * +static natsError * fibonacci(long double *result, natsConnection *nc, int n) { - natsMicroserviceError *err = NULL; + natsError *err = NULL; int i; long double n1, n2; if (n < 0) - return nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "n must be greater than 0"); + err = nats_Errorf(400, "n=%d. must be non-negative", n); if (n < 2) { @@ -561,13 +552,13 @@ fibonacci(long double *result, natsConnection *nc, int n) return NULL; } -static natsMicroserviceError *power2(long double *result, natsConnection *nc, int n) +static natsError *power2(long double *result, natsConnection *nc, int n) { - natsMicroserviceError *err = NULL; + natsError *err = NULL; int i; if (n < 1) - return nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "n must be greater than 0"); + return nats_Errorf(400, "n=%d. must be greater than 0", n); *result = 1; for (i = 1; i <= n; i++) diff --git a/src/micro.c b/src/micro.c index 2259c4aa4..e10f6a3e5 100644 --- a/src/micro.c +++ b/src/micro.c @@ -17,50 +17,115 @@ #include "conn.h" #include "mem.h" -static natsMicroserviceError * -_createMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg); -static void -_retainMicroservice(natsMicroservice *m); -static natsMicroserviceError * -_releaseMicroservice(natsMicroservice *m); -static void -_markMicroserviceStopped(natsMicroservice *m, bool stopped); - -natsMicroserviceError * +static natsError * +stop_and_destroy_microservice(natsMicroservice *m); + +natsError * nats_AddMicroservice(natsMicroservice **new_m, natsConnection *nc, natsMicroserviceConfig *cfg) { if ((new_m == NULL) || (nc == NULL) || (cfg == NULL)) return natsMicroserviceErrorInvalidArg; - return _createMicroservice(new_m, nc, cfg); + natsStatus s = NATS_OK; + natsError *err = NULL; + natsMicroservice *m = NULL; + + // Make a microservice object, with a reference to a natsConnection. + m = (natsMicroservice *)NATS_CALLOC(1, sizeof(natsMicroservice)); + if (m == NULL) + return natsMicroserviceErrorOutOfMemory; + + natsConn_retain(nc); + m->nc = nc; + m->refs = 1; + m->started = nats_Now() * 1000000; + + IFOK(s, NATS_CALLOCS(&m->id, 1, NUID_BUFFER_LEN + 1)) + IFOK(s, natsMutex_Create(&m->mu)); + IFOK(s, natsNUID_Next(m->id, NUID_BUFFER_LEN + 1)); + IFOK(s, nats_clone_microservice_config(&m->cfg, cfg)); + if ((s == NATS_OK) && (cfg->endpoint != NULL)) + { + err = natsMicroservice_AddEndpoint(NULL, m, cfg->endpoint); + if (err != NULL) + { + // status is always set in AddEndpoint errors. + s = err->status; + } + } + + // Set up monitoring (PING, INFO, STATS, etc.) responders. + IFOK(s, micro_monitoring_init(m)); + + if (s == NATS_OK) + { + *new_m = m; + return NULL; + } + else + { + stop_and_destroy_microservice(m); + return natsError_Wrapf(nats_NewStatusError(s), "failed to create microservice"); + } } -natsMicroserviceError * -natsMicroservice_Destroy(natsMicroservice *m) +natsError * +natsMicroservice_Release(natsMicroservice *m) { - return _releaseMicroservice(m); + bool doFree; + + if (m == NULL) + return NULL; + + natsMutex_Lock(m->mu); + m->refs--; + doFree = (m->refs == 0); + natsMutex_Unlock(m->mu); + + if (!doFree) + return NULL; + + return stop_and_destroy_microservice(m); +} + +natsMicroservice * +natsMicroservice_Retain(natsMicroservice *m) +{ + natsMutex_Lock(m->mu); + m->refs++; + natsMutex_Unlock(m->mu); + return m; } // TODO <>/<> update from Go -natsMicroserviceError * +natsError * natsMicroservice_Stop(natsMicroservice *m) { natsStatus s = NATS_OK; + int i; if (m == NULL) return natsMicroserviceErrorInvalidArg; if (natsMicroservice_IsStopped(m)) return NULL; - s = natsMicroserviceEndpoint_Stop(m->root); + for (i = 0; (s == NATS_OK) && (i < m->endpoints_len); i++) + { + s = nats_stop_endpoint(m->endpoints[i]); + } + if (s == NATS_OK) { - _markMicroserviceStopped(m, true); + natsMutex_Lock(m->mu); + m->stopped = true; + m->started = 0; + natsMutex_Unlock(m->mu); + return NULL; } else { - return nats_NewMicroserviceError(NATS_UPDATE_ERR_STACK(s), 500, "failed to stop microservice"); + return natsError_Wrapf(nats_NewStatusError(s), "failed to stop microservice"); } } @@ -78,7 +143,7 @@ bool natsMicroservice_IsStopped(natsMicroservice *m) } // TODO: <>/<> eliminate sleep -natsMicroserviceError * +natsError * natsMicroservice_Run(natsMicroservice *m) { if ((m == NULL) || (m->mu == NULL)) @@ -100,143 +165,279 @@ natsMicroservice_GetConnection(natsMicroservice *m) return m->nc; } -natsMicroserviceError * -natsMicroservice_AddEndpoint(natsMicroserviceEndpoint **new_ep, natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg) +natsError * +natsMicroservice_AddEndpoint(natsMicroserviceEndpoint **new_ep, natsMicroservice *m, natsMicroserviceEndpointConfig *cfg) { natsStatus s = NATS_OK; - if ((m == NULL) || (m->root == NULL)) + int index = -1; + int new_cap; + natsMicroserviceEndpoint *ep = NULL; + + if ((m == NULL) || (cfg == NULL)) { return natsMicroserviceErrorInvalidArg; } - s = natsMicroserviceEndpoint_AddEndpoint(new_ep, m->root, name, cfg); + s = nats_new_endpoint(&ep, m, cfg); + if (s != NATS_OK) + return natsError_Wrapf(nats_NewStatusError(s), "failed to create endpoint"); + + // see if we already have an endpoint with this name + for (int i = 0; i < m->endpoints_len; i++) + { + if (strcmp(m->endpoints[i]->config->name, cfg->name) == 0) + { + index = i; + break; + } + } + + // if not, grow the array as needed. + if (index == -1) + { + if (m->endpoints_len == m->endpoints_cap) + { + new_cap = m->endpoints_cap * 2; + if (new_cap == 0) + new_cap = 4; + natsMicroserviceEndpoint **new_eps = (natsMicroserviceEndpoint **)NATS_CALLOC(new_cap, sizeof(natsMicroserviceEndpoint *)); + if (new_eps == NULL) + return natsMicroserviceErrorOutOfMemory; + for (int i = 0; i < m->endpoints_len; i++) + new_eps[i] = m->endpoints[i]; + NATS_FREE(m->endpoints); + m->endpoints = new_eps; + m->endpoints_cap = new_cap; + } + index = m->endpoints_len; + m->endpoints_len++; + } + + // add the endpoint. + m->endpoints[index] = ep; + + s = nats_start_endpoint(ep); if (s == NATS_OK) + { + if (new_ep != NULL) + *new_ep = ep; return NULL; + } else - return nats_NewMicroserviceError(NATS_UPDATE_ERR_STACK(s), 500, "failed to add endpoint"); + { + nats_stop_and_destroy_endpoint(ep); + return natsError_Wrapf(nats_NewStatusError(NATS_UPDATE_ERR_STACK(s)), "failed to start endpoint"); + } +} + +natsStatus +natsMicroservice_Info(natsMicroserviceInfo **new_info, natsMicroservice *m) +{ + natsMicroserviceInfo *info = NULL; + int i; + int len = 0; + + if ((new_info == NULL) || (m == NULL) || (m->mu == NULL)) + return NATS_INVALID_ARG; + + info = NATS_CALLOC(1, sizeof(natsMicroserviceInfo)); + if (info == NULL) + return NATS_NO_MEMORY; + + info->name = m->cfg->name; + info->version = m->cfg->version; + info->description = m->cfg->description; + info->id = m->id; + info->type = natsMicroserviceInfoResponseType; + + info->subjects = NATS_CALLOC(m->endpoints_len, sizeof(char *)); + if (info->subjects == NULL) + { + NATS_FREE(info); + return NATS_NO_MEMORY; + } + + natsMutex_Lock(m->mu); + for (i = 0; i < m->endpoints_len; i++) + { + natsMicroserviceEndpoint *ep = m->endpoints[i]; + if ((ep != NULL) && (ep->subject != NULL)) + { + info->subjects[len] = ep->subject; + len++; + } + } + info->subjects_len = len; + natsMutex_Unlock(m->mu); + + *new_info = info; + return NATS_OK; +} + +void natsMicroserviceInfo_Destroy(natsMicroserviceInfo *info) +{ + if (info == NULL) + return; + + // subjects themselves must not be freed, just the collection. + NATS_FREE(info->subjects); + NATS_FREE(info); } -static natsMicroserviceError * -_destroyMicroservice(natsMicroservice *m) +natsStatus +natsMicroservice_Stats(natsMicroserviceStats **new_stats, natsMicroservice *m) { + natsMicroserviceStats *stats = NULL; + int i; + int len = 0; + long double avg = 0.0; + + if ((new_stats == NULL) || (m == NULL) || (m->mu == NULL)) + return NATS_INVALID_ARG; + + stats = NATS_CALLOC(1, sizeof(natsMicroserviceStats)); + if (stats == NULL) + return NATS_NO_MEMORY; + + stats->name = m->cfg->name; + stats->version = m->cfg->version; + stats->id = m->id; + stats->started = m->started; + stats->type = natsMicroserviceStatsResponseType; + + // allocate the actual structs, not pointers. + stats->endpoints = NATS_CALLOC(m->endpoints_len, sizeof(natsMicroserviceEndpointStats)); + if (stats->endpoints == NULL) + { + NATS_FREE(stats); + return NATS_NO_MEMORY; + } + + natsMutex_Lock(m->mu); + for (i = 0; i < m->endpoints_len; i++) + { + natsMicroserviceEndpoint *ep = m->endpoints[i]; + if ((ep != NULL) && (ep->mu != NULL)) + { + natsMutex_Lock(ep->mu); + // copy the entire struct, including the last error buffer. + stats->endpoints[len] = ep->stats; + + stats->endpoints[len].name = ep->config->name; + stats->endpoints[len].subject = ep->subject; + avg = (long double)ep->stats.processing_time_s * 1000000000.0 + (long double)ep->stats.processing_time_ns; + avg = avg / (long double)ep->stats.num_requests; + stats->endpoints[len].average_processing_time_ns = (int64_t)avg; + len++; + natsMutex_Unlock(ep->mu); + } + } + natsMutex_Unlock(m->mu); + stats->endpoints_len = len; + + *new_stats = stats; + return NATS_OK; +} + +void natsMicroserviceStats_Destroy(natsMicroserviceStats *stats) +{ + if (stats == NULL) + return; + + NATS_FREE(stats->endpoints); + NATS_FREE(stats); +} + +static natsError * +stop_and_destroy_microservice(natsMicroservice *m) +{ + natsError *err = NULL; natsStatus s = NATS_OK; + int i; - if ((m == NULL) || (m->root == NULL)) + if (m == NULL) return NULL; - s = natsMicroserviceEndpoint_Destroy(m->root); - if (s != NATS_OK) + err = natsMicroservice_Stop(m); + if (err != NULL) + return err; + + for (i = 0; i < m->endpoints_len; i++) { - return nats_NewMicroserviceError(NATS_UPDATE_ERR_STACK(s), 500, "failed to destroy microservice"); + s = nats_stop_and_destroy_endpoint(m->endpoints[i]); + if (s != NATS_OK) + return natsError_Wrapf(nats_NewStatusError(s), "failed to stop and destroy endpoint"); } + nats_free_cloned_microservice_config(m->cfg); natsConn_release(m->nc); natsMutex_Destroy(m->mu); - NATS_FREE(m->identity.id); + NATS_FREE(m->id); NATS_FREE(m); return NULL; } -static natsMicroserviceError * -_createMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg) +natsStatus +nats_clone_microservice_config(natsMicroserviceConfig **out, natsMicroserviceConfig *cfg) { natsStatus s = NATS_OK; - natsMicroserviceError *err = NULL; - natsMicroservice *m = NULL; - natsMutex *mu = NULL; - char tmpNUID[NUID_BUFFER_LEN + 1]; + natsMicroserviceConfig *new_cfg = NULL; + natsMicroserviceEndpointConfig *new_ep = NULL; + char *new_name = NULL; + char *new_version = NULL; + char *new_description = NULL; - // Make a microservice object, with a reference to a natsConnection. - m = (natsMicroservice *)NATS_CALLOC(1, sizeof(natsMicroservice)); - if (m == NULL) - return natsMicroserviceErrorOutOfMemory; + if (out == NULL || cfg == NULL) + return NATS_INVALID_ARG; - // TODO: <>/<> separate PR, make a natsConn_retain return a natsConnection* - natsConn_retain(nc); - m->nc = nc; - m->refs = 1; - - // Need a mutex for concurrent operations. - s = natsMutex_Create(&mu); - if (s == NATS_OK) + s = NATS_CALLOCS(&new_cfg, 1, sizeof(natsMicroserviceConfig)); + if (s == NATS_OK && cfg->name != NULL) { - m->mu = mu; + DUP_STRING(s, new_name, cfg->name); } - - // Generate a unique ID for this microservice. - IFOK(s, natsNUID_Next(tmpNUID, NUID_BUFFER_LEN + 1)); - IF_OK_DUP_STRING(s, m->identity.id, tmpNUID); - - // Copy the config data. - if (s == NATS_OK) + if (s == NATS_OK && cfg->version != NULL) { - m->identity.name = cfg->name; - m->identity.version = cfg->version; - m->cfg = cfg; + DUP_STRING(s, new_version, cfg->version); } - - // Setup the root endpoint. - if (s == NATS_OK) + if (s == NATS_OK && cfg->description != NULL) { - s = _newMicroserviceEndpoint(&m->root, m, "", NULL); + DUP_STRING(s, new_description, cfg->description); } - - // Set up the default endpoint. if (s == NATS_OK && cfg->endpoint != NULL) { - err = natsMicroservice_AddEndpoint(NULL, m, natsMicroserviceDefaultEndpointName, cfg->endpoint); - if (err != NULL) - { - // status is always set in AddEndpoint errors. - s = err->status; - } + s = nats_clone_endpoint_config(&new_ep, cfg->endpoint); } - - // Set up monitoring (PING, STATS, etc.) responders. - IFOK(s, _initMicroserviceMonitoring(m)); - if (s == NATS_OK) { - *new_microservice = m; - return NULL; + memcpy(new_cfg, cfg, sizeof(natsMicroserviceConfig)); + new_cfg->name = new_name; + new_cfg->version = new_version; + new_cfg->description = new_description; + new_cfg->endpoint = new_ep; + *out = new_cfg; } else { - _destroyMicroservice(m); - return nats_NewMicroserviceError(NATS_UPDATE_ERR_STACK(s), 500, "failed to create microservice"); + NATS_FREE(new_cfg); + NATS_FREE(new_name); + NATS_FREE(new_version); + NATS_FREE(new_description); + nats_free_cloned_endpoint_config(new_ep); } -} - -static void -_retainMicroservice(natsMicroservice *m) -{ - natsMutex_Lock(m->mu); - m->refs++; - natsMutex_Unlock(m->mu); -} - -static natsMicroserviceError * -_releaseMicroservice(natsMicroservice *m) -{ - bool doFree; - - if (m == NULL) - return NULL; - natsMutex_Lock(m->mu); - doFree = (--(m->refs) == 0); - natsMutex_Unlock(m->mu); - - if (!doFree) - return NULL; - - return _destroyMicroservice(m); + return NATS_UPDATE_ERR_STACK(s); } -static void -_markMicroserviceStopped(natsMicroservice *m, bool stopped) +void nats_free_cloned_microservice_config(natsMicroserviceConfig *cfg) { - natsMutex_Lock(m->mu); - m->stopped = stopped; - natsMutex_Unlock(m->mu); + if (cfg == NULL) + return; + + // the strings are declared const for the public, but in a clone these need + // to be freed. + NATS_FREE((char *)cfg->name); + NATS_FREE((char *)cfg->version); + NATS_FREE((char *)cfg->description); + nats_free_cloned_endpoint_config(cfg->endpoint); + NATS_FREE(cfg); } diff --git a/src/micro.h b/src/micro.h index 26d715aff..b3fdb7cd0 100644 --- a/src/micro.h +++ b/src/micro.h @@ -18,46 +18,33 @@ #define natsMicroserviceAPIPrefix "$SRV" +#define natsMicroserviceInfoResponseType "io.nats.micro.v1.info_response" +#define natsMicroservicePingResponseType "io.nats.micro.v1.ping_response" +#define natsMicroserviceStatsResponseType "io.nats.micro.v1.stats_response" +#define natsMicroserviceSchemaResponseType "io.nats.micro.v1.schema_response" + +#define natsMicroservicePingVerb "PING" +#define natsMicroserviceStatsVerb "STATS" +#define natsMicroserviceInfoVerb "INFO" +#define natsMicroserviceSchemaVerb "SCHEMA" + #define NATS_MICROSERVICE_STATUS_HDR "Nats-Status" #define NATS_MICROSERVICE_ERROR_HDR "Nats-Service-Error" #define NATS_MICROSERVICE_ERROR_CODE_HDR "Nats-Service-Error-Code" -enum natsMicroserviceVerb -{ - natsMicroserviceVerbPing = 0, - natsMicroserviceVerbStats, - natsMicroserviceVerbInfo, - natsMicroserviceVerbSchema, - natsMicroserviceVerbMax -}; - -typedef enum natsMicroserviceVerb natsMicroserviceVerb; - -/** - * The Microservice error. Note that code and description do not have a - * well-defined lifespan and should be copied if needed to be used later. - */ -typedef struct natsMicroserviceError -{ - natsStatus status; - int code; - const char *description; - - const char *(*String)(struct natsMicroserviceError *err, char *buf, int size); -} natsMicroserviceError; - /** * The Microservice request object. */ -typedef struct __microserviceRequest natsMicroserviceRequest; +typedef struct microservice_request_s natsMicroserviceRequest; /** * The Microservice object. Create and start with #nats_AddMicroservice. */ -typedef struct __microservice natsMicroservice; +typedef struct microservice_s natsMicroservice; /** - * The Microservice configuration object. + * The Microservice configuration object. The service holds on to it, so it must + * be constant for the lifetime of the service. */ typedef struct natsMicroserviceConfig { @@ -67,11 +54,22 @@ typedef struct natsMicroserviceConfig struct natsMicroserviceEndpointConfig *endpoint; } natsMicroserviceConfig; +typedef struct natsMicroserviceInfo +{ + const char *type; + const char *name; + const char *version; + const char *description; + const char *id; + const char **subjects; + int subjects_len; +} natsMicroserviceInfo; + /** * The Microservice endpoint object. * TODO document the interface. */ -typedef struct __microserviceEndpoint natsMicroserviceEndpoint; +typedef struct microservice_endpoint_s natsMicroserviceEndpoint; /** \brief Callback used to deliver messages to a microservice. * @@ -88,9 +86,10 @@ typedef void (*natsMicroserviceRequestHandler)(natsMicroservice *m, natsMicroser */ typedef struct natsMicroserviceEndpointConfig { - const char *subject; + const char *name; natsMicroserviceRequestHandler handler; void *closure; + const char *subject; struct natsMicroserviceSchema *schema; } natsMicroserviceEndpointConfig; @@ -101,14 +100,28 @@ typedef struct natsMicroserviceEndpointStats { const char *name; const char *subject; - int num_requests; - int num_errors; - char *last_error; - int processing_time_ms; - int average_processing_time_ms; - bool stopped; + int64_t num_requests; + int64_t num_errors; + int64_t processing_time_s; + int64_t processing_time_ns; + int64_t average_processing_time_ns; + char last_error_string[2048]; } natsMicroserviceEndpointStats; +/** + * The Microservice stats struct. + */ +typedef struct natsMicroserviceStats +{ + const char *type; + const char *name; + const char *version; + const char *id; + int64_t started; + int endpoints_len; + natsMicroserviceEndpointStats *endpoints; +} natsMicroserviceStats; + /** * The Microservice endpoint schema object. */ @@ -118,120 +131,39 @@ typedef struct natsMicroserviceSchema const char *response; } natsMicroserviceSchema; -/** - * Request unmarshaled as "arguments", a space-separated list of numbers and strings. - * TODO document the interface. - */ -typedef struct __args_t natsArgs; - /** * The Microservice client. Initialize with #nats_NewMicroserviceClient. */ -typedef struct __microserviceClient natsMicroserviceClient; +typedef struct microservice_client_s natsMicroserviceClient; /** - * The Microservice configuration object. Initialize with #natsMicroserviceConfig_Init. - */ -typedef struct natsMicroserviceClientConfig -{ - // TBD in the future. - int dummy; -} natsMicroserviceClientConfig; - -/** \defgroup microserviceGroup Microservice support - * - * A simple NATS-based microservice implementation framework. - * - * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without - * necessarily bumping the major version of the library. - * - * @{ - */ - -/** \brief Makes a new error. - * - * @param new_microservice the location where to store the newly created - * #natsMicroservice object. - * @param nc the pointer to the #natsCOnnection object on which the service will listen on. - * @param cfg the pointer to the #natsMicroserviceConfig configuration - * information used to create the #natsMicroservice object. - */ -NATS_EXTERN natsMicroserviceError * -nats_NewMicroserviceError(natsStatus s, int code, const char *desc); - -NATS_EXTERN void -natsMicroserviceError_Destroy(natsMicroserviceError *err); - -/** \brief Adds a microservice with a given configuration. - * - * Adds a microservice with a given configuration. - * - * \note The return #natsMicroservice object needs to be destroyed using - * #natsMicroservice_Destroy when no longer needed to free allocated memory. - * - * @param new_microservice the location where to store the newly created - * #natsMicroservice object. - * @param nc the pointer to the #natsCOnnection object on which the service will listen on. - * @param cfg the pointer to the #natsMicroserviceConfig configuration - * information used to create the #natsMicroservice object. - */ -NATS_EXTERN natsMicroserviceError * -nats_AddMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg); - -/** \brief Waits for the microservice to stop. - */ -NATS_EXTERN natsMicroserviceError * -natsMicroservice_Run(natsMicroservice *m); - -/** \brief Stops a running microservice. - */ -NATS_EXTERN natsMicroserviceError * -natsMicroservice_Stop(natsMicroservice *m); - -/** \brief Checks if a microservice is stopped. - */ -NATS_EXTERN bool -natsMicroservice_IsStopped(natsMicroservice *m); - -/** \brief Destroys a microservice object. - * - * Destroys a microservice object; frees all memory. The service must be stopped - * first, this function does not check if it is. - */ -NATS_EXTERN natsMicroserviceError * -natsMicroservice_Destroy(natsMicroservice *m); - -/** \brief Adds (and starts) a microservice endpoint. + * The Microservice configuration object. */ -NATS_EXTERN natsMicroserviceError * -natsMicroservice_AddEndpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg); +typedef struct natsMicroserviceClientConfig natsMicroserviceClientConfig; -/** \brief Returns the microservice's NATS connection. - */ -NATS_EXTERN natsConnection * -natsMicroservice_GetConnection(natsMicroservice *m); +typedef struct error_s natsError; -/** \brief Responds to a microservice request. - */ -NATS_EXTERN natsMicroserviceError * -natsMicroserviceRequest_Respond(natsMicroserviceRequest *req, const char *data, int len); +// +// natsMicroservice methods. -/** \brief Responds to a microservice request with an error, does NOT free the - * error. - */ -NATS_EXTERN natsMicroserviceError * -natsMicroserviceRequest_RespondError(natsMicroserviceRequest *req, natsMicroserviceError *err, const char *data, int len); +NATS_EXTERN natsError *nats_AddMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg); +NATS_EXTERN natsError *natsMicroservice_AddEndpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, natsMicroserviceEndpointConfig *cfg); +NATS_EXTERN natsConnection *natsMicroservice_GetConnection(natsMicroservice *m); +NATS_EXTERN bool natsMicroservice_IsStopped(natsMicroservice *m); +NATS_EXTERN natsError *natsMicroservice_Release(natsMicroservice *m); +NATS_EXTERN natsError *natsMicroservice_Run(natsMicroservice *m); +NATS_EXTERN natsError *natsMicroservice_Stop(natsMicroservice *m); -/** \brief A convenience method to respond to a microservice request with a - * simple error (no data), and free the error. - */ -NATS_EXTERN void -natsMicroserviceRequest_Error(natsMicroserviceRequest *req, natsMicroserviceError **err); +// +// natsMicroserviceRequest methods. -/** \brief Returns the original NATS message underlying the request. - */ -NATS_EXTERN natsMsg * -natsMicroserviceRequest_GetMsg(natsMicroserviceRequest *req); +NATS_EXTERN void natsMicroserviceRequest_Error(natsMicroserviceRequest *req, natsError *err); +NATS_EXTERN natsConnection *natsMicroserviceRequest_GetConnection(natsMicroserviceRequest *req); +NATS_EXTERN natsMicroserviceEndpoint *natsMicroserviceRequest_GetEndpoint(natsMicroserviceRequest *req); +NATS_EXTERN natsMicroservice *natsMicroserviceRequest_GetMicroservice(natsMicroserviceRequest *req); +NATS_EXTERN natsMsg *natsMicroserviceRequest_GetMsg(natsMicroserviceRequest *req); +NATS_EXTERN natsError *natsMicroserviceRequest_Respond(natsMicroserviceRequest *req, const char *data, int len); +NATS_EXTERN natsError *natsMicroserviceRequest_RespondError(natsMicroserviceRequest *req, natsError *err, const char *data, int len); #define natsMicroserviceRequestHeader_Set(req, key, value) \ natsMsgHeader_Set(natsMicroserviceRequest_GetMsg(req), (key), (value)) @@ -259,44 +191,38 @@ natsMicroserviceRequest_GetMsg(natsMicroserviceRequest *req); #define natsMicroserviceRequest_GetTime(req) \ natsMsg_GetTime(natsMicroserviceRequest_GetMsg(req)) -NATS_EXTERN natsMicroserviceEndpoint * -natsMicroserviceRequest_GetEndpoint(natsMicroserviceRequest *req); - -NATS_EXTERN natsMicroservice * -natsMicroserviceRequest_GetMicroservice(natsMicroserviceRequest *req); - -NATS_EXTERN natsConnection * -natsMicroserviceRequest_GetConnection(natsMicroserviceRequest *req); - -NATS_EXTERN natsMicroserviceError * -nats_MicroserviceErrorFromMsg(natsStatus s, natsMsg *msg); - -NATS_EXTERN natsMicroserviceError * -natsParseAsArgs(natsArgs **args, const char *data, int data_len); - -NATS_EXTERN int -natsArgs_Count(natsArgs *args); - -NATS_EXTERN natsMicroserviceError * -natsArgs_GetInt(int *val, natsArgs *args, int index); +// +// natsError methods. + +NATS_EXTERN natsError *nats_Errorf(int code, const char *format, ...); +NATS_EXTERN natsError *nats_IsErrorResponse(natsStatus s, natsMsg *msg); +NATS_EXTERN natsError *nats_NewError(int code, const char *desc); +NATS_EXTERN natsError *nats_NewStatusError(natsStatus s); +NATS_EXTERN void natsError_Destroy(natsError *err); +NATS_EXTERN natsStatus natsError_ErrorCode(natsError *err); +NATS_EXTERN natsStatus natsError_StatusCode(natsError *err); +NATS_EXTERN const char *natsError_String(natsError *err, char *buf, int len); +NATS_EXTERN natsError *natsError_Wrapf(natsError *err, const char *format, ...); -NATS_EXTERN natsMicroserviceError * -natsArgs_GetFloat(long double *val, natsArgs *args, int index); +// +// natsClient methods. -NATS_EXTERN natsMicroserviceError * -natsArgs_GetString(const char **val, natsArgs *args, int index); +natsError *nats_NewMicroserviceClient(natsMicroserviceClient **new_client, natsConnection *nc, natsMicroserviceClientConfig *cfg); +void natsMicroserviceClient_Destroy(natsMicroserviceClient *client); +natsError * +natsMicroserviceClient_DoRequest(natsMicroserviceClient *client, natsMsg **reply, const char *subject, const char *data, int data_len); -NATS_EXTERN void -natsArgs_Destroy(natsArgs *args); +// +// natsMicroserviceInfo methods. -natsMicroserviceError * -nats_NewMicroserviceClient(natsMicroserviceClient **new_client, natsConnection *nc, natsMicroserviceClientConfig *cfg); +natsStatus natsMicroservice_Info(natsMicroserviceInfo **new_info, natsMicroservice *m); +void natsMicroserviceInfo_Destroy(natsMicroserviceInfo *info); -void -natsMicroserviceClient_Destroy(natsMicroserviceClient *client); +// +// natsMicroserviceStats methods. -natsMicroserviceError * -natsMicroserviceClient_DoRequest(natsMicroserviceClient *client, natsMsg **reply, const char *subject, const char *data, int data_len); +natsStatus natsMicroservice_Stats(natsMicroserviceStats **new_stats, natsMicroservice *m); +void natsMicroserviceStats_Destroy(natsMicroserviceStats *stats); /** @} */ // end of microserviceGroup diff --git a/src/micro_args_parser.c b/src/micro_args.c similarity index 82% rename from src/micro_args_parser.c rename to src/micro_args.c index f3743087e..03508c274 100644 --- a/src/micro_args_parser.c +++ b/src/micro_args.c @@ -13,26 +13,33 @@ #include "micro.h" #include "microp.h" +#include "micro_args.h" #include "mem.h" -static natsMicroserviceError *parse(void **args, int *args_len, int *i, const char *data, int data_len); +struct args_s +{ + void **args; + int count; +}; + +static natsError *parse(void **args, int *args_len, int *i, const char *data, int data_len); -natsMicroserviceError * -natsParseAsArgs(natsArgs **new_args, const char *data, int data_len) +natsError * +nats_ParseMicroserviceArgs(natsMicroserviceArgs **new_args, const char *data, int data_len) { - natsMicroserviceError *err = NULL; + natsError *err = NULL; int n; int i = 0; - natsArgs *args = NULL; + natsMicroserviceArgs *args = NULL; if ((new_args == NULL) || (data == NULL) || (data_len < 0)) - return nats_NewMicroserviceError(NATS_INVALID_ARG, 500, "Invalid function argument"); + return nats_NewError(500, "invalid function argument"); // parse the number of arguments without allocating. err = parse(NULL, &n, &i, data, data_len); if (err == NULL) { - args = NATS_CALLOC(1, sizeof(natsArgs)); + args = NATS_CALLOC(1, sizeof(natsMicroserviceArgs)); if (args == NULL) err = natsMicroserviceErrorOutOfMemory; } @@ -56,13 +63,13 @@ natsParseAsArgs(natsArgs **new_args, const char *data, int data_len) } else { - natsArgs_Destroy(args); + natsMicroserviceArgs_Destroy(args); } return err; } -void natsArgs_Destroy(natsArgs *args) +void natsMicroserviceArgs_Destroy(natsMicroserviceArgs *args) { int i; @@ -77,7 +84,7 @@ void natsArgs_Destroy(natsArgs *args) NATS_FREE(args); } -int natsArgs_Count(natsArgs *args) +int natsMicroserviceArgs_Count(natsMicroserviceArgs *args) { if (args == NULL) return 0; @@ -85,8 +92,8 @@ int natsArgs_Count(natsArgs *args) return args->count; } -natsMicroserviceError * -natsArgs_GetInt(int *val, natsArgs *args, int index) +natsError * +natsMicroserviceArgs_GetInt(int *val, natsMicroserviceArgs *args, int index) { if ((args == NULL) || (index < 0) || (index >= args->count) || (val == NULL)) return natsMicroserviceErrorInvalidArg; @@ -95,8 +102,8 @@ natsArgs_GetInt(int *val, natsArgs *args, int index) return NULL; } -natsMicroserviceError * -natsArgs_GetFloat(long double *val, natsArgs *args, int index) +natsError * +natsMicroserviceArgs_GetFloat(long double *val, natsMicroserviceArgs *args, int index) { if ((args == NULL) || (index < 0) || (index >= args->count) || (val == NULL)) return natsMicroserviceErrorInvalidArg; @@ -105,8 +112,8 @@ natsArgs_GetFloat(long double *val, natsArgs *args, int index) return NULL; } -natsMicroserviceError * -natsArgs_GetString(const char **val, natsArgs *args, int index) +natsError * +natsMicroserviceArgs_GetString(const char **val, natsMicroserviceArgs *args, int index) { if ((args == NULL) || (index < 0) || (index >= args->count) || (val == NULL)) return natsMicroserviceErrorInvalidArg; @@ -123,7 +130,7 @@ natsArgs_GetString(const char **val, natsArgs *args, int index) /// @param data raw message data /// @param data_len length of data /// @return error in case the string is not properly terminated. -static natsMicroserviceError * +static natsError * decode_rest_of_string(char *dup, int *decoded_len, int *i, const char *data, int data_len) { char c; @@ -183,7 +190,7 @@ decode_rest_of_string(char *dup, int *decoded_len, int *i, const char *data, int } if (!terminated) { - nats_NewMicroserviceError(NATS_INVALID_ARG, 400, "a quoted string is not properly terminated"); + nats_NewError(400, "a quoted string is not properly terminated"); } *decoded_len = len; @@ -196,10 +203,10 @@ decode_rest_of_string(char *dup, int *decoded_len, int *i, const char *data, int /// @param data raw message data /// @param data_len length of data /// @return error. -static natsMicroserviceError * +static natsError * decode_and_dupe_rest_of_string(char **dup, int *i, const char *data, int data_len) { - natsMicroserviceError *err = NULL; + natsError *err = NULL; int start = *i; int decoded_len; @@ -235,14 +242,13 @@ typedef enum parserState NumberArg, } parserState; -static natsMicroserviceError * +static natsError * parse(void **args, int *args_len, int *i, const char *data, int data_len) { - natsMicroserviceError *err = NULL; + natsError *err = NULL; char c; int n = 0; parserState state = NewArg; - char errbuf[1024]; char numbuf[64]; int num_len = 0; bool is_float = false; @@ -293,8 +299,7 @@ parse(void **args, int *args_len, int *i, const char *data, int data_len) break; default: - snprintf(errbuf, sizeof(errbuf), "unexpected '%c', an argument must be a number or a quoted string", c); - return nats_NewMicroserviceError(NATS_ERR, 400, errbuf); + return nats_Errorf(400, "unexpected '%c', an argument must be a number or a quoted string", c); } break; @@ -327,7 +332,7 @@ parse(void **args, int *args_len, int *i, const char *data, int data_len) case ' ': if (args != NULL) { - numbuf[num_len] = 0; + numbuf[num_len] = 0; if (is_float) { args[n] = NATS_CALLOC(1, sizeof(long double)); @@ -353,14 +358,12 @@ parse(void **args, int *args_len, int *i, const char *data, int data_len) break; default: - snprintf(errbuf, sizeof(errbuf), "unexpected '%c', a number must be followed by a space", c); - return nats_NewMicroserviceError(NATS_ERR, 400, errbuf); + return nats_Errorf(400, "unexpected '%c', a number must be followed by a space", c); } break; default: - snprintf(errbuf, sizeof(errbuf), "unreachable: wrong state for a ' ', expected NewArg or NumberArg, got %d", state); - return nats_NewMicroserviceError(NATS_ERR, 500, errbuf); + return nats_Errorf(500, "unreachable: wrong state for a ' ', expected NewArg or NumberArg, got %d", state); } } diff --git a/src/micro_args.h b/src/micro_args.h new file mode 100644 index 000000000..4f643144c --- /dev/null +++ b/src/micro_args.h @@ -0,0 +1,45 @@ +// Copyright 2015-2018 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MICRO_ARGS_H_ +#define MICRO_ARGS_H_ + +#include "micro.h" + +/** + * Request unmarshaled as "arguments", a space-separated list of numbers and strings. + * TODO document the interface. + */ +typedef struct args_s natsMicroserviceArgs; + +NATS_EXTERN natsError * +nats_ParseMicroserviceArgs(natsMicroserviceArgs **args, const char *data, int data_len); + +NATS_EXTERN int +natsMicroserviceArgs_Count(natsMicroserviceArgs *args); + +NATS_EXTERN natsError * +natsMicroserviceArgs_GetInt(int *val, natsMicroserviceArgs *args, int index); + +NATS_EXTERN natsError * +natsMicroserviceArgs_GetFloat(long double *val, natsMicroserviceArgs *args, int index); + +NATS_EXTERN natsError * +natsMicroserviceArgs_GetString(const char **val, natsMicroserviceArgs *args, int index); + +NATS_EXTERN void +natsMicroserviceArgs_Destroy(natsMicroserviceArgs *args); + +/** @} */ // end of microserviceGroup + +#endif /* MICRO_H_ */ diff --git a/src/micro_client.c b/src/micro_client.c index 61cd2170c..fdc25c891 100644 --- a/src/micro_client.c +++ b/src/micro_client.c @@ -16,7 +16,7 @@ #include "mem.h" #include "conn.h" -natsMicroserviceError * +natsError * nats_NewMicroserviceClient(natsMicroserviceClient **new_client, natsConnection *nc, natsMicroserviceClientConfig *cfg) { natsMicroserviceClient *client = NULL; @@ -24,7 +24,7 @@ nats_NewMicroserviceClient(natsMicroserviceClient **new_client, natsConnection * if (new_client == NULL) return natsMicroserviceErrorInvalidArg; - client = NATS_CALLOC(1, sizeof(struct __microserviceClient)); + client = NATS_CALLOC(1, sizeof(struct microservice_client_s)); if (client == NULL) return natsMicroserviceErrorOutOfMemory; @@ -34,8 +34,7 @@ nats_NewMicroserviceClient(natsMicroserviceClient **new_client, natsConnection * return NULL; } -void -natsMicroserviceClient_Destroy(natsMicroserviceClient *client) +void natsMicroserviceClient_Destroy(natsMicroserviceClient *client) { if (client == NULL) return; @@ -44,21 +43,21 @@ natsMicroserviceClient_Destroy(natsMicroserviceClient *client) NATS_FREE(client); } -natsMicroserviceError * +natsError * natsMicroserviceClient_DoRequest(natsMicroserviceClient *client, natsMsg **reply, const char *subject, const char *data, int data_len) { natsStatus s = NATS_OK; - natsMicroserviceError *err = NULL; + natsError *err = NULL; natsMsg *msg = NULL; - if ((client == NULL ) || (reply == NULL)) + if ((client == NULL) || (reply == NULL)) return natsMicroserviceErrorInvalidArg; s = natsConnection_Request(&msg, client->nc, subject, data, data_len, 5000); if (s != NATS_OK) - return nats_NewMicroserviceError(s, 500, "request failed"); + err = natsError_Wrapf(nats_NewStatusError(s), "request failed"); - err = nats_MicroserviceErrorFromMsg(s, msg); + err = nats_IsErrorResponse(s, msg); if (err == NULL) { *reply = msg; diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index 6d0234e6a..cceebdfec 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -18,86 +18,144 @@ #include "mem.h" static natsStatus -_newStats(natsMicroserviceEndpointStats **new_stats, const char *name, const char *subject); -static natsStatus -_setLastError(natsMicroserviceEndpointStats *stats, const char *error); -static void -_freeStats(natsMicroserviceEndpointStats *stats); - +free_endpoint(natsMicroserviceEndpoint *ep); static bool -_isValidName(const char *name); +is_valid_name(const char *name); static bool -_isValidSubject(const char *subject); -static natsStatus -_lazyInitChildren(natsMicroserviceEndpoint *ep); -static natsStatus -_findAndRemovePreviousChild(int *found_index, natsMicroserviceEndpoint *ep, const char *name); +is_valid_subject(const char *subject); static void -_handleEndpointRequest(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); +handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); natsStatus -natsMicroserviceEndpoint_AddEndpoint(natsMicroserviceEndpoint **new_ep, natsMicroserviceEndpoint *parent, const char *name, natsMicroserviceEndpointConfig *cfg) +nats_new_endpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, natsMicroserviceEndpointConfig *cfg) { natsStatus s = NATS_OK; - int index = -1; natsMicroserviceEndpoint *ep = NULL; + natsMicroserviceEndpointConfig *clone_cfg = NULL; + + if (!is_valid_name(cfg->name) || (cfg->handler == NULL)) + { + s = NATS_INVALID_ARG; + } + if ((s == NATS_OK) && (cfg->subject != NULL) && !is_valid_subject(cfg->subject)) + { + s = NATS_INVALID_ARG; + } + IFOK(s, nats_clone_endpoint_config(&clone_cfg, cfg)); + IFOK(s, NATS_CALLOCS(&ep, 1, sizeof(natsMicroserviceEndpoint))); + IFOK(s, natsMutex_Create(&ep->mu)); + if (s == NATS_OK) + { + ep->m = m; + ep->subject = NATS_STRDUP(nats_IsStringEmpty(cfg->subject) ? cfg->name : cfg->subject); + ep->config = clone_cfg; + *new_endpoint = ep; + } + else + { + nats_free_cloned_endpoint_config(clone_cfg); + free_endpoint(ep); + } + return NATS_UPDATE_ERR_STACK(s); +} - // TODO <>/<> return more comprehensive errors - if (parent == NULL) +natsStatus +nats_clone_endpoint_config(natsMicroserviceEndpointConfig **out, natsMicroserviceEndpointConfig *cfg) +{ + natsStatus s = NATS_OK; + natsMicroserviceEndpointConfig *new_cfg = NULL; + natsMicroserviceSchema *new_schema = NULL; + char *new_name = NULL; + char *new_subject = NULL; + char *new_request = NULL; + char *new_response = NULL; + + if (out == NULL || cfg == NULL) + return NATS_INVALID_ARG; + + s = NATS_CALLOCS(&new_cfg, 1, sizeof(natsMicroserviceEndpointConfig)); + if (s == NATS_OK && cfg->name != NULL) { - return nats_setDefaultError(NATS_INVALID_ARG); + DUP_STRING(s, new_name, cfg->name); } - if (cfg != NULL) + if (s == NATS_OK && cfg->subject != NULL) { - if ((cfg->subject == NULL) || (!_isValidName(name)) || (!_isValidSubject(cfg->subject))) + DUP_STRING(s, new_subject, cfg->subject); + } + if (s == NATS_OK && cfg->schema != NULL) + { + s = NATS_CALLOCS(&new_schema, 1, sizeof(natsMicroserviceSchema)); + if (s == NATS_OK && cfg->schema->request != NULL) + { + DUP_STRING(s, new_request, cfg->schema->request); + } + if (s == NATS_OK && cfg->schema->response != NULL) { - return nats_setDefaultError(NATS_INVALID_ARG); + DUP_STRING(s, new_response, cfg->schema->response); + } + if (s == NATS_OK) + { + new_schema->request = new_request; + new_schema->response = new_response; } } - - IFOK(s, _lazyInitChildren(parent)); - IFOK(s, _newMicroserviceEndpoint(&ep, parent->m, name, cfg)); - IFOK(s, _findAndRemovePreviousChild(&index, parent, name)); - IFOK(s, _microserviceEndpointList_Put(parent->children, index, ep)); - IFOK(s, natsMicroserviceEndpoint_Start(ep)); - if (s == NATS_OK) { - if (new_ep != NULL) - *new_ep = ep; + memcpy(new_cfg, cfg, sizeof(natsMicroserviceEndpointConfig)); + new_cfg->schema = new_schema; + new_cfg->name = new_name; + new_cfg->subject = new_subject; + *out = new_cfg; } else { - natsMicroserviceEndpoint_Destroy(ep); + NATS_FREE(new_cfg); + NATS_FREE(new_schema); + NATS_FREE(new_name); + NATS_FREE(new_subject); + NATS_FREE(new_request); + NATS_FREE(new_response); } + return NATS_UPDATE_ERR_STACK(s); } +void nats_free_cloned_endpoint_config(natsMicroserviceEndpointConfig *cfg) +{ + if (cfg == NULL) + return; + + // the strings are declared const for the public, but in a clone these need + // to be freed. + NATS_FREE((char *)cfg->name); + NATS_FREE((char *)cfg->subject); + NATS_FREE((char *)cfg->schema->request); + NATS_FREE((char *)cfg->schema->response); + + NATS_FREE(cfg->schema); + NATS_FREE(cfg); +} + natsStatus -natsMicroserviceEndpoint_Start(natsMicroserviceEndpoint *ep) +nats_start_endpoint(natsMicroserviceEndpoint *ep) { if ((ep->subject == NULL) || (ep->config == NULL) || (ep->config->handler == NULL)) // nothing to do return NATS_OK; + // reset the stats. + memset(&ep->stats, 0, sizeof(ep->stats)); + return natsConnection_QueueSubscribe(&ep->sub, ep->m->nc, ep->subject, - natsMicroserviceQueueGroup, _handleEndpointRequest, ep); + natsMicroserviceQueueGroup, handle_request, ep); } // TODO <>/<> COPY FROM GO natsStatus -natsMicroserviceEndpoint_Stop(natsMicroserviceEndpoint *ep) +nats_stop_endpoint(natsMicroserviceEndpoint *ep) { natsStatus s = NATS_OK; - // TODO <>/<> review locking for modifying endpoints, may not be necessary - // or ep may need its own lock (stats). - - // This is a rare call, usually happens at the initialization of the - // microservice, so it's ok to lock for the duration of the function, may - // not be necessary at all but will not hurt. - natsMutex_Lock(ep->m->mu); - if (ep->sub != NULL) { s = natsSubscription_Drain(ep->sub); @@ -105,47 +163,48 @@ natsMicroserviceEndpoint_Stop(natsMicroserviceEndpoint *ep) if (s == NATS_OK) { ep->sub = NULL; - ep->stopped = true; - // TODO <>/<> unsafe - ep->stats->stopped = true; + + // reset the stats. + memset(&ep->stats, 0, sizeof(ep->stats)); } - natsMutex_Unlock(ep->m->mu); return NATS_UPDATE_ERR_STACK(s); } static natsStatus -_freeMicroserviceEndpoint(natsMicroserviceEndpoint *ep) +free_endpoint(natsMicroserviceEndpoint *ep) { - // ignore ep->children, must be taken care of by the caller. - _freeStats(ep->stats); - NATS_FREE(ep->name); NATS_FREE(ep->subject); + natsMutex_Destroy(ep->mu); + nats_free_cloned_endpoint_config(ep->config); NATS_FREE(ep); return NATS_OK; } natsStatus -natsMicroserviceEndpoint_Destroy(natsMicroserviceEndpoint *ep) +nats_stop_and_destroy_endpoint(natsMicroserviceEndpoint *ep) { natsStatus s = NATS_OK; if (ep == NULL) return NATS_OK; - IFOK(s, _destroyMicroserviceEndpointList(ep->children)); - IFOK(s, natsMicroserviceEndpoint_Stop(ep)); - IFOK(s, _freeMicroserviceEndpoint(ep)); + IFOK(s, nats_stop_endpoint(ep)); + IFOK(s, free_endpoint(ep)); return NATS_UPDATE_ERR_STACK(s); } static bool -_isValidName(const char *name) +is_valid_name(const char *name) { int i; - int len = (int)strlen(name); + int len; + + if (name == NULL) + return false; + len = (int)strlen(name); if (len == 0) return false; @@ -158,11 +217,15 @@ _isValidName(const char *name) } static bool -_isValidSubject(const char *subject) +is_valid_subject(const char *subject) { int i; - int len = (int)strlen(subject); + int len; + if (subject == NULL) + return false; + + len = (int)strlen(subject); if (len == 0) return false; @@ -178,69 +241,16 @@ _isValidSubject(const char *subject) return true; } -static natsStatus -_lazyInitChildren(natsMicroserviceEndpoint *ep) -{ - if (ep->children == NULL) - { - return _newMicroserviceEndpointList(&ep->children); - } - return NATS_OK; -} - -static natsStatus -_findAndRemovePreviousChild(int *found_index, natsMicroserviceEndpoint *ep, const char *name) -{ - natsMicroserviceEndpoint *found = _microserviceEndpointList_Find(ep->children, name, found_index); - if (found != NULL) - { - return natsMicroserviceEndpoint_Destroy(found); - } - return NATS_OK; -} - -natsStatus -_newMicroserviceEndpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg) -{ - natsStatus s = NATS_OK; - natsMicroserviceEndpoint *ep = NULL; - // TODO <>/<> do I really need to duplicate name, subject? - char *dup_name = NULL; - char *dup_subject = NULL; - - IFOK(s, NATS_CALLOCS(&ep, 1, sizeof(natsMicroserviceEndpoint))); - IFOK(s, NATS_STRDUPS(&dup_name, name)); - if ((cfg != NULL) && (cfg->subject != NULL)) - IFOK(s, NATS_STRDUPS(&dup_subject, cfg->subject)); - IFOK(s, _newStats(&ep->stats, dup_name, dup_subject)); - - if (s == NATS_OK) - { - ep->m = m; - ep->name = dup_name; - ep->subject = dup_subject; - ep->config = cfg; - *new_endpoint = ep; - } - else - { - _freeStats(ep->stats); - NATS_FREE(ep); - NATS_FREE(dup_name); - NATS_FREE(dup_subject); - } - return NATS_UPDATE_ERR_STACK(s); -} - static void -_handleEndpointRequest(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { natsStatus s = NATS_OK; natsMicroserviceEndpoint *ep = (natsMicroserviceEndpoint *)closure; - // natsMicroserviceEndpointStats *stats = &ep->stats; + natsMicroserviceEndpointStats *stats = &ep->stats; natsMicroserviceEndpointConfig *cfg = ep->config; natsMicroserviceRequest *req = NULL; natsMicroserviceRequestHandler handler = cfg->handler; + int64_t start, elapsed_ns, full_s; if (handler == NULL) { @@ -251,70 +261,39 @@ _handleEndpointRequest(natsConnection *nc, natsSubscription *sub, natsMsg *msg, } s = _newMicroserviceRequest(&req, msg); - if (s == NATS_OK) + if (s != NATS_OK) { - req->ep = ep; - handler(ep->m, req); - } - - // Update stats - // natsMutex_Lock(ep->mu); - // stats->numRequests++; - // natsMutex_Unlock(ep->mu); - - _freeMicroserviceRequest(req); - natsMsg_Destroy(msg); -} - -static natsStatus -_newStats(natsMicroserviceEndpointStats **new_stats, const char *name, const char *subject) -{ - natsStatus s = NATS_OK; - natsMicroserviceEndpointStats *stats = NULL; - - stats = (natsMicroserviceEndpointStats *)NATS_CALLOC(1, sizeof(natsMicroserviceEndpointStats)); - s = (stats != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); - if (s == NATS_OK) - { - stats->name = name; - stats->subject = subject; - *new_stats = stats; - return NATS_OK; + natsMsg_Destroy(msg); + return; } - NATS_FREE(stats); - return NATS_UPDATE_ERR_STACK(s); -} + // handle the request. + start = nats_NowInNanoSeconds(); + req->ep = ep; + handler(ep->m, req); + elapsed_ns = nats_NowInNanoSeconds() - start; -// All locking is to be done by the caller. -static natsStatus -_setLastError(natsMicroserviceEndpointStats *stats, const char *error) -{ - natsStatus s = NATS_OK; - - if (stats->last_error != NULL) - NATS_FREE(stats->last_error); + // update stats. + natsMutex_Lock(ep->mu); + stats->num_requests++; + stats->processing_time_ns += elapsed_ns; + full_s = stats->processing_time_ns / 1000000000; + stats->processing_time_s += full_s; + stats->processing_time_ns -= full_s * 1000000000; - if (nats_IsStringEmpty(error)) - { - stats->last_error = NULL; - return NATS_OK; - } + natsMutex_Unlock(ep->mu); - stats->last_error = NATS_STRDUP(error); - s = (stats->last_error != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); - - return NATS_UPDATE_ERR_STACK(s); + _freeMicroserviceRequest(req); + natsMsg_Destroy(msg); } -static void -_freeStats(natsMicroserviceEndpointStats *stats) +void natsMicroserviceEndpoint_updateLastError(natsMicroserviceEndpoint *ep, natsError *err) { - if (stats == NULL) + if (err == NULL) return; - if (stats->last_error != NULL) - NATS_FREE(stats->last_error); - - NATS_FREE(stats); + natsMutex_Lock(ep->mu); + ep->stats.num_errors++; + natsError_String(err, ep->stats.last_error_string, sizeof(ep->stats.last_error_string)); + natsMutex_Unlock(ep->mu); } diff --git a/src/micro_endpoint_list.c b/src/micro_endpoint_list.c deleted file mode 100644 index 191e5a721..000000000 --- a/src/micro_endpoint_list.c +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2021-2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "micro.h" -#include "microp.h" -#include "mem.h" - - -natsStatus - _destroyMicroserviceEndpointList(__microserviceEndpointList *list) -{ - natsStatus s = NATS_OK; - if (list == NULL) - return NATS_OK; - - for (int i = 0; i < list->len; i++) - { - s = natsMicroserviceEndpoint_Destroy(list->endpoints[i]); - if (s != NATS_OK) - { - return NATS_UPDATE_ERR_STACK(s); - } - } - - natsMutex_Destroy(list->mu); - NATS_FREE(list->endpoints); - NATS_FREE(list); - return NATS_OK; -} - -natsStatus -_newMicroserviceEndpointList(__microserviceEndpointList **new_list) -{ - natsStatus s = NATS_OK; - __microserviceEndpointList *list = NULL; - - list = NATS_CALLOC(1, sizeof(__microserviceEndpointList)); - if (list == NULL) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - } - IFOK(s, natsMutex_Create(&list->mu)); - - if (s == NATS_OK) - { - *new_list = list; - } - else - { - _destroyMicroserviceEndpointList(list); - } - return NATS_UPDATE_ERR_STACK(s); -} - -natsMicroserviceEndpoint * -_microserviceEndpointList_Find(__microserviceEndpointList *list, const char *name, int *index) -{ - natsMicroserviceEndpoint *ep = NULL; - int i; - - natsMutex_Lock(list->mu); - for (i = 0; i < list->len; i++) - { - if (strcmp(list->endpoints[i]->name, name) == 0) - { - if (index != NULL) - { - *index = i; - } - ep = list->endpoints[i]; - break; - } - } - natsMutex_Unlock(list->mu); - return ep; -} - -natsMicroserviceEndpoint * -_microserviceEndpointList_Get(__microserviceEndpointList *list, const char *name, int *index) -{ - natsMicroserviceEndpoint *ep = NULL; - int i; - - natsMutex_Lock(list->mu); - for (i = 0; i < list->len; i++) - { - if (strcmp(list->endpoints[i]->name, name) == 0) - { - if (index != NULL) - { - *index = i; - } - ep = list->endpoints[i]; - break; - } - } - natsMutex_Unlock(list->mu); - return ep; -} - - -natsStatus -_microserviceEndpointList_Put(__microserviceEndpointList *list, int index, natsMicroserviceEndpoint *ep) -{ - natsStatus s = NATS_OK; - - natsMutex_Lock(list->mu); - - if (index >= list->len) - { - return nats_setDefaultError(NATS_INVALID_ARG); - } - else if (index == -1) - { - list->endpoints = (natsMicroserviceEndpoint **) - NATS_REALLOC(list->endpoints, sizeof(natsMicroserviceEndpoint *) * (list->len + 1)); - if (list->endpoints == NULL) - { - s = nats_setDefaultError(NATS_NO_MEMORY); - } - if (s == NATS_OK) - { - list->endpoints[list->len] = ep; - list->len++; - } - } - else - { - list->endpoints[index] = ep; - } - - natsMutex_Unlock(list->mu); - return NATS_UPDATE_ERR_STACK(s); -} - -natsStatus -_microserviceEndpointList_Remove(__microserviceEndpointList *list, int index) -{ - natsMutex_Lock(list->mu); - - if (index >= list->len || index < 0) - { - return nats_setDefaultError(NATS_INVALID_ARG); - } - if (index < list->len - 1) - { - memmove(&list->endpoints[index], &list->endpoints[index + 1], (list->len - index - 1) * sizeof(natsMicroserviceEndpoint *)); - } - list->len--; - - natsMutex_Unlock(list->mu); - return NATS_OK; -} diff --git a/src/micro_error.c b/src/micro_error.c index 865c15091..1a20eb6f7 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -15,29 +15,54 @@ #include "microp.h" #include "mem.h" -static natsMicroserviceError _errorOutOfMemory = { +#ifdef DEV_MODE +// For type safety + +static void _retain(natsError *err) { err->refs++; } +static void _release(natsError *err) { err->refs--; } + +void natsError_Lock(natsConnection *nc) { natsMutex_Lock(nc->mu); } +void natsConn_Unlock(natsConnection *nc) { natsMutex_Unlock(nc->mu); } + +#else +// We know what we are doing :-) + +#define _retain(c) ((c)->refs++) +#define _release(c) ((c)->refs--) + +#endif // DEV_MODE + +static natsError _errorOutOfMemory = { .status = NATS_NO_MEMORY, .code = 500, .description = "Out of memory", }; -static natsMicroserviceError _errorInvalidArg = { - .status = NATS_INVALID_ARG, - .code = 400, - .description = "Invalid function argument", +static natsError _errorInvalidArg = { + .status = NATS_INVALID_ARG, + .code = 400, + .description = "Invalid function argument", +}; + +static natsError _errorInvalidFormat = { + .status = NATS_INVALID_ARG, + .code = 400, + .description = "Invalid error format string", }; -static natsMicroserviceError *knownErrors[] = { +static natsError *knownErrors[] = { &_errorOutOfMemory, &_errorInvalidArg, + &_errorInvalidFormat, NULL, }; -natsMicroserviceError *natsMicroserviceErrorOutOfMemory = &_errorOutOfMemory; -natsMicroserviceError *natsMicroserviceErrorInvalidArg = &_errorInvalidArg; +natsError *natsMicroserviceErrorOutOfMemory = &_errorOutOfMemory; +natsError *natsMicroserviceErrorInvalidArg = &_errorInvalidArg; -static const char * -_string(natsMicroserviceError *err, char *buf, int size) { +const char * +natsError_String(natsError *err, char *buf, int size) +{ if (err == NULL || buf == NULL) return ""; if (err->status == NATS_OK) @@ -47,26 +72,131 @@ _string(natsMicroserviceError *err, char *buf, int size) { return buf; } -natsMicroserviceError * -nats_NewMicroserviceError(natsStatus s, int code, const char *description) +natsStatus +natsError_StatusCode(natsError *err) { - natsMicroserviceError *err = NULL; + return (err != NULL) ? err->status : NATS_OK; +} - if (s == NATS_OK) - return NULL; +static natsError * +new_error(natsStatus s, int code, char *description) +{ + natsError *err = NULL; - err = NATS_CALLOC(1, sizeof(natsMicroserviceError)); + err = NATS_CALLOC(1, sizeof(natsError)); if (err == NULL) return &_errorOutOfMemory; err->status = s; err->code = code; - err->description = NATS_STRDUP(description); // it's ok if NULL - err->String = _string; + err->description = description; + return err; } -void natsMicroserviceError_Destroy(natsMicroserviceError *err) +natsError * +nats_NewError(int code, const char *description) +{ + return nats_Errorf(NATS_ERR, description); +} + +natsError * +nats_NewStatusError(natsStatus s) +{ + char *dup = NULL; + + if (s == NATS_OK) + return NULL; + + dup = NATS_STRDUP(natsStatus_GetText(s)); + if (dup == NULL) + return &_errorOutOfMemory; + + return new_error(s, 0, dup); +} + +natsError * +natsError_Wrapf(natsError *err, const char *format, ...) +{ + va_list args; + char *buf = NULL; + int len1 = 0, len2 = 0; + natsStatus s; + int code; + + if (err == NULL) + return NULL; + if (nats_IsStringEmpty(format)) + return err; + + va_start(args, format); + len1 = vsnprintf(NULL, 0, format, args); + va_end(args); + if (len1 < 0) + { + return &_errorInvalidFormat; + } + if (!nats_IsStringEmpty(err->description)) + { + len2 = strlen(err->description) + 2; // ": " + } + buf = NATS_MALLOC(len1 + len2 + 1); + if (buf == NULL) + { + return &_errorOutOfMemory; + } + + va_start(args, format); + vsnprintf(buf, len1 + 1, format, args); + va_end(args); + if (!nats_IsStringEmpty(err->description)) + { + buf[len1] = ':'; + buf[len1 + 1] = ' '; + memcpy(buf + len1 + 2, err->description, len2 - 2); + } + buf[len1 + len2] = '\0'; + + code = err->code; + s = err->status; + natsError_Destroy(err); + return new_error(s, code, buf); +} + +natsError * +nats_Errorf(int code, const char *format, ...) +{ + va_list args1, args2; + char *buf = NULL; + int len = 0; + + if ((code == 0) && nats_IsStringEmpty(format)) + return NULL; + + va_start(args1, format); + va_copy(args2, args1); + + len = vsnprintf(NULL, 0, format, args1); + va_end(args1); + if (len < 0) + { + va_end(args2); + return &_errorInvalidFormat; + } + buf = NATS_MALLOC(len + 1); + if (buf == NULL) + { + va_end(args2); + return &_errorOutOfMemory; + } + + vsnprintf(buf, len + 1, format, args2); + va_end(args2); + + return new_error(NATS_ERR, code, buf); +} + +void natsError_Destroy(natsError *err) { int i; @@ -85,46 +215,27 @@ void natsMicroserviceError_Destroy(natsMicroserviceError *err) NATS_FREE(err); } -natsMicroserviceError * -nats_MicroserviceErrorFromMsg(natsStatus status, natsMsg *msg) +natsError * +nats_IsErrorResponse(natsStatus status, natsMsg *msg) { - natsMicroserviceError *err = NULL; + natsError *err = NULL; const char *c = NULL, *d = NULL; bool is_error; - int code = 0; - if (msg == NULL) - return NULL; - - natsMsgHeader_Get(msg, NATS_MICROSERVICE_ERROR_CODE_HDR, &c); - natsMsgHeader_Get(msg, NATS_MICROSERVICE_ERROR_HDR, &d); + if (msg != NULL) + { + natsMsgHeader_Get(msg, NATS_MICROSERVICE_ERROR_CODE_HDR, &c); + natsMsgHeader_Get(msg, NATS_MICROSERVICE_ERROR_HDR, &d); + } is_error = (status != NATS_OK) || !nats_IsStringEmpty(c) || !nats_IsStringEmpty(d); if (!is_error) return NULL; - err = nats_NewMicroserviceError(status, 0, ""); - if (err == NULL) - { - // This is not 100% correct - returning an OOM error that was not in the - // message, but since it is usually a fatal condition, it is ok. - return &_errorOutOfMemory; - } - - if (nats_IsStringEmpty(d) && (status != NATS_OK)) + err = natsError_Wrapf(nats_NewStatusError(status), d); + if (!nats_IsStringEmpty(c) && (err != NULL)) { - d = natsStatus_GetText(status); - if (d == NULL) - d = ""; + err->code = atoi(c); } - - if (!nats_IsStringEmpty(c)) - { - code = atoi(c); - } - err->status = status; - err->code = code; - err->description = d; - return err; } diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index fd8d2f50b..6691c578a 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -16,94 +16,126 @@ #include "micro.h" #include "microp.h" #include "mem.h" +#include "util.h" +static natsStatus +marshal_ping(natsBuffer **new_buf, natsMicroservice *m); static void -_handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); - -static natsStatus _marshalPing(natsBuffer **new_buf, natsMicroservice *m); +handle_ping(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); static natsStatus -_addInternalHandler(natsMicroservice *m, natsMicroserviceVerb verb, const char *kind, const char *id, - const char *name, natsMsgHandler handler); +marshal_info(natsBuffer **new_buf, natsMicroserviceInfo *info); +static void +handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); + static natsStatus -_addVerbHandlers(natsMicroservice *m, natsMicroserviceVerb verb, natsMsgHandler handler); +marshal_stats(natsBuffer **new_buf, natsMicroserviceStats *stats); +static void +handle_stats(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); -static natsStatus _newControlSubject(char **newSubject, natsMicroserviceVerb verb, const char *name, const char *id); -static natsStatus _newDottedSubject(char **new_subject, int count, ...); -static bool _isEmpty(const char *s); +static natsStatus +add_internal_handler(natsMicroservice *m, const char *verb, const char *kind, const char *id, const char *name, natsMsgHandler handler); +static natsStatus +add_verb_handlers(natsMicroservice *m, const char *verb, natsMsgHandler handler); +static natsStatus +new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); +static natsStatus +new_dotted_subject(char **new_subject, int count, ...); static natsStatus -natsMicroserviceVerb_String(const char **new_subject, natsMicroserviceVerb verb) +marshal_duration(natsBuffer *out_buf, bool comma, const char *name, int64_t d); +static void +fmt_frac(char buf[], int w, uint64_t v, int prec, int *nw, uint64_t *nv); +static int +fmt_int(char buf[], int w, uint64_t v); + +natsStatus +micro_monitoring_init(natsMicroservice *m) { - if (new_subject == NULL) - return nats_setDefaultError(NATS_INVALID_ARG); + natsStatus s = NATS_OK; - switch (verb) - { - case natsMicroserviceVerbPing: - *new_subject = "PING"; - return NATS_OK; + IFOK(s, add_verb_handlers(m, natsMicroservicePingVerb, handle_ping)); + IFOK(s, add_verb_handlers(m, natsMicroserviceStatsVerb, handle_stats)); + IFOK(s, add_verb_handlers(m, natsMicroserviceInfoVerb, handle_info)); - case natsMicroserviceVerbStats: - *new_subject = "STATS"; - return NATS_OK; + return NATS_UPDATE_ERR_STACK(s); +} - case natsMicroserviceVerbInfo: - *new_subject = "INFO"; - return NATS_OK; +static void +handle_ping(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +{ + natsStatus s = NATS_OK; + natsMicroservice *m = (natsMicroservice *)closure; + natsMicroserviceRequest req = { + .msg = msg, + }; + natsBuffer *buf = NULL; - case natsMicroserviceVerbSchema: - *new_subject = "SCHEMA"; - return NATS_OK; - default: - return nats_setError(NATS_INVALID_ARG, "Invalid microservice verb %d", verb); + s = marshal_ping(&buf, m); + if (s == NATS_OK) + { + natsMicroserviceRequest_Respond(&req, natsBuf_Data(buf), natsBuf_Len(buf)); } + natsBuf_Destroy(buf); } -natsStatus -_initMicroserviceMonitoring(natsMicroservice *m) +void handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { natsStatus s = NATS_OK; + natsMicroservice *m = (natsMicroservice *)closure; + natsMicroserviceRequest req = { + .msg = msg, + }; + natsMicroserviceInfo *info = NULL; + natsBuffer *buf = NULL; - IFOK(s, _addVerbHandlers(m, natsMicroserviceVerbPing, _handleMicroservicePing)); - - return NATS_UPDATE_ERR_STACK(s); + s = natsMicroservice_Info(&info, m); + IFOK(s, marshal_info(&buf, info)); + if (s == NATS_OK) + { + natsMicroserviceRequest_Respond(&req, natsBuf_Data(buf), natsBuf_Len(buf)); + } + else + { + natsMicroserviceRequest_Error(&req, nats_NewStatusError(s)); + } + natsBuf_Destroy(buf); + natsMicroserviceInfo_Destroy(info); } static void -_handleMicroservicePing(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +handle_stats(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { natsStatus s = NATS_OK; natsMicroservice *m = (natsMicroservice *)closure; natsMicroserviceRequest req = { .msg = msg, }; + natsMicroserviceStats *stats = NULL; natsBuffer *buf = NULL; - s = _marshalPing(&buf, m); + s = natsMicroservice_Stats(&stats, m); + IFOK(s, marshal_stats(&buf, stats)); if (s == NATS_OK) { natsMicroserviceRequest_Respond(&req, natsBuf_Data(buf), natsBuf_Len(buf)); } + else + { + natsMicroserviceRequest_Error(&req, nats_NewStatusError(s)); + } natsBuf_Destroy(buf); -} - -static bool -_isEmpty(const char *s) -{ - return (s == NULL || *s == '\0'); + natsMicroserviceStats_Destroy(stats); } static natsStatus -_newDottedSubject(char **new_subject, int count, ...) +new_dotted_subject(char **new_subject, int count, ...) { - va_list args1, args2; + va_list args; int i, len, n; char *result, *p; - va_start(args1, count); - va_copy(args2, args1); - + va_start(args, count); len = 0; for (i = 0; i < count; i++) { @@ -111,82 +143,76 @@ _newDottedSubject(char **new_subject, int count, ...) { len++; /* for the dot */ } - len += strlen(va_arg(args1, char *)); + len += strlen(va_arg(args, char *)); } - va_end(args1); + va_end(args); result = NATS_MALLOC(len + 1); if (result == NULL) { - va_end(args2); return NATS_NO_MEMORY; } len = 0; + va_start(args, count); for (i = 0; i < count; i++) { if (i > 0) { result[len++] = '.'; } - p = va_arg(args2, char *); + p = va_arg(args, char *); n = strlen(p); memcpy(result + len, p, n); len += n; } - va_end(args2); + va_end(args); *new_subject = result; return NATS_OK; } static natsStatus -_newControlSubject(char **newSubject, natsMicroserviceVerb verb, const char *name, const char *id) +new_control_subject(char **newSubject, const char *verb, const char *name, const char *id) { - natsStatus s = NATS_OK; - const char *verbStr; - - s = natsMicroserviceVerb_String(&verbStr, verb); - if (s != NATS_OK) - { - return NATS_UPDATE_ERR_STACK(s); - } - - if (_isEmpty(name) && !_isEmpty(id)) + if (nats_IsStringEmpty(name) && !nats_IsStringEmpty(id)) { NATS_UPDATE_ERR_TXT("service name is required when id is provided: %s", id); return NATS_UPDATE_ERR_STACK(NATS_INVALID_ARG); } - else if (_isEmpty(name) && _isEmpty(id)) - return _newDottedSubject(newSubject, 2, natsMicroserviceAPIPrefix, verbStr); - else if (_isEmpty(id)) - return _newDottedSubject(newSubject, 3, natsMicroserviceAPIPrefix, verbStr, name); + else if (nats_IsStringEmpty(name) && nats_IsStringEmpty(id)) + return new_dotted_subject(newSubject, 2, natsMicroserviceAPIPrefix, verb); + else if (nats_IsStringEmpty(id)) + return new_dotted_subject(newSubject, 3, natsMicroserviceAPIPrefix, verb, name); else - return _newDottedSubject(newSubject, 4, natsMicroserviceAPIPrefix, verbStr, name, id); + return new_dotted_subject(newSubject, 4, natsMicroserviceAPIPrefix, verb, name, id); } static natsStatus -_addInternalHandler(natsMicroservice *m, natsMicroserviceVerb verb, const char *kind, - const char *id, const char *name, natsMsgHandler handler) +add_internal_handler(natsMicroservice *m, const char *verb, const char *kind, + const char *id, const char *name, natsMsgHandler handler) { natsStatus s = NATS_OK; natsSubscription *sub = NULL; char *subj = NULL; - s = _newControlSubject(&subj, verb, kind, id); + s = new_control_subject(&subj, verb, kind, id); if (s == NATS_OK) { s = natsConnection_Subscribe(&sub, m->nc, subj, handler, m); } + if (s == NATS_OK) { return NATS_OK; } - - natsMicroservice_Stop(m); - return NATS_UPDATE_ERR_STACK(s); + else + { + natsMicroservice_Stop(m); + return NATS_UPDATE_ERR_STACK(s); + } } // __verbHandlers generates control handlers for a specific verb. Each request @@ -194,32 +220,36 @@ _addInternalHandler(natsMicroservice *m, natsMicroserviceVerb verb, const char * // written with the framework, one that handles all services of a particular // kind, and finally a specific service instance. static natsStatus -_addVerbHandlers(natsMicroservice *m, natsMicroserviceVerb verb, natsMsgHandler handler) +add_verb_handlers(natsMicroservice *m, const char *verb, natsMsgHandler handler) { natsStatus s = NATS_OK; - char name[256]; - const char *verbStr; + char name[1024]; - s = natsMicroserviceVerb_String(&verbStr, verb); if (s == NATS_OK) { - snprintf(name, sizeof(name), "%s-all", verbStr); - s = _addInternalHandler(m, verb, "", "", name, handler); + snprintf(name, sizeof(name), "%s-all", verb); + s = add_internal_handler(m, verb, "", "", name, handler); } if (s == NATS_OK) { - snprintf(name, sizeof(name), "%s-kind", verbStr); - s = _addInternalHandler(m, verb, m->identity.name, "", name, handler); + snprintf(name, sizeof(name), "%s-kind", verb); + s = add_internal_handler(m, verb, m->cfg->name, "", name, handler); } if (s == NATS_OK) { - s = _addInternalHandler(m, verb, m->identity.name, m->identity.id, verbStr, handler); + s = add_internal_handler(m, verb, m->cfg->name, m->id, verb, handler); } return NATS_UPDATE_ERR_STACK(s); } +// name and sep must be a string literal +#define IFOK_attr(_name, _value, _sep) \ + IFOK(s, natsBuf_Append(buf, "\"" _name "\":\"", -1)); \ + IFOK(s, natsBuf_Append(buf, (_value) != NULL ? (_value) : "", -1)); \ + IFOK(s, natsBuf_Append(buf, "\"" _sep, -1)); + static natsStatus -_marshalPing(natsBuffer **new_buf, natsMicroservice *m) +marshal_ping(natsBuffer **new_buf, natsMicroservice *m) { natsBuffer *buf = NULL; natsStatus s; @@ -228,16 +258,10 @@ _marshalPing(natsBuffer **new_buf, natsMicroservice *m) if (s != NATS_OK) return NATS_UPDATE_ERR_STACK(s); -// name and sep must be a string literal -#define IFOK_attr(_name, _value, _sep) \ - IFOK(s, natsBuf_Append(buf, "\"" _name "\":\"", -1)); \ - IFOK(s, natsBuf_Append(buf, _value, -1)); \ - IFOK(s, natsBuf_Append(buf, "\"" _sep, -1)); - s = natsBuf_Append(buf, "{", -1); - IFOK_attr("name", m->identity.name, ","); - IFOK_attr("version", m->identity.version, ","); - IFOK_attr("id", m->identity.id, ","); + IFOK_attr("name", m->cfg->name, ","); + IFOK_attr("version", m->cfg->version, ","); + IFOK_attr("id", m->id, ","); IFOK_attr("type", natsMicroservicePingResponseType, ""); IFOK(s, natsBuf_AppendByte(buf, '}')); @@ -246,7 +270,256 @@ _marshalPing(natsBuffer **new_buf, natsMicroservice *m) *new_buf = buf; return NATS_OK; } + else + { + natsBuf_Destroy(buf); + return NATS_UPDATE_ERR_STACK(s); + } +} - natsBuf_Destroy(buf); +static natsStatus +marshal_info(natsBuffer **new_buf, natsMicroserviceInfo *info) +{ + natsBuffer *buf = NULL; + natsStatus s; + + s = natsBuf_Create(&buf, 4096); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + + s = natsBuf_Append(buf, "{", -1); + IFOK_attr("description", info->description, ","); + IFOK_attr("id", info->id, ","); + IFOK_attr("name", info->name, ","); + IFOK_attr("type", info->type, ","); + if ((s == NATS_OK) && (info->subjects_len > 0)) + { + int i; + IFOK(s, natsBuf_Append(buf, "\"subjects\":[", -1)); + for (i = 0; i < info->subjects_len; i++) + { + IFOK(s, natsBuf_Append(buf, "\"", -1)); + IFOK(s, natsBuf_Append(buf, info->subjects[i], -1)); + IFOK(s, natsBuf_Append(buf, "\"", -1)); + if (i < (info->subjects_len - 1)) + IFOK(s, natsBuf_Append(buf, ",", -1)); + } + IFOK(s, natsBuf_Append(buf, "],", -1)); + } + IFOK_attr("version", info->version, ""); + IFOK(s, natsBuf_AppendByte(buf, '}')); + + if (s == NATS_OK) + { + *new_buf = buf; + return NATS_OK; + } + else + { + natsBuf_Destroy(buf); + return NATS_UPDATE_ERR_STACK(s); + } +} + +static natsStatus +marshal_stats(natsBuffer **new_buf, natsMicroserviceStats *stats) +{ + natsBuffer *buf = NULL; + natsStatus s; + int i; + char timebuf[128]; + natsMicroserviceEndpointStats *ep; + + s = natsBuf_Create(&buf, 8 * 1024); + if (s != NATS_OK) + return NATS_UPDATE_ERR_STACK(s); + + s = natsBuf_AppendByte(buf, '{'); + IFOK_attr("id", stats->id, ","); + IFOK_attr("name", stats->name, ","); + IFOK_attr("type", stats->type, ","); + IFOK(s, nats_EncodeTimeUTC(timebuf, sizeof(timebuf), stats->started)); + IFOK_attr("started", timebuf, ","); + + if ((s == NATS_OK) && (stats->endpoints_len > 0)) + { + IFOK(s, natsBuf_Append(buf, "\"endpoints\":[", -1)); + for (i = 0; i < stats->endpoints_len; i++) + { + ep = &stats->endpoints[i]; + IFOK(s, natsBuf_AppendByte(buf, '{')); + IFOK_attr("name", ep->name, ","); + IFOK_attr("subject", ep->subject, ","); + IFOK(s, nats_marshalLong(buf, false, "num_requests", ep->num_requests)); + IFOK(s, nats_marshalLong(buf, true, "num_errors", ep->num_errors)); + // IFOK(s, nats_marshalLong(buf, true, "average_processing_time", ep->average_processing_time_ns)); + IFOK(s, marshal_duration(buf, true, "average_processing_time", ep->average_processing_time_ns)); + IFOK(s, natsBuf_AppendByte(buf, ',')); + IFOK_attr("last_error", ep->last_error_string, ""); + IFOK(s, natsBuf_Append(buf, "}", -1)); + + if (i < (stats->endpoints_len - 1)) + IFOK(s, natsBuf_Append(buf, ",", -1)); + } + IFOK(s, natsBuf_Append(buf, "],", -1)); + } + + IFOK_attr("version", stats->version, ""); + IFOK(s, natsBuf_AppendByte(buf, '}')); + + if (s == NATS_OK) + { + *new_buf = buf; + return NATS_OK; + } + else + { + natsBuf_Destroy(buf); + return NATS_UPDATE_ERR_STACK(s); + } +} + +static natsStatus marshal_duration(natsBuffer *out_buf, bool comma, const char *field_name, int64_t d) +{ + // Largest time is 2540400h10m10.000000000s + char buf[32]; + int w = 32; + uint64_t u = d; + bool neg = d < 0; + int prec; + natsStatus s = NATS_OK; + const char *start = (comma ? ",\"" : "\""); + + if (neg) + u = -u; + + if (u < 1000000000) + { + // Special case: if duration is smaller than a second, + // use smaller units, like 1.2ms + w--; + buf[w] = 's'; + w--; + if (u == 0) + { + return natsBuf_Append(out_buf, "0s", 2); + } + else if (u < 1000) + { + // print nanoseconds + prec = 0; + buf[w] = 'n'; + } + else if (u < 1000000) + { + // print microseconds + prec = 3; + // U+00B5 'µ' micro sign == 0xC2 0xB5 (in reverse?) + buf[w] = '\xB5'; + w--; // Need room for two bytes. + buf[w] = '\xC2'; + } + else + { + // print milliseconds + prec = 6; + buf[w] = 'm'; + } + fmt_frac(buf, w, u, prec, &w, &u); + w = fmt_int(buf, w, u); + } + else + { + w--; + buf[w] = 's'; + + fmt_frac(buf, w, u, 9, &w, &u); + + // u is now integer seconds + w = fmt_int(buf, w, u % 60); + u /= 60; + + // u is now integer minutes + if (u > 0) + { + w--; + buf[w] = 'm'; + w = fmt_int(buf, w, u % 60); + u /= 60; + + // u is now integer hours + // Stop at hours because days can be different lengths. + if (u > 0) + { + w--; + buf[w] = 'h'; + w = fmt_int(buf, w, u); + } + } + } + + if (neg) + { + w--; + buf[w] = '-'; + } + + s = natsBuf_Append(out_buf, start, -1); + IFOK(s, natsBuf_Append(out_buf, field_name, -1)); + IFOK(s, natsBuf_Append(out_buf, "\":\"", -1)); + IFOK(s, natsBuf_Append(out_buf, buf + w, sizeof(buf) - w)); + IFOK(s, natsBuf_Append(out_buf, "\":\"", -1)); return NATS_UPDATE_ERR_STACK(s); } + +// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the +// tail of buf, omitting trailing zeros. It omits the decimal +// point too when the fraction is 0. It returns the index where the +// output bytes begin and the value v/10**prec. +static void fmt_frac(char *buf, int w, uint64_t v, int prec, int *nw, uint64_t *nv) +{ + // Omit trailing zeros up to and including decimal point. + bool print = false; + int i; + int digit; + + for (i = 0; i < prec; i++) + { + digit = v % 10; + print = print || digit != 0; + if (print) + { + w--; + buf[w] = digit + '0'; + } + v /= 10; + } + if (print) + { + w--; + buf[w] = '.'; + } + *nw = w; + *nv = v; +} + +// fmtInt formats v into the tail of buf. +// It returns the index where the output begins. +static int fmt_int(char *buf, int w, uint64_t v) +{ + if (v == 0) + { + w--; + buf[w] = '0'; + } + else + { + while (v > 0) + { + w--; + buf[w] = v % 10 + '0'; + v /= 10; + } + } + return w; +} diff --git a/src/micro_request.c b/src/micro_request.c index c3b38fdd6..7746b0058 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -39,10 +39,11 @@ natsMicroserviceRequest_GetConnection(natsMicroserviceRequest *req) return ((req != NULL) && (req->ep != NULL) && (req->ep->m != NULL)) ? req->ep->m->nc : NULL; } -natsMicroserviceError * +natsError * natsMicroserviceRequest_Respond(natsMicroserviceRequest *req, const char *data, int len) { natsStatus s = NATS_OK; + natsError *err = NULL; if ((req == NULL) || (req->msg == NULL) || (req->msg->sub == NULL) || (req->msg->sub->conn == NULL)) return natsMicroserviceErrorInvalidArg; @@ -50,12 +51,14 @@ natsMicroserviceRequest_Respond(natsMicroserviceRequest *req, const char *data, s = natsConnection_Publish(req->msg->sub->conn, natsMsg_GetReply(req->msg), data, len); if (s == NATS_OK) return NULL; - else - return nats_NewMicroserviceError(s, 500, "failed to respond to message"); + + err = natsError_Wrapf(nats_NewStatusError(s), "failed to respond to a message"); + natsMicroserviceEndpoint_updateLastError(req->ep, err); + return err; } -natsMicroserviceError * -natsMicroserviceRequest_RespondError(natsMicroserviceRequest *req, natsMicroserviceError *err, const char *data, int len) +natsError * +natsMicroserviceRequest_RespondError(natsMicroserviceRequest *req, natsError *err, const char *data, int len) { natsMsg *msg = NULL; natsStatus s = NATS_OK; @@ -72,7 +75,7 @@ natsMicroserviceRequest_RespondError(natsMicroserviceRequest *req, natsMicroserv { s = natsMsgHeader_Set(msg, NATS_MICROSERVICE_STATUS_HDR, natsStatus_GetText(err->status)); } - if (s == NATS_OK) + if (s == NATS_OK) { s = natsMsgHeader_Set(msg, NATS_MICROSERVICE_ERROR_HDR, err->description); } @@ -82,32 +85,32 @@ natsMicroserviceRequest_RespondError(natsMicroserviceRequest *req, natsMicroserv s = natsMsgHeader_Set(msg, NATS_MICROSERVICE_ERROR_CODE_HDR, buf); } IFOK(s, natsConnection_PublishMsg(req->msg->sub->conn, msg)); - + natsMsg_Destroy(msg); + natsMicroserviceEndpoint_updateLastError(req->ep, err); if (s == NATS_OK) + { return NULL; + } else - return nats_NewMicroserviceError(s, 500, "failed to respond to message"); + { + err = natsError_Wrapf(nats_NewStatusError(s), "failed to respond to a message with an error"); + return err; + } } -void -natsMicroserviceRequest_Error(natsMicroserviceRequest *req, natsMicroserviceError **err) +void natsMicroserviceRequest_Error(natsMicroserviceRequest *req, natsError *err) { - if (err == NULL) - return; - natsMicroserviceRequest_RespondError(req, *err, NULL, 0); - natsMicroserviceError_Destroy(*err); - *err = NULL; -} + if (err == NULL) + return; + natsMicroserviceRequest_RespondError(req, err, NULL, 0); + natsError_Destroy(err); +} void _freeMicroserviceRequest(natsMicroserviceRequest *req) { - if (req == NULL) - return; - - NATS_FREE(req->err); NATS_FREE(req); } diff --git a/src/microp.h b/src/microp.h index 80987bd48..5aa87f1ff 100644 --- a/src/microp.h +++ b/src/microp.h @@ -16,131 +16,89 @@ #include "natsp.h" -#define natsMicroserviceInfoResponseType "io.nats.micro.v1.info_response" -#define natsMicroservicePingResponseType "io.nats.micro.v1.ping_response" -#define natsMicroserviceStatsResponseType "io.nats.micro.v1.stats_response" -#define natsMicroserviceSchemaResponseType "io.nats.micro.v1.schema_response" - #define natsMicroserviceQueueGroup "q" #define natsMicroserviceDefaultEndpointName "default" -struct __microserviceClient -{ - natsConnection *nc; -}; - -struct __microserviceConfig +struct error_s { - const char *name; - const char *version; + natsStatus status; + int code; const char *description; }; -struct __microserviceIdentity +struct microservice_client_s { - const char *name; - const char *version; - char *id; + natsConnection *nc; }; -typedef struct __microserviceEndpointList +struct microservice_endpoint_s { - natsMutex *mu; - int len; - struct __microserviceEndpoint **endpoints; -} __microserviceEndpointList; + // The subject that the endpoint is listening on (may be different from + // one specified in config). + char *subject; -struct __microserviceEndpoint -{ - // The name of the endpoint, uniquely identifies the endpoint. The name "" - // is reserved for the top level endpoint of the service. - char *name; + // References to other entities. + natsMicroservice *m; + natsMicroserviceEndpointConfig *config; - // Indicates if the endpoint is stopped, or is actively subscribed to a - // subject. - bool stopped; + // Mutex for starting/stopping the endpoint, and for updating the stats. + natsMutex *mu; - // The subject that the endpoint is listening on. The subject is also used - // as the prefix for the children endpoints. - char *subject; + // The subscription for the endpoint. If NULL, the endpoint is stopped. natsSubscription *sub; - // Children endpoints. Their subscriptions are prefixed with the parent's - // subject. Their stats are summarized in the parent's stats when requested. - __microserviceEndpointList *children; - int len_children; - // Endpoint stats. These are initialized only for running endpoints, and are // cleared if the endpoint is stopped. - natsMutex *stats_mu; - natsMicroserviceEndpointStats *stats; - - // References to other entities. - natsMicroservice *m; - natsMicroserviceEndpointConfig *config; + natsMicroserviceEndpointStats stats; }; -struct __microservice +struct microservice_s { + // these are set at initialization time time and do not change. natsConnection *nc; - int refs; - natsMutex *mu; - struct __microserviceIdentity identity; struct natsMicroserviceConfig *cfg; + char *id; + + // these are are updated concurrently with access as the service runs, so + // need to be protected by mutex. + natsMutex *mu; + + struct microservice_endpoint_s **endpoints; + int endpoints_len; + int endpoints_cap; + + int64_t started; // UTC time expressed as number of nanoseconds since epoch. bool stopped; - struct __microserviceEndpoint *root; + int refs; }; -struct __microserviceRequest +struct microservice_request_s { natsMsg *msg; - struct __microserviceEndpoint *ep; - char *err; + struct microservice_endpoint_s *ep; void *closure; }; -typedef struct __args_t -{ - void **args; - int count; -} __args_t; - -extern natsMicroserviceError *natsMicroserviceErrorOutOfMemory; -extern natsMicroserviceError *natsMicroserviceErrorInvalidArg; - -natsStatus -_initMicroserviceMonitoring(natsMicroservice *m); - -natsStatus -_newMicroserviceEndpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, const char *name, natsMicroserviceEndpointConfig *cfg); - -natsStatus -natsMicroserviceEndpoint_AddEndpoint(natsMicroserviceEndpoint **new_ep, natsMicroserviceEndpoint *parent, const char *name, natsMicroserviceEndpointConfig *cfg); +extern natsError *natsMicroserviceErrorOutOfMemory; +extern natsError *natsMicroserviceErrorInvalidArg; natsStatus -natsMicroserviceEndpoint_Start(natsMicroserviceEndpoint *ep); +micro_monitoring_init(natsMicroservice *m); -natsStatus -natsMicroserviceEndpoint_Stop(natsMicroserviceEndpoint *ep); +natsStatus nats_clone_microservice_config(natsMicroserviceConfig **new_cfg, natsMicroserviceConfig *cfg); +void nats_free_cloned_microservice_config(natsMicroserviceConfig *cfg); -natsStatus -natsMicroserviceEndpoint_Destroy(natsMicroserviceEndpoint *ep); +natsStatus nats_new_endpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, natsMicroserviceEndpointConfig *cfg); +natsStatus nats_clone_endpoint_config(natsMicroserviceEndpointConfig **new_cfg, natsMicroserviceEndpointConfig *cfg); +natsStatus nats_start_endpoint(natsMicroserviceEndpoint *ep); +natsStatus nats_stop_endpoint(natsMicroserviceEndpoint *ep); +void nats_free_cloned_endpoint_config(natsMicroserviceEndpointConfig *cfg); natsStatus -_destroyMicroserviceEndpointList(__microserviceEndpointList *list); +nats_stop_and_destroy_endpoint(natsMicroserviceEndpoint *ep); -natsStatus -_newMicroserviceEndpointList(__microserviceEndpointList **new_list); - -natsMicroserviceEndpoint * -_microserviceEndpointList_Find(__microserviceEndpointList *list, const char *name, int *index); - -natsStatus -_microserviceEndpointList_Put(__microserviceEndpointList *list, int index, natsMicroserviceEndpoint *ep); - -natsStatus -_microserviceEndpointList_Remove(__microserviceEndpointList *list, int index); +void natsMicroserviceEndpoint_updateLastError(natsMicroserviceEndpoint *ep, natsError *err); natsStatus _newMicroserviceRequest(natsMicroserviceRequest **new_request, natsMsg *msg); diff --git a/src/natsp.h b/src/natsp.h index 21c71e8a4..c98bbb283 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -132,8 +132,6 @@ #endif #define IFOK(s, c) if (s == NATS_OK) { s = (c); } -// TODO <>/<> do I really need it? -#define IFOK_DO(s, c) if (s == NATS_OK) { c; } #define NATS_MILLIS_TO_NANOS(d) (((int64_t)d)*(int64_t)1E6) #define NATS_SECONDS_TO_NANOS(d) (((int64_t)d)*(int64_t)1E9) From dcfdbea2e57bacb4f101a5b4303e5ae271666953 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 27 Mar 2023 09:56:25 -0700 Subject: [PATCH 08/85] refactored with the 'micro' prefix --- examples/microservice.c | 421 ++++++++++++++++------------------------ src/micro.c | 158 +++++++-------- src/micro.h | 211 ++++++++++---------- src/micro_args.c | 107 +++++----- src/micro_args.h | 22 +-- src/micro_client.c | 26 +-- src/micro_endpoint.c | 60 +++--- src/micro_error.c | 56 +++--- src/micro_monitoring.c | 118 +++++------ src/micro_request.c | 100 ++++------ src/microp.h | 69 +++---- 11 files changed, 592 insertions(+), 756 deletions(-) diff --git a/examples/microservice.c b/examples/microservice.c index 994d4e9b4..004542062 100644 --- a/examples/microservice.c +++ b/examples/microservice.c @@ -28,44 +28,44 @@ static void *run_functions(void *closure); static void *run_sequence(void *closure); // a generic service runner, used by the services' "main" functions. -static void *run_service(natsConnection *conn, natsMicroserviceConfig *svc, - natsMicroserviceEndpointConfig **endpoints, int len_endpoints); +static void *run_service(natsConnection *conn, microServiceConfig *svc, + microEndpointConfig **endpoints, int len_endpoints); // Callers and handlers for operations (2 floating point args, and a single int arg). -static natsError * +static microError * call_arithmetics(long double *result, natsConnection *nc, const char *subject, long double a1, long double a2); -typedef natsError *(*arithmeticsOP)( +typedef microError *(*arithmeticsOP)( long double *result, natsConnection *conn, long double a1, long double a2); -static void handle_arithmetics_op(natsMicroserviceRequest *req, arithmeticsOP op); +static void handle_arithmetics_op(microRequest *req, arithmeticsOP op); -static natsError * +static microError * call_function(long double *result, natsConnection *nc, const char *subject, int n); -typedef natsError *(*functionOP)(long double *result, natsConnection *conn, int n); -static void handle_function_op(natsMicroserviceRequest *req, functionOP op); +typedef microError *(*functionOP)(long double *result, natsConnection *conn, int n); +static void handle_function_op(microRequest *req, functionOP op); // Stop handler is the same for all services. -static void handle_stop(natsMicroservice *m, natsMicroserviceRequest *req); +static void handle_stop(microService *m, microRequest *req); // Handler for "sequence", the main endpoint of the sequence service. -static void handle_sequence(natsMicroservice *m, natsMicroserviceRequest *req); +static void handle_sequence(microService *m, microRequest *req); // Math operations, wrapped as handlers. -static natsError *add(long double *result, natsConnection *nc, long double a1, long double a2); -static natsError *divide(long double *result, natsConnection *nc, long double a1, long double a2); -static natsError *multiply(long double *result, natsConnection *nc, long double a1, long double a2); - -static void handle_add(natsMicroservice *m, natsMicroserviceRequest *req) { handle_arithmetics_op(req, add); } -static void handle_divide(natsMicroservice *m, natsMicroserviceRequest *req) { handle_arithmetics_op(req, divide); } -static void handle_multiply(natsMicroservice *m, natsMicroserviceRequest *req) { handle_arithmetics_op(req, multiply); } - -static natsError *factorial(long double *result, natsConnection *nc, int n); -static natsError *fibonacci(long double *result, natsConnection *nc, int n); -static natsError *power2(long double *result, natsConnection *nc, int n); -static void handle_factorial(natsMicroservice *m, natsMicroserviceRequest *req) { handle_function_op(req, factorial); } -static void handle_fibonacci(natsMicroservice *m, natsMicroserviceRequest *req) { handle_function_op(req, fibonacci); } -static void handle_power2(natsMicroservice *m, natsMicroserviceRequest *req) { handle_function_op(req, power2); } - -static natsMicroserviceEndpointConfig stop_cfg = { +static microError *add(long double *result, natsConnection *nc, long double a1, long double a2); +static microError *divide(long double *result, natsConnection *nc, long double a1, long double a2); +static microError *multiply(long double *result, natsConnection *nc, long double a1, long double a2); + +static void handle_add(microService *m, microRequest *req) { handle_arithmetics_op(req, add); } +static void handle_divide(microService *m, microRequest *req) { handle_arithmetics_op(req, divide); } +static void handle_multiply(microService *m, microRequest *req) { handle_arithmetics_op(req, multiply); } + +static microError *factorial(long double *result, natsConnection *nc, int n); +static microError *fibonacci(long double *result, natsConnection *nc, int n); +static microError *power2(long double *result, natsConnection *nc, int n); +static void handle_factorial(microService *m, microRequest *req) { handle_function_op(req, factorial); } +static void handle_fibonacci(microService *m, microRequest *req) { handle_function_op(req, fibonacci); } +static void handle_power2(microService *m, microRequest *req) { handle_function_op(req, power2); } + +static microEndpointConfig stop_cfg = { .name = "stop", .handler = handle_stop, .closure = &fakeClosure, @@ -75,6 +75,7 @@ static natsMicroserviceEndpointConfig stop_cfg = { int main(int argc, char **argv) { natsStatus s = NATS_OK; + microError *err = NULL; natsConnection *conn = NULL; natsOptions *opts = NULL; pthread_t arithmetics_th; @@ -84,6 +85,7 @@ int main(int argc, char **argv) pthread_t sequence_th; void *s_val = NULL; int errno; + char errorbuf[1024]; // Connect and start the services opts = parseArgs(argc, argv, ""); @@ -115,48 +117,51 @@ int main(int argc, char **argv) // Wait for the services to stop and self-destruct. if (s == NATS_OK) { - pthread_join(arithmetics_th, &a_val); - s = (natsStatus)(uintptr_t)a_val; - } - if (s == NATS_OK) - { - pthread_join(functions_th, &f_val); - s = (natsStatus)(uintptr_t)f_val; - } - if (s == NATS_OK) - { - pthread_join(sequence_th, &s_val); - s = (natsStatus)(uintptr_t)s_val; + MICRO_DO(err, + { + if (pthread_join(arithmetics_th, &a_val) == 0) + err = (microError *)a_val; + }); + MICRO_DO(err, + { + if (pthread_join(functions_th, &f_val) == 0) + err = (microError *)f_val; + }); + MICRO_DO(err, + { + if (pthread_join(sequence_th, &s_val) == 0) + err = (microError *)s_val; + }); } - if (s == NATS_OK) + if (s != NATS_OK) { - return 0; + err = microError_FromStatus(s); } - else + if (err != NULL) { - printf("Error: %u - %s\n", s, natsStatus_GetText(s)); - nats_PrintLastErrorStack(stderr); + printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); return 1; } + return 0; } static void *run_sequence(void *closure) { natsConnection *conn = (natsConnection *)closure; - natsMicroserviceConfig cfg = { + microServiceConfig cfg = { .description = "Sequence adder - NATS microservice example in C", .name = "c-sequence", .version = "1.0.0", }; - natsMicroserviceEndpointConfig sequence_cfg = { + microEndpointConfig sequence_cfg = { .subject = "sequence", .name = "sequence-service", .handler = handle_sequence, .closure = &fakeClosure, .schema = NULL, }; - natsMicroserviceEndpointConfig *endpoints[] = {&sequence_cfg}; + microEndpointConfig *endpoints[] = {&sequence_cfg}; return run_service(conn, &cfg, endpoints, 1); } @@ -165,11 +170,11 @@ static void *run_sequence(void *closure) // (float), f name (string), and N (int). E.g.: '10.0 "power2" 10' will // calculate 10/2 + 10/4 + 10/8 + 10/16 + 10/32 + 10/64 + 10/128 + 10/256 + // 10/512 + 10/1024 = 20.998046875 -static void handle_sequence(natsMicroservice *m, natsMicroserviceRequest *req) +static void handle_sequence(microService *m, microRequest *req) { - natsError *err = NULL; - natsConnection *nc = natsMicroservice_GetConnection(m); - natsMicroserviceArgs *args = NULL; + microError *err = NULL; + natsConnection *nc = microService_GetConnection(m); + microArgs *args = NULL; int n = 0; int i; const char *function; @@ -177,87 +182,61 @@ static void handle_sequence(natsMicroservice *m, natsMicroserviceRequest *req) long double value = 1.0; long double denominator = 0; char result[64]; + int result_len = 0; - err = nats_ParseMicroserviceArgs(&args, natsMicroserviceRequest_GetData(req), natsMicroserviceRequest_GetDataLength(req)); - if ((err == NULL) && - (natsMicroserviceArgs_Count(args) != 2)) - { - err = nats_Errorf(400, "Invalid number of arguments, expected 2 got %d", natsMicroserviceArgs_Count(args)); - } + MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); + MICRO_CALL_IF(err, (microArgs_Count(args) != 2), + micro_NewErrorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args))); - if (err == NULL) - { - err = natsMicroserviceArgs_GetString(&function, args, 0); - } - if (err == NULL) - { - err = natsMicroserviceArgs_GetInt(&n, args, 1); - } - if ((err == NULL) && - (strcmp(function, "factorial") != 0) && - (strcmp(function, "power2") != 0) && - (strcmp(function, "fibonacci") != 0)) - { - err = nats_Errorf(400, "Invalid function name '%s', must be 'factorial', 'power2', or 'fibonacci'", function); - } - if ((err == NULL) && - (n < 1)) - { - err = nats_Errorf(400, "Invalid number of iterations %d, must be at least 1", n); - } + MICRO_CALL(err, microArgs_GetString(&function, args, 0)); + MICRO_CALL_IF(err, ((strcmp(function, "factorial") != 0) && (strcmp(function, "power2") != 0) && (strcmp(function, "fibonacci") != 0)), + micro_NewErrorf(400, "Invalid function name '%s', must be 'factorial', 'power2', or 'fibonacci'", function)); + MICRO_CALL(err, microArgs_GetInt(&n, args, 1)); + MICRO_CALL_IF(err, + (n < 1), + micro_NewErrorf(400, "Invalid number of iterations %d, must be at least 1", n)); for (i = 1; (err == NULL) && (i <= n); i++) { - err = call_function(&denominator, nc, function, i); - if (err == NULL && denominator == 0) - { - err = nats_Errorf(500, "division by zero at step %d", i); - } - if (err == NULL) - { - value = value + initialValue / denominator; - } + MICRO_CALL(err, call_function(&denominator, nc, function, i)); + MICRO_CALL_IF(err, denominator == 0, + micro_NewErrorf(500, "division by zero at step %d", i)); + MICRO_DO(err, value = value + initialValue / denominator); } + + MICRO_DO(err, result_len = snprintf(result, sizeof(result), "%Lf", value)); - if (err == NULL) - { - snprintf(result, sizeof(result), "%Lf", value); - err = natsMicroserviceRequest_Respond(req, result, strlen(result)); - } - else - { - natsMicroserviceRequest_Error(req, err); - } - natsMicroserviceArgs_Destroy(args); + microRequest_Respond(req, &err, result, result_len); + microArgs_Destroy(args); } static void *run_arithmetics(void *closure) { natsConnection *conn = (natsConnection *)closure; - natsMicroserviceConfig cfg = { + microServiceConfig cfg = { .description = "Arithmetic operations - NATS microservice example in C", .name = "c-arithmetics", .version = "1.0.0", }; - natsMicroserviceEndpointConfig add_cfg = { + microEndpointConfig add_cfg = { .name = "add", .handler = handle_add, .closure = &fakeClosure, .schema = NULL, }; - natsMicroserviceEndpointConfig divide_cfg = { + microEndpointConfig divide_cfg = { .name = "divide", .handler = handle_divide, .closure = &fakeClosure, .schema = NULL, }; - natsMicroserviceEndpointConfig multiply_cfg = { + microEndpointConfig multiply_cfg = { .name = "multiply", .handler = handle_multiply, .closure = &fakeClosure, .schema = NULL, }; - natsMicroserviceEndpointConfig *endpoints[] = + microEndpointConfig *endpoints[] = {&add_cfg, ÷_cfg, &multiply_cfg}; return run_service(conn, &cfg, endpoints, 3); @@ -266,254 +245,182 @@ static void *run_arithmetics(void *closure) static void *run_functions(void *closure) { natsConnection *conn = (natsConnection *)closure; - natsMicroserviceConfig cfg = { + microServiceConfig cfg = { .description = "Functions - NATS microservice example in C", .name = "c-functions", .version = "1.0.0", }; - natsMicroserviceEndpointConfig factorial_cfg = { + microEndpointConfig factorial_cfg = { .name = "factorial", .handler = handle_factorial, .closure = &fakeClosure, .schema = NULL, }; - natsMicroserviceEndpointConfig fibonacci_cfg = { + microEndpointConfig fibonacci_cfg = { .name = "fibonacci", .handler = handle_fibonacci, .closure = &fakeClosure, .schema = NULL, }; - natsMicroserviceEndpointConfig power2_cfg = { + microEndpointConfig power2_cfg = { .name = "power2", .handler = handle_power2, .closure = &fakeClosure, .schema = NULL, }; - natsMicroserviceEndpointConfig *endpoints[] = + microEndpointConfig *endpoints[] = {&factorial_cfg, &fibonacci_cfg, &power2_cfg}; return run_service(conn, &cfg, endpoints, 3); } static void -handle_arithmetics_op(natsMicroserviceRequest *req, arithmeticsOP op) +handle_arithmetics_op(microRequest *req, arithmeticsOP op) { - natsError *err = NULL; - natsMicroserviceArgs *args = NULL; + microError *err = NULL; + microArgs *args = NULL; long double a1, a2, result; char buf[1024]; - int len; - - err = nats_ParseMicroserviceArgs(&args, natsMicroserviceRequest_GetData(req), natsMicroserviceRequest_GetDataLength(req)); - if ((err == NULL) && (natsMicroserviceArgs_Count(args) != 2)) - { - err = nats_Errorf(400, "Invalid number of arguments, expected 2 got %d", natsMicroserviceArgs_Count(args)); - } - if (err == NULL) - { - err = natsMicroserviceArgs_GetFloat(&a1, args, 0); - } - if (err == NULL) - { - err = natsMicroserviceArgs_GetFloat(&a2, args, 1); - } - if (err == NULL) - { - err = op(&result, natsMicroserviceRequest_GetConnection(req), a1, a2); - } - if (err == NULL) - { - len = snprintf(buf, sizeof(buf), "%Lf", result); - err = natsMicroserviceRequest_Respond(req, buf, len); - } - else - { - natsMicroserviceRequest_Error(req, err); - } - natsMicroserviceArgs_Destroy(args); + int len = 0; + + MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); + MICRO_CALL_IF(err, (microArgs_Count(args) != 2), + micro_NewErrorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args))); + MICRO_CALL(err, microArgs_GetFloat(&a1, args, 0)); + MICRO_CALL(err, microArgs_GetFloat(&a2, args, 1)); + MICRO_CALL(err, op(&result, microRequest_GetConnection(req), a1, a2)); + MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf", result)); + + microRequest_Respond(req, &err, buf, len); + microArgs_Destroy(args); } static void -handle_function_op(natsMicroserviceRequest *req, functionOP op) +handle_function_op(microRequest *req, functionOP op) { - natsError *err = NULL; - natsMicroserviceArgs *args = NULL; + microError *err = NULL; + microArgs *args = NULL; int n; long double result; char buf[1024]; - int len; + int len = 0; - err = nats_ParseMicroserviceArgs(&args, natsMicroserviceRequest_GetData(req), natsMicroserviceRequest_GetDataLength(req)); - if ((err == NULL) && (natsMicroserviceArgs_Count(args) != 1)) - { - err = nats_Errorf(400, "Invalid number of arguments, expected 1 got %d", natsMicroserviceArgs_Count(args)); - } - if (err == NULL) - { - err = natsMicroserviceArgs_GetInt(&n, args, 0); - } - if (err == NULL) - { - err = op(&result, natsMicroserviceRequest_GetConnection(req), n); - } - if (err == NULL) - { - len = snprintf(buf, sizeof(buf), "%Lf", result); - err = natsMicroserviceRequest_Respond(req, buf, len); - } - else - { - natsMicroserviceRequest_Error(req, err); - } - natsMicroserviceArgs_Destroy(args); + MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); + MICRO_CALL_IF(err, (microArgs_Count(args) != 1), + micro_NewErrorf(400, "Invalid number of arguments, expected 1 got %d", microArgs_Count(args))); + MICRO_CALL(err, microArgs_GetInt(&n, args, 0)); + MICRO_CALL(err, op(&result, microRequest_GetConnection(req), n)); + MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf", result)); + + microRequest_Respond(req, &err, buf, len); + microArgs_Destroy(args); } -static natsError * +static microError * call_arithmetics(long double *result, natsConnection *nc, const char *subject, long double a1, long double a2) { - natsError *err = NULL; - natsMicroserviceClient *client = NULL; + microError *err = NULL; + microClient *client = NULL; natsMsg *response = NULL; - natsMicroserviceArgs *args = NULL; + microArgs *args = NULL; char buf[1024]; int len; - err = nats_NewMicroserviceClient(&client, nc, NULL); - if (err == NULL) - { - len = snprintf(buf, sizeof(buf), "%Lf %Lf", a1, a2); - err = natsMicroserviceClient_DoRequest(client, &response, subject, buf, len); - } - if (err == NULL) - { - err = nats_ParseMicroserviceArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response)); - } - if (err == NULL) - { - err = natsMicroserviceArgs_GetFloat(result, args, 0); - } + MICRO_CALL(err, microClient_Create(&client, nc, NULL)); + MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf %Lf", a1, a2)); + MICRO_CALL(err, microClient_DoRequest(client, &response, subject, buf, len)); + MICRO_CALL(err, micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response))); + MICRO_CALL(err, microArgs_GetFloat(result, args, 0)); - natsMicroserviceClient_Destroy(client); + microClient_Destroy(client); natsMsg_Destroy(response); return err; } -static natsError * +static microError * call_function(long double *result, natsConnection *nc, const char *subject, int n) { - natsError *err = NULL; - natsMicroserviceClient *client = NULL; + microError *err = NULL; + microClient *client = NULL; natsMsg *response = NULL; - natsMicroserviceArgs *args = NULL; + microArgs *args = NULL; char buf[1024]; int len; - err = nats_NewMicroserviceClient(&client, nc, NULL); - if (err == NULL) - { - len = snprintf(buf, sizeof(buf), "%d", n); - err = natsMicroserviceClient_DoRequest(client, &response, subject, buf, len); - } - if (err == NULL) - { - err = nats_ParseMicroserviceArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response)); - } - if (err == NULL) - { - err = natsMicroserviceArgs_GetFloat(result, args, 0); - } + MICRO_CALL(err, microClient_Create(&client, nc, NULL)); + MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%d", n)); + MICRO_CALL(err, microClient_DoRequest(client, &response, subject, buf, len)); + MICRO_CALL(err, micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response))); + MICRO_CALL(err, microArgs_GetFloat(result, args, 0)); - natsMicroserviceClient_Destroy(client); + microClient_Destroy(client); natsMsg_Destroy(response); return err; } -static void handle_stop(natsMicroservice *m, natsMicroserviceRequest *req) +static void handle_stop(microService *m, microRequest *req) { - natsError *err; + microError *err = NULL; + const char *response = "OK"; + int len = 0; + void *ret; - err = natsMicroservice_Stop(m); - if (err == NULL) - { - err = natsMicroserviceRequest_Respond(req, "OK", 2); - } + MICRO_CALL(err, microService_Stop(m)); + MICRO_DO(err, len = strlen(response)); - if (err == NULL) - { - pthread_exit((void *)(NATS_OK)); - } - else - { - natsMicroserviceRequest_Error(req, err); - pthread_exit((void *)(natsError_StatusCode(err))); - } + ret = (void *)(microError_Status(err)); + microRequest_Respond(req, &err, response, len); + pthread_exit(ret); } -static void *run_service(natsConnection *conn, natsMicroserviceConfig *svc, - natsMicroserviceEndpointConfig **endpoints, int len_endpoints) +static void *run_service(natsConnection *conn, microServiceConfig *svc, + microEndpointConfig **endpoints, int len_endpoints) { - natsError *err = NULL; - natsMicroservice *m = NULL; + microError *err = NULL; + microService *m = NULL; char errbuf[1024]; int i; - err = nats_AddMicroservice(&m, conn, svc); + MICRO_CALL(err, microService_Create(&m, conn, svc)); for (i = 0; (err == NULL) && (i < len_endpoints); i++) { - err = natsMicroservice_AddEndpoint(NULL, m, endpoints[i]); - if (err != NULL) - { - break; - } - } - if (err == NULL) - { - // TODO <>/<>: add a way to subscribe to broadcast messages, this one is - // queued. - err = natsMicroservice_AddEndpoint(NULL, m, &stop_cfg); - } - if (err == NULL) - { - err = natsMicroservice_Run(m); + MICRO_CALL(err, microService_AddEndpoint(NULL, m, endpoints[i])); } + MICRO_CALL(err, microService_AddEndpoint(NULL, m, &stop_cfg)); + MICRO_CALL(err, microService_Run(m)); - natsMicroservice_Release(m); - if (err != NULL) - { - printf("Error: %s\n", natsError_String(err, errbuf, sizeof(errbuf))); - return (void *)(natsError_StatusCode(err)); - } - return (void *)NATS_OK; + microService_Release(m); + return err; } -static natsError * +static microError * add(long double *result, natsConnection *nc, long double a1, long double a2) { *result = a1 + a2; return NULL; } -static natsError * +static microError * divide(long double *result, natsConnection *nc, long double a1, long double a2) { *result = a1 / a2; return NULL; } -static natsError *multiply(long double *result, natsConnection *nc, long double a1, long double a2) +static microError *multiply(long double *result, natsConnection *nc, long double a1, long double a2) { *result = a1 * a2; return NULL; } -static natsError * +static microError * factorial(long double *result, natsConnection *nc, int n) { - natsError *err = NULL; + microError *err = NULL; int i; if (n < 1) - err = nats_Errorf(400, "n=%d. must be greater than 0", n); + err = micro_NewErrorf(400, "n=%d. must be greater than 0", n); *result = 1; for (i = 1; i <= n; i++) @@ -525,15 +432,15 @@ factorial(long double *result, natsConnection *nc, int n) return NULL; } -static natsError * +static microError * fibonacci(long double *result, natsConnection *nc, int n) { - natsError *err = NULL; + microError *err = NULL; int i; long double n1, n2; if (n < 0) - err = nats_Errorf(400, "n=%d. must be non-negative", n); + err = micro_NewErrorf(400, "n=%d. must be non-negative", n); if (n < 2) { @@ -552,13 +459,13 @@ fibonacci(long double *result, natsConnection *nc, int n) return NULL; } -static natsError *power2(long double *result, natsConnection *nc, int n) +static microError *power2(long double *result, natsConnection *nc, int n) { - natsError *err = NULL; + microError *err = NULL; int i; if (n < 1) - return nats_Errorf(400, "n=%d. must be greater than 0", n); + return micro_NewErrorf(400, "n=%d. must be greater than 0", n); *result = 1; for (i = 1; i <= n; i++) diff --git a/src/micro.c b/src/micro.c index e10f6a3e5..9ffae3406 100644 --- a/src/micro.c +++ b/src/micro.c @@ -17,23 +17,23 @@ #include "conn.h" #include "mem.h" -static natsError * -stop_and_destroy_microservice(natsMicroservice *m); +static microError * +stop_and_destroy_microservice(microService *m); -natsError * -nats_AddMicroservice(natsMicroservice **new_m, natsConnection *nc, natsMicroserviceConfig *cfg) +microError * +microService_Create(microService **new_m, natsConnection *nc, microServiceConfig *cfg) { if ((new_m == NULL) || (nc == NULL) || (cfg == NULL)) - return natsMicroserviceErrorInvalidArg; + return micro_ErrorInvalidArg; natsStatus s = NATS_OK; - natsError *err = NULL; - natsMicroservice *m = NULL; + microError *err = NULL; + microService *m = NULL; // Make a microservice object, with a reference to a natsConnection. - m = (natsMicroservice *)NATS_CALLOC(1, sizeof(natsMicroservice)); + m = (microService *)NATS_CALLOC(1, sizeof(microService)); if (m == NULL) - return natsMicroserviceErrorOutOfMemory; + return micro_ErrorOutOfMemory; natsConn_retain(nc); m->nc = nc; @@ -43,10 +43,10 @@ nats_AddMicroservice(natsMicroservice **new_m, natsConnection *nc, natsMicroserv IFOK(s, NATS_CALLOCS(&m->id, 1, NUID_BUFFER_LEN + 1)) IFOK(s, natsMutex_Create(&m->mu)); IFOK(s, natsNUID_Next(m->id, NUID_BUFFER_LEN + 1)); - IFOK(s, nats_clone_microservice_config(&m->cfg, cfg)); + IFOK(s, micro_clone_service_config(&m->cfg, cfg)); if ((s == NATS_OK) && (cfg->endpoint != NULL)) { - err = natsMicroservice_AddEndpoint(NULL, m, cfg->endpoint); + err = microService_AddEndpoint(NULL, m, cfg->endpoint); if (err != NULL) { // status is always set in AddEndpoint errors. @@ -55,7 +55,7 @@ nats_AddMicroservice(natsMicroservice **new_m, natsConnection *nc, natsMicroserv } // Set up monitoring (PING, INFO, STATS, etc.) responders. - IFOK(s, micro_monitoring_init(m)); + IFOK(s, micro_init_monitoring(m)); if (s == NATS_OK) { @@ -65,12 +65,12 @@ nats_AddMicroservice(natsMicroservice **new_m, natsConnection *nc, natsMicroserv else { stop_and_destroy_microservice(m); - return natsError_Wrapf(nats_NewStatusError(s), "failed to create microservice"); + return microError_Wrapf(microError_FromStatus(s), "failed to create microservice"); } } -natsError * -natsMicroservice_Release(natsMicroservice *m) +microError * +microService_Release(microService *m) { bool doFree; @@ -88,8 +88,8 @@ natsMicroservice_Release(natsMicroservice *m) return stop_and_destroy_microservice(m); } -natsMicroservice * -natsMicroservice_Retain(natsMicroservice *m) +microService * +natsMicroservice_Retain(microService *m) { natsMutex_Lock(m->mu); m->refs++; @@ -98,20 +98,20 @@ natsMicroservice_Retain(natsMicroservice *m) } // TODO <>/<> update from Go -natsError * -natsMicroservice_Stop(natsMicroservice *m) +microError * +microService_Stop(microService *m) { natsStatus s = NATS_OK; int i; if (m == NULL) - return natsMicroserviceErrorInvalidArg; - if (natsMicroservice_IsStopped(m)) + return micro_ErrorInvalidArg; + if (microService_IsStopped(m)) return NULL; for (i = 0; (s == NATS_OK) && (i < m->endpoints_len); i++) { - s = nats_stop_endpoint(m->endpoints[i]); + s = micro_stop_endpoint(m->endpoints[i]); } if (s == NATS_OK) @@ -125,11 +125,11 @@ natsMicroservice_Stop(natsMicroservice *m) } else { - return natsError_Wrapf(nats_NewStatusError(s), "failed to stop microservice"); + return microError_Wrapf(microError_FromStatus(s), "failed to stop microservice"); } } -bool natsMicroservice_IsStopped(natsMicroservice *m) +bool microService_IsStopped(microService *m) { bool stopped; @@ -143,13 +143,13 @@ bool natsMicroservice_IsStopped(natsMicroservice *m) } // TODO: <>/<> eliminate sleep -natsError * -natsMicroservice_Run(natsMicroservice *m) +microError * +microService_Run(microService *m) { if ((m == NULL) || (m->mu == NULL)) - return natsMicroserviceErrorInvalidArg; + return micro_ErrorInvalidArg; - for (; !natsMicroservice_IsStopped(m);) + for (; !microService_IsStopped(m);) { nats_Sleep(50); } @@ -158,29 +158,29 @@ natsMicroservice_Run(natsMicroservice *m) } NATS_EXTERN natsConnection * -natsMicroservice_GetConnection(natsMicroservice *m) +microService_GetConnection(microService *m) { if (m == NULL) return NULL; return m->nc; } -natsError * -natsMicroservice_AddEndpoint(natsMicroserviceEndpoint **new_ep, natsMicroservice *m, natsMicroserviceEndpointConfig *cfg) +microError * +microService_AddEndpoint(microEndpoint **new_ep, microService *m, microEndpointConfig *cfg) { natsStatus s = NATS_OK; int index = -1; int new_cap; - natsMicroserviceEndpoint *ep = NULL; + microEndpoint *ep = NULL; if ((m == NULL) || (cfg == NULL)) { - return natsMicroserviceErrorInvalidArg; + return micro_ErrorInvalidArg; } - s = nats_new_endpoint(&ep, m, cfg); + s = micro_new_endpoint(&ep, m, cfg); if (s != NATS_OK) - return natsError_Wrapf(nats_NewStatusError(s), "failed to create endpoint"); + return microError_Wrapf(microError_FromStatus(s), "failed to create endpoint"); // see if we already have an endpoint with this name for (int i = 0; i < m->endpoints_len; i++) @@ -200,9 +200,9 @@ natsMicroservice_AddEndpoint(natsMicroserviceEndpoint **new_ep, natsMicroservice new_cap = m->endpoints_cap * 2; if (new_cap == 0) new_cap = 4; - natsMicroserviceEndpoint **new_eps = (natsMicroserviceEndpoint **)NATS_CALLOC(new_cap, sizeof(natsMicroserviceEndpoint *)); + microEndpoint **new_eps = (microEndpoint **)NATS_CALLOC(new_cap, sizeof(microEndpoint *)); if (new_eps == NULL) - return natsMicroserviceErrorOutOfMemory; + return micro_ErrorOutOfMemory; for (int i = 0; i < m->endpoints_len; i++) new_eps[i] = m->endpoints[i]; NATS_FREE(m->endpoints); @@ -216,7 +216,7 @@ natsMicroservice_AddEndpoint(natsMicroserviceEndpoint **new_ep, natsMicroservice // add the endpoint. m->endpoints[index] = ep; - s = nats_start_endpoint(ep); + s = micro_start_endpoint(ep); if (s == NATS_OK) { if (new_ep != NULL) @@ -225,42 +225,42 @@ natsMicroservice_AddEndpoint(natsMicroserviceEndpoint **new_ep, natsMicroservice } else { - nats_stop_and_destroy_endpoint(ep); - return natsError_Wrapf(nats_NewStatusError(NATS_UPDATE_ERR_STACK(s)), "failed to start endpoint"); + micro_stop_and_destroy_endpoint(ep); + return microError_Wrapf(microError_FromStatus(NATS_UPDATE_ERR_STACK(s)), "failed to start endpoint"); } } -natsStatus -natsMicroservice_Info(natsMicroserviceInfo **new_info, natsMicroservice *m) +microError * +microService_GetInfo(microServiceInfo **new_info, microService *m) { - natsMicroserviceInfo *info = NULL; + microServiceInfo *info = NULL; int i; int len = 0; if ((new_info == NULL) || (m == NULL) || (m->mu == NULL)) - return NATS_INVALID_ARG; + return micro_ErrorInvalidArg; - info = NATS_CALLOC(1, sizeof(natsMicroserviceInfo)); + info = NATS_CALLOC(1, sizeof(microServiceInfo)); if (info == NULL) - return NATS_NO_MEMORY; + return micro_ErrorOutOfMemory; info->name = m->cfg->name; info->version = m->cfg->version; info->description = m->cfg->description; info->id = m->id; - info->type = natsMicroserviceInfoResponseType; + info->type = MICRO_INFO_RESPONSE_TYPE; info->subjects = NATS_CALLOC(m->endpoints_len, sizeof(char *)); if (info->subjects == NULL) { NATS_FREE(info); - return NATS_NO_MEMORY; + return micro_ErrorOutOfMemory; } natsMutex_Lock(m->mu); for (i = 0; i < m->endpoints_len; i++) { - natsMicroserviceEndpoint *ep = m->endpoints[i]; + microEndpoint *ep = m->endpoints[i]; if ((ep != NULL) && (ep->subject != NULL)) { info->subjects[len] = ep->subject; @@ -271,10 +271,10 @@ natsMicroservice_Info(natsMicroserviceInfo **new_info, natsMicroservice *m) natsMutex_Unlock(m->mu); *new_info = info; - return NATS_OK; + return NULL; } -void natsMicroserviceInfo_Destroy(natsMicroserviceInfo *info) +void microServiceInfo_Destroy(microServiceInfo *info) { if (info == NULL) return; @@ -284,39 +284,39 @@ void natsMicroserviceInfo_Destroy(natsMicroserviceInfo *info) NATS_FREE(info); } -natsStatus -natsMicroservice_Stats(natsMicroserviceStats **new_stats, natsMicroservice *m) +microError * +natsMicroservice_GetStats(microServiceStats **new_stats, microService *m) { - natsMicroserviceStats *stats = NULL; + microServiceStats *stats = NULL; int i; int len = 0; long double avg = 0.0; if ((new_stats == NULL) || (m == NULL) || (m->mu == NULL)) - return NATS_INVALID_ARG; + return micro_ErrorInvalidArg; - stats = NATS_CALLOC(1, sizeof(natsMicroserviceStats)); + stats = NATS_CALLOC(1, sizeof(microServiceStats)); if (stats == NULL) - return NATS_NO_MEMORY; + return micro_ErrorOutOfMemory; stats->name = m->cfg->name; stats->version = m->cfg->version; stats->id = m->id; stats->started = m->started; - stats->type = natsMicroserviceStatsResponseType; + stats->type = MICRO_STATS_RESPONSE_TYPE; // allocate the actual structs, not pointers. - stats->endpoints = NATS_CALLOC(m->endpoints_len, sizeof(natsMicroserviceEndpointStats)); + stats->endpoints = NATS_CALLOC(m->endpoints_len, sizeof(microEndpointStats)); if (stats->endpoints == NULL) { NATS_FREE(stats); - return NATS_NO_MEMORY; + return micro_ErrorOutOfMemory; } natsMutex_Lock(m->mu); for (i = 0; i < m->endpoints_len; i++) { - natsMicroserviceEndpoint *ep = m->endpoints[i]; + microEndpoint *ep = m->endpoints[i]; if ((ep != NULL) && (ep->mu != NULL)) { natsMutex_Lock(ep->mu); @@ -336,10 +336,10 @@ natsMicroservice_Stats(natsMicroserviceStats **new_stats, natsMicroservice *m) stats->endpoints_len = len; *new_stats = stats; - return NATS_OK; + return NULL; } -void natsMicroserviceStats_Destroy(natsMicroserviceStats *stats) +void natsMicroserviceStats_Destroy(microServiceStats *stats) { if (stats == NULL) return; @@ -348,28 +348,28 @@ void natsMicroserviceStats_Destroy(natsMicroserviceStats *stats) NATS_FREE(stats); } -static natsError * -stop_and_destroy_microservice(natsMicroservice *m) +static microError * +stop_and_destroy_microservice(microService *m) { - natsError *err = NULL; + microError *err = NULL; natsStatus s = NATS_OK; int i; if (m == NULL) return NULL; - err = natsMicroservice_Stop(m); + err = microService_Stop(m); if (err != NULL) return err; for (i = 0; i < m->endpoints_len; i++) { - s = nats_stop_and_destroy_endpoint(m->endpoints[i]); + s = micro_stop_and_destroy_endpoint(m->endpoints[i]); if (s != NATS_OK) - return natsError_Wrapf(nats_NewStatusError(s), "failed to stop and destroy endpoint"); + return microError_Wrapf(microError_FromStatus(s), "failed to stop and destroy endpoint"); } - nats_free_cloned_microservice_config(m->cfg); + micro_free_cloned_service_config(m->cfg); natsConn_release(m->nc); natsMutex_Destroy(m->mu); NATS_FREE(m->id); @@ -378,11 +378,11 @@ stop_and_destroy_microservice(natsMicroservice *m) } natsStatus -nats_clone_microservice_config(natsMicroserviceConfig **out, natsMicroserviceConfig *cfg) +micro_clone_service_config(microServiceConfig **out, microServiceConfig *cfg) { natsStatus s = NATS_OK; - natsMicroserviceConfig *new_cfg = NULL; - natsMicroserviceEndpointConfig *new_ep = NULL; + microServiceConfig *new_cfg = NULL; + microEndpointConfig *new_ep = NULL; char *new_name = NULL; char *new_version = NULL; char *new_description = NULL; @@ -390,7 +390,7 @@ nats_clone_microservice_config(natsMicroserviceConfig **out, natsMicroserviceCon if (out == NULL || cfg == NULL) return NATS_INVALID_ARG; - s = NATS_CALLOCS(&new_cfg, 1, sizeof(natsMicroserviceConfig)); + s = NATS_CALLOCS(&new_cfg, 1, sizeof(microServiceConfig)); if (s == NATS_OK && cfg->name != NULL) { DUP_STRING(s, new_name, cfg->name); @@ -405,11 +405,11 @@ nats_clone_microservice_config(natsMicroserviceConfig **out, natsMicroserviceCon } if (s == NATS_OK && cfg->endpoint != NULL) { - s = nats_clone_endpoint_config(&new_ep, cfg->endpoint); + s = micro_clone_endpoint_config(&new_ep, cfg->endpoint); } if (s == NATS_OK) { - memcpy(new_cfg, cfg, sizeof(natsMicroserviceConfig)); + memcpy(new_cfg, cfg, sizeof(microServiceConfig)); new_cfg->name = new_name; new_cfg->version = new_version; new_cfg->description = new_description; @@ -422,13 +422,13 @@ nats_clone_microservice_config(natsMicroserviceConfig **out, natsMicroserviceCon NATS_FREE(new_name); NATS_FREE(new_version); NATS_FREE(new_description); - nats_free_cloned_endpoint_config(new_ep); + micro_free_cloned_endpoint_config(new_ep); } return NATS_UPDATE_ERR_STACK(s); } -void nats_free_cloned_microservice_config(natsMicroserviceConfig *cfg) +void micro_free_cloned_service_config(microServiceConfig *cfg) { if (cfg == NULL) return; @@ -438,6 +438,6 @@ void nats_free_cloned_microservice_config(natsMicroserviceConfig *cfg) NATS_FREE((char *)cfg->name); NATS_FREE((char *)cfg->version); NATS_FREE((char *)cfg->description); - nats_free_cloned_endpoint_config(cfg->endpoint); + micro_free_cloned_endpoint_config(cfg->endpoint); NATS_FREE(cfg); } diff --git a/src/micro.h b/src/micro.h index b3fdb7cd0..ad6f80e91 100644 --- a/src/micro.h +++ b/src/micro.h @@ -16,45 +16,61 @@ #include "nats.h" -#define natsMicroserviceAPIPrefix "$SRV" - -#define natsMicroserviceInfoResponseType "io.nats.micro.v1.info_response" -#define natsMicroservicePingResponseType "io.nats.micro.v1.ping_response" -#define natsMicroserviceStatsResponseType "io.nats.micro.v1.stats_response" -#define natsMicroserviceSchemaResponseType "io.nats.micro.v1.schema_response" - -#define natsMicroservicePingVerb "PING" -#define natsMicroserviceStatsVerb "STATS" -#define natsMicroserviceInfoVerb "INFO" -#define natsMicroserviceSchemaVerb "SCHEMA" - -#define NATS_MICROSERVICE_STATUS_HDR "Nats-Status" -#define NATS_MICROSERVICE_ERROR_HDR "Nats-Service-Error" -#define NATS_MICROSERVICE_ERROR_CODE_HDR "Nats-Service-Error-Code" +#define MICRO_API_PREFIX "$SRV" + +#define MICRO_INFO_RESPONSE_TYPE "io.nats.micro.v1.info_response" +#define MICRO_PING_RESPONSE_TYPE "io.nats.micro.v1.ping_response" +#define MICRO_STATS_RESPONSE_TYPE "io.nats.micro.v1.stats_response" +#define MICRO_STATS_SCHEMA_TYPE "io.nats.micro.v1.schema_response" + +#define MICRO_PING_VERB "PING" +#define MICRO_STATS_VERB "STATS" +#define MICRO_INFO_VERB "INFO" +#define MICRO_SCHEMA_VERB "SCHEMA" + +#define MICRO_STATUS_HDR "Nats-Status" +#define MICRO_ERROR_HDR "Nats-Service-Error" +#define MICRO_ERROR_CODE_HDR "Nats-Service-Error-Code" + +#define MICRO_CALL(__err, __call) \ + if ((__err) == NULL) \ + { \ + __err = (__call); \ + } + +#define MICRO_CALL_IF(__err, __cond, __call) \ + if (((__err) == NULL) && (__cond)) \ + { \ + __err = (__call); \ + } + +#define MICRO_DO(__err, __block) \ + if ((__err) == NULL) \ + __block; /** * The Microservice request object. */ -typedef struct microservice_request_s natsMicroserviceRequest; +typedef struct micro_request_s microRequest; /** - * The Microservice object. Create and start with #nats_AddMicroservice. + * The Microservice object. Create and start with #microService_Create. */ -typedef struct microservice_s natsMicroservice; +typedef struct micro_service_s microService; /** * The Microservice configuration object. The service holds on to it, so it must * be constant for the lifetime of the service. */ -typedef struct natsMicroserviceConfig +typedef struct microServiceConfig { const char *name; const char *version; const char *description; - struct natsMicroserviceEndpointConfig *endpoint; -} natsMicroserviceConfig; + struct microEndpointConfig *endpoint; +} microServiceConfig; -typedef struct natsMicroserviceInfo +typedef struct microServiceInfo { const char *type; const char *name; @@ -63,13 +79,13 @@ typedef struct natsMicroserviceInfo const char *id; const char **subjects; int subjects_len; -} natsMicroserviceInfo; +} microServiceInfo; /** * The Microservice endpoint object. * TODO document the interface. */ -typedef struct microservice_endpoint_s natsMicroserviceEndpoint; +typedef struct micro_endpoint_s microEndpoint; /** \brief Callback used to deliver messages to a microservice. * @@ -77,26 +93,26 @@ typedef struct microservice_endpoint_s natsMicroserviceEndpoint; * The library will invoke this callback for each message arriving to the * specified subject. * - * @see natsMicroservice_AddEndpoint() + * @see microService_AddEndpoint() */ -typedef void (*natsMicroserviceRequestHandler)(natsMicroservice *m, natsMicroserviceRequest *req); +typedef void (*microRequestHandler)(microService *m, microRequest *req); /** * The Microservice endpoint configuration object. */ -typedef struct natsMicroserviceEndpointConfig +typedef struct microEndpointConfig { const char *name; - natsMicroserviceRequestHandler handler; + microRequestHandler handler; void *closure; const char *subject; - struct natsMicroserviceSchema *schema; -} natsMicroserviceEndpointConfig; + struct microSchema *schema; +} microEndpointConfig; /** * The Microservice endpoint stats struct. */ -typedef struct natsMicroserviceEndpointStats +typedef struct microEndpointStats { const char *name; const char *subject; @@ -106,12 +122,12 @@ typedef struct natsMicroserviceEndpointStats int64_t processing_time_ns; int64_t average_processing_time_ns; char last_error_string[2048]; -} natsMicroserviceEndpointStats; +} microEndpointStats; /** * The Microservice stats struct. */ -typedef struct natsMicroserviceStats +typedef struct microServiceStats { const char *type; const char *name; @@ -119,110 +135,93 @@ typedef struct natsMicroserviceStats const char *id; int64_t started; int endpoints_len; - natsMicroserviceEndpointStats *endpoints; -} natsMicroserviceStats; + microEndpointStats *endpoints; +} microServiceStats; /** * The Microservice endpoint schema object. */ -typedef struct natsMicroserviceSchema +typedef struct microSchema { const char *request; const char *response; -} natsMicroserviceSchema; +} microSchema; /** - * The Microservice client. Initialize with #nats_NewMicroserviceClient. + * The Microservice client. Initialize with #microClient_Create. */ -typedef struct microservice_client_s natsMicroserviceClient; +typedef struct micro_client_s microClient; /** * The Microservice configuration object. */ -typedef struct natsMicroserviceClientConfig natsMicroserviceClientConfig; +typedef struct microClientConfig microClientConfig; -typedef struct error_s natsError; +typedef struct micro_error_s microError; // -// natsMicroservice methods. - -NATS_EXTERN natsError *nats_AddMicroservice(natsMicroservice **new_microservice, natsConnection *nc, natsMicroserviceConfig *cfg); -NATS_EXTERN natsError *natsMicroservice_AddEndpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, natsMicroserviceEndpointConfig *cfg); -NATS_EXTERN natsConnection *natsMicroservice_GetConnection(natsMicroservice *m); -NATS_EXTERN bool natsMicroservice_IsStopped(natsMicroservice *m); -NATS_EXTERN natsError *natsMicroservice_Release(natsMicroservice *m); -NATS_EXTERN natsError *natsMicroservice_Run(natsMicroservice *m); -NATS_EXTERN natsError *natsMicroservice_Stop(natsMicroservice *m); +// microService methods. + +NATS_EXTERN microError *microService_AddEndpoint(microEndpoint **new_endpoint, microService *m, microEndpointConfig *cfg); +NATS_EXTERN microError *microService_Create(microService **new_microservice, natsConnection *nc, microServiceConfig *cfg); +NATS_EXTERN natsConnection *microService_GetConnection(microService *m); +NATS_EXTERN microError *microService_GetInfo(microServiceInfo **new_info, microService *m); +NATS_EXTERN microError *natsMicroservice_GetStats(microServiceStats **new_stats, microService *m); +NATS_EXTERN bool microService_IsStopped(microService *m); +NATS_EXTERN microError *microService_Release(microService *m); +NATS_EXTERN microError *microService_Run(microService *m); +NATS_EXTERN microError *microService_Stop(microService *m); // -// natsMicroserviceRequest methods. - -NATS_EXTERN void natsMicroserviceRequest_Error(natsMicroserviceRequest *req, natsError *err); -NATS_EXTERN natsConnection *natsMicroserviceRequest_GetConnection(natsMicroserviceRequest *req); -NATS_EXTERN natsMicroserviceEndpoint *natsMicroserviceRequest_GetEndpoint(natsMicroserviceRequest *req); -NATS_EXTERN natsMicroservice *natsMicroserviceRequest_GetMicroservice(natsMicroserviceRequest *req); -NATS_EXTERN natsMsg *natsMicroserviceRequest_GetMsg(natsMicroserviceRequest *req); -NATS_EXTERN natsError *natsMicroserviceRequest_Respond(natsMicroserviceRequest *req, const char *data, int len); -NATS_EXTERN natsError *natsMicroserviceRequest_RespondError(natsMicroserviceRequest *req, natsError *err, const char *data, int len); - -#define natsMicroserviceRequestHeader_Set(req, key, value) \ - natsMsgHeader_Set(natsMicroserviceRequest_GetMsg(req), (key), (value)) -#define natsMicroserviceRequestHeader_Add(req, key, value) \ - natsMsgHeader_Add(natsMicroserviceRequest_GetMsg(req), (key), (value)) -#define natsMicroserviceRequestHeader_Get(req, key, value) \ - natsMsgHeader_Get(natsMicroserviceRequest_GetMsg(req), (key), (value)) -#define natsMicroserviceRequestHeader_Values(req, key, values, count) \ - natsMsgHeader_Values(natsMicroserviceRequest_GetMsg(req), (key), (values), (count)) -#define natsMicroserviceRequestHeader_Keys(req, key, keys, count) \ - natsMsgHeader_Keys(natsMicroserviceRequest_GetMsg(req), (key), (keys), (count)) -#define natsMicroserviceRequestHeader_Delete(req, key) \ - natsMsgHeader_Delete(natsMicroserviceRequest_GetMsg(req), (key)) - -#define natsMicroserviceRequest_GetSubject(req) \ - natsMsg_GetSubject(natsMicroserviceRequest_GetMsg(req)) -#define natsMicroserviceRequest_GetReply(req) \ - natsMsg_GetReply(natsMicroserviceRequest_GetMsg(req)) -#define natsMicroserviceRequest_GetData(req) \ - natsMsg_GetData(natsMicroserviceRequest_GetMsg(req)) -#define natsMicroserviceRequest_GetDataLength(req) \ - natsMsg_GetDataLength(natsMicroserviceRequest_GetMsg(req)) -#define natsMicroserviceRequest_GetSequence(req) \ - natsMsg_GetSequence(natsMicroserviceRequest_GetMsg(req)) -#define natsMicroserviceRequest_GetTime(req) \ - natsMsg_GetTime(natsMicroserviceRequest_GetMsg(req)) +// microRequest methods. + +NATS_EXTERN natsConnection *microRequest_GetConnection(microRequest *req); +NATS_EXTERN microEndpoint *microRequest_GetEndpoint(microRequest *req); +NATS_EXTERN natsMsg *microRequest_GetMsg(microRequest *req); +NATS_EXTERN microService *microRequest_GetService(microRequest *req); +NATS_EXTERN microError *microRequest_Respond(microRequest *req, microError **err_will_free, const char *data, size_t len); + +#define microRequest_AddHeader(req, key, value) natsMsgHeader_Add(microRequest_GetMsg(req), (key), (value)) +#define microRequest_DeleteHeader(req, key) natsMsgHeader_Delete(microRequest_GetMsg(req), (key)) +#define microRequest_GetData(req) natsMsg_GetData(microRequest_GetMsg(req)) +#define microRequest_GetDataLength(req) natsMsg_GetDataLength(microRequest_GetMsg(req)) +#define microRequest_GetHeader(req, key, value) natsMsgHeader_Get(microRequest_GetMsg(req), (key), (value)) +#define microRequest_GetHeaderKeys(req, key, keys, count) natsMsgHeader_Keys(microRequest_GetMsg(req), (key), (keys), (count)) +#define microRequest_GetHeaderValues(req, key, values, count) natsMsgHeader_Values(microRequest_GetMsg(req), (key), (values), (count)) +#define microRequest_GetReply(req) natsMsg_GetReply(microRequest_GetMsg(req)) +#define microRequest_GetSequence(req) natsMsg_GetSequence(microRequest_GetMsg(req)) +#define microRequest_GetSubject(req) natsMsg_GetSubject(microRequest_GetMsg(req)) +#define microRequest_GetTime(req) natsMsg_GetTime(microRequest_GetMsg(req)) +#define microRequest_SetHeader(req, key, value) natsMsgHeader_Set(microRequest_GetMsg(req), (key), (value)) // -// natsError methods. - -NATS_EXTERN natsError *nats_Errorf(int code, const char *format, ...); -NATS_EXTERN natsError *nats_IsErrorResponse(natsStatus s, natsMsg *msg); -NATS_EXTERN natsError *nats_NewError(int code, const char *desc); -NATS_EXTERN natsError *nats_NewStatusError(natsStatus s); -NATS_EXTERN void natsError_Destroy(natsError *err); -NATS_EXTERN natsStatus natsError_ErrorCode(natsError *err); -NATS_EXTERN natsStatus natsError_StatusCode(natsError *err); -NATS_EXTERN const char *natsError_String(natsError *err, char *buf, int len); -NATS_EXTERN natsError *natsError_Wrapf(natsError *err, const char *format, ...); +// microError methods. + +NATS_EXTERN microError *micro_NewErrorf(int code, const char *format, ...); +NATS_EXTERN natsStatus microError_Code(microError *err); +NATS_EXTERN void microError_Destroy(microError *err); +NATS_EXTERN microError *microError_FromResponse(natsStatus s, natsMsg *msg); +NATS_EXTERN microError *microError_FromStatus(natsStatus s); +NATS_EXTERN natsStatus microError_Status(microError *err); +NATS_EXTERN const char *microError_String(microError *err, char *buf, int len); +NATS_EXTERN microError *microError_Wrapf(microError *err, const char *format, ...); // // natsClient methods. -natsError *nats_NewMicroserviceClient(natsMicroserviceClient **new_client, natsConnection *nc, natsMicroserviceClientConfig *cfg); -void natsMicroserviceClient_Destroy(natsMicroserviceClient *client); -natsError * -natsMicroserviceClient_DoRequest(natsMicroserviceClient *client, natsMsg **reply, const char *subject, const char *data, int data_len); +microError *microClient_Create(microClient **new_client, natsConnection *nc, microClientConfig *cfg); +void microClient_Destroy(microClient *client); +microError *microClient_DoRequest(microClient *client, natsMsg **reply, const char *subject, const char *data, int data_len); // -// natsMicroserviceInfo methods. +// microServiceInfo methods. -natsStatus natsMicroservice_Info(natsMicroserviceInfo **new_info, natsMicroservice *m); -void natsMicroserviceInfo_Destroy(natsMicroserviceInfo *info); +void microServiceInfo_Destroy(microServiceInfo *info); // -// natsMicroserviceStats methods. +// microServiceStats methods. -natsStatus natsMicroservice_Stats(natsMicroserviceStats **new_stats, natsMicroservice *m); -void natsMicroserviceStats_Destroy(natsMicroserviceStats *stats); +void natsMicroserviceStats_Destroy(microServiceStats *stats); /** @} */ // end of microserviceGroup diff --git a/src/micro_args.c b/src/micro_args.c index 03508c274..61c26725a 100644 --- a/src/micro_args.c +++ b/src/micro_args.c @@ -22,40 +22,23 @@ struct args_s int count; }; -static natsError *parse(void **args, int *args_len, int *i, const char *data, int data_len); +static microError *parse(void **args, int *args_len, const char *data, int data_len); -natsError * -nats_ParseMicroserviceArgs(natsMicroserviceArgs **new_args, const char *data, int data_len) +microError * +micro_ParseArgs(microArgs **new_args, const char *data, int data_len) { - natsError *err = NULL; + microError *err = NULL; int n; - int i = 0; - natsMicroserviceArgs *args = NULL; + microArgs *args = NULL; if ((new_args == NULL) || (data == NULL) || (data_len < 0)) - return nats_NewError(500, "invalid function argument"); + return micro_NewErrorf(500, "invalid function argument"); - // parse the number of arguments without allocating. - err = parse(NULL, &n, &i, data, data_len); - if (err == NULL) - { - args = NATS_CALLOC(1, sizeof(natsMicroserviceArgs)); - if (args == NULL) - err = natsMicroserviceErrorOutOfMemory; - } - if (err == NULL) - { - args->args = NATS_CALLOC(n, sizeof(void *)); - if (args == NULL) - err = natsMicroserviceErrorOutOfMemory; - else - args->count = n; - } - if (err == NULL) - { - i = 0; - err = parse(args->args, &n, &i, data, data_len); - } + MICRO_CALL(err, parse(NULL, &n, data, data_len)); + MICRO_CALL(err, MICRO_CALLOC(args, 1, sizeof(microArgs))); + MICRO_CALL(err, MICRO_CALLOC(args->args, n, sizeof(void *))); + MICRO_DO(err, args->count = n); + MICRO_CALL(err, parse(args->args, &n, data, data_len)); if (err == NULL) { @@ -63,13 +46,12 @@ nats_ParseMicroserviceArgs(natsMicroserviceArgs **new_args, const char *data, in } else { - natsMicroserviceArgs_Destroy(args); + microArgs_Destroy(args); } - return err; } -void natsMicroserviceArgs_Destroy(natsMicroserviceArgs *args) +void microArgs_Destroy(microArgs *args) { int i; @@ -84,7 +66,7 @@ void natsMicroserviceArgs_Destroy(natsMicroserviceArgs *args) NATS_FREE(args); } -int natsMicroserviceArgs_Count(natsMicroserviceArgs *args) +int microArgs_Count(microArgs *args) { if (args == NULL) return 0; @@ -92,31 +74,31 @@ int natsMicroserviceArgs_Count(natsMicroserviceArgs *args) return args->count; } -natsError * -natsMicroserviceArgs_GetInt(int *val, natsMicroserviceArgs *args, int index) +microError * +microArgs_GetInt(int *val, microArgs *args, int index) { if ((args == NULL) || (index < 0) || (index >= args->count) || (val == NULL)) - return natsMicroserviceErrorInvalidArg; + return micro_ErrorInvalidArg; *val = *((int *)args->args[index]); return NULL; } -natsError * -natsMicroserviceArgs_GetFloat(long double *val, natsMicroserviceArgs *args, int index) +microError * +microArgs_GetFloat(long double *val, microArgs *args, int index) { if ((args == NULL) || (index < 0) || (index >= args->count) || (val == NULL)) - return natsMicroserviceErrorInvalidArg; + return micro_ErrorInvalidArg; *val = *((long double *)args->args[index]); return NULL; } -natsError * -natsMicroserviceArgs_GetString(const char **val, natsMicroserviceArgs *args, int index) +microError * +microArgs_GetString(const char **val, microArgs *args, int index) { if ((args == NULL) || (index < 0) || (index >= args->count) || (val == NULL)) - return natsMicroserviceErrorInvalidArg; + return micro_ErrorInvalidArg; *val = (const char *)args->args[index]; return NULL; @@ -130,7 +112,7 @@ natsMicroserviceArgs_GetString(const char **val, natsMicroserviceArgs *args, int /// @param data raw message data /// @param data_len length of data /// @return error in case the string is not properly terminated. -static natsError * +static microError * decode_rest_of_string(char *dup, int *decoded_len, int *i, const char *data, int data_len) { char c; @@ -190,7 +172,7 @@ decode_rest_of_string(char *dup, int *decoded_len, int *i, const char *data, int } if (!terminated) { - nats_NewError(400, "a quoted string is not properly terminated"); + micro_NewErrorf(400, "a quoted string is not properly terminated"); } *decoded_len = len; @@ -203,10 +185,10 @@ decode_rest_of_string(char *dup, int *decoded_len, int *i, const char *data, int /// @param data raw message data /// @param data_len length of data /// @return error. -static natsError * +static microError * decode_and_dupe_rest_of_string(char **dup, int *i, const char *data, int data_len) { - natsError *err = NULL; + microError *err = NULL; int start = *i; int decoded_len; @@ -226,7 +208,7 @@ decode_and_dupe_rest_of_string(char **dup, int *i, const char *data, int data_le *dup = NATS_CALLOC(decoded_len + 1, sizeof(char)); if (*dup == NULL) { - return natsMicroserviceErrorOutOfMemory; + return micro_ErrorOutOfMemory; } // no need to check for error the 2nd time, we already know the string is @@ -242,10 +224,11 @@ typedef enum parserState NumberArg, } parserState; -static natsError * -parse(void **args, int *args_len, int *i, const char *data, int data_len) +static microError * +parse(void **args, int *args_len, const char *data, int data_len) { - natsError *err = NULL; + int i = 0; + microError *err = NULL; char c; int n = 0; parserState state = NewArg; @@ -254,9 +237,9 @@ parse(void **args, int *args_len, int *i, const char *data, int data_len) bool is_float = false; #define EOS 0 - for (; *i < data_len + 1;) + for (; i < data_len + 1;) { - c = (*i < data_len) ? data[*i] : EOS; + c = (i < data_len) ? data[i] : EOS; switch (state) { @@ -265,12 +248,12 @@ parse(void **args, int *args_len, int *i, const char *data, int data_len) { case EOS: case ' ': - (*i)++; + i++; break; case '"': - (*i)++; // consume the opening quote. - err = decode_and_dupe_rest_of_string((char **)(&args[n]), i, data, data_len); + i++; // consume the opening quote. + err = decode_and_dupe_rest_of_string((char **)(&args[n]), &i, data, data_len); if (err != NULL) { return err; @@ -295,11 +278,11 @@ parse(void **args, int *args_len, int *i, const char *data, int data_len) num_len = 0; numbuf[num_len++] = c; is_float = (c == '.'); - (*i)++; + i++; break; default: - return nats_Errorf(400, "unexpected '%c', an argument must be a number or a quoted string", c); + return micro_NewErrorf(400, "unexpected '%c', an argument must be a number or a quoted string", c); } break; @@ -325,7 +308,7 @@ parse(void **args, int *args_len, int *i, const char *data, int data_len) numbuf[num_len] = c; num_len++; is_float = is_float || (c == '.') || (c == 'e') || (c == 'E'); - (*i)++; + i++; break; case EOS: @@ -338,7 +321,7 @@ parse(void **args, int *args_len, int *i, const char *data, int data_len) args[n] = NATS_CALLOC(1, sizeof(long double)); if (args[n] == NULL) { - return natsMicroserviceErrorOutOfMemory; + return micro_ErrorOutOfMemory; } *(long double *)args[n] = strtold(numbuf, NULL); } @@ -347,23 +330,23 @@ parse(void **args, int *args_len, int *i, const char *data, int data_len) args[n] = NATS_CALLOC(1, sizeof(int)); if (args[n] == NULL) { - return natsMicroserviceErrorOutOfMemory; + return micro_ErrorOutOfMemory; } *(int *)args[n] = atoi(numbuf); } } n++; - (*i)++; + i++; state = NewArg; break; default: - return nats_Errorf(400, "unexpected '%c', a number must be followed by a space", c); + return micro_NewErrorf(400, "unexpected '%c', a number must be followed by a space", c); } break; default: - return nats_Errorf(500, "unreachable: wrong state for a ' ', expected NewArg or NumberArg, got %d", state); + return micro_NewErrorf(500, "unreachable: wrong state for a ' ', expected NewArg or NumberArg, got %d", state); } } diff --git a/src/micro_args.h b/src/micro_args.h index 4f643144c..e4373ac44 100644 --- a/src/micro_args.h +++ b/src/micro_args.h @@ -20,25 +20,25 @@ * Request unmarshaled as "arguments", a space-separated list of numbers and strings. * TODO document the interface. */ -typedef struct args_s natsMicroserviceArgs; +typedef struct args_s microArgs; -NATS_EXTERN natsError * -nats_ParseMicroserviceArgs(natsMicroserviceArgs **args, const char *data, int data_len); +NATS_EXTERN microError * +micro_ParseArgs(microArgs **args, const char *data, int data_len); NATS_EXTERN int -natsMicroserviceArgs_Count(natsMicroserviceArgs *args); +microArgs_Count(microArgs *args); -NATS_EXTERN natsError * -natsMicroserviceArgs_GetInt(int *val, natsMicroserviceArgs *args, int index); +NATS_EXTERN microError * +microArgs_GetInt(int *val, microArgs *args, int index); -NATS_EXTERN natsError * -natsMicroserviceArgs_GetFloat(long double *val, natsMicroserviceArgs *args, int index); +NATS_EXTERN microError * +microArgs_GetFloat(long double *val, microArgs *args, int index); -NATS_EXTERN natsError * -natsMicroserviceArgs_GetString(const char **val, natsMicroserviceArgs *args, int index); +NATS_EXTERN microError * +microArgs_GetString(const char **val, microArgs *args, int index); NATS_EXTERN void -natsMicroserviceArgs_Destroy(natsMicroserviceArgs *args); +microArgs_Destroy(microArgs *args); /** @} */ // end of microserviceGroup diff --git a/src/micro_client.c b/src/micro_client.c index fdc25c891..5def1e873 100644 --- a/src/micro_client.c +++ b/src/micro_client.c @@ -16,17 +16,17 @@ #include "mem.h" #include "conn.h" -natsError * -nats_NewMicroserviceClient(natsMicroserviceClient **new_client, natsConnection *nc, natsMicroserviceClientConfig *cfg) +microError * +microClient_Create(microClient **new_client, natsConnection *nc, microClientConfig *cfg) { - natsMicroserviceClient *client = NULL; + microClient *client = NULL; if (new_client == NULL) - return natsMicroserviceErrorInvalidArg; + return micro_ErrorInvalidArg; - client = NATS_CALLOC(1, sizeof(struct microservice_client_s)); + client = NATS_CALLOC(1, sizeof(struct micro_client_s)); if (client == NULL) - return natsMicroserviceErrorOutOfMemory; + return micro_ErrorOutOfMemory; natsConn_retain(nc); client->nc = nc; @@ -34,7 +34,7 @@ nats_NewMicroserviceClient(natsMicroserviceClient **new_client, natsConnection * return NULL; } -void natsMicroserviceClient_Destroy(natsMicroserviceClient *client) +void microClient_Destroy(microClient *client) { if (client == NULL) return; @@ -43,21 +43,21 @@ void natsMicroserviceClient_Destroy(natsMicroserviceClient *client) NATS_FREE(client); } -natsError * -natsMicroserviceClient_DoRequest(natsMicroserviceClient *client, natsMsg **reply, const char *subject, const char *data, int data_len) +microError * +microClient_DoRequest(microClient *client, natsMsg **reply, const char *subject, const char *data, int data_len) { natsStatus s = NATS_OK; - natsError *err = NULL; + microError *err = NULL; natsMsg *msg = NULL; if ((client == NULL) || (reply == NULL)) - return natsMicroserviceErrorInvalidArg; + return micro_ErrorInvalidArg; s = natsConnection_Request(&msg, client->nc, subject, data, data_len, 5000); if (s != NATS_OK) - err = natsError_Wrapf(nats_NewStatusError(s), "request failed"); + return microError_Wrapf(microError_FromStatus(s), "request failed"); - err = nats_IsErrorResponse(s, msg); + err = microError_FromResponse(s, msg); if (err == NULL) { *reply = msg; diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index cceebdfec..8e2f9dcfa 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -18,7 +18,7 @@ #include "mem.h" static natsStatus -free_endpoint(natsMicroserviceEndpoint *ep); +free_endpoint(microEndpoint *ep); static bool is_valid_name(const char *name); static bool @@ -27,11 +27,11 @@ static void handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); natsStatus -nats_new_endpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, natsMicroserviceEndpointConfig *cfg) +micro_new_endpoint(microEndpoint **new_endpoint, microService *m, microEndpointConfig *cfg) { natsStatus s = NATS_OK; - natsMicroserviceEndpoint *ep = NULL; - natsMicroserviceEndpointConfig *clone_cfg = NULL; + microEndpoint *ep = NULL; + microEndpointConfig *clone_cfg = NULL; if (!is_valid_name(cfg->name) || (cfg->handler == NULL)) { @@ -41,8 +41,8 @@ nats_new_endpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, { s = NATS_INVALID_ARG; } - IFOK(s, nats_clone_endpoint_config(&clone_cfg, cfg)); - IFOK(s, NATS_CALLOCS(&ep, 1, sizeof(natsMicroserviceEndpoint))); + IFOK(s, micro_clone_endpoint_config(&clone_cfg, cfg)); + IFOK(s, NATS_CALLOCS(&ep, 1, sizeof(microEndpoint))); IFOK(s, natsMutex_Create(&ep->mu)); if (s == NATS_OK) { @@ -53,18 +53,18 @@ nats_new_endpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, } else { - nats_free_cloned_endpoint_config(clone_cfg); + micro_free_cloned_endpoint_config(clone_cfg); free_endpoint(ep); } return NATS_UPDATE_ERR_STACK(s); } natsStatus -nats_clone_endpoint_config(natsMicroserviceEndpointConfig **out, natsMicroserviceEndpointConfig *cfg) +micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) { natsStatus s = NATS_OK; - natsMicroserviceEndpointConfig *new_cfg = NULL; - natsMicroserviceSchema *new_schema = NULL; + microEndpointConfig *new_cfg = NULL; + microSchema *new_schema = NULL; char *new_name = NULL; char *new_subject = NULL; char *new_request = NULL; @@ -73,7 +73,7 @@ nats_clone_endpoint_config(natsMicroserviceEndpointConfig **out, natsMicroservic if (out == NULL || cfg == NULL) return NATS_INVALID_ARG; - s = NATS_CALLOCS(&new_cfg, 1, sizeof(natsMicroserviceEndpointConfig)); + s = NATS_CALLOCS(&new_cfg, 1, sizeof(microEndpointConfig)); if (s == NATS_OK && cfg->name != NULL) { DUP_STRING(s, new_name, cfg->name); @@ -84,7 +84,7 @@ nats_clone_endpoint_config(natsMicroserviceEndpointConfig **out, natsMicroservic } if (s == NATS_OK && cfg->schema != NULL) { - s = NATS_CALLOCS(&new_schema, 1, sizeof(natsMicroserviceSchema)); + s = NATS_CALLOCS(&new_schema, 1, sizeof(microSchema)); if (s == NATS_OK && cfg->schema->request != NULL) { DUP_STRING(s, new_request, cfg->schema->request); @@ -101,7 +101,7 @@ nats_clone_endpoint_config(natsMicroserviceEndpointConfig **out, natsMicroservic } if (s == NATS_OK) { - memcpy(new_cfg, cfg, sizeof(natsMicroserviceEndpointConfig)); + memcpy(new_cfg, cfg, sizeof(microEndpointConfig)); new_cfg->schema = new_schema; new_cfg->name = new_name; new_cfg->subject = new_subject; @@ -120,7 +120,7 @@ nats_clone_endpoint_config(natsMicroserviceEndpointConfig **out, natsMicroservic return NATS_UPDATE_ERR_STACK(s); } -void nats_free_cloned_endpoint_config(natsMicroserviceEndpointConfig *cfg) +void micro_free_cloned_endpoint_config(microEndpointConfig *cfg) { if (cfg == NULL) return; @@ -137,7 +137,7 @@ void nats_free_cloned_endpoint_config(natsMicroserviceEndpointConfig *cfg) } natsStatus -nats_start_endpoint(natsMicroserviceEndpoint *ep) +micro_start_endpoint(microEndpoint *ep) { if ((ep->subject == NULL) || (ep->config == NULL) || (ep->config->handler == NULL)) // nothing to do @@ -147,12 +147,12 @@ nats_start_endpoint(natsMicroserviceEndpoint *ep) memset(&ep->stats, 0, sizeof(ep->stats)); return natsConnection_QueueSubscribe(&ep->sub, ep->m->nc, ep->subject, - natsMicroserviceQueueGroup, handle_request, ep); + MICRO_QUEUE_GROUP, handle_request, ep); } // TODO <>/<> COPY FROM GO natsStatus -nats_stop_endpoint(natsMicroserviceEndpoint *ep) +micro_stop_endpoint(microEndpoint *ep) { natsStatus s = NATS_OK; @@ -172,24 +172,24 @@ nats_stop_endpoint(natsMicroserviceEndpoint *ep) } static natsStatus -free_endpoint(natsMicroserviceEndpoint *ep) +free_endpoint(microEndpoint *ep) { NATS_FREE(ep->subject); natsMutex_Destroy(ep->mu); - nats_free_cloned_endpoint_config(ep->config); + micro_free_cloned_endpoint_config(ep->config); NATS_FREE(ep); return NATS_OK; } natsStatus -nats_stop_and_destroy_endpoint(natsMicroserviceEndpoint *ep) +micro_stop_and_destroy_endpoint(microEndpoint *ep) { natsStatus s = NATS_OK; if (ep == NULL) return NATS_OK; - IFOK(s, nats_stop_endpoint(ep)); + IFOK(s, micro_stop_endpoint(ep)); IFOK(s, free_endpoint(ep)); return NATS_UPDATE_ERR_STACK(s); @@ -245,11 +245,11 @@ static void handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { natsStatus s = NATS_OK; - natsMicroserviceEndpoint *ep = (natsMicroserviceEndpoint *)closure; - natsMicroserviceEndpointStats *stats = &ep->stats; - natsMicroserviceEndpointConfig *cfg = ep->config; - natsMicroserviceRequest *req = NULL; - natsMicroserviceRequestHandler handler = cfg->handler; + microEndpoint *ep = (microEndpoint *)closure; + microEndpointStats *stats = &ep->stats; + microEndpointConfig *cfg = ep->config; + microRequest *req = NULL; + microRequestHandler handler = cfg->handler; int64_t start, elapsed_ns, full_s; if (handler == NULL) @@ -260,7 +260,7 @@ handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *cl return; } - s = _newMicroserviceRequest(&req, msg); + s = micro_new_request(&req, msg); if (s != NATS_OK) { natsMsg_Destroy(msg); @@ -283,17 +283,17 @@ handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *cl natsMutex_Unlock(ep->mu); - _freeMicroserviceRequest(req); + micro_free_request(req); natsMsg_Destroy(msg); } -void natsMicroserviceEndpoint_updateLastError(natsMicroserviceEndpoint *ep, natsError *err) +void micro_update_last_error(microEndpoint *ep, microError *err) { if (err == NULL) return; natsMutex_Lock(ep->mu); ep->stats.num_errors++; - natsError_String(err, ep->stats.last_error_string, sizeof(ep->stats.last_error_string)); + microError_String(err, ep->stats.last_error_string, sizeof(ep->stats.last_error_string)); natsMutex_Unlock(ep->mu); } diff --git a/src/micro_error.c b/src/micro_error.c index 1a20eb6f7..73f6e0eeb 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -32,36 +32,36 @@ void natsConn_Unlock(natsConnection *nc) { natsMutex_Unlock(nc->mu); } #endif // DEV_MODE -static natsError _errorOutOfMemory = { +static microError _errorOutOfMemory = { .status = NATS_NO_MEMORY, .code = 500, .description = "Out of memory", }; -static natsError _errorInvalidArg = { +static microError _errorInvalidArg = { .status = NATS_INVALID_ARG, .code = 400, .description = "Invalid function argument", }; -static natsError _errorInvalidFormat = { +static microError _errorInvalidFormat = { .status = NATS_INVALID_ARG, .code = 400, .description = "Invalid error format string", }; -static natsError *knownErrors[] = { +static microError *knownErrors[] = { &_errorOutOfMemory, &_errorInvalidArg, &_errorInvalidFormat, NULL, }; -natsError *natsMicroserviceErrorOutOfMemory = &_errorOutOfMemory; -natsError *natsMicroserviceErrorInvalidArg = &_errorInvalidArg; +microError *micro_ErrorOutOfMemory = &_errorOutOfMemory; +microError *micro_ErrorInvalidArg = &_errorInvalidArg; const char * -natsError_String(natsError *err, char *buf, int size) +microError_String(microError *err, char *buf, int size) { if (err == NULL || buf == NULL) return ""; @@ -73,17 +73,17 @@ natsError_String(natsError *err, char *buf, int size) } natsStatus -natsError_StatusCode(natsError *err) +microError_Status(microError *err) { return (err != NULL) ? err->status : NATS_OK; } -static natsError * +static microError * new_error(natsStatus s, int code, char *description) { - natsError *err = NULL; + microError *err = NULL; - err = NATS_CALLOC(1, sizeof(natsError)); + err = NATS_CALLOC(1, sizeof(microError)); if (err == NULL) return &_errorOutOfMemory; @@ -94,14 +94,8 @@ new_error(natsStatus s, int code, char *description) return err; } -natsError * -nats_NewError(int code, const char *description) -{ - return nats_Errorf(NATS_ERR, description); -} - -natsError * -nats_NewStatusError(natsStatus s) +microError * +microError_FromStatus(natsStatus s) { char *dup = NULL; @@ -115,8 +109,8 @@ nats_NewStatusError(natsStatus s) return new_error(s, 0, dup); } -natsError * -natsError_Wrapf(natsError *err, const char *format, ...) +microError * +microError_Wrapf(microError *err, const char *format, ...) { va_list args; char *buf = NULL; @@ -159,12 +153,12 @@ natsError_Wrapf(natsError *err, const char *format, ...) code = err->code; s = err->status; - natsError_Destroy(err); + microError_Destroy(err); return new_error(s, code, buf); } -natsError * -nats_Errorf(int code, const char *format, ...) +microError * +micro_NewErrorf(int code, const char *format, ...) { va_list args1, args2; char *buf = NULL; @@ -196,7 +190,7 @@ nats_Errorf(int code, const char *format, ...) return new_error(NATS_ERR, code, buf); } -void natsError_Destroy(natsError *err) +void microError_Destroy(microError *err) { int i; @@ -215,24 +209,24 @@ void natsError_Destroy(natsError *err) NATS_FREE(err); } -natsError * -nats_IsErrorResponse(natsStatus status, natsMsg *msg) +microError * +microError_FromResponse(natsStatus status, natsMsg *msg) { - natsError *err = NULL; + microError *err = NULL; const char *c = NULL, *d = NULL; bool is_error; if (msg != NULL) { - natsMsgHeader_Get(msg, NATS_MICROSERVICE_ERROR_CODE_HDR, &c); - natsMsgHeader_Get(msg, NATS_MICROSERVICE_ERROR_HDR, &d); + natsMsgHeader_Get(msg, MICRO_ERROR_CODE_HDR, &c); + natsMsgHeader_Get(msg, MICRO_ERROR_HDR, &d); } is_error = (status != NATS_OK) || !nats_IsStringEmpty(c) || !nats_IsStringEmpty(d); if (!is_error) return NULL; - err = natsError_Wrapf(nats_NewStatusError(status), d); + err = microError_Wrapf(microError_FromStatus(status), d); if (!nats_IsStringEmpty(c) && (err != NULL)) { err->code = atoi(c); diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 6691c578a..9d0f87505 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -19,24 +19,24 @@ #include "util.h" static natsStatus -marshal_ping(natsBuffer **new_buf, natsMicroservice *m); +marshal_ping(natsBuffer **new_buf, microService *m); static void handle_ping(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); -static natsStatus -marshal_info(natsBuffer **new_buf, natsMicroserviceInfo *info); +static microError* +marshal_info(natsBuffer **new_buf, microServiceInfo *info); static void handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); -static natsStatus -marshal_stats(natsBuffer **new_buf, natsMicroserviceStats *stats); +static microError * +marshal_stats(natsBuffer **new_buf, microServiceStats *stats); static void handle_stats(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); static natsStatus -add_internal_handler(natsMicroservice *m, const char *verb, const char *kind, const char *id, const char *name, natsMsgHandler handler); +add_internal_handler(microService *m, const char *verb, const char *kind, const char *id, const char *name, natsMsgHandler handler); static natsStatus -add_verb_handlers(natsMicroservice *m, const char *verb, natsMsgHandler handler); +add_verb_handlers(microService *m, const char *verb, natsMsgHandler handler); static natsStatus new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); static natsStatus @@ -50,13 +50,13 @@ static int fmt_int(char buf[], int w, uint64_t v); natsStatus -micro_monitoring_init(natsMicroservice *m) +micro_init_monitoring(microService *m) { natsStatus s = NATS_OK; - IFOK(s, add_verb_handlers(m, natsMicroservicePingVerb, handle_ping)); - IFOK(s, add_verb_handlers(m, natsMicroserviceStatsVerb, handle_stats)); - IFOK(s, add_verb_handlers(m, natsMicroserviceInfoVerb, handle_info)); + IFOK(s, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); + IFOK(s, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); + IFOK(s, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); return NATS_UPDATE_ERR_STACK(s); } @@ -65,8 +65,8 @@ static void handle_ping(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { natsStatus s = NATS_OK; - natsMicroservice *m = (natsMicroservice *)closure; - natsMicroserviceRequest req = { + microService *m = (microService *)closure; + microRequest req = { .msg = msg, }; natsBuffer *buf = NULL; @@ -74,56 +74,44 @@ handle_ping(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closu s = marshal_ping(&buf, m); if (s == NATS_OK) { - natsMicroserviceRequest_Respond(&req, natsBuf_Data(buf), natsBuf_Len(buf)); + microRequest_Respond(&req, NULL, natsBuf_Data(buf), natsBuf_Len(buf)); } natsBuf_Destroy(buf); } void handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { - natsStatus s = NATS_OK; - natsMicroservice *m = (natsMicroservice *)closure; - natsMicroserviceRequest req = { + microError *err = NULL; + microService *m = (microService *)closure; + microRequest req = { .msg = msg, }; - natsMicroserviceInfo *info = NULL; + microServiceInfo *info = NULL; natsBuffer *buf = NULL; - s = natsMicroservice_Info(&info, m); - IFOK(s, marshal_info(&buf, info)); - if (s == NATS_OK) - { - natsMicroserviceRequest_Respond(&req, natsBuf_Data(buf), natsBuf_Len(buf)); - } - else - { - natsMicroserviceRequest_Error(&req, nats_NewStatusError(s)); - } + MICRO_CALL(err, microService_GetInfo(&info, m)); + MICRO_CALL(err, marshal_info(&buf, info)); + + microRequest_Respond(&req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); natsBuf_Destroy(buf); - natsMicroserviceInfo_Destroy(info); + microServiceInfo_Destroy(info); } static void handle_stats(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { - natsStatus s = NATS_OK; - natsMicroservice *m = (natsMicroservice *)closure; - natsMicroserviceRequest req = { + microError *err = NULL; + microService *m = (microService *)closure; + microRequest req = { .msg = msg, }; - natsMicroserviceStats *stats = NULL; + microServiceStats *stats = NULL; natsBuffer *buf = NULL; - s = natsMicroservice_Stats(&stats, m); - IFOK(s, marshal_stats(&buf, stats)); - if (s == NATS_OK) - { - natsMicroserviceRequest_Respond(&req, natsBuf_Data(buf), natsBuf_Len(buf)); - } - else - { - natsMicroserviceRequest_Error(&req, nats_NewStatusError(s)); - } + MICRO_CALL(err, natsMicroservice_GetStats(&stats, m)); + MICRO_CALL(err, marshal_stats(&buf, stats)); + + microRequest_Respond(&req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); natsBuf_Destroy(buf); natsMicroserviceStats_Destroy(stats); } @@ -182,15 +170,15 @@ new_control_subject(char **newSubject, const char *verb, const char *name, const } else if (nats_IsStringEmpty(name) && nats_IsStringEmpty(id)) - return new_dotted_subject(newSubject, 2, natsMicroserviceAPIPrefix, verb); + return new_dotted_subject(newSubject, 2, MICRO_API_PREFIX, verb); else if (nats_IsStringEmpty(id)) - return new_dotted_subject(newSubject, 3, natsMicroserviceAPIPrefix, verb, name); + return new_dotted_subject(newSubject, 3, MICRO_API_PREFIX, verb, name); else - return new_dotted_subject(newSubject, 4, natsMicroserviceAPIPrefix, verb, name, id); + return new_dotted_subject(newSubject, 4, MICRO_API_PREFIX, verb, name, id); } static natsStatus -add_internal_handler(natsMicroservice *m, const char *verb, const char *kind, +add_internal_handler(microService *m, const char *verb, const char *kind, const char *id, const char *name, natsMsgHandler handler) { @@ -210,7 +198,7 @@ add_internal_handler(natsMicroservice *m, const char *verb, const char *kind, } else { - natsMicroservice_Stop(m); + microService_Stop(m); return NATS_UPDATE_ERR_STACK(s); } } @@ -220,7 +208,7 @@ add_internal_handler(natsMicroservice *m, const char *verb, const char *kind, // written with the framework, one that handles all services of a particular // kind, and finally a specific service instance. static natsStatus -add_verb_handlers(natsMicroservice *m, const char *verb, natsMsgHandler handler) +add_verb_handlers(microService *m, const char *verb, natsMsgHandler handler) { natsStatus s = NATS_OK; char name[1024]; @@ -249,7 +237,7 @@ add_verb_handlers(natsMicroservice *m, const char *verb, natsMsgHandler handler) IFOK(s, natsBuf_Append(buf, "\"" _sep, -1)); static natsStatus -marshal_ping(natsBuffer **new_buf, natsMicroservice *m) +marshal_ping(natsBuffer **new_buf, microService *m) { natsBuffer *buf = NULL; natsStatus s; @@ -262,7 +250,7 @@ marshal_ping(natsBuffer **new_buf, natsMicroservice *m) IFOK_attr("name", m->cfg->name, ","); IFOK_attr("version", m->cfg->version, ","); IFOK_attr("id", m->id, ","); - IFOK_attr("type", natsMicroservicePingResponseType, ""); + IFOK_attr("type", MICRO_PING_RESPONSE_TYPE, ""); IFOK(s, natsBuf_AppendByte(buf, '}')); if (s == NATS_OK) @@ -277,17 +265,14 @@ marshal_ping(natsBuffer **new_buf, natsMicroservice *m) } } -static natsStatus -marshal_info(natsBuffer **new_buf, natsMicroserviceInfo *info) +static microError* +marshal_info(natsBuffer **new_buf, microServiceInfo *info) { natsBuffer *buf = NULL; natsStatus s; s = natsBuf_Create(&buf, 4096); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - s = natsBuf_Append(buf, "{", -1); + IFOK(s, natsBuf_Append(buf, "{", -1)); IFOK_attr("description", info->description, ","); IFOK_attr("id", info->id, ","); IFOK_attr("name", info->name, ","); @@ -312,29 +297,26 @@ marshal_info(natsBuffer **new_buf, natsMicroserviceInfo *info) if (s == NATS_OK) { *new_buf = buf; - return NATS_OK; + return NULL; } else { natsBuf_Destroy(buf); - return NATS_UPDATE_ERR_STACK(s); + return microError_Wrapf(microError_FromStatus(s), "failed to marshal service info"); } } -static natsStatus -marshal_stats(natsBuffer **new_buf, natsMicroserviceStats *stats) +static microError* +marshal_stats(natsBuffer **new_buf, microServiceStats *stats) { natsBuffer *buf = NULL; natsStatus s; int i; char timebuf[128]; - natsMicroserviceEndpointStats *ep; + microEndpointStats *ep; s = natsBuf_Create(&buf, 8 * 1024); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - s = natsBuf_AppendByte(buf, '{'); + IFOK(s, natsBuf_AppendByte(buf, '{')); IFOK_attr("id", stats->id, ","); IFOK_attr("name", stats->name, ","); IFOK_attr("type", stats->type, ","); @@ -370,12 +352,12 @@ marshal_stats(natsBuffer **new_buf, natsMicroserviceStats *stats) if (s == NATS_OK) { *new_buf = buf; - return NATS_OK; + return NULL; } else { natsBuf_Destroy(buf); - return NATS_UPDATE_ERR_STACK(s); + return microError_Wrapf(microError_FromStatus(s), "failed to marshal service info"); } } diff --git a/src/micro_request.c b/src/micro_request.c index 7746b0058..b20a25cd1 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -16,111 +16,87 @@ #include "mem.h" natsMsg * -natsMicroserviceRequest_GetMsg(natsMicroserviceRequest *req) +microRequest_GetMsg(microRequest *req) { return req != NULL ? req->msg : NULL; } -natsMicroserviceEndpoint * -natsMicroserviceRequest_GetEndpoint(natsMicroserviceRequest *req) +microEndpoint * +microRequest_GetEndpoint(microRequest *req) { return (req != NULL) ? req->ep : NULL; } -natsMicroservice * -natsMicroserviceRequest_GetMicroservice(natsMicroserviceRequest *req) +microService * +microRequest_GetService(microRequest *req) { return ((req != NULL) && (req->ep != NULL)) ? req->ep->m : NULL; } natsConnection * -natsMicroserviceRequest_GetConnection(natsMicroserviceRequest *req) +microRequest_GetConnection(microRequest *req) { return ((req != NULL) && (req->ep != NULL) && (req->ep->m != NULL)) ? req->ep->m->nc : NULL; } -natsError * -natsMicroserviceRequest_Respond(natsMicroserviceRequest *req, const char *data, int len) -{ - natsStatus s = NATS_OK; - natsError *err = NULL; - - if ((req == NULL) || (req->msg == NULL) || (req->msg->sub == NULL) || (req->msg->sub->conn == NULL)) - return natsMicroserviceErrorInvalidArg; - - s = natsConnection_Publish(req->msg->sub->conn, natsMsg_GetReply(req->msg), data, len); - if (s == NATS_OK) - return NULL; - - err = natsError_Wrapf(nats_NewStatusError(s), "failed to respond to a message"); - natsMicroserviceEndpoint_updateLastError(req->ep, err); - return err; -} - -natsError * -natsMicroserviceRequest_RespondError(natsMicroserviceRequest *req, natsError *err, const char *data, int len) +microError * +microRequest_Respond(microRequest *req, microError **err_will_free, const char *data, size_t len) { natsMsg *msg = NULL; + microError *err = NULL; natsStatus s = NATS_OK; char buf[64]; - if ((req == NULL) || (req->msg == NULL) || (req->msg->sub == NULL) || (req->msg->sub->conn == NULL) || (err == NULL)) + if ((req == NULL) || (req->msg == NULL) || (req->msg->sub == NULL) || (req->msg->sub->conn == NULL)) { - return natsMicroserviceErrorInvalidArg; + return micro_ErrorInvalidArg; } IFOK(s, natsMsg_Create(&msg, natsMsg_GetReply(req->msg), NULL, data, len)); - if ((s == NATS_OK) && (err->status != NATS_OK)) - { - s = natsMsgHeader_Set(msg, NATS_MICROSERVICE_STATUS_HDR, natsStatus_GetText(err->status)); - } - if (s == NATS_OK) - { - s = natsMsgHeader_Set(msg, NATS_MICROSERVICE_ERROR_HDR, err->description); - } - if (s == NATS_OK) + if ((err_will_free != NULL) && (*err_will_free != NULL)) { - snprintf(buf, sizeof(buf), "%d", err->code); - s = natsMsgHeader_Set(msg, NATS_MICROSERVICE_ERROR_CODE_HDR, buf); + err = *err_will_free; + if ((s == NATS_OK) && (err->status != NATS_OK)) + { + s = natsMsgHeader_Set(msg, MICRO_STATUS_HDR, natsStatus_GetText(err->status)); + } + if (s == NATS_OK) + { + s = natsMsgHeader_Set(msg, MICRO_ERROR_HDR, err->description); + } + if (s == NATS_OK) + { + snprintf(buf, sizeof(buf), "%d", err->code); + s = natsMsgHeader_Set(msg, MICRO_ERROR_CODE_HDR, buf); + } + micro_update_last_error(req->ep, err); } IFOK(s, natsConnection_PublishMsg(req->msg->sub->conn, msg)); natsMsg_Destroy(msg); - natsMicroserviceEndpoint_updateLastError(req->ep, err); - - if (s == NATS_OK) + if (err_will_free != NULL) { - return NULL; + microError_Destroy(*err_will_free); + *err_will_free = NULL; } - else - { - err = natsError_Wrapf(nats_NewStatusError(s), "failed to respond to a message with an error"); - return err; - } -} - -void natsMicroserviceRequest_Error(natsMicroserviceRequest *req, natsError *err) -{ - if (err == NULL) - return; - - natsMicroserviceRequest_RespondError(req, err, NULL, 0); - natsError_Destroy(err); + return microError_Wrapf( + microError_FromStatus(s), + "failed to respond to a message with an error"); } -void _freeMicroserviceRequest(natsMicroserviceRequest *req) +void micro_free_request(microRequest *req) { NATS_FREE(req); } natsStatus -_newMicroserviceRequest(natsMicroserviceRequest **new_request, natsMsg *msg) +micro_new_request(microRequest **new_request, natsMsg *msg) { natsStatus s = NATS_OK; - natsMicroserviceRequest *req = NULL; + microRequest *req = NULL; - req = (natsMicroserviceRequest *)NATS_CALLOC(1, sizeof(natsMicroserviceRequest)); + req = (microRequest *)NATS_CALLOC(1, sizeof(microRequest)); s = (req != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); if (s == NATS_OK) { @@ -129,6 +105,6 @@ _newMicroserviceRequest(natsMicroserviceRequest **new_request, natsMsg *msg) return NATS_OK; } - _freeMicroserviceRequest(req); + micro_free_request(req); return NATS_UPDATE_ERR_STACK(s); } diff --git a/src/microp.h b/src/microp.h index 5aa87f1ff..c32b8e4b5 100644 --- a/src/microp.h +++ b/src/microp.h @@ -15,32 +15,33 @@ #define MICROP_H_ #include "natsp.h" +#include "mem.h" -#define natsMicroserviceQueueGroup "q" +#define MICRO_QUEUE_GROUP "q" -#define natsMicroserviceDefaultEndpointName "default" +#define MICRO_DEFAULT_ENDPOINT_NAME "default" -struct error_s +struct micro_error_s { natsStatus status; int code; const char *description; }; -struct microservice_client_s +struct micro_client_s { natsConnection *nc; }; -struct microservice_endpoint_s +struct micro_endpoint_s { // The subject that the endpoint is listening on (may be different from // one specified in config). char *subject; // References to other entities. - natsMicroservice *m; - natsMicroserviceEndpointConfig *config; + microService *m; + microEndpointConfig *config; // Mutex for starting/stopping the endpoint, and for updating the stats. natsMutex *mu; @@ -50,21 +51,21 @@ struct microservice_endpoint_s // Endpoint stats. These are initialized only for running endpoints, and are // cleared if the endpoint is stopped. - natsMicroserviceEndpointStats stats; + microEndpointStats stats; }; -struct microservice_s +struct micro_service_s { // these are set at initialization time time and do not change. natsConnection *nc; - struct natsMicroserviceConfig *cfg; + struct microServiceConfig *cfg; char *id; // these are are updated concurrently with access as the service runs, so // need to be protected by mutex. natsMutex *mu; - struct microservice_endpoint_s **endpoints; + struct micro_endpoint_s **endpoints; int endpoints_len; int endpoints_cap; @@ -73,36 +74,30 @@ struct microservice_s int refs; }; -struct microservice_request_s +struct micro_request_s { natsMsg *msg; - struct microservice_endpoint_s *ep; + struct micro_endpoint_s *ep; void *closure; }; -extern natsError *natsMicroserviceErrorOutOfMemory; -extern natsError *natsMicroserviceErrorInvalidArg; - -natsStatus -micro_monitoring_init(natsMicroservice *m); - -natsStatus nats_clone_microservice_config(natsMicroserviceConfig **new_cfg, natsMicroserviceConfig *cfg); -void nats_free_cloned_microservice_config(natsMicroserviceConfig *cfg); - -natsStatus nats_new_endpoint(natsMicroserviceEndpoint **new_endpoint, natsMicroservice *m, natsMicroserviceEndpointConfig *cfg); -natsStatus nats_clone_endpoint_config(natsMicroserviceEndpointConfig **new_cfg, natsMicroserviceEndpointConfig *cfg); -natsStatus nats_start_endpoint(natsMicroserviceEndpoint *ep); -natsStatus nats_stop_endpoint(natsMicroserviceEndpoint *ep); -void nats_free_cloned_endpoint_config(natsMicroserviceEndpointConfig *cfg); - -natsStatus -nats_stop_and_destroy_endpoint(natsMicroserviceEndpoint *ep); - -void natsMicroserviceEndpoint_updateLastError(natsMicroserviceEndpoint *ep, natsError *err); - -natsStatus -_newMicroserviceRequest(natsMicroserviceRequest **new_request, natsMsg *msg); - -void _freeMicroserviceRequest(natsMicroserviceRequest *req); +extern microError *micro_ErrorOutOfMemory; +extern microError *micro_ErrorInvalidArg; + +#define MICRO_CALLOC(__ptr, __count, __size) \ + (__ptr = NATS_CALLOC((__count), (__size)), (__ptr) != NULL ? NULL : micro_ErrorOutOfMemory) + +natsStatus micro_clone_endpoint_config(microEndpointConfig **new_cfg, microEndpointConfig *cfg); +natsStatus micro_clone_service_config(microServiceConfig **new_cfg, microServiceConfig *cfg); +void micro_free_cloned_endpoint_config(microEndpointConfig *cfg); +void micro_free_cloned_service_config(microServiceConfig *cfg); +void micro_free_request(microRequest *req); +natsStatus micro_init_monitoring(microService *m); +natsStatus micro_new_endpoint(microEndpoint **new_endpoint, microService *m, microEndpointConfig *cfg); +natsStatus micro_new_request(microRequest **new_request, natsMsg *msg); +natsStatus micro_start_endpoint(microEndpoint *ep); +natsStatus micro_stop_endpoint(microEndpoint *ep); +natsStatus micro_stop_and_destroy_endpoint(microEndpoint *ep); +void micro_update_last_error(microEndpoint *ep, microError *err); #endif /* MICROP_H_ */ From 57d181c8e9a3a95e5a2b1915bd8337499e1ed52d Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 27 Mar 2023 10:38:53 -0700 Subject: [PATCH 09/85] fixed includes --- src/micro_error.c | 3 ++- src/micro_monitoring.c | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/micro_error.c b/src/micro_error.c index 73f6e0eeb..a0e886901 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -11,9 +11,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include "micro.h" #include "microp.h" -#include "mem.h" #ifdef DEV_MODE // For type safety diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 9d0f87505..d0cf6cbec 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -15,7 +15,6 @@ #include "micro.h" #include "microp.h" -#include "mem.h" #include "util.h" static natsStatus From 3af08464ede75aa783a62b60b646ee92a836dec8 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 27 Mar 2023 10:38:53 -0700 Subject: [PATCH 10/85] wip custom error handler --- examples/{microservice.c => micro-multi.c} | 75 ++-- examples/micro-stats.c | 149 ++++++++ src/conn.c | 85 +++++ src/conn.h | 19 + src/crypto.c | 2 +- src/mem.h | 8 - src/micro.c | 410 +++++++++++++-------- src/micro.h | 111 +++--- src/micro_args.c | 47 ++- src/micro_client.c | 10 +- src/micro_endpoint.c | 402 +++++++++++--------- src/micro_error.c | 166 ++++----- src/micro_monitoring.c | 359 ++++++------------ src/micro_request.c | 181 ++++++--- src/microp.h | 66 ++-- src/nats.c | 72 ++-- src/util.c | 150 +++++++- src/util.h | 3 + test/list.txt | 5 + test/test.c | 362 ++++++++++++++++++ 20 files changed, 1832 insertions(+), 850 deletions(-) rename examples/{microservice.c => micro-multi.c} (84%) create mode 100644 examples/micro-stats.c diff --git a/examples/microservice.c b/examples/micro-multi.c similarity index 84% rename from examples/microservice.c rename to examples/micro-multi.c index 004542062..8966bd701 100644 --- a/examples/microservice.c +++ b/examples/micro-multi.c @@ -44,31 +44,31 @@ typedef microError *(*functionOP)(long double *result, natsConnection *conn, int static void handle_function_op(microRequest *req, functionOP op); // Stop handler is the same for all services. -static void handle_stop(microService *m, microRequest *req); +static void handle_stop( microRequest *req); // Handler for "sequence", the main endpoint of the sequence service. -static void handle_sequence(microService *m, microRequest *req); +static void handle_sequence( microRequest *req); // Math operations, wrapped as handlers. static microError *add(long double *result, natsConnection *nc, long double a1, long double a2); static microError *divide(long double *result, natsConnection *nc, long double a1, long double a2); static microError *multiply(long double *result, natsConnection *nc, long double a1, long double a2); -static void handle_add(microService *m, microRequest *req) { handle_arithmetics_op(req, add); } -static void handle_divide(microService *m, microRequest *req) { handle_arithmetics_op(req, divide); } -static void handle_multiply(microService *m, microRequest *req) { handle_arithmetics_op(req, multiply); } +static void handle_add( microRequest *req) { handle_arithmetics_op(req, add); } +static void handle_divide( microRequest *req) { handle_arithmetics_op(req, divide); } +static void handle_multiply(microRequest *req) { handle_arithmetics_op(req, multiply); } static microError *factorial(long double *result, natsConnection *nc, int n); static microError *fibonacci(long double *result, natsConnection *nc, int n); static microError *power2(long double *result, natsConnection *nc, int n); -static void handle_factorial(microService *m, microRequest *req) { handle_function_op(req, factorial); } -static void handle_fibonacci(microService *m, microRequest *req) { handle_function_op(req, fibonacci); } -static void handle_power2(microService *m, microRequest *req) { handle_function_op(req, power2); } +static void handle_factorial( microRequest *req) { handle_function_op(req, factorial); } +static void handle_fibonacci( microRequest *req) { handle_function_op(req, fibonacci); } +static void handle_power2(microRequest *req) { handle_function_op(req, power2); } static microEndpointConfig stop_cfg = { .name = "stop", .handler = handle_stop, - .closure = &fakeClosure, + .state = &fakeClosure, .schema = NULL, }; @@ -136,7 +136,7 @@ int main(int argc, char **argv) if (s != NATS_OK) { - err = microError_FromStatus(s); + err = micro_ErrorFromStatus(s); } if (err != NULL) { @@ -158,7 +158,7 @@ static void *run_sequence(void *closure) .subject = "sequence", .name = "sequence-service", .handler = handle_sequence, - .closure = &fakeClosure, + .state = &fakeClosure, .schema = NULL, }; microEndpointConfig *endpoints[] = {&sequence_cfg}; @@ -170,10 +170,10 @@ static void *run_sequence(void *closure) // (float), f name (string), and N (int). E.g.: '10.0 "power2" 10' will // calculate 10/2 + 10/4 + 10/8 + 10/16 + 10/32 + 10/64 + 10/128 + 10/256 + // 10/512 + 10/1024 = 20.998046875 -static void handle_sequence(microService *m, microRequest *req) +static void handle_sequence( microRequest *req) { microError *err = NULL; - natsConnection *nc = microService_GetConnection(m); + natsConnection *nc = microRequest_GetConnection(req); microArgs *args = NULL; int n = 0; int i; @@ -186,24 +186,24 @@ static void handle_sequence(microService *m, microRequest *req) MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); MICRO_CALL_IF(err, (microArgs_Count(args) != 2), - micro_NewErrorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args))); + micro_Errorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args))); MICRO_CALL(err, microArgs_GetString(&function, args, 0)); MICRO_CALL_IF(err, ((strcmp(function, "factorial") != 0) && (strcmp(function, "power2") != 0) && (strcmp(function, "fibonacci") != 0)), - micro_NewErrorf(400, "Invalid function name '%s', must be 'factorial', 'power2', or 'fibonacci'", function)); + micro_Errorf(400, "Invalid function name '%s', must be 'factorial', 'power2', or 'fibonacci'", function)); MICRO_CALL(err, microArgs_GetInt(&n, args, 1)); MICRO_CALL_IF(err, (n < 1), - micro_NewErrorf(400, "Invalid number of iterations %d, must be at least 1", n)); + micro_Errorf(400, "Invalid number of iterations %d, must be at least 1", n)); for (i = 1; (err == NULL) && (i <= n); i++) { MICRO_CALL(err, call_function(&denominator, nc, function, i)); MICRO_CALL_IF(err, denominator == 0, - micro_NewErrorf(500, "division by zero at step %d", i)); + micro_Errorf(500, "division by zero at step %d", i)); MICRO_DO(err, value = value + initialValue / denominator); } - + MICRO_DO(err, result_len = snprintf(result, sizeof(result), "%Lf", value)); microRequest_Respond(req, &err, result, result_len); @@ -221,19 +221,19 @@ static void *run_arithmetics(void *closure) microEndpointConfig add_cfg = { .name = "add", .handler = handle_add, - .closure = &fakeClosure, + .state = &fakeClosure, .schema = NULL, }; microEndpointConfig divide_cfg = { .name = "divide", .handler = handle_divide, - .closure = &fakeClosure, + .state = &fakeClosure, .schema = NULL, }; microEndpointConfig multiply_cfg = { .name = "multiply", .handler = handle_multiply, - .closure = &fakeClosure, + .state = &fakeClosure, .schema = NULL, }; microEndpointConfig *endpoints[] = @@ -253,19 +253,19 @@ static void *run_functions(void *closure) microEndpointConfig factorial_cfg = { .name = "factorial", .handler = handle_factorial, - .closure = &fakeClosure, + .state = &fakeClosure, .schema = NULL, }; microEndpointConfig fibonacci_cfg = { .name = "fibonacci", .handler = handle_fibonacci, - .closure = &fakeClosure, + .state = &fakeClosure, .schema = NULL, }; microEndpointConfig power2_cfg = { .name = "power2", .handler = handle_power2, - .closure = &fakeClosure, + .state = &fakeClosure, .schema = NULL, }; microEndpointConfig *endpoints[] = @@ -285,7 +285,7 @@ handle_arithmetics_op(microRequest *req, arithmeticsOP op) MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); MICRO_CALL_IF(err, (microArgs_Count(args) != 2), - micro_NewErrorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args))); + micro_Errorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args))); MICRO_CALL(err, microArgs_GetFloat(&a1, args, 0)); MICRO_CALL(err, microArgs_GetFloat(&a2, args, 1)); MICRO_CALL(err, op(&result, microRequest_GetConnection(req), a1, a2)); @@ -307,7 +307,7 @@ handle_function_op(microRequest *req, functionOP op) MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); MICRO_CALL_IF(err, (microArgs_Count(args) != 1), - micro_NewErrorf(400, "Invalid number of arguments, expected 1 got %d", microArgs_Count(args))); + micro_Errorf(400, "Invalid number of arguments, expected 1 got %d", microArgs_Count(args))); MICRO_CALL(err, microArgs_GetInt(&n, args, 0)); MICRO_CALL(err, op(&result, microRequest_GetConnection(req), n)); MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf", result)); @@ -326,9 +326,9 @@ call_arithmetics(long double *result, natsConnection *nc, const char *subject, l char buf[1024]; int len; - MICRO_CALL(err, microClient_Create(&client, nc, NULL)); + MICRO_CALL(err, micro_NewClient(&client, nc, NULL)); MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf %Lf", a1, a2)); - MICRO_CALL(err, microClient_DoRequest(client, &response, subject, buf, len)); + MICRO_CALL(err, microClient_DoRequest(&response, client, subject, buf, len)); MICRO_CALL(err, micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response))); MICRO_CALL(err, microArgs_GetFloat(result, args, 0)); @@ -347,9 +347,9 @@ call_function(long double *result, natsConnection *nc, const char *subject, int char buf[1024]; int len; - MICRO_CALL(err, microClient_Create(&client, nc, NULL)); + MICRO_CALL(err, micro_NewClient(&client, nc, NULL)); MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%d", n)); - MICRO_CALL(err, microClient_DoRequest(client, &response, subject, buf, len)); + MICRO_CALL(err, microClient_DoRequest(&response, client, subject, buf, len)); MICRO_CALL(err, micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response))); MICRO_CALL(err, microArgs_GetFloat(result, args, 0)); @@ -358,14 +358,14 @@ call_function(long double *result, natsConnection *nc, const char *subject, int return err; } -static void handle_stop(microService *m, microRequest *req) +static void handle_stop( microRequest *req) { microError *err = NULL; const char *response = "OK"; int len = 0; void *ret; - MICRO_CALL(err, microService_Stop(m)); + MICRO_CALL(err, microService_Stop(microRequest_GetService(req))); MICRO_DO(err, len = strlen(response)); ret = (void *)(microError_Status(err)); @@ -378,10 +378,9 @@ static void *run_service(natsConnection *conn, microServiceConfig *svc, { microError *err = NULL; microService *m = NULL; - char errbuf[1024]; int i; - MICRO_CALL(err, microService_Create(&m, conn, svc)); + MICRO_CALL(err, micro_AddService(&m, conn, svc)); for (i = 0; (err == NULL) && (i < len_endpoints); i++) { MICRO_CALL(err, microService_AddEndpoint(NULL, m, endpoints[i])); @@ -389,7 +388,7 @@ static void *run_service(natsConnection *conn, microServiceConfig *svc, MICRO_CALL(err, microService_AddEndpoint(NULL, m, &stop_cfg)); MICRO_CALL(err, microService_Run(m)); - microService_Release(m); + microService_Destroy(m); return err; } @@ -420,7 +419,7 @@ factorial(long double *result, natsConnection *nc, int n) int i; if (n < 1) - err = micro_NewErrorf(400, "n=%d. must be greater than 0", n); + err = micro_Errorf(400, "n=%d. must be greater than 0", n); *result = 1; for (i = 1; i <= n; i++) @@ -440,7 +439,7 @@ fibonacci(long double *result, natsConnection *nc, int n) long double n1, n2; if (n < 0) - err = micro_NewErrorf(400, "n=%d. must be non-negative", n); + err = micro_Errorf(400, "n=%d. must be non-negative", n); if (n < 2) { @@ -465,7 +464,7 @@ static microError *power2(long double *result, natsConnection *nc, int n) int i; if (n < 1) - return micro_NewErrorf(400, "n=%d. must be greater than 0", n); + return micro_Errorf(400, "n=%d. must be greater than 0", n); *result = 1; for (i = 1; i <= n; i++) diff --git a/examples/micro-stats.c b/examples/micro-stats.c new file mode 100644 index 000000000..76562b4b0 --- /dev/null +++ b/examples/micro-stats.c @@ -0,0 +1,149 @@ +// Copyright 2021 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "examples.h" + +typedef struct service_state_s +{ + // in a real application this should be protected by a mutex. In this + // example, the main control flow provides synchronization. + int odd_count; +} service_state_t; + +static void handle_default(microRequest *req) +{ + char buf[64]; + const char *response = "odd"; + int n; + service_state_t *state = microRequest_GetServiceState(req); + + snprintf(buf, sizeof(buf), "%.*s", microRequest_GetDataLength(req), microRequest_GetData(req)); + n = atoi(buf); + if (n % 2 != 0) + { + // this should be protected by a mutex in a real application. + state->odd_count++; + + response = "even"; + } + microRequest_Respond(req, NULL, response, strlen(response)); + return; +} + +static void handle_stats(microRequest *req) +{ + microError *err = NULL; + microServiceStats *stats = NULL; + char buf[2048]; + service_state_t *service_state = microRequest_GetServiceState(req); + int total, custom, len; + + err = microService_GetStats(&stats, req->service); + if (err != NULL) + { + microRequest_Respond(req, &err, NULL, 0); + return; + } + + total = stats->endpoints[0].num_requests; + custom = service_state->odd_count; + len = snprintf(buf, sizeof(buf), + "{\"total\":%d,\"odd\":%d}", total, custom); + microRequest_Respond(req, NULL, buf, len); +} + +static microError * +run_example(natsConnection *conn, microRequestHandler stats_handler, char *buf, int buf_cap) +{ + microError *err = NULL; + microService *m = NULL; + microClient *c = NULL; + service_state_t service_state = { + .odd_count = 0, + }; + microEndpointConfig default_cfg = { + .name = "default", + .handler = handle_default, + }; + microServiceConfig cfg = { + .name = "c-stats", + .description = "NATS microservice in C with a custom stats handler", + .version = "1.0.0", + .endpoint = &default_cfg, + .stats_handler = stats_handler, + .state = &service_state, + }; + int i; + int len; + natsMsg *resp = NULL; + natsMsg *stats_resp = NULL; + + MICRO_CALL(err, micro_AddService(&m, conn, &cfg)); + MICRO_CALL(err, micro_NewClient(&c, conn, NULL)); + + if (err == NULL) + { + for (i = 0; i < 10; i++) + { + len = snprintf(buf, buf_cap, "%d", i); + MICRO_CALL(err, microClient_DoRequest(&resp, c, "default", buf, len)); + MICRO_DO(err, natsMsg_Destroy(resp)); + } + } + + MICRO_CALL(err, microClient_DoRequest(&stats_resp, c, "$SRV.STATS.c-stats", "", 0)); + if (err == NULL) + { + len = natsMsg_GetDataLength(stats_resp); + if (len > buf_cap-1) + { + len = buf_cap-1; + } + memcpy(buf, natsMsg_GetData(stats_resp), len); + buf[len] = '\0'; + + natsMsg_Destroy(stats_resp); + } + + microService_Destroy(m); + microClient_Destroy(c); + return err; +} + +int main(int argc, char **argv) +{ + microError *err = NULL; + natsOptions *opts = parseArgs(argc, argv, ""); + natsConnection *conn = NULL; + char buf[2048]; + + MICRO_CALL(err, micro_ErrorFromStatus(natsConnection_Connect(&conn, opts))); + + MICRO_CALL(err, run_example(conn, NULL, buf, sizeof(buf))); + MICRO_DO(err, printf("Default stats response:\n----\n%s\n----\n\n", buf)); + + MICRO_CALL(err, run_example(conn, handle_stats, buf, sizeof(buf))); + MICRO_DO(err, printf("Custom stats response:\n----\n%s\n----\n\n", buf)); + + if (err != NULL) + { + fprintf(stderr, "Error: %s\n", microError_String(err, buf, sizeof(buf))); + } + + microError_Destroy(err); + return err == NULL ? 0 : 1; +} + diff --git a/src/conn.c b/src/conn.c index 0dddffc41..7bffc7b84 100644 --- a/src/conn.c +++ b/src/conn.c @@ -4432,3 +4432,88 @@ natsConn_defaultErrHandler(natsConnection *nc, natsSubscription *sub, natsStatus } fflush(stderr); } + +natsStatus +natsConn_getErrorCallback(natsErrHandler *cb, void **closure, natsConnection *nc) +{ + if ((nc == NULL) || (cb == NULL) || (closure == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + + natsConn_Lock(nc); + *cb = nc->opts->asyncErrCb; + *closure = nc->opts->asyncErrCbClosure; + natsConn_Unlock(nc); + + return NATS_OK; +} + +natsStatus +natsConn_setErrorCallback(natsConnection *nc, natsErrHandler cb, void *closure) +{ + // The error callback must not be NULL, other code may rely on it. + if ((nc == NULL) || (cb == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + + natsConn_Lock(nc); + nc->opts->asyncErrCb = cb; + nc->opts->asyncErrCbClosure = closure; + natsConn_Unlock(nc); + + return NATS_OK; +} + +natsStatus +natsConn_getClosedCallback(natsConnectionHandler *cb, void **closure, natsConnection *nc) +{ + if ((nc == NULL) || (cb == NULL) || (closure == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + + natsConn_Lock(nc); + *cb = nc->opts->closedCb; + *closure = nc->opts->closedCbClosure; + natsConn_Unlock(nc); + + return NATS_OK; +} + +natsStatus +natsConn_setClosedCallback(natsConnection *nc, natsConnectionHandler cb, void *closure) +{ + if (nc == NULL) + return nats_setDefaultError(NATS_INVALID_ARG); + + natsConn_Lock(nc); + nc->opts->closedCb = cb; + nc->opts->closedCbClosure = closure; + natsConn_Unlock(nc); + + return NATS_OK; +} + +natsStatus +natsConn_getDisconnectedCallback(natsConnectionHandler *cb, void **closure, natsConnection *nc) +{ + if ((nc == NULL) || (cb == NULL) || (closure == NULL)) + return nats_setDefaultError(NATS_INVALID_ARG); + + natsConn_Lock(nc); + *cb = nc->opts->disconnectedCb; + *closure = nc->opts->disconnectedCbClosure; + natsConn_Unlock(nc); + + return NATS_OK; +} + +natsStatus +natsConn_setDisconnectedCallback(natsConnection *nc, natsConnectionHandler cb, void *closure) +{ + if (nc == NULL) + return nats_setDefaultError(NATS_INVALID_ARG); + + natsConn_Lock(nc); + nc->opts->disconnectedCb = cb; + nc->opts->disconnectedCbClosure = closure; + natsConn_Unlock(nc); + + return NATS_OK; +} diff --git a/src/conn.h b/src/conn.h index 41e93f402..7538aa921 100644 --- a/src/conn.h +++ b/src/conn.h @@ -160,4 +160,23 @@ natsConn_close(natsConnection *nc); void natsConn_destroy(natsConnection *nc, bool fromPublicDestroy); +natsStatus +natsConn_setErrorCallback(natsConnection *nc, natsErrHandler cb, void *closure); + +natsStatus +natsConn_getErrorCallback(natsErrHandler *cb, void **closure, natsConnection *nc); + +natsStatus +natsConn_setClosedCallback(natsConnection *nc, natsConnectionHandler cb, void *closure); + +natsStatus +natsConn_getClosedCallback(natsConnectionHandler *cb, void **closure, natsConnection *nc); + +natsStatus +natsConn_setDisconnectedCallback(natsConnection *nc, natsConnectionHandler cb, void *closure); + +natsStatus +natsConn_getDisconnectedCallback(natsConnectionHandler *cb, void **closure, natsConnection *nc); + + #endif /* CONN_H_ */ diff --git a/src/crypto.c b/src/crypto.c index ee8aa38fa..caff4dd3f 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -485,7 +485,7 @@ secure_memzero(void * const pnt, const size_t len) } natsStatus -natsCrypto_Init() +natsCrypto_Init(void) { return NATS_OK; } diff --git a/src/mem.h b/src/mem.h index 6ef6e2e66..a73f43070 100644 --- a/src/mem.h +++ b/src/mem.h @@ -27,14 +27,6 @@ #endif #define NATS_FREE(p) free((p)) -#define NATS_CALLOCS(dupe, count, size) ( \ - *(dupe) = NATS_CALLOC((count), (size)), \ - *(dupe) != NULL ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY)) - -#define NATS_STRDUPS(dupe, str) ( \ - *(dupe) = NATS_STRDUP(str), \ - *(dupe) != NULL ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY)) - // GNU C Library version 2.25 or later. #if defined(__GLIBC__) && \ (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25)) diff --git a/src/micro.c b/src/micro.c index 9ffae3406..81ca39ee3 100644 --- a/src/micro.c +++ b/src/micro.c @@ -19,114 +19,122 @@ static microError * stop_and_destroy_microservice(microService *m); +static microError * +wrap_connection_event_callbacks(microService *m); +static microError * +unwrap_connection_event_callbacks(microService *m); -microError * -microService_Create(microService **new_m, natsConnection *nc, microServiceConfig *cfg) +static microError * +new_service(microService **ptr) { - if ((new_m == NULL) || (nc == NULL) || (cfg == NULL)) - return micro_ErrorInvalidArg; + *ptr = NATS_CALLOC(1, sizeof(microService)); + return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; +} +microError * +micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *cfg) +{ natsStatus s = NATS_OK; microError *err = NULL; microService *m = NULL; + if ((new_m == NULL) || (nc == NULL) || (cfg == NULL)) + return micro_ErrorInvalidArg; + // Make a microservice object, with a reference to a natsConnection. - m = (microService *)NATS_CALLOC(1, sizeof(microService)); - if (m == NULL) - return micro_ErrorOutOfMemory; + MICRO_CALL(err, new_service(&m)); + if (err != NULL) + return err; natsConn_retain(nc); m->nc = nc; m->refs = 1; m->started = nats_Now() * 1000000; - IFOK(s, NATS_CALLOCS(&m->id, 1, NUID_BUFFER_LEN + 1)) IFOK(s, natsMutex_Create(&m->mu)); - IFOK(s, natsNUID_Next(m->id, NUID_BUFFER_LEN + 1)); - IFOK(s, micro_clone_service_config(&m->cfg, cfg)); - if ((s == NATS_OK) && (cfg->endpoint != NULL)) - { - err = microService_AddEndpoint(NULL, m, cfg->endpoint); - if (err != NULL) - { - // status is always set in AddEndpoint errors. - s = err->status; - } - } + IFOK(s, natsNUID_Next(m->id, sizeof(m->id))); + err = micro_ErrorFromStatus(s); - // Set up monitoring (PING, INFO, STATS, etc.) responders. - IFOK(s, micro_init_monitoring(m)); + MICRO_CALL(err, micro_clone_service_config(&m->cfg, cfg)); - if (s == NATS_OK) - { - *new_m = m; - return NULL; - } - else - { - stop_and_destroy_microservice(m); - return microError_Wrapf(microError_FromStatus(s), "failed to create microservice"); - } -} + // Wrap the connection callbacks before we subscribe to anything. + MICRO_CALL(err, wrap_connection_event_callbacks(m)) -microError * -microService_Release(microService *m) -{ - bool doFree; + // Add the endpoints and monitoring subscriptions. + MICRO_CALL(err, microService_AddEndpoint(NULL, m, cfg->endpoint)); + MICRO_CALL(err, micro_init_monitoring(m)); - if (m == NULL) - return NULL; - - natsMutex_Lock(m->mu); - m->refs--; - doFree = (m->refs == 0); - natsMutex_Unlock(m->mu); - - if (!doFree) - return NULL; - - return stop_and_destroy_microservice(m); -} + if (err != NULL) + { + microService_Destroy(m); + return microError_Wrapf(micro_ErrorFromStatus(s), "failed to add microservice"); + } -microService * -natsMicroservice_Retain(microService *m) -{ - natsMutex_Lock(m->mu); - m->refs++; - natsMutex_Unlock(m->mu); - return m; + *new_m = m; + return NULL; } // TODO <>/<> update from Go microError * microService_Stop(microService *m) { + microError *err = NULL; natsStatus s = NATS_OK; int i; + void *free_eps, *free_subs; if (m == NULL) return micro_ErrorInvalidArg; - if (microService_IsStopped(m)) - return NULL; - for (i = 0; (s == NATS_OK) && (i < m->endpoints_len); i++) + natsMutex_Lock(m->mu); + if (m->is_stopping || m->stopped) { - s = micro_stop_endpoint(m->endpoints[i]); + natsMutex_Unlock(m->mu); + return NULL; } + m->is_stopping = true; + natsMutex_Unlock(m->mu); - if (s == NATS_OK) - { - natsMutex_Lock(m->mu); - m->stopped = true; - m->started = 0; - natsMutex_Unlock(m->mu); + err = unwrap_connection_event_callbacks(m); + if (err != NULL) + return err; - return NULL; + for (i = 0; i < m->endpoints_len; i++) + { + if (err = micro_stop_and_destroy_endpoint(m->endpoints[i]), err != NULL) + { + return microError_Wrapf(err, "failed to stop endpoint"); + } } - else + for (i = 0; i < m->monitoring_subs_len; i++) { - return microError_Wrapf(microError_FromStatus(s), "failed to stop microservice"); + if (!natsConnection_IsClosed(m->nc)) + { + s = natsSubscription_Drain(m->monitoring_subs[i]); + if (s != NATS_OK) + return microError_Wrapf(micro_ErrorFromStatus(s), "failed to drain monitoring subscription"); + } + natsSubscription_Destroy(m->monitoring_subs[i]); } + + // probably being paranoid here, there should be no need to lock. + free_eps = m->endpoints; + free_subs = m->monitoring_subs; + + natsMutex_Lock(m->mu); + m->stopped = true; + m->is_stopping = false; + m->started = 0; + m->monitoring_subs_len = 0; + m->monitoring_subs = NULL; + m->endpoints_len = 0; + m->endpoints = NULL; + natsMutex_Unlock(m->mu); + + NATS_FREE(free_eps); + NATS_FREE(free_subs); + + return NULL; } bool microService_IsStopped(microService *m) @@ -139,17 +147,31 @@ bool microService_IsStopped(microService *m) natsMutex_Lock(m->mu); stopped = m->stopped; natsMutex_Unlock(m->mu); + return stopped; } -// TODO: <>/<> eliminate sleep +bool micro_service_is_stopping(microService *m) +{ + bool stopping; + + if ((m == NULL) || (m->mu == NULL)) + return true; + + natsMutex_Lock(m->mu); + stopping = m->is_stopping; + natsMutex_Unlock(m->mu); + + return stopping; +} + microError * microService_Run(microService *m) { if ((m == NULL) || (m->mu == NULL)) return micro_ErrorInvalidArg; - for (; !microService_IsStopped(m);) + while (!microService_IsStopped(m)) { nats_Sleep(50); } @@ -157,7 +179,7 @@ microService_Run(microService *m) return NULL; } -NATS_EXTERN natsConnection * +natsConnection * microService_GetConnection(microService *m) { if (m == NULL) @@ -168,19 +190,19 @@ microService_GetConnection(microService *m) microError * microService_AddEndpoint(microEndpoint **new_ep, microService *m, microEndpointConfig *cfg) { - natsStatus s = NATS_OK; + microError *err = NULL; int index = -1; int new_cap; microEndpoint *ep = NULL; - if ((m == NULL) || (cfg == NULL)) - { + if (m == NULL) return micro_ErrorInvalidArg; - } + if (cfg == NULL) + return NULL; - s = micro_new_endpoint(&ep, m, cfg); - if (s != NATS_OK) - return microError_Wrapf(microError_FromStatus(s), "failed to create endpoint"); + err = micro_new_endpoint(&ep, m, cfg); + if (err != NULL) + return microError_Wrapf(err, "failed to create endpoint"); // see if we already have an endpoint with this name for (int i = 0; i < m->endpoints_len; i++) @@ -192,9 +214,9 @@ microService_AddEndpoint(microEndpoint **new_ep, microService *m, microEndpointC } } - // if not, grow the array as needed. if (index == -1) { + // if not, grow the array as needed. if (m->endpoints_len == m->endpoints_cap) { new_cap = m->endpoints_cap * 2; @@ -212,22 +234,24 @@ microService_AddEndpoint(microEndpoint **new_ep, microService *m, microEndpointC index = m->endpoints_len; m->endpoints_len++; } - - // add the endpoint. - m->endpoints[index] = ep; - - s = micro_start_endpoint(ep); - if (s == NATS_OK) + else { - if (new_ep != NULL) - *new_ep = ep; - return NULL; + // stop and destroy the existing endpoint. + micro_stop_and_destroy_endpoint(m->endpoints[index]); } - else + + err = micro_start_endpoint(ep); + if (err != NULL) { micro_stop_and_destroy_endpoint(ep); - return microError_Wrapf(microError_FromStatus(NATS_UPDATE_ERR_STACK(s)), "failed to start endpoint"); + return microError_Wrapf(err, "failed to start endpoint"); } + + // add the endpoint. + m->endpoints[index] = ep; + if (new_ep != NULL) + *new_ep = ep; + return NULL; } microError * @@ -285,7 +309,7 @@ void microServiceInfo_Destroy(microServiceInfo *info) } microError * -natsMicroservice_GetStats(microServiceStats **new_stats, microService *m) +microService_GetStats(microServiceStats **new_stats, microService *m) { microServiceStats *stats = NULL; int i; @@ -348,12 +372,10 @@ void natsMicroserviceStats_Destroy(microServiceStats *stats) NATS_FREE(stats); } -static microError * -stop_and_destroy_microservice(microService *m) +microError * +microService_Destroy(microService *m) { microError *err = NULL; - natsStatus s = NATS_OK; - int i; if (m == NULL) return NULL; @@ -362,73 +384,52 @@ stop_and_destroy_microservice(microService *m) if (err != NULL) return err; - for (i = 0; i < m->endpoints_len; i++) - { - s = micro_stop_and_destroy_endpoint(m->endpoints[i]); - if (s != NATS_OK) - return microError_Wrapf(microError_FromStatus(s), "failed to stop and destroy endpoint"); - } - - micro_free_cloned_service_config(m->cfg); + micro_destroy_cloned_service_config(m->cfg); natsConn_release(m->nc); natsMutex_Destroy(m->mu); - NATS_FREE(m->id); + NATS_FREE(m->endpoints); + NATS_FREE(m->monitoring_subs); NATS_FREE(m); return NULL; } -natsStatus +static inline microError *new_service_config(microServiceConfig **ptr) +{ + *ptr = NATS_CALLOC(1, sizeof(microServiceConfig)); + return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; +} + +microError * micro_clone_service_config(microServiceConfig **out, microServiceConfig *cfg) { - natsStatus s = NATS_OK; + microError *err = NULL; microServiceConfig *new_cfg = NULL; - microEndpointConfig *new_ep = NULL; - char *new_name = NULL; - char *new_version = NULL; - char *new_description = NULL; if (out == NULL || cfg == NULL) - return NATS_INVALID_ARG; + return micro_ErrorInvalidArg; - s = NATS_CALLOCS(&new_cfg, 1, sizeof(microServiceConfig)); - if (s == NATS_OK && cfg->name != NULL) - { - DUP_STRING(s, new_name, cfg->name); - } - if (s == NATS_OK && cfg->version != NULL) - { - DUP_STRING(s, new_version, cfg->version); - } - if (s == NATS_OK && cfg->description != NULL) - { - DUP_STRING(s, new_description, cfg->description); - } - if (s == NATS_OK && cfg->endpoint != NULL) - { - s = micro_clone_endpoint_config(&new_ep, cfg->endpoint); - } - if (s == NATS_OK) + err = new_service_config(&new_cfg); + if (err == NULL) { memcpy(new_cfg, cfg, sizeof(microServiceConfig)); - new_cfg->name = new_name; - new_cfg->version = new_version; - new_cfg->description = new_description; - new_cfg->endpoint = new_ep; - *out = new_cfg; } - else + // the strings are declared const for the public, but in a clone these need + // to be duplicated. + MICRO_CALL(err, micro_strdup((char **)&new_cfg->name, cfg->name)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->version, cfg->version)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->description, cfg->description)); + MICRO_CALL(err, micro_clone_endpoint_config(&new_cfg->endpoint, cfg->endpoint)); + if (err != NULL) { - NATS_FREE(new_cfg); - NATS_FREE(new_name); - NATS_FREE(new_version); - NATS_FREE(new_description); - micro_free_cloned_endpoint_config(new_ep); + micro_destroy_cloned_service_config(new_cfg); + return err; } - return NATS_UPDATE_ERR_STACK(s); + *out = new_cfg; + return NULL; } -void micro_free_cloned_service_config(microServiceConfig *cfg) +void micro_destroy_cloned_service_config(microServiceConfig *cfg) { if (cfg == NULL) return; @@ -438,6 +439,127 @@ void micro_free_cloned_service_config(microServiceConfig *cfg) NATS_FREE((char *)cfg->name); NATS_FREE((char *)cfg->version); NATS_FREE((char *)cfg->description); - micro_free_cloned_endpoint_config(cfg->endpoint); + micro_destroy_cloned_endpoint_config(cfg->endpoint); NATS_FREE(cfg); } + +static void +on_connection_closed(natsConnection *nc, void *closure) +{ + microService *m = (microService *)closure; + if (m == NULL) + return; + + microService_Stop(m); + + if (m->prev_on_connection_closed != NULL) + { + (*m->prev_on_connection_closed)(nc, m->prev_on_connection_closed_closure); + } +} + +static void +on_connection_disconnected(natsConnection *nc, void *closure) +{ + microService *m = (microService *)closure; + + if (m == NULL) + return; + + microService_Stop(m); + + if (m->prev_on_connection_disconnected != NULL) + { + (*m->prev_on_connection_closed)(nc, m->prev_on_connection_disconnected_closure); + } +} + +static void +on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *closure) +{ + microService *m = (microService *)closure; + microEndpoint *ep = NULL; + microError *err = NULL; + bool our_subject = false; + int i; + + if ((m == NULL) || (sub == NULL)) + return; + + for (i = 0; !our_subject && (i < m->monitoring_subs_len); i++) + { + if (strcmp(m->monitoring_subs[i]->subject, sub->subject) == 0) + { + our_subject = true; + } + } + + for (i = 0; !our_subject && i < m->endpoints_len; i++) + { + ep = m->endpoints[i]; + if (ep == NULL) + continue; // shouldn't happen + + if (micro_match_endpoint_subject(ep->subject, sub->subject)) + { + our_subject = true; + } + } + + if (our_subject) + { + if (m->cfg->err_handler != NULL) + { + (*m->cfg->err_handler)(m, ep, s); + } + + if (ep != NULL) + { + err = micro_ErrorFromStatus(s); + micro_update_last_error(ep, err); + microError_Destroy(err); + } + } + + if (m->prev_on_error != NULL) + { + (*m->prev_on_error)(nc, sub, s, m->prev_on_error_closure); + } + + microService_Stop(m); +} + +static microError * +wrap_connection_event_callbacks(microService *m) +{ + natsStatus s = NATS_OK; + + if (m == NULL) + return micro_ErrorInvalidArg; + + IFOK(s, natsConn_getClosedCallback(&m->prev_on_connection_closed, &m->prev_on_connection_closed_closure, m->nc)); + IFOK(s, natsConn_setClosedCallback(m->nc, on_connection_closed, m)); + + IFOK(s, natsConn_getDisconnectedCallback(&m->prev_on_connection_disconnected, &m->prev_on_connection_disconnected_closure, m->nc)); + IFOK(s, natsConn_setDisconnectedCallback(m->nc, on_connection_disconnected, m)); + + IFOK(s, natsConn_getErrorCallback(&m->prev_on_error, &m->prev_on_error_closure, m->nc)); + IFOK(s, natsConn_setErrorCallback(m->nc, on_error, m)); + + return microError_Wrapf(micro_ErrorFromStatus(s), "failed to wrap connection event callbacks"); +} + +static microError * +unwrap_connection_event_callbacks(microService *m) +{ + natsStatus s = NATS_OK; + + if (m == NULL) + return micro_ErrorInvalidArg; + + IFOK(s, natsConn_setClosedCallback(m->nc, m->prev_on_connection_closed, m->prev_on_connection_closed_closure)); + IFOK(s, natsConn_setDisconnectedCallback(m->nc, m->prev_on_connection_disconnected, m->prev_on_connection_disconnected_closure)); + IFOK(s, natsConn_setErrorCallback(m->nc, m->prev_on_error, m->prev_on_error_closure)); + + return microError_Wrapf(micro_ErrorFromStatus(s), "failed to unwrap connection event callbacks"); +} diff --git a/src/micro.h b/src/micro.h index ad6f80e91..4f3f2e1c2 100644 --- a/src/micro.h +++ b/src/micro.h @@ -49,14 +49,42 @@ __block; /** - * The Microservice request object. + * The Microservice object. Create and start with #microService_Create. */ -typedef struct micro_request_s microRequest; +typedef struct micro_service_s microService; /** - * The Microservice object. Create and start with #microService_Create. + * The Microservice endpoint object. + * TODO document the interface. */ -typedef struct micro_service_s microService; +typedef struct micro_endpoint_s microEndpoint; + +/** + * The Microservice request object. + */ +typedef struct microRequest +{ + natsMsg *message; + + // service is guaranteed to be set to the microservice processing the + // request; endpoint may be NULL for requests on internal (monitoring) + // subjects. + microService *service; + microEndpoint *endpoint; +} microRequest; + +/** \brief Callback type for request processing. + * + * This is the callback that one provides when creating a microservice endpoint. + * The library will invoke this callback for each message arriving to the + * specified subject. + * + * @see microService_AddEndpoint() + */ +typedef void (*microRequestHandler)(microRequest *req); + + +typedef void (*microErrorHandler)(microService *m, microEndpoint *ep, natsStatus s); /** * The Microservice configuration object. The service holds on to it, so it must @@ -68,6 +96,9 @@ typedef struct microServiceConfig const char *version; const char *description; struct microEndpointConfig *endpoint; + microRequestHandler stats_handler; + microErrorHandler err_handler; + void *state; } microServiceConfig; typedef struct microServiceInfo @@ -81,22 +112,6 @@ typedef struct microServiceInfo int subjects_len; } microServiceInfo; -/** - * The Microservice endpoint object. - * TODO document the interface. - */ -typedef struct micro_endpoint_s microEndpoint; - -/** \brief Callback used to deliver messages to a microservice. - * - * This is the callback that one provides when creating a microservice endpoint. - * The library will invoke this callback for each message arriving to the - * specified subject. - * - * @see microService_AddEndpoint() - */ -typedef void (*microRequestHandler)(microService *m, microRequest *req); - /** * The Microservice endpoint configuration object. */ @@ -104,7 +119,7 @@ typedef struct microEndpointConfig { const char *name; microRequestHandler handler; - void *closure; + void *state; const char *subject; struct microSchema *schema; } microEndpointConfig; @@ -162,66 +177,66 @@ typedef struct micro_error_s microError; // // microService methods. +NATS_EXTERN microError *micro_AddService(microService **new_microservice, natsConnection *nc, microServiceConfig *cfg); NATS_EXTERN microError *microService_AddEndpoint(microEndpoint **new_endpoint, microService *m, microEndpointConfig *cfg); -NATS_EXTERN microError *microService_Create(microService **new_microservice, natsConnection *nc, microServiceConfig *cfg); +NATS_EXTERN microError *microService_Destroy(microService *m); NATS_EXTERN natsConnection *microService_GetConnection(microService *m); -NATS_EXTERN microError *microService_GetInfo(microServiceInfo **new_info, microService *m); -NATS_EXTERN microError *natsMicroservice_GetStats(microServiceStats **new_stats, microService *m); NATS_EXTERN bool microService_IsStopped(microService *m); -NATS_EXTERN microError *microService_Release(microService *m); NATS_EXTERN microError *microService_Run(microService *m); NATS_EXTERN microError *microService_Stop(microService *m); // // microRequest methods. +NATS_EXTERN microError *microRequest_AddHeader(microRequest *req, const char *key, const char *value); +NATS_EXTERN microError *microRequest_DeleteHeader(microRequest *req, const char *key); NATS_EXTERN natsConnection *microRequest_GetConnection(microRequest *req); +NATS_EXTERN const char *microRequest_GetData(microRequest *req); +NATS_EXTERN int microRequest_GetDataLength(microRequest *req); NATS_EXTERN microEndpoint *microRequest_GetEndpoint(microRequest *req); +NATS_EXTERN void *microRequest_GetEndpointState(microRequest *req); +NATS_EXTERN microError *microRequest_GetHeaderKeys(microRequest *req, const char ***keys, int *count); +NATS_EXTERN microError *microRequest_GetHeaderValue(microRequest *req, const char *key, const char **value); +NATS_EXTERN microError *microRequest_GetHeaderValues(microRequest *req,const char *key, const char ***values, int *count); NATS_EXTERN natsMsg *microRequest_GetMsg(microRequest *req); +NATS_EXTERN const char *microRequest_GetReply(microRequest *req); NATS_EXTERN microService *microRequest_GetService(microRequest *req); +NATS_EXTERN void *microRequest_GetServiceState(microRequest *req); +NATS_EXTERN uint64_t microRequest_GetSequence(microRequest *req); +NATS_EXTERN const char *microRequest_GetSubject(microRequest *req); +NATS_EXTERN int64_t microRequest_GetTime(microRequest *req); NATS_EXTERN microError *microRequest_Respond(microRequest *req, microError **err_will_free, const char *data, size_t len); - -#define microRequest_AddHeader(req, key, value) natsMsgHeader_Add(microRequest_GetMsg(req), (key), (value)) -#define microRequest_DeleteHeader(req, key) natsMsgHeader_Delete(microRequest_GetMsg(req), (key)) -#define microRequest_GetData(req) natsMsg_GetData(microRequest_GetMsg(req)) -#define microRequest_GetDataLength(req) natsMsg_GetDataLength(microRequest_GetMsg(req)) -#define microRequest_GetHeader(req, key, value) natsMsgHeader_Get(microRequest_GetMsg(req), (key), (value)) -#define microRequest_GetHeaderKeys(req, key, keys, count) natsMsgHeader_Keys(microRequest_GetMsg(req), (key), (keys), (count)) -#define microRequest_GetHeaderValues(req, key, values, count) natsMsgHeader_Values(microRequest_GetMsg(req), (key), (values), (count)) -#define microRequest_GetReply(req) natsMsg_GetReply(microRequest_GetMsg(req)) -#define microRequest_GetSequence(req) natsMsg_GetSequence(microRequest_GetMsg(req)) -#define microRequest_GetSubject(req) natsMsg_GetSubject(microRequest_GetMsg(req)) -#define microRequest_GetTime(req) natsMsg_GetTime(microRequest_GetMsg(req)) -#define microRequest_SetHeader(req, key, value) natsMsgHeader_Set(microRequest_GetMsg(req), (key), (value)) +NATS_EXTERN microError *microRequest_SetHeader(microRequest *req, const char *key, const char *value); // // microError methods. -NATS_EXTERN microError *micro_NewErrorf(int code, const char *format, ...); -NATS_EXTERN natsStatus microError_Code(microError *err); +NATS_EXTERN microError *micro_Errorf(int code, const char *format, ...); +NATS_EXTERN microError *micro_ErrorFromResponse(natsStatus s, natsMsg *msg); +NATS_EXTERN microError *micro_ErrorFromStatus(natsStatus s); +NATS_EXTERN int microError_Code(microError *err); NATS_EXTERN void microError_Destroy(microError *err); -NATS_EXTERN microError *microError_FromResponse(natsStatus s, natsMsg *msg); -NATS_EXTERN microError *microError_FromStatus(natsStatus s); NATS_EXTERN natsStatus microError_Status(microError *err); NATS_EXTERN const char *microError_String(microError *err, char *buf, int len); NATS_EXTERN microError *microError_Wrapf(microError *err, const char *format, ...); // -// natsClient methods. - -microError *microClient_Create(microClient **new_client, natsConnection *nc, microClientConfig *cfg); +// microClient methods. +microError *micro_NewClient(microClient **new_client, natsConnection *nc, microClientConfig *cfg); void microClient_Destroy(microClient *client); -microError *microClient_DoRequest(microClient *client, natsMsg **reply, const char *subject, const char *data, int data_len); +microError *microClient_DoRequest(natsMsg **reply, microClient *client, const char *subject, const char *data, int data_len); // // microServiceInfo methods. -void microServiceInfo_Destroy(microServiceInfo *info); +NATS_EXTERN microError *microService_GetInfo(microServiceInfo **new_info, microService *m); +NATS_EXTERN void microServiceInfo_Destroy(microServiceInfo *info); // // microServiceStats methods. -void natsMicroserviceStats_Destroy(microServiceStats *stats); +NATS_EXTERN microError *microService_GetStats(microServiceStats **new_stats, microService *m); +NATS_EXTERN void natsMicroserviceStats_Destroy(microServiceStats *stats); /** @} */ // end of microserviceGroup diff --git a/src/micro_args.c b/src/micro_args.c index 61c26725a..917399616 100644 --- a/src/micro_args.c +++ b/src/micro_args.c @@ -24,31 +24,44 @@ struct args_s static microError *parse(void **args, int *args_len, const char *data, int data_len); +static inline microError *new_args(microArgs **ptr, int n) +{ + *ptr = NATS_CALLOC(1, sizeof(microArgs)); + if (*ptr == NULL) + return micro_ErrorOutOfMemory; + + (*ptr)->args = NATS_CALLOC(n, sizeof(void *)); + if ((*ptr)->args == NULL) + { + NATS_FREE(*ptr); + return micro_ErrorOutOfMemory; + } + + (*ptr)->count = n; + return NULL; +} + microError * -micro_ParseArgs(microArgs **new_args, const char *data, int data_len) +micro_ParseArgs(microArgs **ptr, const char *data, int data_len) { microError *err = NULL; - int n; microArgs *args = NULL; + int n; - if ((new_args == NULL) || (data == NULL) || (data_len < 0)) - return micro_NewErrorf(500, "invalid function argument"); + if ((ptr == NULL) || (data == NULL) || (data_len < 0)) + return micro_Errorf(500, "invalid function argument"); MICRO_CALL(err, parse(NULL, &n, data, data_len)); - MICRO_CALL(err, MICRO_CALLOC(args, 1, sizeof(microArgs))); - MICRO_CALL(err, MICRO_CALLOC(args->args, n, sizeof(void *))); - MICRO_DO(err, args->count = n); + MICRO_CALL(err, new_args(&args, n)); MICRO_CALL(err, parse(args->args, &n, data, data_len)); - if (err == NULL) - { - *new_args = args; - } - else + if (err != NULL) { microArgs_Destroy(args); + return err; } - return err; + *ptr = args; + return NULL; } void microArgs_Destroy(microArgs *args) @@ -172,7 +185,7 @@ decode_rest_of_string(char *dup, int *decoded_len, int *i, const char *data, int } if (!terminated) { - micro_NewErrorf(400, "a quoted string is not properly terminated"); + micro_Errorf(400, "a quoted string is not properly terminated"); } *decoded_len = len; @@ -282,7 +295,7 @@ parse(void **args, int *args_len, const char *data, int data_len) break; default: - return micro_NewErrorf(400, "unexpected '%c', an argument must be a number or a quoted string", c); + return micro_Errorf(400, "unexpected '%c', an argument must be a number or a quoted string", c); } break; @@ -341,12 +354,12 @@ parse(void **args, int *args_len, const char *data, int data_len) break; default: - return micro_NewErrorf(400, "unexpected '%c', a number must be followed by a space", c); + return micro_Errorf(400, "unexpected '%c', a number must be followed by a space", c); } break; default: - return micro_NewErrorf(500, "unreachable: wrong state for a ' ', expected NewArg or NumberArg, got %d", state); + return micro_Errorf(500, "unreachable: wrong state for a ' ', expected NewArg or NumberArg, got %d", state); } } diff --git a/src/micro_client.c b/src/micro_client.c index 5def1e873..2636edae3 100644 --- a/src/micro_client.c +++ b/src/micro_client.c @@ -17,7 +17,7 @@ #include "conn.h" microError * -microClient_Create(microClient **new_client, natsConnection *nc, microClientConfig *cfg) +micro_NewClient(microClient **new_client, natsConnection *nc, microClientConfig *cfg) { microClient *client = NULL; @@ -44,7 +44,7 @@ void microClient_Destroy(microClient *client) } microError * -microClient_DoRequest(microClient *client, natsMsg **reply, const char *subject, const char *data, int data_len) +microClient_DoRequest(natsMsg **reply, microClient *client, const char *subject, const char *data, int data_len) { natsStatus s = NATS_OK; microError *err = NULL; @@ -55,9 +55,11 @@ microClient_DoRequest(microClient *client, natsMsg **reply, const char *subject, s = natsConnection_Request(&msg, client->nc, subject, data, data_len, 5000); if (s != NATS_OK) - return microError_Wrapf(microError_FromStatus(s), "request failed"); + { + return microError_Wrapf(micro_ErrorFromStatus(s), "request failed"); + } - err = microError_FromResponse(s, msg); + err = micro_ErrorFromResponse(s, msg); if (err == NULL) { *reply = msg; diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index 8e2f9dcfa..43b7d4836 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -17,8 +17,6 @@ #include "microp.h" #include "mem.h" -static natsStatus -free_endpoint(microEndpoint *ep); static bool is_valid_name(const char *name); static bool @@ -26,173 +24,135 @@ is_valid_subject(const char *subject); static void handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); -natsStatus -micro_new_endpoint(microEndpoint **new_endpoint, microService *m, microEndpointConfig *cfg) +static microError *new_endpoint(microEndpoint **ptr); + +microError * +micro_new_endpoint(microEndpoint **new_ep, microService *m, microEndpointConfig *cfg) { - natsStatus s = NATS_OK; + microError *err = NULL; microEndpoint *ep = NULL; - microEndpointConfig *clone_cfg = NULL; if (!is_valid_name(cfg->name) || (cfg->handler == NULL)) - { - s = NATS_INVALID_ARG; - } - if ((s == NATS_OK) && (cfg->subject != NULL) && !is_valid_subject(cfg->subject)) - { - s = NATS_INVALID_ARG; - } - IFOK(s, micro_clone_endpoint_config(&clone_cfg, cfg)); - IFOK(s, NATS_CALLOCS(&ep, 1, sizeof(microEndpoint))); - IFOK(s, natsMutex_Create(&ep->mu)); - if (s == NATS_OK) - { - ep->m = m; - ep->subject = NATS_STRDUP(nats_IsStringEmpty(cfg->subject) ? cfg->name : cfg->subject); - ep->config = clone_cfg; - *new_endpoint = ep; - } - else - { - micro_free_cloned_endpoint_config(clone_cfg); - free_endpoint(ep); - } - return NATS_UPDATE_ERR_STACK(s); -} + return micro_ErrorInvalidArg; -natsStatus -micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) -{ - natsStatus s = NATS_OK; - microEndpointConfig *new_cfg = NULL; - microSchema *new_schema = NULL; - char *new_name = NULL; - char *new_subject = NULL; - char *new_request = NULL; - char *new_response = NULL; + if ((cfg->subject != NULL) && !is_valid_subject(cfg->subject)) + return micro_ErrorInvalidArg; - if (out == NULL || cfg == NULL) - return NATS_INVALID_ARG; - - s = NATS_CALLOCS(&new_cfg, 1, sizeof(microEndpointConfig)); - if (s == NATS_OK && cfg->name != NULL) - { - DUP_STRING(s, new_name, cfg->name); - } - if (s == NATS_OK && cfg->subject != NULL) - { - DUP_STRING(s, new_subject, cfg->subject); - } - if (s == NATS_OK && cfg->schema != NULL) - { - s = NATS_CALLOCS(&new_schema, 1, sizeof(microSchema)); - if (s == NATS_OK && cfg->schema->request != NULL) - { - DUP_STRING(s, new_request, cfg->schema->request); - } - if (s == NATS_OK && cfg->schema->response != NULL) - { - DUP_STRING(s, new_response, cfg->schema->response); - } - if (s == NATS_OK) - { - new_schema->request = new_request; - new_schema->response = new_response; - } - } - if (s == NATS_OK) - { - memcpy(new_cfg, cfg, sizeof(microEndpointConfig)); - new_cfg->schema = new_schema; - new_cfg->name = new_name; - new_cfg->subject = new_subject; - *out = new_cfg; - } - else + MICRO_CALL(err, new_endpoint(&ep)); + MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->mu))); + MICRO_CALL(err, micro_clone_endpoint_config(&ep->config, cfg)); + MICRO_CALL(err, micro_strdup(&ep->subject, nats_IsStringEmpty(cfg->subject) ? cfg->name : cfg->subject)); + if (err != NULL) { - NATS_FREE(new_cfg); - NATS_FREE(new_schema); - NATS_FREE(new_name); - NATS_FREE(new_subject); - NATS_FREE(new_request); - NATS_FREE(new_response); + micro_stop_and_destroy_endpoint(ep); + return err; } - return NATS_UPDATE_ERR_STACK(s); + ep->m = m; + *new_ep = ep; + return NULL; } -void micro_free_cloned_endpoint_config(microEndpointConfig *cfg) -{ - if (cfg == NULL) - return; - - // the strings are declared const for the public, but in a clone these need - // to be freed. - NATS_FREE((char *)cfg->name); - NATS_FREE((char *)cfg->subject); - NATS_FREE((char *)cfg->schema->request); - NATS_FREE((char *)cfg->schema->response); - - NATS_FREE(cfg->schema); - NATS_FREE(cfg); -} - -natsStatus +microError * micro_start_endpoint(microEndpoint *ep) { + natsStatus s = NATS_OK; if ((ep->subject == NULL) || (ep->config == NULL) || (ep->config->handler == NULL)) // nothing to do - return NATS_OK; + return NULL; // reset the stats. memset(&ep->stats, 0, sizeof(ep->stats)); - return natsConnection_QueueSubscribe(&ep->sub, ep->m->nc, ep->subject, - MICRO_QUEUE_GROUP, handle_request, ep); + s = natsConnection_QueueSubscribe(&ep->sub, ep->m->nc, ep->subject, + MICRO_QUEUE_GROUP, handle_request, ep); + if (s != NATS_OK) + return micro_ErrorFromStatus(s); + + return NULL; } -// TODO <>/<> COPY FROM GO -natsStatus -micro_stop_endpoint(microEndpoint *ep) +microError * +micro_stop_and_destroy_endpoint(microEndpoint *ep) { natsStatus s = NATS_OK; - if (ep->sub != NULL) + if ((ep == NULL) || (ep->sub == NULL)) + return NULL; + + if (!natsConnection_IsClosed(ep->m->nc)) { s = natsSubscription_Drain(ep->sub); + if (s != NATS_OK) + return micro_ErrorFromStatus(s); } - if (s == NATS_OK) - { - ep->sub = NULL; - - // reset the stats. - memset(&ep->stats, 0, sizeof(ep->stats)); - } - - return NATS_UPDATE_ERR_STACK(s); -} -static natsStatus -free_endpoint(microEndpoint *ep) -{ NATS_FREE(ep->subject); + natsSubscription_Destroy(ep->sub); natsMutex_Destroy(ep->mu); - micro_free_cloned_endpoint_config(ep->config); + micro_destroy_cloned_endpoint_config(ep->config); NATS_FREE(ep); - return NATS_OK; + + return NULL; } -natsStatus -micro_stop_and_destroy_endpoint(microEndpoint *ep) +static void +handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { - natsStatus s = NATS_OK; + // natsStatus s = NATS_OK; + microError *err = NULL; + microEndpoint *ep = (microEndpoint *)closure; + microEndpointStats *stats; + microRequestHandler handler; + microRequest *req = NULL; + int64_t start, elapsed_ns, full_s; - if (ep == NULL) - return NATS_OK; + if (ep == NULL || ep->config == NULL || ep->config->handler == NULL) + { + // This is a bug, we should not have received a message on this + // subscription. + natsMsg_Destroy(msg); + return; + } + stats = &ep->stats; + handler = ep->config->handler; - IFOK(s, micro_stop_endpoint(ep)); - IFOK(s, free_endpoint(ep)); + err = micro_new_request(&req, ep->m, ep, msg); + if (err != NULL) + { + natsMsg_Destroy(msg); + return; + } + req->endpoint = ep; + + // handle the request. + start = nats_NowInNanoSeconds(); + handler(req); + elapsed_ns = nats_NowInNanoSeconds() - start; + + // Update stats. Note that unlike in the go client, the error stats are + // handled by req.Respond function. + natsMutex_Lock(ep->mu); + stats->num_requests++; + stats->processing_time_ns += elapsed_ns; + full_s = stats->processing_time_ns / 1000000000; + stats->processing_time_s += full_s; + stats->processing_time_ns -= full_s * 1000000000; + natsMutex_Unlock(ep->mu); + + micro_destroy_request(req); + natsMsg_Destroy(msg); +} + +void micro_update_last_error(microEndpoint *ep, microError *err) +{ + if (err == NULL || ep == NULL) + return; - return NATS_UPDATE_ERR_STACK(s); + natsMutex_Lock(ep->mu); + ep->stats.num_errors++; + microError_String(err, ep->stats.last_error_string, sizeof(ep->stats.last_error_string)); + natsMutex_Unlock(ep->mu); } static bool @@ -241,59 +201,159 @@ is_valid_subject(const char *subject) return true; } -static void -handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +static inline void +destroy_schema(microSchema *schema) { - natsStatus s = NATS_OK; - microEndpoint *ep = (microEndpoint *)closure; - microEndpointStats *stats = &ep->stats; - microEndpointConfig *cfg = ep->config; - microRequest *req = NULL; - microRequestHandler handler = cfg->handler; - int64_t start, elapsed_ns, full_s; + if (schema == NULL) + return; - if (handler == NULL) + NATS_FREE((char *)schema->request); + NATS_FREE((char *)schema->response); + NATS_FREE(schema); +} + +static microError * +clone_schema(microSchema **to, const microSchema *from) +{ + microError *err = NULL; + if (from == NULL) { - // This is a bug, we should not have received a message on this - // subscription. - natsMsg_Destroy(msg); - return; + *to = NULL; + return NULL; } - s = micro_new_request(&req, msg); - if (s != NATS_OK) + *to = NATS_CALLOC(1, sizeof(microSchema)); + if (*to == NULL) + return micro_ErrorOutOfMemory; + + MICRO_CALL(err, micro_strdup((char **)&((*to)->request), from->request)); + MICRO_CALL(err, micro_strdup((char **)&((*to)->response), from->response)); + + if (err != NULL) { - natsMsg_Destroy(msg); - return; + destroy_schema(*to); + *to = NULL; + return err; } - // handle the request. - start = nats_NowInNanoSeconds(); - req->ep = ep; - handler(ep->m, req); - elapsed_ns = nats_NowInNanoSeconds() - start; - - // update stats. - natsMutex_Lock(ep->mu); - stats->num_requests++; - stats->processing_time_ns += elapsed_ns; - full_s = stats->processing_time_ns / 1000000000; - stats->processing_time_s += full_s; - stats->processing_time_ns -= full_s * 1000000000; + return NULL; +} - natsMutex_Unlock(ep->mu); +static microError * +new_endpoint(microEndpoint **ptr) +{ + *ptr = NATS_CALLOC(1, sizeof(microEndpoint)); + return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; +} - micro_free_request(req); - natsMsg_Destroy(msg); +static inline microError * +new_endpoint_config(microEndpointConfig **ptr) +{ + *ptr = NATS_CALLOC(1, sizeof(microEndpointConfig)); + return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; } -void micro_update_last_error(microEndpoint *ep, microError *err) +microError * +micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) { + microError *err = NULL; + microEndpointConfig *new_cfg = NULL; + + if (out == NULL) + return micro_ErrorInvalidArg; + + if (cfg == NULL) + { + *out = NULL; + return NULL; + } + + err = new_endpoint_config(&new_cfg); if (err == NULL) + { + memcpy(new_cfg, cfg, sizeof(microEndpointConfig)); + } + + MICRO_CALL(err, micro_strdup((char **)&new_cfg->name, cfg->name)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->subject, cfg->subject)); + MICRO_CALL(err, clone_schema(&new_cfg->schema, cfg->schema)); + + if (err != NULL) + { + micro_destroy_cloned_endpoint_config(new_cfg); + return err; + } + + *out = new_cfg; + return NULL; +} + +void micro_destroy_cloned_endpoint_config(microEndpointConfig *cfg) +{ + if (cfg == NULL) return; - natsMutex_Lock(ep->mu); - ep->stats.num_errors++; - microError_String(err, ep->stats.last_error_string, sizeof(ep->stats.last_error_string)); - natsMutex_Unlock(ep->mu); + // the strings are declared const for the public, but in a clone these need + // to be freed. + NATS_FREE((char *)cfg->name); + NATS_FREE((char *)cfg->subject); + + destroy_schema(cfg->schema); + NATS_FREE(cfg); +} + +bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject) +{ + const char *e = ep_subject; + const char *a = actual_subject; + const char *etok, *enext; + int etok_len; + bool last_etok = false; + const char *atok, *anext; + int atok_len; + bool last_atok = false; + + if (e == NULL || a == NULL) + return false; + + while (true) + { + enext = strchr(e, '.'); + if (enext == NULL) + { + enext = e + strlen(e); + last_etok = true; + } + etok = e; + etok_len = (int)(enext - e); + e = enext + 1; + + anext = strchr(a, '.'); + if (anext == NULL) + { + anext = a + strlen(a); + last_atok = true; + } + atok = a; + atok_len = (int)(anext - a); + a = anext + 1; + + if (last_etok) + { + if (etok_len == 1 && etok[0] == '>') + return true; + + if (!last_atok) + return false; + } + if (!(etok_len == 1 && etok[0] == '*') && + !(etok_len == atok_len && strncmp(etok, atok, etok_len) == 0)) + { + return false; + } + if (last_atok) + { + return last_etok; + } + } } diff --git a/src/micro_error.c b/src/micro_error.c index 73f6e0eeb..497bd1b6e 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -11,26 +11,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include "micro.h" #include "microp.h" -#include "mem.h" - -#ifdef DEV_MODE -// For type safety - -static void _retain(natsError *err) { err->refs++; } -static void _release(natsError *err) { err->refs--; } - -void natsError_Lock(natsConnection *nc) { natsMutex_Lock(nc->mu); } -void natsConn_Unlock(natsConnection *nc) { natsMutex_Unlock(nc->mu); } - -#else -// We know what we are doing :-) -#define _retain(c) ((c)->refs++) -#define _release(c) ((c)->refs--) - -#endif // DEV_MODE +static microError * +new_error(natsStatus s, int code, char *description); static microError _errorOutOfMemory = { .status = NATS_NO_MEMORY, @@ -60,42 +47,41 @@ static microError *knownErrors[] = { microError *micro_ErrorOutOfMemory = &_errorOutOfMemory; microError *micro_ErrorInvalidArg = &_errorInvalidArg; -const char * -microError_String(microError *err, char *buf, int size) +microError * +micro_Errorf(int code, const char *format, ...) { - if (err == NULL || buf == NULL) - return ""; - if (err->status == NATS_OK) - snprintf(buf, size, "%d: %s", err->code, err->description); - else - snprintf(buf, size, "%d:%d: %s", err->status, err->code, err->description); - return buf; -} + va_list args1, args2; + char *buf = NULL; + int len = 0; -natsStatus -microError_Status(microError *err) -{ - return (err != NULL) ? err->status : NATS_OK; -} + if ((code == 0) && nats_IsStringEmpty(format)) + return NULL; -static microError * -new_error(natsStatus s, int code, char *description) -{ - microError *err = NULL; + va_start(args1, format); + va_copy(args2, args1); - err = NATS_CALLOC(1, sizeof(microError)); - if (err == NULL) + len = vsnprintf(NULL, 0, format, args1); + va_end(args1); + if (len < 0) + { + va_end(args2); + return &_errorInvalidFormat; + } + buf = NATS_MALLOC(len + 1); + if (buf == NULL) + { + va_end(args2); return &_errorOutOfMemory; + } - err->status = s; - err->code = code; - err->description = description; + vsnprintf(buf, len + 1, format, args2); + va_end(args2); - return err; + return new_error(NATS_ERR, code, buf); } microError * -microError_FromStatus(natsStatus s) +micro_ErrorFromStatus(natsStatus s) { char *dup = NULL; @@ -109,6 +95,31 @@ microError_FromStatus(natsStatus s) return new_error(s, 0, dup); } +microError * +micro_ErrorFromResponse(natsStatus status, natsMsg *msg) +{ + microError *err = NULL; + const char *c = NULL, *d = NULL; + bool is_error; + + if (msg != NULL) + { + natsMsgHeader_Get(msg, MICRO_ERROR_CODE_HDR, &c); + natsMsgHeader_Get(msg, MICRO_ERROR_HDR, &d); + } + + is_error = (status != NATS_OK) || !nats_IsStringEmpty(c) || !nats_IsStringEmpty(d); + if (!is_error) + return NULL; + + err = microError_Wrapf(micro_ErrorFromStatus(status), d); + if (!nats_IsStringEmpty(c) && (err != NULL)) + { + err->code = atoi(c); + } + return err; +} + microError * microError_Wrapf(microError *err, const char *format, ...) { @@ -157,37 +168,30 @@ microError_Wrapf(microError *err, const char *format, ...) return new_error(s, code, buf); } -microError * -micro_NewErrorf(int code, const char *format, ...) +const char * +microError_String(microError *err, char *buf, int size) { - va_list args1, args2; - char *buf = NULL; - int len = 0; - - if ((code == 0) && nats_IsStringEmpty(format)) - return NULL; - - va_start(args1, format); - va_copy(args2, args1); - - len = vsnprintf(NULL, 0, format, args1); - va_end(args1); - if (len < 0) + if (buf == NULL) + return ""; + if (err == NULL) { - va_end(args2); - return &_errorInvalidFormat; + snprintf(buf, size, "null"); } - buf = NATS_MALLOC(len + 1); - if (buf == NULL) + else if (err->status == NATS_OK) { - va_end(args2); - return &_errorOutOfMemory; + snprintf(buf, size, "%d: %s", err->code, err->description); } + else + { + snprintf(buf, size, "%d:%d: %s", err->status, err->code, err->description); + } + return buf; +} - vsnprintf(buf, len + 1, format, args2); - va_end(args2); - - return new_error(NATS_ERR, code, buf); +natsStatus +microError_Status(microError *err) +{ + return (err != NULL) ? err->status : NATS_OK; } void microError_Destroy(microError *err) @@ -209,27 +213,19 @@ void microError_Destroy(microError *err) NATS_FREE(err); } -microError * -microError_FromResponse(natsStatus status, natsMsg *msg) +static microError * +new_error(natsStatus s, int code, char *description) { microError *err = NULL; - const char *c = NULL, *d = NULL; - bool is_error; - if (msg != NULL) - { - natsMsgHeader_Get(msg, MICRO_ERROR_CODE_HDR, &c); - natsMsgHeader_Get(msg, MICRO_ERROR_HDR, &d); - } + err = NATS_CALLOC(1, sizeof(microError)); + if (err == NULL) + return &_errorOutOfMemory; - is_error = (status != NATS_OK) || !nats_IsStringEmpty(c) || !nats_IsStringEmpty(d); - if (!is_error) - return NULL; + err->status = s; + err->code = code; + err->description = description; - err = microError_Wrapf(microError_FromStatus(status), d); - if (!nats_IsStringEmpty(c) && (err != NULL)) - { - err->code = atoi(c); - } return err; } + diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 9d0f87505..8dbe60335 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -15,15 +15,14 @@ #include "micro.h" #include "microp.h" -#include "mem.h" #include "util.h" -static natsStatus +static microError * marshal_ping(natsBuffer **new_buf, microService *m); static void handle_ping(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); -static microError* +static microError * marshal_info(natsBuffer **new_buf, microServiceInfo *info); static void handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); @@ -33,90 +32,115 @@ marshal_stats(natsBuffer **new_buf, microServiceStats *stats); static void handle_stats(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); -static natsStatus +static microError * add_internal_handler(microService *m, const char *verb, const char *kind, const char *id, const char *name, natsMsgHandler handler); -static natsStatus +static microError * add_verb_handlers(microService *m, const char *verb, natsMsgHandler handler); -static natsStatus +static microError * new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); -static natsStatus +static microError * new_dotted_subject(char **new_subject, int count, ...); -static natsStatus -marshal_duration(natsBuffer *out_buf, bool comma, const char *name, int64_t d); -static void -fmt_frac(char buf[], int w, uint64_t v, int prec, int *nw, uint64_t *nv); -static int -fmt_int(char buf[], int w, uint64_t v); - -natsStatus +microError * micro_init_monitoring(microService *m) { - natsStatus s = NATS_OK; + microError *err = NULL; - IFOK(s, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); - IFOK(s, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); - IFOK(s, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); + m->monitoring_subs = NATS_CALLOC(MICRO_MONITORING_SUBS_CAP, sizeof(natsSubscription*)); + if (m->monitoring_subs == NULL) + return micro_ErrorOutOfMemory; + m->monitoring_subs_len = 0; - return NATS_UPDATE_ERR_STACK(s); + MICRO_CALL(err, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); + MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); + MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); + return err; } static void handle_ping(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { - natsStatus s = NATS_OK; + microError *err = NULL; microService *m = (microService *)closure; - microRequest req = { - .msg = msg, - }; + microRequest *req = NULL; natsBuffer *buf = NULL; - s = marshal_ping(&buf, m); - if (s == NATS_OK) - { - microRequest_Respond(&req, NULL, natsBuf_Data(buf), natsBuf_Len(buf)); - } + if ((m == NULL) || (m->cfg == NULL)) + return; // Should not happen + + MICRO_CALL(err, marshal_ping(&buf, m)); + MICRO_CALL(err, micro_new_request(&req, m, NULL, msg)); + + // respond for both success and error cases. + microRequest_Respond(req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); + + micro_destroy_request(req); natsBuf_Destroy(buf); } -void handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +static void +handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { microError *err = NULL; microService *m = (microService *)closure; - microRequest req = { - .msg = msg, - }; + microRequest *req = NULL; microServiceInfo *info = NULL; natsBuffer *buf = NULL; + if ((m == NULL) || (m->cfg == NULL)) + return; // Should not happen + MICRO_CALL(err, microService_GetInfo(&info, m)); MICRO_CALL(err, marshal_info(&buf, info)); + MICRO_CALL(err, micro_new_request(&req, m, NULL, msg)); + + // respond for both success and error cases. + microRequest_Respond(req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); - microRequest_Respond(&req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); natsBuf_Destroy(buf); microServiceInfo_Destroy(info); } static void -handle_stats(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +handle_stats_internal(microRequest *req) { microError *err = NULL; - microService *m = (microService *)closure; - microRequest req = { - .msg = msg, - }; microServiceStats *stats = NULL; natsBuffer *buf = NULL; - MICRO_CALL(err, natsMicroservice_GetStats(&stats, m)); + if ((req == NULL) || (req->service == NULL) || (req->service->cfg == NULL)) + return; // Should not happen + + MICRO_CALL(err, microService_GetStats(&stats, req->service)); MICRO_CALL(err, marshal_stats(&buf, stats)); - microRequest_Respond(&req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); + // respond for both success and error cases. + microRequest_Respond(req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); + natsBuf_Destroy(buf); natsMicroserviceStats_Destroy(stats); } -static natsStatus +static void +handle_stats(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +{ + microError *err = NULL; + microService *m = (microService *)closure; + microRequest *req = NULL; + microRequestHandler h = handle_stats_internal; + + if ((m == NULL) || (m->cfg == NULL)) + return; // Should not happen + + if (m->cfg->stats_handler != NULL) + h = m->cfg->stats_handler; + + MICRO_CALL(err, micro_new_request(&req, m, NULL, msg)); + MICRO_DO(err, h(req)); + MICRO_DO(err, micro_destroy_request(req)); +} + +static microError * new_dotted_subject(char **new_subject, int count, ...) { va_list args; @@ -138,7 +162,7 @@ new_dotted_subject(char **new_subject, int count, ...) result = NATS_MALLOC(len + 1); if (result == NULL) { - return NATS_NO_MEMORY; + return micro_ErrorInvalidArg; } len = 0; @@ -157,16 +181,15 @@ new_dotted_subject(char **new_subject, int count, ...) va_end(args); *new_subject = result; - return NATS_OK; + return NULL; } -static natsStatus +static microError * new_control_subject(char **newSubject, const char *verb, const char *name, const char *id) { if (nats_IsStringEmpty(name) && !nats_IsStringEmpty(id)) { - NATS_UPDATE_ERR_TXT("service name is required when id is provided: %s", id); - return NATS_UPDATE_ERR_STACK(NATS_INVALID_ARG); + return micro_Errorf(400, "service name is required when id is provided: %s", id); } else if (nats_IsStringEmpty(name) && nats_IsStringEmpty(id)) @@ -177,57 +200,57 @@ new_control_subject(char **newSubject, const char *verb, const char *name, const return new_dotted_subject(newSubject, 4, MICRO_API_PREFIX, verb, name, id); } -static natsStatus +static microError * add_internal_handler(microService *m, const char *verb, const char *kind, const char *id, const char *name, natsMsgHandler handler) { - + microError *err = NULL; natsStatus s = NATS_OK; natsSubscription *sub = NULL; char *subj = NULL; - s = new_control_subject(&subj, verb, kind, id); - if (s == NATS_OK) - { - s = natsConnection_Subscribe(&sub, m->nc, subj, handler, m); - } + if (m->monitoring_subs_len >= MICRO_MONITORING_SUBS_CAP) + return micro_Errorf(500, "too many monitoring subscriptions (max: %d)", MICRO_MONITORING_SUBS_CAP); - if (s == NATS_OK) - { - return NATS_OK; - } - else + err = new_control_subject(&subj, verb, kind, id); + if (err != NULL) + return err; + + s = natsConnection_Subscribe(&sub, m->nc, subj, handler, m); + if (s != NATS_OK) { microService_Stop(m); - return NATS_UPDATE_ERR_STACK(s); + return micro_ErrorFromStatus(s); } + + m->monitoring_subs[m->monitoring_subs_len] = sub; + m->monitoring_subs_len++; + + return NULL; } // __verbHandlers generates control handlers for a specific verb. Each request // generates 3 subscriptions, one for the general verb affecting all services // written with the framework, one that handles all services of a particular // kind, and finally a specific service instance. -static natsStatus +static microError * add_verb_handlers(microService *m, const char *verb, natsMsgHandler handler) { - natsStatus s = NATS_OK; + microError *err = NULL; char name[1024]; - if (s == NATS_OK) - { - snprintf(name, sizeof(name), "%s-all", verb); - s = add_internal_handler(m, verb, "", "", name, handler); - } - if (s == NATS_OK) + snprintf(name, sizeof(name), "%s-all", verb); + err = add_internal_handler(m, verb, "", "", name, handler); + if (err == NULL) { snprintf(name, sizeof(name), "%s-kind", verb); - s = add_internal_handler(m, verb, m->cfg->name, "", name, handler); + err = add_internal_handler(m, verb, m->cfg->name, "", name, handler); } - if (s == NATS_OK) + if (err == NULL) { - s = add_internal_handler(m, verb, m->cfg->name, m->id, verb, handler); + err = add_internal_handler(m, verb, m->cfg->name, m->id, verb, handler); } - return NATS_UPDATE_ERR_STACK(s); + return err; } // name and sep must be a string literal @@ -236,36 +259,34 @@ add_verb_handlers(microService *m, const char *verb, natsMsgHandler handler) IFOK(s, natsBuf_Append(buf, (_value) != NULL ? (_value) : "", -1)); \ IFOK(s, natsBuf_Append(buf, "\"" _sep, -1)); -static natsStatus +static microError * marshal_ping(natsBuffer **new_buf, microService *m) { natsBuffer *buf = NULL; natsStatus s; s = natsBuf_Create(&buf, 1024); - if (s != NATS_OK) - return NATS_UPDATE_ERR_STACK(s); - - s = natsBuf_Append(buf, "{", -1); - IFOK_attr("name", m->cfg->name, ","); - IFOK_attr("version", m->cfg->version, ","); - IFOK_attr("id", m->id, ","); - IFOK_attr("type", MICRO_PING_RESPONSE_TYPE, ""); - IFOK(s, natsBuf_AppendByte(buf, '}')); - if (s == NATS_OK) { - *new_buf = buf; - return NATS_OK; + s = natsBuf_Append(buf, "{", -1); + IFOK_attr("name", m->cfg->name, ","); + IFOK_attr("version", m->cfg->version, ","); + IFOK_attr("id", m->id, ","); + IFOK_attr("type", MICRO_PING_RESPONSE_TYPE, ""); + IFOK(s, natsBuf_AppendByte(buf, '}')); } - else + + if (s != NATS_OK) { natsBuf_Destroy(buf); - return NATS_UPDATE_ERR_STACK(s); + return micro_ErrorFromStatus(s); } + + *new_buf = buf; + return NULL; } -static microError* +static microError * marshal_info(natsBuffer **new_buf, microServiceInfo *info) { natsBuffer *buf = NULL; @@ -294,19 +315,16 @@ marshal_info(natsBuffer **new_buf, microServiceInfo *info) IFOK_attr("version", info->version, ""); IFOK(s, natsBuf_AppendByte(buf, '}')); - if (s == NATS_OK) - { - *new_buf = buf; - return NULL; - } - else + if (s != NATS_OK) { natsBuf_Destroy(buf); - return microError_Wrapf(microError_FromStatus(s), "failed to marshal service info"); + return microError_Wrapf(micro_ErrorFromStatus(s), "failed to marshal service info"); } + *new_buf = buf; + return NULL; } -static microError* +static microError * marshal_stats(natsBuffer **new_buf, microServiceStats *stats) { natsBuffer *buf = NULL; @@ -334,8 +352,7 @@ marshal_stats(natsBuffer **new_buf, microServiceStats *stats) IFOK_attr("subject", ep->subject, ","); IFOK(s, nats_marshalLong(buf, false, "num_requests", ep->num_requests)); IFOK(s, nats_marshalLong(buf, true, "num_errors", ep->num_errors)); - // IFOK(s, nats_marshalLong(buf, true, "average_processing_time", ep->average_processing_time_ns)); - IFOK(s, marshal_duration(buf, true, "average_processing_time", ep->average_processing_time_ns)); + IFOK(s, nats_marshalDuration(buf, true, "average_processing_time", ep->average_processing_time_ns)); IFOK(s, natsBuf_AppendByte(buf, ',')); IFOK_attr("last_error", ep->last_error_string, ""); IFOK(s, natsBuf_Append(buf, "}", -1)); @@ -357,151 +374,7 @@ marshal_stats(natsBuffer **new_buf, microServiceStats *stats) else { natsBuf_Destroy(buf); - return microError_Wrapf(microError_FromStatus(s), "failed to marshal service info"); + return microError_Wrapf(micro_ErrorFromStatus(s), "failed to marshal service info"); } } -static natsStatus marshal_duration(natsBuffer *out_buf, bool comma, const char *field_name, int64_t d) -{ - // Largest time is 2540400h10m10.000000000s - char buf[32]; - int w = 32; - uint64_t u = d; - bool neg = d < 0; - int prec; - natsStatus s = NATS_OK; - const char *start = (comma ? ",\"" : "\""); - - if (neg) - u = -u; - - if (u < 1000000000) - { - // Special case: if duration is smaller than a second, - // use smaller units, like 1.2ms - w--; - buf[w] = 's'; - w--; - if (u == 0) - { - return natsBuf_Append(out_buf, "0s", 2); - } - else if (u < 1000) - { - // print nanoseconds - prec = 0; - buf[w] = 'n'; - } - else if (u < 1000000) - { - // print microseconds - prec = 3; - // U+00B5 'µ' micro sign == 0xC2 0xB5 (in reverse?) - buf[w] = '\xB5'; - w--; // Need room for two bytes. - buf[w] = '\xC2'; - } - else - { - // print milliseconds - prec = 6; - buf[w] = 'm'; - } - fmt_frac(buf, w, u, prec, &w, &u); - w = fmt_int(buf, w, u); - } - else - { - w--; - buf[w] = 's'; - - fmt_frac(buf, w, u, 9, &w, &u); - - // u is now integer seconds - w = fmt_int(buf, w, u % 60); - u /= 60; - - // u is now integer minutes - if (u > 0) - { - w--; - buf[w] = 'm'; - w = fmt_int(buf, w, u % 60); - u /= 60; - - // u is now integer hours - // Stop at hours because days can be different lengths. - if (u > 0) - { - w--; - buf[w] = 'h'; - w = fmt_int(buf, w, u); - } - } - } - - if (neg) - { - w--; - buf[w] = '-'; - } - - s = natsBuf_Append(out_buf, start, -1); - IFOK(s, natsBuf_Append(out_buf, field_name, -1)); - IFOK(s, natsBuf_Append(out_buf, "\":\"", -1)); - IFOK(s, natsBuf_Append(out_buf, buf + w, sizeof(buf) - w)); - IFOK(s, natsBuf_Append(out_buf, "\":\"", -1)); - return NATS_UPDATE_ERR_STACK(s); -} - -// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the -// tail of buf, omitting trailing zeros. It omits the decimal -// point too when the fraction is 0. It returns the index where the -// output bytes begin and the value v/10**prec. -static void fmt_frac(char *buf, int w, uint64_t v, int prec, int *nw, uint64_t *nv) -{ - // Omit trailing zeros up to and including decimal point. - bool print = false; - int i; - int digit; - - for (i = 0; i < prec; i++) - { - digit = v % 10; - print = print || digit != 0; - if (print) - { - w--; - buf[w] = digit + '0'; - } - v /= 10; - } - if (print) - { - w--; - buf[w] = '.'; - } - *nw = w; - *nv = v; -} - -// fmtInt formats v into the tail of buf. -// It returns the index where the output begins. -static int fmt_int(char *buf, int w, uint64_t v) -{ - if (v == 0) - { - w--; - buf[w] = '0'; - } - else - { - while (v > 0) - { - w--; - buf[w] = v % 10 + '0'; - v /= 10; - } - } - return w; -} diff --git a/src/micro_request.c b/src/micro_request.c index b20a25cd1..d5012915a 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -15,30 +15,6 @@ #include "microp.h" #include "mem.h" -natsMsg * -microRequest_GetMsg(microRequest *req) -{ - return req != NULL ? req->msg : NULL; -} - -microEndpoint * -microRequest_GetEndpoint(microRequest *req) -{ - return (req != NULL) ? req->ep : NULL; -} - -microService * -microRequest_GetService(microRequest *req) -{ - return ((req != NULL) && (req->ep != NULL)) ? req->ep->m : NULL; -} - -natsConnection * -microRequest_GetConnection(microRequest *req) -{ - return ((req != NULL) && (req->ep != NULL) && (req->ep->m != NULL)) ? req->ep->m->nc : NULL; -} - microError * microRequest_Respond(microRequest *req, microError **err_will_free, const char *data, size_t len) { @@ -47,16 +23,16 @@ microRequest_Respond(microRequest *req, microError **err_will_free, const char * natsStatus s = NATS_OK; char buf[64]; - if ((req == NULL) || (req->msg == NULL) || (req->msg->sub == NULL) || (req->msg->sub->conn == NULL)) + if ((req == NULL) || (req->message == NULL) || (req->message->sub == NULL) || (req->message->sub->conn == NULL)) { return micro_ErrorInvalidArg; } - IFOK(s, natsMsg_Create(&msg, natsMsg_GetReply(req->msg), NULL, data, len)); - - if ((err_will_free != NULL) && (*err_will_free != NULL)) + IFOK(s, natsMsg_Create(&msg, natsMsg_GetReply(req->message), NULL, data, len)); + if ((s == NATS_OK) && (err_will_free != NULL) && (*err_will_free != NULL)) { err = *err_will_free; + micro_update_last_error(req->endpoint, err); if ((s == NATS_OK) && (err->status != NATS_OK)) { s = natsMsgHeader_Set(msg, MICRO_STATUS_HDR, natsStatus_GetText(err->status)); @@ -70,9 +46,8 @@ microRequest_Respond(microRequest *req, microError **err_will_free, const char * snprintf(buf, sizeof(buf), "%d", err->code); s = natsMsgHeader_Set(msg, MICRO_ERROR_CODE_HDR, buf); } - micro_update_last_error(req->ep, err); } - IFOK(s, natsConnection_PublishMsg(req->msg->sub->conn, msg)); + IFOK(s, natsConnection_PublishMsg(req->message->sub->conn, msg)); natsMsg_Destroy(msg); if (err_will_free != NULL) @@ -81,30 +56,148 @@ microRequest_Respond(microRequest *req, microError **err_will_free, const char * *err_will_free = NULL; } return microError_Wrapf( - microError_FromStatus(s), + micro_ErrorFromStatus(s), "failed to respond to a message with an error"); } -void micro_free_request(microRequest *req) +microError * +microRequest_AddHeader(microRequest *req, const char *key, const char *value) +{ + return micro_ErrorFromStatus( + natsMsgHeader_Add(microRequest_GetMsg(req), key, value)); +} + +microError * +microRequest_DeleteHeader(microRequest *req, const char *key) +{ + return micro_ErrorFromStatus( + natsMsgHeader_Delete(microRequest_GetMsg(req), key)); +} + +natsConnection * +microRequest_GetConnection(microRequest *req) +{ + return ((req != NULL) && (req->service != NULL)) ? req->service->nc : NULL; +} + +const char * +microRequest_GetData(microRequest *req) +{ + return natsMsg_GetData(microRequest_GetMsg(req)); +} + +int microRequest_GetDataLength(microRequest *req) +{ + return natsMsg_GetDataLength(microRequest_GetMsg(req)); +} + +microEndpoint * +microRequest_GetEndpoint(microRequest *req) +{ + return (req != NULL) ? req->endpoint : NULL; +} + +microError * +microRequest_GetHeaderKeys(microRequest *req, const char ***keys, int *count) +{ + return micro_ErrorFromStatus( + natsMsgHeader_Keys(microRequest_GetMsg(req), keys, count)); +} + +microError * +microRequest_GetHeaderValue(microRequest *req, const char *key, const char **value) +{ + return micro_ErrorFromStatus( + natsMsgHeader_Get(microRequest_GetMsg(req), key, value)); +} + +microError * +microRequest_GetHeaderValues(microRequest *req, const char *key, const char ***values, int *count) +{ + return micro_ErrorFromStatus( + natsMsgHeader_Values(microRequest_GetMsg(req), key, values, count)); +} + +natsMsg * +microRequest_GetMsg(microRequest *req) +{ + return (req != NULL) ? req->message : NULL; +} + +const char * +microRequest_GetReply(microRequest *req) +{ + return natsMsg_GetReply(microRequest_GetMsg(req)); +} + +uint64_t microRequest_GetSequence(microRequest *req) +{ + return natsMsg_GetSequence(microRequest_GetMsg(req)); +} + +const char *microRequest_GetSubject(microRequest *req) +{ + return natsMsg_GetSubject(microRequest_GetMsg(req)); +} + +void *microRequest_GetServiceState(microRequest *req) +{ + if ((req == NULL) || (req->service == NULL) || (req->service->cfg == NULL)) + { + return NULL; + } + return req->service->cfg->state; +} + +void *microRequest_GetEndpointState(microRequest *req) +{ + if ((req == NULL) || (req->endpoint == NULL) || (req->endpoint->config == NULL)) + { + return NULL; + } + return req->endpoint->config->state; +} + +int64_t microRequest_GetTime(microRequest *req) +{ + return natsMsg_GetTime(microRequest_GetMsg(req)); +} + +microError * +microRequest_SetHeader(microRequest *req, const char *key, const char *value) +{ + return micro_ErrorFromStatus( + natsMsgHeader_Set(microRequest_GetMsg(req), key, value)); +} + +microService * +microRequest_GetService(microRequest *req) +{ + return (req != NULL) ? req->service : NULL; +} + + +void micro_destroy_request(microRequest *req) { NATS_FREE(req); } -natsStatus -micro_new_request(microRequest **new_request, natsMsg *msg) +microError * +micro_new_request(microRequest **new_request, microService *m, microEndpoint *ep, natsMsg *msg) { - natsStatus s = NATS_OK; microRequest *req = NULL; + // endpoint is optional, service and message references are required. + if ((new_request == NULL) || (m == NULL) || (msg == NULL)) + return micro_ErrorInvalidArg; + req = (microRequest *)NATS_CALLOC(1, sizeof(microRequest)); - s = (req != NULL) ? NATS_OK : nats_setDefaultError(NATS_NO_MEMORY); - if (s == NATS_OK) - { - req->msg = msg; - *new_request = req; - return NATS_OK; - } + if (req == NULL) + return micro_ErrorOutOfMemory; - micro_free_request(req); - return NATS_UPDATE_ERR_STACK(s); + req->message = msg; + req->service = m; + req->endpoint = ep; + *new_request = req; + return NULL; } diff --git a/src/microp.h b/src/microp.h index c32b8e4b5..d92b4b52e 100644 --- a/src/microp.h +++ b/src/microp.h @@ -21,6 +21,10 @@ #define MICRO_DEFAULT_ENDPOINT_NAME "default" +// 4 verbs: INFO, STATS, PING, SCHEMA; +// 3 subjects for each verb. +#define MICRO_MONITORING_SUBS_CAP (4 * 3) + struct micro_error_s { natsStatus status; @@ -59,7 +63,7 @@ struct micro_service_s // these are set at initialization time time and do not change. natsConnection *nc; struct microServiceConfig *cfg; - char *id; + char id[NUID_BUFFER_LEN + 1]; // these are are updated concurrently with access as the service runs, so // need to be protected by mutex. @@ -69,35 +73,53 @@ struct micro_service_s int endpoints_len; int endpoints_cap; + natsSubscription **monitoring_subs; + int monitoring_subs_len; + + natsConnectionHandler prev_on_connection_closed; + void * prev_on_connection_closed_closure; + natsConnectionHandler prev_on_connection_disconnected; + void * prev_on_connection_disconnected_closure; + natsErrHandler prev_on_error; + void * prev_on_error_closure; + int64_t started; // UTC time expressed as number of nanoseconds since epoch. bool stopped; + bool is_stopping; int refs; }; -struct micro_request_s -{ - natsMsg *msg; - struct micro_endpoint_s *ep; - void *closure; -}; - extern microError *micro_ErrorOutOfMemory; extern microError *micro_ErrorInvalidArg; -#define MICRO_CALLOC(__ptr, __count, __size) \ - (__ptr = NATS_CALLOC((__count), (__size)), (__ptr) != NULL ? NULL : micro_ErrorOutOfMemory) - -natsStatus micro_clone_endpoint_config(microEndpointConfig **new_cfg, microEndpointConfig *cfg); -natsStatus micro_clone_service_config(microServiceConfig **new_cfg, microServiceConfig *cfg); -void micro_free_cloned_endpoint_config(microEndpointConfig *cfg); -void micro_free_cloned_service_config(microServiceConfig *cfg); -void micro_free_request(microRequest *req); -natsStatus micro_init_monitoring(microService *m); -natsStatus micro_new_endpoint(microEndpoint **new_endpoint, microService *m, microEndpointConfig *cfg); -natsStatus micro_new_request(microRequest **new_request, natsMsg *msg); -natsStatus micro_start_endpoint(microEndpoint *ep); -natsStatus micro_stop_endpoint(microEndpoint *ep); -natsStatus micro_stop_and_destroy_endpoint(microEndpoint *ep); +microError *micro_clone_endpoint_config(microEndpointConfig **new_cfg, microEndpointConfig *cfg); +microError *micro_clone_service_config(microServiceConfig **new_cfg, microServiceConfig *cfg); +void micro_destroy_cloned_endpoint_config(microEndpointConfig *cfg); +void micro_destroy_cloned_service_config(microServiceConfig *cfg); +void micro_destroy_request(microRequest *req); +microError *micro_init_monitoring(microService *m); +bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject); +microError *micro_new_endpoint(microEndpoint **new_endpoint, microService *m, microEndpointConfig *cfg); +microError *micro_new_request(microRequest **new_request, microService *m, microEndpoint *ep, natsMsg *msg); +microError *micro_start_endpoint(microEndpoint *ep); +microError *micro_stop_endpoint(microEndpoint *ep); +microError *micro_stop_and_destroy_endpoint(microEndpoint *ep); void micro_update_last_error(microEndpoint *ep, microError *err); +bool micro_service_is_stopping(microService *m); + + +static inline microError *micro_strdup(char **ptr, const char *str) +{ + // Make a strdup(NULL) be a no-op, so we don't have to check for NULL + // everywhere. + if (str == NULL) { + *ptr = NULL; + return NULL; + } + *ptr = NATS_STRDUP(str); + if (*ptr == NULL) + return micro_ErrorOutOfMemory; + return NULL; +} #endif /* MICROP_H_ */ diff --git a/src/nats.c b/src/nats.c index fcaaf0e14..c13726a2b 100644 --- a/src/nats.c +++ b/src/nats.c @@ -726,11 +726,14 @@ _timerThread(void *arg) static void _asyncCbsThread(void *arg) { - natsLibAsyncCbs *asyncCbs = &(gLib.asyncCbs); - natsAsyncCbInfo *cb = NULL; - natsConnection *nc = NULL; + natsLibAsyncCbs *asyncCbs = &(gLib.asyncCbs); + natsAsyncCbInfo *cb = NULL; + natsConnection *nc = NULL; + natsConnectionHandler cbHandler = NULL; + natsErrHandler errHandler = NULL; + void *cbClosure = NULL; #if defined(NATS_HAS_STREAMING) - stanConnection *sc = NULL; + stanConnection *sc = NULL; #endif WAIT_LIB_INITIALIZED; @@ -760,42 +763,65 @@ _asyncCbsThread(void *arg) sc = cb->sc; #endif + // callback handlers can be updated on a live connection, so we need to + // lock. + cbHandler = NULL; + errHandler = NULL; + cbClosure = NULL; + +#define __set_handler(_h, _cb, _cl) \ + { \ + natsMutex_Lock(nc->mu); \ + _h = nc->opts->_cb; \ + cbClosure = nc->opts->_cl; \ + natsMutex_Unlock(nc->mu); \ + } + switch (cb->type) { case ASYNC_CLOSED: - (*(nc->opts->closedCb))(nc, nc->opts->closedCbClosure); - break; + __set_handler(cbHandler, closedCb, closedCbClosure); + break; case ASYNC_DISCONNECTED: - (*(nc->opts->disconnectedCb))(nc, nc->opts->disconnectedCbClosure); + __set_handler(cbHandler, disconnectedCb, disconnectedCbClosure); break; case ASYNC_RECONNECTED: - (*(nc->opts->reconnectedCb))(nc, nc->opts->reconnectedCbClosure); + __set_handler(cbHandler, reconnectedCb, reconnectedCbClosure); break; case ASYNC_CONNECTED: - (*(nc->opts->connectedCb))(nc, nc->opts->connectedCbClosure); + __set_handler(cbHandler, connectedCb, connectedCbClosure); break; case ASYNC_DISCOVERED_SERVERS: - (*(nc->opts->discoveredServersCb))(nc, nc->opts->discoveredServersClosure); + __set_handler(cbHandler, discoveredServersCb, discoveredServersClosure); break; case ASYNC_LAME_DUCK_MODE: - (*(nc->opts->lameDuckCb))(nc, nc->opts->lameDuckClosure); + __set_handler(cbHandler, lameDuckCb, lameDuckClosure); break; case ASYNC_ERROR: - { - if (cb->errTxt != NULL) - nats_setErrStatusAndTxt(cb->err, cb->errTxt); - (*(nc->opts->asyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); - break; - } -#if defined(NATS_HAS_STREAMING) - case ASYNC_STAN_CONN_LOST: - (*(sc->opts->connectionLostCB))(sc, sc->connLostErrTxt, sc->opts->connectionLostCBClosure); + __set_handler(errHandler, asyncErrCb, asyncErrCbClosure); break; -#endif default: break; } + // Invoke the callback + if (cbHandler != NULL) + { + (*(cbHandler))(nc, cbClosure); + } + else if (errHandler != NULL) + { + if (cb->errTxt != NULL) + nats_setErrStatusAndTxt(cb->err, cb->errTxt); + (*(errHandler))(nc, cb->sub, cb->err, cbClosure); + } +#if defined(NATS_HAS_STREAMING) + else if (cb->type == ASYNC_STAN_CONN_LOST) + { + (*(sc->opts->connectionLostCB))(sc, sc->connLostErrTxt, sc->opts->connectionLostCBClosure); + } +#endif + natsAsyncCb_Destroy(cb); nats_clearLastError(); @@ -1969,13 +1995,13 @@ natsLib_msgDeliveryAssignWorker(natsSubscription *sub) } bool -natsLib_isLibHandlingMsgDeliveryByDefault() +natsLib_isLibHandlingMsgDeliveryByDefault(void) { return gLib.libHandlingMsgDeliveryByDefault; } int64_t -natsLib_defaultWriteDeadline() +natsLib_defaultWriteDeadline(void) { return gLib.libDefaultWriteDeadline; } diff --git a/src/util.c b/src/util.c index 350774c38..f39e4d755 100644 --- a/src/util.c +++ b/src/util.c @@ -2228,9 +2228,155 @@ nats_marshalULong(natsBuffer *buf, bool comma, const char *fieldName, uint64_t u return NATS_UPDATE_ERR_STACK(s); } +// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the +// tail of buf, omitting trailing zeros. It omits the decimal +// point too when the fraction is 0. It returns the index where the +// output bytes begin and the value v/10**prec. +static void +fmt_frac(char *buf, int w, uint64_t v, int prec, int *nw, uint64_t *nv) +{ + // Omit trailing zeros up to and including decimal point. + bool print = false; + int i; + int digit; -bool -nats_IsSubjectValid(const char *subject, bool wcAllowed) + for (i = 0; i < prec; i++) + { + digit = v % 10; + print = print || digit != 0; + if (print) + { + w--; + buf[w] = digit + '0'; + } + v /= 10; + } + if (print) + { + w--; + buf[w] = '.'; + } + *nw = w; + *nv = v; +} + +// fmtInt formats v into the tail of buf. +// It returns the index where the output begins. +static int +fmt_int(char *buf, int w, uint64_t v) +{ + if (v == 0) + { + w--; + buf[w] = '0'; + } + else + { + while (v > 0) + { + w--; + buf[w] = v % 10 + '0'; + v /= 10; + } + } + return w; +} + +natsStatus +nats_marshalDuration(natsBuffer *out_buf, bool comma, const char *field_name, int64_t d) +{ + // Largest time is 2540400h10m10.000000000s + char buf[32]; + int w = 32; + uint64_t u = d; + bool neg = d < 0; + int prec; + natsStatus s = NATS_OK; + const char *start = (comma ? ",\"" : "\""); + + if (neg) + u = -u; + + if (u < 1000000000) + { + // Special case: if duration is smaller than a second, + // use smaller units, like 1.2ms + w--; + buf[w] = 's'; + w--; + if (u == 0) + { + return natsBuf_Append(out_buf, "0s", 2); + } + else if (u < 1000) + { + // print nanoseconds + prec = 0; + buf[w] = 'n'; + } + else if (u < 1000000) + { + // print microseconds + prec = 3; + // U+00B5 'µ' micro sign == 0xC2 0xB5 (in reverse?) + buf[w] = '\xB5'; + w--; // Need room for two bytes. + buf[w] = '\xC2'; + } + else + { + // print milliseconds + prec = 6; + buf[w] = 'm'; + } + fmt_frac(buf, w, u, prec, &w, &u); + w = fmt_int(buf, w, u); + } + else + { + w--; + buf[w] = 's'; + + fmt_frac(buf, w, u, 9, &w, &u); + + // u is now integer seconds + w = fmt_int(buf, w, u % 60); + u /= 60; + + // u is now integer minutes + if (u > 0) + { + w--; + buf[w] = 'm'; + w = fmt_int(buf, w, u % 60); + u /= 60; + + // u is now integer hours + // Stop at hours because days can be different lengths. + if (u > 0) + { + w--; + buf[w] = 'h'; + w = fmt_int(buf, w, u); + } + } + } + + if (neg) + { + w--; + buf[w] = '-'; + } + + s = natsBuf_Append(out_buf, start, -1); + IFOK(s, natsBuf_Append(out_buf, field_name, -1)); + IFOK(s, natsBuf_Append(out_buf, "\":\"", -1)); + IFOK(s, natsBuf_Append(out_buf, buf + w, sizeof(buf) - w)); + IFOK(s, natsBuf_Append(out_buf, "\":\"", -1)); + return NATS_UPDATE_ERR_STACK(s); +} + +bool nats_IsSubjectValid(const char *subject, bool wcAllowed) { int i = 0; int len = 0; diff --git a/src/util.h b/src/util.h index d8a3f3b23..d2a6ce963 100644 --- a/src/util.h +++ b/src/util.h @@ -240,6 +240,9 @@ nats_marshalLong(natsBuffer *buf, bool comma, const char *fieldName, int64_t lva natsStatus nats_marshalULong(natsBuffer *buf, bool comma, const char *fieldName, uint64_t uval); +natsStatus +nats_marshalDuration(natsBuffer *out_buf, bool comma, const char *field_name, int64_t d); + bool nats_IsSubjectValid(const char *subject, bool wcAllowed); diff --git a/test/list.txt b/test/list.txt index 0429b024c..4818fa4f8 100644 --- a/test/list.txt +++ b/test/list.txt @@ -67,6 +67,7 @@ ConnectionWithNULLOptions ConnectionToWithNullURLs ConnectionStatus ConnClosedCB +SetConnClosedCB CloseDisconnectedCB ServerStopDisconnectedCB ClosedConnections @@ -147,6 +148,7 @@ AsyncSubscriptionPendingDrain SyncSubscriptionPending SyncSubscriptionPendingDrain AsyncErrHandler +SetAsyncErrHandler AsyncSubscriberStarvation AsyncSubscriberOnClose NextMsgCallOnAsyncSub @@ -257,6 +259,9 @@ KeyValueDiscardOldToNew KeyValueRePublish KeyValueMirrorDirectGet KeyValueMirrorCrossDomains +MicroMatchEndpointSubject +MicroServiceStopsOnClosedConn +MicroServiceStopsWhenServerStops StanPBufAllocator StanConnOptions StanSubOptions diff --git a/test/test.c b/test/test.c index a7cf40ed7..8bbdab807 100644 --- a/test/test.c +++ b/test/test.c @@ -38,6 +38,8 @@ #include "parser.h" #include "js.h" #include "kv.h" +#include "micro.h" +#include "microp.h" #if defined(NATS_HAS_STREAMING) #include "stan/conn.h" #include "stan/pub.h" @@ -8532,6 +8534,53 @@ test_ConnClosedCB(void) _stopServer(serverPid); } +static void +test_SetConnClosedCB(void) +{ + natsStatus s; + natsConnection *nc = NULL; + natsOptions *opts = NULL; + natsPid serverPid = NATS_INVALID_PID; + struct threadArg arg; + + s = _createDefaultThreadArgsForCbTests(&arg); + if (s == NATS_OK) + opts = _createReconnectOptions(); + + if ((opts == NULL) + || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) + { + FAIL("Unable to setup test for SetConnClosedCB!"); + } + + serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); + CHECK_SERVER_STARTED(serverPid); + + s = natsConnection_Connect(&nc, opts); + + // Set the connection closed handler in-flight + IFOK(s, natsConn_setClosedCallback(nc, _closedCb, (void*) &arg)); + if (s == NATS_OK) + natsConnection_Close(nc); + + test("Test connection closed CB invoked: "); + + natsMutex_Lock(arg.m); + s = NATS_OK; + while ((s != NATS_TIMEOUT) && !arg.closed) + s = natsCondition_TimedWait(arg.c, arg.m, 1000); + natsMutex_Unlock(arg.m); + + testCond((s == NATS_OK) && arg.closed); + + natsOptions_Destroy(opts); + natsConnection_Destroy(nc); + + _destroyDefaultThreadArgs(&arg); + + _stopServer(serverPid); +} + static void test_CloseDisconnectedCB(void) { @@ -14373,6 +14422,79 @@ test_AsyncErrHandler(void) _stopServer(serverPid); } +static void +test_SetAsyncErrHandler(void) +{ + natsStatus s; + natsConnection *nc = NULL; + natsOptions *opts = NULL; + natsSubscription *sub = NULL; + natsPid serverPid = NATS_INVALID_PID; + struct threadArg arg; + + s = _createDefaultThreadArgsForCbTests(&arg); + if (s != NATS_OK) + FAIL("Unable to setup test!"); + + arg.status = NATS_OK; + arg.control= 7; + + s = natsOptions_Create(&opts); + IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); + IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 10)); + + if (s != NATS_OK) + FAIL("Unable to create options for test AsyncErrHandler"); + + serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); + CHECK_SERVER_STARTED(serverPid); + + s = natsConnection_Connect(&nc, opts); + IFOK(s, natsConnection_Subscribe(&sub, nc, "async_test", _recvTestString, (void*) &arg)); + + natsMutex_Lock(arg.m); + arg.sub = sub; + natsMutex_Unlock(arg.m); + + // Start sending messages + for (int i=0; + (s == NATS_OK) && (i < (opts->maxPendingMsgs)); i++) + { + s = natsConnection_PublishString(nc, "async_test", "hello"); + } + + // Set the error handler in-flight + IFOK(s, natsConn_setErrorCallback(nc, _asyncErrCb, (void*) &arg)); + + for (int i=0; + (s == NATS_OK) && (i < 100); i++) + { + s = natsConnection_PublishString(nc, "async_test", "hello"); + } + IFOK(s, natsConnection_Flush(nc)); + + // Wait for async err callback + natsMutex_Lock(arg.m); + while ((s != NATS_TIMEOUT) && !arg.done) + s = natsCondition_TimedWait(arg.c, arg.m, 2000); + natsMutex_Unlock(arg.m); + + test("Aync fired properly, and all checks are good: "); + testCond((s == NATS_OK) + && arg.done + && arg.closed + && (arg.status == NATS_OK)); + + natsOptions_Destroy(opts); + natsSubscription_Destroy(sub); + natsConnection_Destroy(nc); + + _destroyDefaultThreadArgs(&arg); + + _stopServer(serverPid); +} + + static void _responseCb(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { @@ -32058,6 +32180,241 @@ test_KeyValueMirrorCrossDomains(void) remove(lconfFile); } +static void +test_MicroMatchEndpointSubject(void) +{ + // endpoint, actual, match + const char *test_cases[] = { + "foo", "foo", "true", + "foo.bar.meh", "foo.bar.meh", "true", + "foo.bar.meh", "foo.bar", NULL, + "foo.bar", "foo.bar.meh", NULL, + "foo.*.meh", "foo.bar.meh", "true", + "*.bar.meh", "foo.bar.meh", "true", + "foo.bar.*", "foo.bar.meh", "true", + "foo.bar.>", "foo.bar.meh", "true", + "foo.>", "foo.bar.meh", "true", + "foo.b*ar.meh", "foo.bar.meh", NULL, + }; + char buf[1024]; + int i; + const char *ep_subject; + const char *actual_subject; + bool expected_match; + + for (i=0; i<(int) (sizeof(test_cases)/sizeof(const char*)); i=i+3) + { + ep_subject = test_cases[i]; + actual_subject = test_cases[i+1]; + expected_match = (test_cases[i+2] != NULL); + + snprintf(buf, sizeof(buf), "endpoint subject '%s', actual subject: '%s': ", ep_subject, actual_subject); + test(buf) + testCond(micro_match_endpoint_subject(ep_subject, actual_subject) == expected_match); + } +} + +static void +test_MicroServiceStopsOnClosedConn(void) +{ + natsStatus s; + natsConnection *nc = NULL; + natsOptions *opts = NULL; + natsPid serverPid = NATS_INVALID_PID; + struct threadArg arg; + microService *m = NULL; + microServiceConfig cfg = { + .name = "test", + .version = "1.0.0", + }; + natsMsg *reply = NULL; + + s = _createDefaultThreadArgsForCbTests(&arg); + if (s == NATS_OK) + opts = _createReconnectOptions(); + + if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) + { + FAIL("Unable to setup test for MicroConnectionEvents!"); + } + + serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); + CHECK_SERVER_STARTED(serverPid); + + test("Connect for microservice: "); + testCond(NATS_OK == natsConnection_Connect(&nc, opts)); + + test("Start microservice: "); + testCond(NULL == micro_AddService(&m, nc, &cfg)); + + test("Test microservice is running: "); + testCond(!microService_IsStopped(m)) + + test("Test microservice is responding to PING: "); + testCond(NATS_OK == natsConnection_RequestString(&reply, nc, "$SRV.PING.test", "", 500)); + natsMsg_Destroy(reply); + reply = NULL; + + test("Stop microservice: "); + testCond(NULL == microService_Stop(m)) + + test("Test microservice is not running: "); + testCond(microService_IsStopped(m)) + + test("Test microservice is not responding to PING: "); + testCond(NATS_OK != natsConnection_RequestString(&reply, nc, "$SRV.PING.test", "", 500)); + + test("Destroy microservice (cleanup): "); + testCond(NULL == microService_Destroy(m)) + + test("Start microservice again: "); + testCond(NULL == micro_AddService(&m, nc, &cfg)); + + // void *p; + // natsHashIter iter; + // test("Check subs count AFTER START: "); + // natsMutex_Lock(nc->subsMu); + // printf("<>/<> actual count: %d\n", natsHash_Count(nc->subs)); + // p = NULL; + // natsHashIter_Init(&iter, nc->subs); + // while (natsHashIter_Next(&iter, NULL, &p)) + // { + // printf("<>/<> subject: '%s'\n", ((natsSubscription *)p)->subject); + // } + // natsHashIter_Done(&iter); + // natsMutex_Unlock(nc->subsMu); + + test("Close the connection: "); + testCond(NATS_OK == natsConnection_Drain(nc)); + natsConnection_Close(nc); + + while (micro_service_is_stopping(m)) + { + nats_Sleep(1); + } + test("Test microservice is stopped: "); + testCond(microService_IsStopped(m)); + + + microService_Destroy(m); + natsOptions_Destroy(opts); + natsConnection_Destroy(nc); + _destroyDefaultThreadArgs(&arg); + _stopServer(serverPid); +} + +static void +test_MicroServiceStopsWhenServerStops(void) +{ + natsStatus s; + natsConnection *nc = NULL; + natsOptions *opts = NULL; + natsPid serverPid = NATS_INVALID_PID; + struct threadArg arg; + microService *m = NULL; + microServiceConfig cfg = { + .name = "test", + .version = "1.0.0", + }; + + s = _createDefaultThreadArgsForCbTests(&arg); + if (s == NATS_OK) + opts = _createReconnectOptions(); + + if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) + { + FAIL("Unable to setup test for MicroConnectionEvents!"); + } + + serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); + CHECK_SERVER_STARTED(serverPid); + + test("Connect for microservice: "); + testCond(NATS_OK == natsConnection_Connect(&nc, opts)); + + test("Start microservice: "); + testCond(NULL == micro_AddService(&m, nc, &cfg)); + + test("Test microservice is running: "); + testCond(!microService_IsStopped(m)) + + test("Stop the server: "); + testCond((_stopServer(serverPid), true)); + + test("Test microservice is not running: "); + testCond(microService_IsStopped(m)) + + microService_Destroy(m); + natsOptions_Destroy(opts); + natsConnection_Destroy(nc); + _destroyDefaultThreadArgs(&arg); +} + +// static void +// test_AsyncErrHandler(void) +// { +// natsStatus s; +// natsConnection *nc = NULL; +// natsOptions *opts = NULL; +// natsSubscription *sub = NULL; +// natsPid serverPid = NATS_INVALID_PID; +// struct threadArg arg; + +// s = _createDefaultThreadArgsForCbTests(&arg); +// if (s != NATS_OK) +// FAIL("Unable to setup test!"); + +// arg.status = NATS_OK; +// arg.control= 7; + +// s = natsOptions_Create(&opts); +// IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); +// IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 10)); +// IFOK(s, natsOptions_SetErrorHandler(opts, _asyncErrCb, (void*) &arg)); + +// if (s != NATS_OK) +// FAIL("Unable to create options for test AsyncErrHandler"); + +// serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); +// CHECK_SERVER_STARTED(serverPid); + +// s = natsConnection_Connect(&nc, opts); +// IFOK(s, natsConnection_Subscribe(&sub, nc, "async_test", _recvTestString, (void*) &arg)); + +// natsMutex_Lock(arg.m); +// arg.sub = sub; +// natsMutex_Unlock(arg.m); + +// for (int i=0; +// (s == NATS_OK) && (i < (opts->maxPendingMsgs + 100)); i++) +// { +// s = natsConnection_PublishString(nc, "async_test", "hello"); +// } +// IFOK(s, natsConnection_Flush(nc)); + +// // Wait for async err callback +// natsMutex_Lock(arg.m); +// while ((s != NATS_TIMEOUT) && !arg.done) +// s = natsCondition_TimedWait(arg.c, arg.m, 2000); +// natsMutex_Unlock(arg.m); + +// test("Aync fired properly, and all checks are good: "); +// testCond((s == NATS_OK) +// && arg.done +// && arg.closed +// && (arg.status == NATS_OK)); + +// natsOptions_Destroy(opts); +// natsSubscription_Destroy(sub); +// natsConnection_Destroy(nc); + +// _destroyDefaultThreadArgs(&arg); + +// _stopServer(serverPid); +// } + + + #if defined(NATS_HAS_STREAMING) static int @@ -34358,6 +34715,7 @@ static testInfo allTests[] = {"ConnectionToWithNullURLs", test_ConnectionToWithNullURLs}, {"ConnectionStatus", test_ConnectionStatus}, {"ConnClosedCB", test_ConnClosedCB}, + {"SetConnClosedCB", test_SetConnClosedCB}, {"CloseDisconnectedCB", test_CloseDisconnectedCB}, {"ServerStopDisconnectedCB", test_ServerStopDisconnectedCB}, {"ClosedConnections", test_ClosedConnections}, @@ -34440,6 +34798,7 @@ static testInfo allTests[] = {"SyncSubscriptionPending", test_SyncSubscriptionPending}, {"SyncSubscriptionPendingDrain", test_SyncSubscriptionPendingDrain}, {"AsyncErrHandler", test_AsyncErrHandler}, + {"SetAsyncErrHandler", test_SetAsyncErrHandler}, {"AsyncSubscriberStarvation", test_AsyncSubscriberStarvation}, {"AsyncSubscriberOnClose", test_AsyncSubscriberOnClose}, {"NextMsgCallOnAsyncSub", test_NextMsgCallOnAsyncSub}, @@ -34556,6 +34915,9 @@ static testInfo allTests[] = {"KeyValueMirrorDirectGet", test_KeyValueMirrorDirectGet}, {"KeyValueMirrorCrossDomains", test_KeyValueMirrorCrossDomains}, + {"MicroMatchEndpointSubject", test_MicroMatchEndpointSubject}, + {"MicroServiceStopsOnClosedConn", test_MicroServiceStopsOnClosedConn}, + {"MicroServiceStopsWhenServerStops", test_MicroServiceStopsWhenServerStops}, #if defined(NATS_HAS_STREAMING) {"StanPBufAllocator", test_StanPBufAllocator}, {"StanConnOptions", test_StanConnOptions}, From 63ede6a31da9b4e7f573e03f745bfa3bc0608017 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 17 Apr 2023 08:20:36 -0700 Subject: [PATCH 11/85] simplified examples --- examples/micro-arithmetics.c | 138 ++++++++++ examples/micro-func.c | 211 ++++++++++++++++ examples/micro-hello.c | 89 +++++++ examples/micro-multi.c | 477 ----------------------------------- examples/micro-sequence.c | 177 +++++++++++++ src/micro.h | 6 - 6 files changed, 615 insertions(+), 483 deletions(-) create mode 100644 examples/micro-arithmetics.c create mode 100644 examples/micro-func.c create mode 100644 examples/micro-hello.c delete mode 100644 examples/micro-multi.c create mode 100644 examples/micro-sequence.c diff --git a/examples/micro-arithmetics.c b/examples/micro-arithmetics.c new file mode 100644 index 000000000..009e8f56c --- /dev/null +++ b/examples/micro-arithmetics.c @@ -0,0 +1,138 @@ +// Copyright 2021 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "examples.h" + +// Sequence NATS microservice example. +// +// This example illustrated multiple NATS microservices communicating with each +// other. Please see the main microservice, micro-sequence.c for a more detailed +// explanation. +// +// This specific microservice implements add, multiply, and divide operations. + +// arithmeticsOp is a type for a C function that implements am operation: add, +// multiply, divide. +typedef microError *(*arithmeticsOP)(long double *result, long double a1, long double a2); + +// handle_arithmetics_op is a helper function that wraps an implementation of an +// operation into a request handler. +static void +handle_arithmetics_op(microRequest *req, arithmeticsOP op) +{ + microError *err = NULL; + microArgs *args = NULL; + long double a1, a2, result; + char buf[1024]; + int len = 0; + + MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); + if ((err == NULL) && (microArgs_Count(args) != 2)) + { + err = micro_Errorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args)); + } + + MICRO_CALL(err, microArgs_GetFloat(&a1, args, 0)); + MICRO_CALL(err, microArgs_GetFloat(&a2, args, 1)); + MICRO_CALL(err, op(&result, a1, a2)); + MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf", result)); + + microRequest_Respond(req, &err, buf, len); + microArgs_Destroy(args); +} + +static microError * +add(long double *result, long double a1, long double a2) +{ + *result = a1 + a2; + return NULL; +} + +static microError * +divide(long double *result, long double a1, long double a2) +{ + *result = a1 / a2; + return NULL; +} + +static microError *multiply(long double *result, long double a1, long double a2) +{ + *result = a1 * a2; + return NULL; +} + +// request handlers for each operation. +static void handle_add(microRequest *req) { handle_arithmetics_op(req, add); } +static void handle_divide(microRequest *req) { handle_arithmetics_op(req, divide); } +static void handle_multiply(microRequest *req) { handle_arithmetics_op(req, multiply); } + +// main is the main entry point for the microservice. +int main(int argc, char **argv) +{ + natsStatus s = NATS_OK; + microError *err = NULL; + natsConnection *conn = NULL; + natsOptions *opts = NULL; + microService *m = NULL; + char errorbuf[1024]; + + microServiceConfig cfg = { + .description = "Arithmetic operations - NATS microservice example in C", + .name = "c-arithmetics", + .version = "1.0.0", + }; + microEndpointConfig add_cfg = { + .name = "add", + .handler = handle_add, + }; + microEndpointConfig divide_cfg = { + .name = "divide", + .handler = handle_divide, + }; + microEndpointConfig multiply_cfg = { + .name = "multiply", + .handler = handle_multiply, + }; + + // Connect to NATS server + opts = parseArgs(argc, argv, ""); + s = natsConnection_Connect(&conn, opts); + if (s != NATS_OK) + { + printf("Error: %u - %s\n", s, natsStatus_GetText(s)); + nats_PrintLastErrorStack(stderr); + return 1; + } + + // Create the Microservice that listens on nc. + MICRO_CALL(err, micro_AddService(&m, conn, &cfg)); + + // Add the endpoints for the functions. + MICRO_CALL(err, microService_AddEndpoint(NULL, m, &add_cfg)); + MICRO_CALL(err, microService_AddEndpoint(NULL, m, &multiply_cfg)); + MICRO_CALL(err, microService_AddEndpoint(NULL, m, ÷_cfg)); + + // Run the service, until stopped. + MICRO_CALL(err, microService_Run(m)); + + // Cleanup. + microService_Destroy(m); + if (err != NULL) + { + printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); + microError_Destroy(err); + return 1; + } + return 0; +} + diff --git a/examples/micro-func.c b/examples/micro-func.c new file mode 100644 index 000000000..559a60bf3 --- /dev/null +++ b/examples/micro-func.c @@ -0,0 +1,211 @@ +// Copyright 2021 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "examples.h" + +// Sequence NATS microservice example. +// +// This example illustrated multiple NATS microservices communicating with each +// other. Please see the main microservice, micro-sequence.c for a more detailed +// explanation. +// +// This specific microservice implements factorial, fibonacci, and power2 +// functions. Instead of performing arithmetic operations locally, we call the +// arithmetics microservice to perform the operations. + +// functionHandler is a type for a C function that implements a "function", i.e. +// power2, factorial, etc. +typedef microError *(*functionHandler)(long double *result, natsConnection *conn, int n); + +// callArithmetics is a helper function that calls the arithmetics microservice. +static microError * +call_arithmetics(long double *result, natsConnection *nc, const char *subject, long double a1, long double a2) +{ + microError *err = NULL; + microClient *client = NULL; + natsMsg *response = NULL; + microArgs *args = NULL; + char buf[1024]; + int len; + + MICRO_CALL(err, micro_NewClient(&client, nc, NULL)); + MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf %Lf", a1, a2)); + MICRO_CALL(err, microClient_DoRequest(&response, client, subject, buf, len)); + MICRO_CALL(err, micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response))); + MICRO_CALL(err, microArgs_GetFloat(result, args, 0)); + + microClient_Destroy(client); + natsMsg_Destroy(response); + return err; +} + +// factorial implements the factorial(N) function. Calls the arithmetics service +// for all multiplications. +static microError * +factorial(long double *result, natsConnection *nc, int n) +{ + microError *err = NULL; + int i; + + if (n < 1) + err = micro_Errorf(400, "n=%d. must be greater than 0", n); + + *result = 1; + for (i = 1; i <= n; i++) + { + err = call_arithmetics(result, nc, "multiply", *result, i); + if (err != NULL) + return err; + } + return NULL; +} + +// fibonacci implements the fibonacci(N) function. Calls the arithmetics service +// for all additions. +static microError * +fibonacci(long double *result, natsConnection *nc, int n) +{ + microError *err = NULL; + int i; + long double n1, n2; + + if (n < 0) + err = micro_Errorf(400, "n=%d. must be non-negative", n); + + if (n < 2) + { + *result = n; + return NULL; + } + + for (i = 1, n1 = 0, n2 = 1; i <= n; i++) + { + err = call_arithmetics(result, nc, "add", n1, n2); + if (err != NULL) + return err; + n1 = n2; + n2 = *result; + } + return NULL; +} + +// power2 implements the 2**N function. Calls the arithmetics service +// for all multiplications. +static microError *power2(long double *result, natsConnection *nc, int n) +{ + microError *err = NULL; + int i; + + if (n < 1) + return micro_Errorf(400, "n=%d. must be greater than 0", n); + + *result = 1; + for (i = 1; i <= n; i++) + { + err = call_arithmetics(result, nc, "multiply", *result, 2); + if (err != NULL) + return err; + } + return NULL; +} + +// handle_function_op is a helper function that wraps an implementation function +// like factorial, fibonacci, etc. into a request handler. +static void +handle_function_op(microRequest *req, functionHandler op) +{ + microError *err = NULL; + microArgs *args = NULL; + int n; + long double result; + char buf[1024]; + int len = 0; + + MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); + if ((err == NULL) && (microArgs_Count(args) != 1)) + { + err = micro_Errorf(400, "Invalid number of arguments, expected 1 got %d", microArgs_Count(args)); + } + + MICRO_CALL(err, microArgs_GetInt(&n, args, 0)); + MICRO_CALL(err, op(&result, microRequest_GetConnection(req), n)); + MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf", result)); + + microRequest_Respond(req, &err, buf, len); + microArgs_Destroy(args); +} + +// handle_... are the request handlers for each function. +static void handle_factorial(microRequest *req) { handle_function_op(req, factorial); } +static void handle_fibonacci(microRequest *req) { handle_function_op(req, fibonacci); } +static void handle_power2(microRequest *req) { handle_function_op(req, power2); } + +// main is the main entry point for the microservice. +int main(int argc, char **argv) +{ + natsStatus s = NATS_OK; + microError *err = NULL; + natsConnection *conn = NULL; + microService *m = NULL; + natsOptions *opts = NULL; + char errorbuf[1024]; + + microServiceConfig cfg = { + .description = "Functions - NATS microservice example in C", + .name = "c-functions", + .version = "1.0.0", + }; + microEndpointConfig factorial_cfg = { + .name = "factorial", + .handler = handle_factorial, + }; + microEndpointConfig fibonacci_cfg = { + .name = "fibonacci", + .handler = handle_fibonacci, + }; + microEndpointConfig power2_cfg = { + .name = "power2", + .handler = handle_power2, + }; + + // Connect to NATS server + opts = parseArgs(argc, argv, ""); + s = natsConnection_Connect(&conn, opts); + if (s != NATS_OK) + { + printf("Error: %u - %s\n", s, natsStatus_GetText(s)); + nats_PrintLastErrorStack(stderr); + return 1; + } + + // Create the Microservice that listens on nc. + MICRO_CALL(err, micro_AddService(&m, conn, &cfg)); + + // Add the endpoints for the functions. + MICRO_CALL(err, microService_AddEndpoint(NULL, m, &factorial_cfg)); + MICRO_CALL(err, microService_AddEndpoint(NULL, m, &fibonacci_cfg)); + MICRO_CALL(err, microService_AddEndpoint(NULL, m, &power2_cfg)); + + // Run the service, until stopped. + MICRO_CALL(err, microService_Run(m)); + + // Cleanup. + microService_Destroy(m); + if (err != NULL) + { + printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); + microError_Destroy(err); + return 1; + } + return 0; +} diff --git a/examples/micro-hello.c b/examples/micro-hello.c new file mode 100644 index 000000000..1b0d55a7b --- /dev/null +++ b/examples/micro-hello.c @@ -0,0 +1,89 @@ +// Copyright 2021 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "examples.h" + +// Hello World! NATS microservice example. +// +// Requires NATS server and CLI, and the nats.c examples fully built. See +// https://github.com/nats-io/nats.c#building +// +// RUN: +// ```sh +// $NATS_SERVER & # NATS_SERVER points to the NATS server binary +// nats_pid=$! +// sleep 2 # wait for server to start +// ./examples/nats-micro-hello & +// hello_pid=$! +// sleep 2 # wait for microservice to start +// nats request 'hello' '' +// kill $hello_pid $nats_pid +// ``` +// +// OUTPUT: +// ``` +// 06:34:57 Sending request on "hello" +// 06:34:57 Received with rtt 1.08ms +// Hello, World! +// ``` + +#define HELLO "Hello, World!" + +static void +handle(microRequest *req) +{ + microRequest_Respond(req, NULL, HELLO, sizeof(HELLO)); +} + +int main(int argc, char **argv) +{ + natsStatus s = NATS_OK; + microError *err = NULL; + natsConnection *conn = NULL; + natsOptions *opts = NULL; + microService *m = NULL; + char errorbuf[1024]; + + microEndpointConfig hello_cfg = { + .name = "hello", + .handler = handle, + }; + microServiceConfig cfg = { + .description = "Hello World! - NATS microservice example in C", + .name = "c-hello", + .version = "1.0.0", + .endpoint = &hello_cfg, + }; + + // Connect and start the services + opts = parseArgs(argc, argv, ""); + s = natsConnection_Connect(&conn, opts); + if (s != NATS_OK) + { + printf("Error: %u - %s\n", s, natsStatus_GetText(s)); + nats_PrintLastErrorStack(stderr); + return 1; + } + MICRO_CALL(err, micro_AddService(&m, conn, &cfg)); + MICRO_CALL(err, microService_Run(m)); + + microService_Destroy(m); + if (err != NULL) + { + printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); + microError_Destroy(err); + return 1; + } + return 0; +} + diff --git a/examples/micro-multi.c b/examples/micro-multi.c deleted file mode 100644 index 8966bd701..000000000 --- a/examples/micro-multi.c +++ /dev/null @@ -1,477 +0,0 @@ -// Copyright 2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifdef _WIN32 -#error "This example does not work on Windows" -#endif - -#include -#include - -#include "examples.h" - -static int fakeClosure = 0; - -// The "main" functions for the services, each is run as a pthread. -static void *run_arithmetics(void *closure); -static void *run_functions(void *closure); -static void *run_sequence(void *closure); - -// a generic service runner, used by the services' "main" functions. -static void *run_service(natsConnection *conn, microServiceConfig *svc, - microEndpointConfig **endpoints, int len_endpoints); - -// Callers and handlers for operations (2 floating point args, and a single int arg). -static microError * -call_arithmetics(long double *result, natsConnection *nc, const char *subject, long double a1, long double a2); -typedef microError *(*arithmeticsOP)( - long double *result, natsConnection *conn, long double a1, long double a2); -static void handle_arithmetics_op(microRequest *req, arithmeticsOP op); - -static microError * -call_function(long double *result, natsConnection *nc, const char *subject, int n); -typedef microError *(*functionOP)(long double *result, natsConnection *conn, int n); -static void handle_function_op(microRequest *req, functionOP op); - -// Stop handler is the same for all services. -static void handle_stop( microRequest *req); - -// Handler for "sequence", the main endpoint of the sequence service. -static void handle_sequence( microRequest *req); - -// Math operations, wrapped as handlers. -static microError *add(long double *result, natsConnection *nc, long double a1, long double a2); -static microError *divide(long double *result, natsConnection *nc, long double a1, long double a2); -static microError *multiply(long double *result, natsConnection *nc, long double a1, long double a2); - -static void handle_add( microRequest *req) { handle_arithmetics_op(req, add); } -static void handle_divide( microRequest *req) { handle_arithmetics_op(req, divide); } -static void handle_multiply(microRequest *req) { handle_arithmetics_op(req, multiply); } - -static microError *factorial(long double *result, natsConnection *nc, int n); -static microError *fibonacci(long double *result, natsConnection *nc, int n); -static microError *power2(long double *result, natsConnection *nc, int n); -static void handle_factorial( microRequest *req) { handle_function_op(req, factorial); } -static void handle_fibonacci( microRequest *req) { handle_function_op(req, fibonacci); } -static void handle_power2(microRequest *req) { handle_function_op(req, power2); } - -static microEndpointConfig stop_cfg = { - .name = "stop", - .handler = handle_stop, - .state = &fakeClosure, - .schema = NULL, -}; - -int main(int argc, char **argv) -{ - natsStatus s = NATS_OK; - microError *err = NULL; - natsConnection *conn = NULL; - natsOptions *opts = NULL; - pthread_t arithmetics_th; - void *a_val = NULL; - pthread_t functions_th; - void *f_val = NULL; - pthread_t sequence_th; - void *s_val = NULL; - int errno; - char errorbuf[1024]; - - // Connect and start the services - opts = parseArgs(argc, argv, ""); - s = natsConnection_Connect(&conn, opts); - if (s == NATS_OK) - { - errno = pthread_create(&arithmetics_th, NULL, run_arithmetics, conn); - if (errno != 0) - { - printf("Error creating arithmetics thread: %d: %s\n", errno, strerror(errno)); - return 1; - } - - errno = pthread_create(&functions_th, NULL, run_functions, conn); - if (errno != 0) - { - printf("Error creating functions thread: %d: %s\n", errno, strerror(errno)); - return 1; - } - - errno = pthread_create(&sequence_th, NULL, run_sequence, conn); - if (errno != 0) - { - printf("Error creating sequence thread: %d: %s\n", errno, strerror(errno)); - return 1; - } - } - - // Wait for the services to stop and self-destruct. - if (s == NATS_OK) - { - MICRO_DO(err, - { - if (pthread_join(arithmetics_th, &a_val) == 0) - err = (microError *)a_val; - }); - MICRO_DO(err, - { - if (pthread_join(functions_th, &f_val) == 0) - err = (microError *)f_val; - }); - MICRO_DO(err, - { - if (pthread_join(sequence_th, &s_val) == 0) - err = (microError *)s_val; - }); - } - - if (s != NATS_OK) - { - err = micro_ErrorFromStatus(s); - } - if (err != NULL) - { - printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); - return 1; - } - return 0; -} - -static void *run_sequence(void *closure) -{ - natsConnection *conn = (natsConnection *)closure; - microServiceConfig cfg = { - .description = "Sequence adder - NATS microservice example in C", - .name = "c-sequence", - .version = "1.0.0", - }; - microEndpointConfig sequence_cfg = { - .subject = "sequence", - .name = "sequence-service", - .handler = handle_sequence, - .state = &fakeClosure, - .schema = NULL, - }; - microEndpointConfig *endpoints[] = {&sequence_cfg}; - - return run_service(conn, &cfg, endpoints, 1); -} - -// calculates the sum of X/f(1) + X/f(2)... up to N (included). The inputs are X -// (float), f name (string), and N (int). E.g.: '10.0 "power2" 10' will -// calculate 10/2 + 10/4 + 10/8 + 10/16 + 10/32 + 10/64 + 10/128 + 10/256 + -// 10/512 + 10/1024 = 20.998046875 -static void handle_sequence( microRequest *req) -{ - microError *err = NULL; - natsConnection *nc = microRequest_GetConnection(req); - microArgs *args = NULL; - int n = 0; - int i; - const char *function; - long double initialValue = 1.0; - long double value = 1.0; - long double denominator = 0; - char result[64]; - int result_len = 0; - - MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); - MICRO_CALL_IF(err, (microArgs_Count(args) != 2), - micro_Errorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args))); - - MICRO_CALL(err, microArgs_GetString(&function, args, 0)); - MICRO_CALL_IF(err, ((strcmp(function, "factorial") != 0) && (strcmp(function, "power2") != 0) && (strcmp(function, "fibonacci") != 0)), - micro_Errorf(400, "Invalid function name '%s', must be 'factorial', 'power2', or 'fibonacci'", function)); - MICRO_CALL(err, microArgs_GetInt(&n, args, 1)); - MICRO_CALL_IF(err, - (n < 1), - micro_Errorf(400, "Invalid number of iterations %d, must be at least 1", n)); - - for (i = 1; (err == NULL) && (i <= n); i++) - { - MICRO_CALL(err, call_function(&denominator, nc, function, i)); - MICRO_CALL_IF(err, denominator == 0, - micro_Errorf(500, "division by zero at step %d", i)); - MICRO_DO(err, value = value + initialValue / denominator); - } - - MICRO_DO(err, result_len = snprintf(result, sizeof(result), "%Lf", value)); - - microRequest_Respond(req, &err, result, result_len); - microArgs_Destroy(args); -} - -static void *run_arithmetics(void *closure) -{ - natsConnection *conn = (natsConnection *)closure; - microServiceConfig cfg = { - .description = "Arithmetic operations - NATS microservice example in C", - .name = "c-arithmetics", - .version = "1.0.0", - }; - microEndpointConfig add_cfg = { - .name = "add", - .handler = handle_add, - .state = &fakeClosure, - .schema = NULL, - }; - microEndpointConfig divide_cfg = { - .name = "divide", - .handler = handle_divide, - .state = &fakeClosure, - .schema = NULL, - }; - microEndpointConfig multiply_cfg = { - .name = "multiply", - .handler = handle_multiply, - .state = &fakeClosure, - .schema = NULL, - }; - microEndpointConfig *endpoints[] = - {&add_cfg, ÷_cfg, &multiply_cfg}; - - return run_service(conn, &cfg, endpoints, 3); -} - -static void *run_functions(void *closure) -{ - natsConnection *conn = (natsConnection *)closure; - microServiceConfig cfg = { - .description = "Functions - NATS microservice example in C", - .name = "c-functions", - .version = "1.0.0", - }; - microEndpointConfig factorial_cfg = { - .name = "factorial", - .handler = handle_factorial, - .state = &fakeClosure, - .schema = NULL, - }; - microEndpointConfig fibonacci_cfg = { - .name = "fibonacci", - .handler = handle_fibonacci, - .state = &fakeClosure, - .schema = NULL, - }; - microEndpointConfig power2_cfg = { - .name = "power2", - .handler = handle_power2, - .state = &fakeClosure, - .schema = NULL, - }; - microEndpointConfig *endpoints[] = - {&factorial_cfg, &fibonacci_cfg, &power2_cfg}; - - return run_service(conn, &cfg, endpoints, 3); -} - -static void -handle_arithmetics_op(microRequest *req, arithmeticsOP op) -{ - microError *err = NULL; - microArgs *args = NULL; - long double a1, a2, result; - char buf[1024]; - int len = 0; - - MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); - MICRO_CALL_IF(err, (microArgs_Count(args) != 2), - micro_Errorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args))); - MICRO_CALL(err, microArgs_GetFloat(&a1, args, 0)); - MICRO_CALL(err, microArgs_GetFloat(&a2, args, 1)); - MICRO_CALL(err, op(&result, microRequest_GetConnection(req), a1, a2)); - MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf", result)); - - microRequest_Respond(req, &err, buf, len); - microArgs_Destroy(args); -} - -static void -handle_function_op(microRequest *req, functionOP op) -{ - microError *err = NULL; - microArgs *args = NULL; - int n; - long double result; - char buf[1024]; - int len = 0; - - MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); - MICRO_CALL_IF(err, (microArgs_Count(args) != 1), - micro_Errorf(400, "Invalid number of arguments, expected 1 got %d", microArgs_Count(args))); - MICRO_CALL(err, microArgs_GetInt(&n, args, 0)); - MICRO_CALL(err, op(&result, microRequest_GetConnection(req), n)); - MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf", result)); - - microRequest_Respond(req, &err, buf, len); - microArgs_Destroy(args); -} - -static microError * -call_arithmetics(long double *result, natsConnection *nc, const char *subject, long double a1, long double a2) -{ - microError *err = NULL; - microClient *client = NULL; - natsMsg *response = NULL; - microArgs *args = NULL; - char buf[1024]; - int len; - - MICRO_CALL(err, micro_NewClient(&client, nc, NULL)); - MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf %Lf", a1, a2)); - MICRO_CALL(err, microClient_DoRequest(&response, client, subject, buf, len)); - MICRO_CALL(err, micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response))); - MICRO_CALL(err, microArgs_GetFloat(result, args, 0)); - - microClient_Destroy(client); - natsMsg_Destroy(response); - return err; -} - -static microError * -call_function(long double *result, natsConnection *nc, const char *subject, int n) -{ - microError *err = NULL; - microClient *client = NULL; - natsMsg *response = NULL; - microArgs *args = NULL; - char buf[1024]; - int len; - - MICRO_CALL(err, micro_NewClient(&client, nc, NULL)); - MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%d", n)); - MICRO_CALL(err, microClient_DoRequest(&response, client, subject, buf, len)); - MICRO_CALL(err, micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response))); - MICRO_CALL(err, microArgs_GetFloat(result, args, 0)); - - microClient_Destroy(client); - natsMsg_Destroy(response); - return err; -} - -static void handle_stop( microRequest *req) -{ - microError *err = NULL; - const char *response = "OK"; - int len = 0; - void *ret; - - MICRO_CALL(err, microService_Stop(microRequest_GetService(req))); - MICRO_DO(err, len = strlen(response)); - - ret = (void *)(microError_Status(err)); - microRequest_Respond(req, &err, response, len); - pthread_exit(ret); -} - -static void *run_service(natsConnection *conn, microServiceConfig *svc, - microEndpointConfig **endpoints, int len_endpoints) -{ - microError *err = NULL; - microService *m = NULL; - int i; - - MICRO_CALL(err, micro_AddService(&m, conn, svc)); - for (i = 0; (err == NULL) && (i < len_endpoints); i++) - { - MICRO_CALL(err, microService_AddEndpoint(NULL, m, endpoints[i])); - } - MICRO_CALL(err, microService_AddEndpoint(NULL, m, &stop_cfg)); - MICRO_CALL(err, microService_Run(m)); - - microService_Destroy(m); - return err; -} - -static microError * -add(long double *result, natsConnection *nc, long double a1, long double a2) -{ - *result = a1 + a2; - return NULL; -} - -static microError * -divide(long double *result, natsConnection *nc, long double a1, long double a2) -{ - *result = a1 / a2; - return NULL; -} - -static microError *multiply(long double *result, natsConnection *nc, long double a1, long double a2) -{ - *result = a1 * a2; - return NULL; -} - -static microError * -factorial(long double *result, natsConnection *nc, int n) -{ - microError *err = NULL; - int i; - - if (n < 1) - err = micro_Errorf(400, "n=%d. must be greater than 0", n); - - *result = 1; - for (i = 1; i <= n; i++) - { - err = call_arithmetics(result, nc, "multiply", *result, i); - if (err != NULL) - return err; - } - return NULL; -} - -static microError * -fibonacci(long double *result, natsConnection *nc, int n) -{ - microError *err = NULL; - int i; - long double n1, n2; - - if (n < 0) - err = micro_Errorf(400, "n=%d. must be non-negative", n); - - if (n < 2) - { - *result = n; - return NULL; - } - - for (i = 1, n1 = 0, n2 = 1; i <= n; i++) - { - err = call_arithmetics(result, nc, "add", n1, n2); - if (err != NULL) - return err; - n1 = n2; - n2 = *result; - } - return NULL; -} - -static microError *power2(long double *result, natsConnection *nc, int n) -{ - microError *err = NULL; - int i; - - if (n < 1) - return micro_Errorf(400, "n=%d. must be greater than 0", n); - - *result = 1; - for (i = 1; i <= n; i++) - { - err = call_arithmetics(result, nc, "multiply", *result, 2); - if (err != NULL) - return err; - } - return NULL; -} diff --git a/examples/micro-sequence.c b/examples/micro-sequence.c new file mode 100644 index 000000000..21602a753 --- /dev/null +++ b/examples/micro-sequence.c @@ -0,0 +1,177 @@ +// Copyright 2021 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "examples.h" + +// Sequence NATS microservice example. +// +// This example illustrated multiple NATS microservices communicating with each +// other. +// +// The main service (c-sequence) calculates the sum of 1/f(1) + 1/f(2)... up to +// N (included). It exposes one (default) endpoint, "sequence". The inputs are +// f (the function name) and N. name, can be "factorial", "fibonacci", or +// "power2"). +// +// c-sequence parses the request, then calculates the sequence by calling the +// c-functions microservice to calculate f(1), f(2), etc. The c-functions +// service in turn uses c-arithmetics microservice for all arithmetic +// operations. +// +// Requires NATS server and CLI, and the nats.c examples fully built. See +// https://github.com/nats-io/nats.c#building +// +// RUN: +// ```sh +// $NATS_SERVER & # NATS_SERVER points to the NATS server binary +// nats_pid=$! +// sleep 2 # wait for server to start +// ./examples/nats-micro-sequence & +// sequence_pid=$! +// ./examples/nats-micro-func & +// func_pid=$! +// ./examples/nats-micro-arithmetics & +// arithmetics_pid=$! +// sleep 2 # wait for microservice to start +// nats request -r 'sequence' '"factorial" 10' +// nats request -r 'sequence' '"power2" 10' +// nats request -r 'sequence' '"fibonacci" 10' +// kill $sequence_pid $func_pid $arithmetics_pid $nats_pid +// ``` +// +// OUTPUT: +// ``` +// 2.718282 +// 1.999023 +// 3.341705 +// ``` + +static microError * +call_function(long double *result, natsConnection *nc, const char *subject, int n) +{ + microError *err = NULL; + microClient *client = NULL; + natsMsg *response = NULL; + microArgs *args = NULL; + char buf[1024]; + int len; + + MICRO_CALL(err, micro_NewClient(&client, nc, NULL)); + MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%d", n)); + MICRO_CALL(err, microClient_DoRequest(&response, client, subject, buf, len)); + MICRO_CALL(err, micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response))); + MICRO_CALL(err, microArgs_GetFloat(result, args, 0)); + + microClient_Destroy(client); + natsMsg_Destroy(response); + return err; +} + +// calculates the sum of X/f(1) + X/f(2)... up to N (included). The inputs are X +// (float), f name (string), and N (int). E.g.: '10.0 "power2" 10' will +// calculate 10/2 + 10/4 + 10/8 + 10/16 + 10/32 + 10/64 + 10/128 + 10/256 + +// 10/512 + 10/1024 = 20.998046875 +static void handle_sequence(microRequest *req) +{ + microError *err = NULL; + natsConnection *nc = microRequest_GetConnection(req); + microArgs *args = NULL; + int n = 0; + int i; + const char *function; + long double initialValue = 1.0; + long double value = 1.0; + long double denominator = 0; + char result[64]; + int result_len = 0; + + MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); + if ((err == NULL) && (microArgs_Count(args) != 2)) + { + err = micro_Errorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args)); + } + + MICRO_CALL(err, microArgs_GetString(&function, args, 0)); + if ((err == NULL) && + (strcmp(function, "factorial") != 0) && + (strcmp(function, "power2") != 0) && + (strcmp(function, "fibonacci") != 0)) + { + err = micro_Errorf(400, "Invalid function name '%s', must be 'factorial', 'power2', or 'fibonacci'", function); + } + + MICRO_CALL(err, microArgs_GetInt(&n, args, 1)); + if ((err == NULL) && (n < 1)) + { + err = micro_Errorf(400, "Invalid number of iterations %d, must be at least 1", n); + } + + for (i = 1; (err == NULL) && (i <= n); i++) + { + MICRO_CALL(err, call_function(&denominator, nc, function, i)); + if ((err == NULL) && (denominator == 0)) + { + err = micro_Errorf(500, "division by zero at step %d", i); + } + MICRO_DO(err, value = value + initialValue / denominator); + } + + MICRO_DO(err, result_len = snprintf(result, sizeof(result), "%Lf", value)); + + microRequest_Respond(req, &err, result, result_len); + microArgs_Destroy(args); +} + +int main(int argc, char **argv) +{ + natsStatus s = NATS_OK; + microError *err = NULL; + natsConnection *conn = NULL; + natsOptions *opts = NULL; + microService *m = NULL; + char errorbuf[1024]; + + microEndpointConfig sequence_cfg = { + .subject = "sequence", + .name = "sequence-service", + .handler = handle_sequence, + .schema = NULL, + }; + microServiceConfig cfg = { + .description = "Sequence adder - NATS microservice example in C", + .name = "c-sequence", + .version = "1.0.0", + .endpoint = &sequence_cfg, + }; + + opts = parseArgs(argc, argv, ""); + s = natsConnection_Connect(&conn, opts); + if (s != NATS_OK) + { + printf("Error: %u - %s\n", s, natsStatus_GetText(s)); + nats_PrintLastErrorStack(stderr); + return 1; + } + + MICRO_CALL(err, micro_AddService(&m, conn, &cfg)); + MICRO_CALL(err, microService_Run(m)); + + microService_Destroy(m); + if (err != NULL) + { + printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); + microError_Destroy(err); + return 1; + } + return 0; +} diff --git a/src/micro.h b/src/micro.h index 4f3f2e1c2..cad554271 100644 --- a/src/micro.h +++ b/src/micro.h @@ -38,12 +38,6 @@ __err = (__call); \ } -#define MICRO_CALL_IF(__err, __cond, __call) \ - if (((__err) == NULL) && (__cond)) \ - { \ - __err = (__call); \ - } - #define MICRO_DO(__err, __block) \ if ((__err) == NULL) \ __block; From 8e27edea29ad4d5d9225dcb08e5fc366412a5420 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 17 Apr 2023 09:16:39 -0700 Subject: [PATCH 12/85] Fixed leaks, debugged the tests --- src/micro.c | 14 -------------- src/micro_error.c | 4 ++-- src/micro_monitoring.c | 9 ++++++--- src/microp.h | 2 -- test/test.c | 11 ++++++----- 5 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/micro.c b/src/micro.c index 81ca39ee3..002511312 100644 --- a/src/micro.c +++ b/src/micro.c @@ -151,20 +151,6 @@ bool microService_IsStopped(microService *m) return stopped; } -bool micro_service_is_stopping(microService *m) -{ - bool stopping; - - if ((m == NULL) || (m->mu == NULL)) - return true; - - natsMutex_Lock(m->mu); - stopping = m->is_stopping; - natsMutex_Unlock(m->mu); - - return stopping; -} - microError * microService_Run(microService *m) { diff --git a/src/micro_error.c b/src/micro_error.c index 497bd1b6e..786eab907 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -67,7 +67,7 @@ micro_Errorf(int code, const char *format, ...) va_end(args2); return &_errorInvalidFormat; } - buf = NATS_MALLOC(len + 1); + buf = NATS_CALLOC(1, len + 1); if (buf == NULL) { va_end(args2); @@ -145,7 +145,7 @@ microError_Wrapf(microError *err, const char *format, ...) { len2 = strlen(err->description) + 2; // ": " } - buf = NATS_MALLOC(len1 + len2 + 1); + buf = NATS_CALLOC(1, len1 + len2 + 1); if (buf == NULL) { return &_errorOutOfMemory; diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 8dbe60335..8bd39c08c 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -46,7 +46,7 @@ micro_init_monitoring(microService *m) { microError *err = NULL; - m->monitoring_subs = NATS_CALLOC(MICRO_MONITORING_SUBS_CAP, sizeof(natsSubscription*)); + m->monitoring_subs = NATS_CALLOC(MICRO_MONITORING_SUBS_CAP, sizeof(natsSubscription *)); if (m->monitoring_subs == NULL) return micro_ErrorOutOfMemory; m->monitoring_subs_len = 0; @@ -75,6 +75,7 @@ handle_ping(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closu microRequest_Respond(req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); micro_destroy_request(req); + natsMsg_Destroy(msg); natsBuf_Destroy(buf); } @@ -98,6 +99,7 @@ handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closu microRequest_Respond(req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); natsBuf_Destroy(buf); + natsMsg_Destroy(msg); microServiceInfo_Destroy(info); } @@ -138,6 +140,7 @@ handle_stats(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *clos MICRO_CALL(err, micro_new_request(&req, m, NULL, msg)); MICRO_DO(err, h(req)); MICRO_DO(err, micro_destroy_request(req)); + natsMsg_Destroy(msg); } static microError * @@ -159,7 +162,7 @@ new_dotted_subject(char **new_subject, int count, ...) } va_end(args); - result = NATS_MALLOC(len + 1); + result = NATS_CALLOC(len + 1, 1); if (result == NULL) { return micro_ErrorInvalidArg; @@ -217,6 +220,7 @@ add_internal_handler(microService *m, const char *verb, const char *kind, return err; s = natsConnection_Subscribe(&sub, m->nc, subj, handler, m); + NATS_FREE(subj); if (s != NATS_OK) { microService_Stop(m); @@ -377,4 +381,3 @@ marshal_stats(natsBuffer **new_buf, microServiceStats *stats) return microError_Wrapf(micro_ErrorFromStatus(s), "failed to marshal service info"); } } - diff --git a/src/microp.h b/src/microp.h index d92b4b52e..2e4771cdf 100644 --- a/src/microp.h +++ b/src/microp.h @@ -105,8 +105,6 @@ microError *micro_start_endpoint(microEndpoint *ep); microError *micro_stop_endpoint(microEndpoint *ep); microError *micro_stop_and_destroy_endpoint(microEndpoint *ep); void micro_update_last_error(microEndpoint *ep, microError *err); -bool micro_service_is_stopping(microService *m); - static inline microError *micro_strdup(char **ptr, const char *str) { diff --git a/test/test.c b/test/test.c index 8bbdab807..ce374d3a2 100644 --- a/test/test.c +++ b/test/test.c @@ -32288,14 +32288,12 @@ test_MicroServiceStopsOnClosedConn(void) testCond(NATS_OK == natsConnection_Drain(nc)); natsConnection_Close(nc); - while (micro_service_is_stopping(m)) - { - nats_Sleep(1); - } + test("<>/<> Wait for the service to stop: "); + testCond((nats_Sleep(100), true)); + test("Test microservice is stopped: "); testCond(microService_IsStopped(m)); - microService_Destroy(m); natsOptions_Destroy(opts); natsConnection_Destroy(nc); @@ -32341,6 +32339,9 @@ test_MicroServiceStopsWhenServerStops(void) test("Stop the server: "); testCond((_stopServer(serverPid), true)); + test("<>/<> Wait for the service to stop: "); + testCond((nats_Sleep(100), true)); + test("Test microservice is not running: "); testCond(microService_IsStopped(m)) From 39cc6b2661042a5b90f0b51fc449a5c334f6f475 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 18 Apr 2023 08:33:08 -0700 Subject: [PATCH 13/85] Groups --- examples/micro-arithmetics.c | 11 ++-- examples/micro-func.c | 20 ++++---- examples/micro-hello.c | 3 +- examples/micro-sequence.c | 12 +++-- examples/micro-stats.c | 11 ++-- src/micro.c | 97 ++++++++++++++++++++++++++++++++---- src/micro.h | 14 ++++-- src/micro_endpoint.c | 36 +++++++++++-- src/micro_error.c | 3 +- src/micro_request.c | 1 - src/microp.h | 29 ++++++++--- 11 files changed, 183 insertions(+), 54 deletions(-) diff --git a/examples/micro-arithmetics.c b/examples/micro-arithmetics.c index 009e8f56c..ebe17c20f 100644 --- a/examples/micro-arithmetics.c +++ b/examples/micro-arithmetics.c @@ -84,6 +84,7 @@ int main(int argc, char **argv) natsConnection *conn = NULL; natsOptions *opts = NULL; microService *m = NULL; + microGroup *g = NULL; char errorbuf[1024]; microServiceConfig cfg = { @@ -114,13 +115,14 @@ int main(int argc, char **argv) return 1; } - // Create the Microservice that listens on nc. + // Create the Microservice that listens on nc. MICRO_CALL(err, micro_AddService(&m, conn, &cfg)); // Add the endpoints for the functions. - MICRO_CALL(err, microService_AddEndpoint(NULL, m, &add_cfg)); - MICRO_CALL(err, microService_AddEndpoint(NULL, m, &multiply_cfg)); - MICRO_CALL(err, microService_AddEndpoint(NULL, m, ÷_cfg)); + MICRO_CALL(err, microService_AddGroup(&g, m, "op")); + MICRO_CALL(err, microGroup_AddEndpoint(g, &add_cfg)); + MICRO_CALL(err, microGroup_AddEndpoint(g, &multiply_cfg)); + MICRO_CALL(err, microGroup_AddEndpoint(g, ÷_cfg)); // Run the service, until stopped. MICRO_CALL(err, microService_Run(m)); @@ -135,4 +137,3 @@ int main(int argc, char **argv) } return 0; } - diff --git a/examples/micro-func.c b/examples/micro-func.c index 559a60bf3..e3fc45c71 100644 --- a/examples/micro-func.c +++ b/examples/micro-func.c @@ -63,7 +63,7 @@ factorial(long double *result, natsConnection *nc, int n) *result = 1; for (i = 1; i <= n; i++) { - err = call_arithmetics(result, nc, "multiply", *result, i); + err = call_arithmetics(result, nc, "op.multiply", *result, i); if (err != NULL) return err; } @@ -90,7 +90,7 @@ fibonacci(long double *result, natsConnection *nc, int n) for (i = 1, n1 = 0, n2 = 1; i <= n; i++) { - err = call_arithmetics(result, nc, "add", n1, n2); + err = call_arithmetics(result, nc, "op.add", n1, n2); if (err != NULL) return err; n1 = n2; @@ -112,7 +112,7 @@ static microError *power2(long double *result, natsConnection *nc, int n) *result = 1; for (i = 1; i <= n; i++) { - err = call_arithmetics(result, nc, "multiply", *result, 2); + err = call_arithmetics(result, nc, "op.multiply", *result, 2); if (err != NULL) return err; } @@ -155,9 +155,10 @@ int main(int argc, char **argv) { natsStatus s = NATS_OK; microError *err = NULL; + natsOptions *opts = NULL; natsConnection *conn = NULL; microService *m = NULL; - natsOptions *opts = NULL; + microGroup *g = NULL; char errorbuf[1024]; microServiceConfig cfg = { @@ -188,14 +189,15 @@ int main(int argc, char **argv) return 1; } - // Create the Microservice that listens on nc. + // Create the Microservice that listens on nc. MICRO_CALL(err, micro_AddService(&m, conn, &cfg)); // Add the endpoints for the functions. - MICRO_CALL(err, microService_AddEndpoint(NULL, m, &factorial_cfg)); - MICRO_CALL(err, microService_AddEndpoint(NULL, m, &fibonacci_cfg)); - MICRO_CALL(err, microService_AddEndpoint(NULL, m, &power2_cfg)); - + MICRO_CALL(err, microService_AddGroup(&g, m, "f")); + MICRO_CALL(err, microGroup_AddEndpoint(g, &factorial_cfg)); + MICRO_CALL(err, microGroup_AddEndpoint(g, &fibonacci_cfg)); + MICRO_CALL(err, microGroup_AddEndpoint(g, &power2_cfg)); + // Run the service, until stopped. MICRO_CALL(err, microService_Run(m)); diff --git a/examples/micro-hello.c b/examples/micro-hello.c index 1b0d55a7b..028b4ab6f 100644 --- a/examples/micro-hello.c +++ b/examples/micro-hello.c @@ -27,7 +27,7 @@ // hello_pid=$! // sleep 2 # wait for microservice to start // nats request 'hello' '' -// kill $hello_pid $nats_pid +// kill $hello_pid $nats_pid // ``` // // OUTPUT: @@ -86,4 +86,3 @@ int main(int argc, char **argv) } return 0; } - diff --git a/examples/micro-sequence.c b/examples/micro-sequence.c index 21602a753..9583d5ca3 100644 --- a/examples/micro-sequence.c +++ b/examples/micro-sequence.c @@ -16,7 +16,7 @@ // Sequence NATS microservice example. // // This example illustrated multiple NATS microservices communicating with each -// other. +// other. // // The main service (c-sequence) calculates the sum of 1/f(1) + 1/f(2)... up to // N (included). It exposes one (default) endpoint, "sequence". The inputs are @@ -46,7 +46,7 @@ // nats request -r 'sequence' '"factorial" 10' // nats request -r 'sequence' '"power2" 10' // nats request -r 'sequence' '"fibonacci" 10' -// kill $sequence_pid $func_pid $arithmetics_pid $nats_pid +// kill $sequence_pid $func_pid $arithmetics_pid $nats_pid // ``` // // OUTPUT: @@ -63,12 +63,14 @@ call_function(long double *result, natsConnection *nc, const char *subject, int microClient *client = NULL; natsMsg *response = NULL; microArgs *args = NULL; - char buf[1024]; + char buf[256]; + char sbuf[256]; int len; + len = snprintf(buf, sizeof(buf), "%d", n); + snprintf(sbuf, sizeof(sbuf), "f.%s", subject); MICRO_CALL(err, micro_NewClient(&client, nc, NULL)); - MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%d", n)); - MICRO_CALL(err, microClient_DoRequest(&response, client, subject, buf, len)); + MICRO_CALL(err, microClient_DoRequest(&response, client, sbuf, buf, len)); MICRO_CALL(err, micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response))); MICRO_CALL(err, microArgs_GetFloat(result, args, 0)); diff --git a/examples/micro-stats.c b/examples/micro-stats.c index 76562b4b0..ec6a950ca 100644 --- a/examples/micro-stats.c +++ b/examples/micro-stats.c @@ -108,13 +108,13 @@ run_example(natsConnection *conn, microRequestHandler stats_handler, char *buf, if (err == NULL) { len = natsMsg_GetDataLength(stats_resp); - if (len > buf_cap-1) + if (len > buf_cap - 1) { - len = buf_cap-1; + len = buf_cap - 1; } memcpy(buf, natsMsg_GetData(stats_resp), len); buf[len] = '\0'; - + natsMsg_Destroy(stats_resp); } @@ -134,10 +134,10 @@ int main(int argc, char **argv) MICRO_CALL(err, run_example(conn, NULL, buf, sizeof(buf))); MICRO_DO(err, printf("Default stats response:\n----\n%s\n----\n\n", buf)); - + MICRO_CALL(err, run_example(conn, handle_stats, buf, sizeof(buf))); MICRO_DO(err, printf("Custom stats response:\n----\n%s\n----\n\n", buf)); - + if (err != NULL) { fprintf(stderr, "Error: %s\n", microError_String(err, buf, sizeof(buf))); @@ -146,4 +146,3 @@ int main(int argc, char **argv) microError_Destroy(err); return err == NULL ? 0 : 1; } - diff --git a/src/micro.c b/src/micro.c index 002511312..d91defa69 100644 --- a/src/micro.c +++ b/src/micro.c @@ -61,7 +61,7 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c MICRO_CALL(err, wrap_connection_event_callbacks(m)) // Add the endpoints and monitoring subscriptions. - MICRO_CALL(err, microService_AddEndpoint(NULL, m, cfg->endpoint)); + MICRO_CALL(err, microService_AddEndpoint(m, cfg->endpoint)); MICRO_CALL(err, micro_init_monitoring(m)); if (err != NULL) @@ -74,7 +74,6 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c return NULL; } -// TODO <>/<> update from Go microError * microService_Stop(microService *m) { @@ -87,7 +86,7 @@ microService_Stop(microService *m) return micro_ErrorInvalidArg; natsMutex_Lock(m->mu); - if (m->is_stopping || m->stopped) + if (m->is_stopping || m->is_stopped) { natsMutex_Unlock(m->mu); return NULL; @@ -122,7 +121,7 @@ microService_Stop(microService *m) free_subs = m->monitoring_subs; natsMutex_Lock(m->mu); - m->stopped = true; + m->is_stopped = true; m->is_stopping = false; m->started = 0; m->monitoring_subs_len = 0; @@ -139,16 +138,16 @@ microService_Stop(microService *m) bool microService_IsStopped(microService *m) { - bool stopped; + bool is_stopped; if ((m == NULL) || (m->mu == NULL)) return true; natsMutex_Lock(m->mu); - stopped = m->stopped; + is_stopped = m->is_stopped; natsMutex_Unlock(m->mu); - return stopped; + return is_stopped; } microError * @@ -173,8 +172,8 @@ microService_GetConnection(microService *m) return m->nc; } -microError * -microService_AddEndpoint(microEndpoint **new_ep, microService *m, microEndpointConfig *cfg) +static microError * +add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg) { microError *err = NULL; int index = -1; @@ -186,7 +185,7 @@ microService_AddEndpoint(microEndpoint **new_ep, microService *m, microEndpointC if (cfg == NULL) return NULL; - err = micro_new_endpoint(&ep, m, cfg); + err = micro_new_endpoint(&ep, m, prefix, cfg); if (err != NULL) return microError_Wrapf(err, "failed to create endpoint"); @@ -240,6 +239,12 @@ microService_AddEndpoint(microEndpoint **new_ep, microService *m, microEndpointC return NULL; } +microError * +microService_AddEndpoint(microService *m, microEndpointConfig *cfg) +{ + return add_endpoint(NULL, m, NULL, cfg); +} + microError * microService_GetInfo(microServiceInfo **new_info, microService *m) { @@ -362,6 +367,7 @@ microError * microService_Destroy(microService *m) { microError *err = NULL; + microGroup *next = NULL; if (m == NULL) return NULL; @@ -370,6 +376,18 @@ microService_Destroy(microService *m) if (err != NULL) return err; + // destroy all groups. + if (m->groups != NULL) + { + microGroup *g = m->groups; + while (g != NULL) + { + next = g->next; + NATS_FREE(g); + g = next; + } + } + micro_destroy_cloned_service_config(m->cfg); natsConn_release(m->nc); natsMutex_Destroy(m->mu); @@ -549,3 +567,62 @@ unwrap_connection_event_callbacks(microService *m) return microError_Wrapf(micro_ErrorFromStatus(s), "failed to unwrap connection event callbacks"); } + +microError * +microService_AddGroup(microGroup **new_group, microService *m, const char *prefix) +{ + if ((m == NULL) || (new_group == NULL) || (prefix == NULL)) + return micro_ErrorInvalidArg; + + *new_group = NATS_CALLOC(1, sizeof(microGroup) + strlen(prefix) + 1); + if (new_group == NULL) + { + return micro_ErrorOutOfMemory; + } + + memcpy((*new_group)->prefix, prefix, strlen(prefix) + 1); + (*new_group)->m = m; + (*new_group)->next = m->groups; + m->groups = *new_group; + + return NULL; +} + +microError * +microGroup_AddGroup(microGroup **new_group, microGroup *parent, const char *prefix) +{ + char *p; + int len; + + if ((parent == NULL) || (new_group == NULL) || (prefix == NULL)) + return micro_ErrorInvalidArg; + + *new_group = NATS_CALLOC(1, sizeof(microGroup) + + strlen(parent->prefix) + 1 + // "parent_prefix." + strlen(prefix) + 1); // "prefix\0" + if (new_group == NULL) + { + return micro_ErrorOutOfMemory; + } + + p = (*new_group)->prefix; + len = strlen(parent->prefix); + memcpy(p, parent->prefix, len); + p[len] = '.'; + p += len + 1; + memcpy(p, prefix, strlen(prefix) + 1); + (*new_group)->m = parent->m; + (*new_group)->next = parent->m->groups; + parent->m->groups = *new_group; + + return NULL; +} + +microError * +microGroup_AddEndpoint(microGroup *g, microEndpointConfig *cfg) +{ + if (g == NULL) + return micro_ErrorInvalidArg; + + return add_endpoint(NULL, g->m, g->prefix, cfg); +} diff --git a/src/micro.h b/src/micro.h index cad554271..22061f238 100644 --- a/src/micro.h +++ b/src/micro.h @@ -53,6 +53,8 @@ typedef struct micro_service_s microService; */ typedef struct micro_endpoint_s microEndpoint; +typedef struct micro_group_s microGroup; + /** * The Microservice request object. */ @@ -77,7 +79,6 @@ typedef struct microRequest */ typedef void (*microRequestHandler)(microRequest *req); - typedef void (*microErrorHandler)(microService *m, microEndpoint *ep, natsStatus s); /** @@ -172,13 +173,20 @@ typedef struct micro_error_s microError; // microService methods. NATS_EXTERN microError *micro_AddService(microService **new_microservice, natsConnection *nc, microServiceConfig *cfg); -NATS_EXTERN microError *microService_AddEndpoint(microEndpoint **new_endpoint, microService *m, microEndpointConfig *cfg); +NATS_EXTERN microError *microService_AddEndpoint(microService *m, microEndpointConfig *cfg); +NATS_EXTERN microError *microService_AddGroup(microGroup **new_group, microService *m, const char *prefix); NATS_EXTERN microError *microService_Destroy(microService *m); NATS_EXTERN natsConnection *microService_GetConnection(microService *m); NATS_EXTERN bool microService_IsStopped(microService *m); NATS_EXTERN microError *microService_Run(microService *m); NATS_EXTERN microError *microService_Stop(microService *m); +// +// microGroup methods. + +NATS_EXTERN microError *microGroup_AddGroup(microGroup **new_group, microGroup *parent, const char *prefix); +NATS_EXTERN microError *microGroup_AddEndpoint(microGroup *g, microEndpointConfig *cfg); + // // microRequest methods. @@ -191,7 +199,7 @@ NATS_EXTERN microEndpoint *microRequest_GetEndpoint(microRequest *req); NATS_EXTERN void *microRequest_GetEndpointState(microRequest *req); NATS_EXTERN microError *microRequest_GetHeaderKeys(microRequest *req, const char ***keys, int *count); NATS_EXTERN microError *microRequest_GetHeaderValue(microRequest *req, const char *key, const char **value); -NATS_EXTERN microError *microRequest_GetHeaderValues(microRequest *req,const char *key, const char ***values, int *count); +NATS_EXTERN microError *microRequest_GetHeaderValues(microRequest *req, const char *key, const char ***values, int *count); NATS_EXTERN natsMsg *microRequest_GetMsg(microRequest *req); NATS_EXTERN const char *microRequest_GetReply(microRequest *req); NATS_EXTERN microService *microRequest_GetService(microRequest *req); diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index 43b7d4836..b318d16d0 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -27,10 +27,14 @@ handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *cl static microError *new_endpoint(microEndpoint **ptr); microError * -micro_new_endpoint(microEndpoint **new_ep, microService *m, microEndpointConfig *cfg) +micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg) { microError *err = NULL; microEndpoint *ep = NULL; + const char *subj; + char *effective_subj; + char *p; + int len; if (!is_valid_name(cfg->name) || (cfg->handler == NULL)) return micro_ErrorInvalidArg; @@ -41,7 +45,33 @@ micro_new_endpoint(microEndpoint **new_ep, microService *m, microEndpointConfig MICRO_CALL(err, new_endpoint(&ep)); MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->mu))); MICRO_CALL(err, micro_clone_endpoint_config(&ep->config, cfg)); - MICRO_CALL(err, micro_strdup(&ep->subject, nats_IsStringEmpty(cfg->subject) ? cfg->name : cfg->subject)); + + if (err == NULL) + { + subj = nats_IsStringEmpty(cfg->subject) ? cfg->name : cfg->subject; + len = (int)strlen(subj) + 1; + if (prefix != NULL) + len += (int)strlen(prefix) + 1; + effective_subj = NATS_CALLOC(1, len); + if (effective_subj != NULL) + { + p = effective_subj; + if (prefix != NULL) + { + len = strlen(prefix); + memcpy(p, prefix, len); + p[len] = '.'; + p += len + 1; + } + memcpy(p, subj, strlen(subj) + 1); + ep->subject = effective_subj; + } + else + { + err = micro_ErrorOutOfMemory; + } + } + if (err != NULL) { micro_stop_and_destroy_endpoint(ep); @@ -80,7 +110,7 @@ micro_stop_and_destroy_endpoint(microEndpoint *ep) if ((ep == NULL) || (ep->sub == NULL)) return NULL; - if (!natsConnection_IsClosed(ep->m->nc)) + if (!natsConnection_IsClosed(ep->m->nc)) { s = natsSubscription_Drain(ep->sub); if (s != NATS_OK) diff --git a/src/micro_error.c b/src/micro_error.c index 786eab907..57448a46a 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -173,7 +173,7 @@ microError_String(microError *err, char *buf, int size) { if (buf == NULL) return ""; - if (err == NULL) + if (err == NULL) { snprintf(buf, size, "null"); } @@ -228,4 +228,3 @@ new_error(natsStatus s, int code, char *description) return err; } - diff --git a/src/micro_request.c b/src/micro_request.c index d5012915a..fcea5eaa9 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -176,7 +176,6 @@ microRequest_GetService(microRequest *req) return (req != NULL) ? req->service : NULL; } - void micro_destroy_request(microRequest *req) { NATS_FREE(req); diff --git a/src/microp.h b/src/microp.h index 2e4771cdf..b0a38f4d6 100644 --- a/src/microp.h +++ b/src/microp.h @@ -58,6 +58,13 @@ struct micro_endpoint_s microEndpointStats stats; }; +struct micro_group_s +{ + struct micro_service_s *m; + struct micro_group_s *next; + char prefix[]; +}; + struct micro_service_s { // these are set at initialization time time and do not change. @@ -65,28 +72,33 @@ struct micro_service_s struct microServiceConfig *cfg; char id[NUID_BUFFER_LEN + 1]; + // groups are just convenient wrappers to make "prefixed" endpoints with + // AddEndpoint. They are added at initializaton time, so no need to lock. + struct micro_group_s *groups; + // these are are updated concurrently with access as the service runs, so // need to be protected by mutex. natsMutex *mu; + int refs; + struct micro_endpoint_s **endpoints; int endpoints_len; int endpoints_cap; natsSubscription **monitoring_subs; int monitoring_subs_len; - + natsConnectionHandler prev_on_connection_closed; - void * prev_on_connection_closed_closure; + void *prev_on_connection_closed_closure; natsConnectionHandler prev_on_connection_disconnected; - void * prev_on_connection_disconnected_closure; + void *prev_on_connection_disconnected_closure; natsErrHandler prev_on_error; - void * prev_on_error_closure; + void *prev_on_error_closure; int64_t started; // UTC time expressed as number of nanoseconds since epoch. - bool stopped; + bool is_stopped; bool is_stopping; - int refs; }; extern microError *micro_ErrorOutOfMemory; @@ -99,7 +111,7 @@ void micro_destroy_cloned_service_config(microServiceConfig *cfg); void micro_destroy_request(microRequest *req); microError *micro_init_monitoring(microService *m); bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject); -microError *micro_new_endpoint(microEndpoint **new_endpoint, microService *m, microEndpointConfig *cfg); +microError *micro_new_endpoint(microEndpoint **new_endpoint, microService *m, const char *prefix, microEndpointConfig *cfg); microError *micro_new_request(microRequest **new_request, microService *m, microEndpoint *ep, natsMsg *msg); microError *micro_start_endpoint(microEndpoint *ep); microError *micro_stop_endpoint(microEndpoint *ep); @@ -110,7 +122,8 @@ static inline microError *micro_strdup(char **ptr, const char *str) { // Make a strdup(NULL) be a no-op, so we don't have to check for NULL // everywhere. - if (str == NULL) { + if (str == NULL) + { *ptr = NULL; return NULL; } From dde5993c9e6f809760fe6dcacad0cd33b4b64ec8 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 25 Apr 2023 07:02:55 -0700 Subject: [PATCH 14/85] "Basic" test. --- src/conn.c | 36 +++--- src/micro_monitoring.c | 8 +- src/microp.h | 2 + src/util.c | 2 +- test/list.txt | 1 + test/test.c | 249 ++++++++++++++++++++++++++++++++++++++--- 6 files changed, 258 insertions(+), 40 deletions(-) diff --git a/src/conn.c b/src/conn.c index 7bffc7b84..bb7d13f02 100644 --- a/src/conn.c +++ b/src/conn.c @@ -4436,13 +4436,13 @@ natsConn_defaultErrHandler(natsConnection *nc, natsSubscription *sub, natsStatus natsStatus natsConn_getErrorCallback(natsErrHandler *cb, void **closure, natsConnection *nc) { - if ((nc == NULL) || (cb == NULL) || (closure == NULL)) + if ((nc == NULL) || (nc->opts == NULL) || (nc->opts->mu == NULL) || (cb == NULL) || (closure == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); - natsConn_Lock(nc); + natsMutex_Lock(nc->opts->mu); *cb = nc->opts->asyncErrCb; *closure = nc->opts->asyncErrCbClosure; - natsConn_Unlock(nc); + natsMutex_Unlock(nc->opts->mu); return NATS_OK; } @@ -4451,13 +4451,13 @@ natsStatus natsConn_setErrorCallback(natsConnection *nc, natsErrHandler cb, void *closure) { // The error callback must not be NULL, other code may rely on it. - if ((nc == NULL) || (cb == NULL)) + if ((nc == NULL) || (nc->opts == NULL) || (nc->opts->mu == NULL) || (cb == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); - natsConn_Lock(nc); + natsMutex_Lock(nc->opts->mu); nc->opts->asyncErrCb = cb; nc->opts->asyncErrCbClosure = closure; - natsConn_Unlock(nc); + natsMutex_Unlock(nc->opts->mu); return NATS_OK; } @@ -4465,13 +4465,13 @@ natsConn_setErrorCallback(natsConnection *nc, natsErrHandler cb, void *closure) natsStatus natsConn_getClosedCallback(natsConnectionHandler *cb, void **closure, natsConnection *nc) { - if ((nc == NULL) || (cb == NULL) || (closure == NULL)) + if ((nc == NULL) || (nc->opts == NULL) || (nc->opts->mu == NULL) || (cb == NULL) || (closure == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); - natsConn_Lock(nc); + natsMutex_Lock(nc->opts->mu); *cb = nc->opts->closedCb; *closure = nc->opts->closedCbClosure; - natsConn_Unlock(nc); + natsMutex_Unlock(nc->opts->mu); return NATS_OK; } @@ -4479,13 +4479,13 @@ natsConn_getClosedCallback(natsConnectionHandler *cb, void **closure, natsConnec natsStatus natsConn_setClosedCallback(natsConnection *nc, natsConnectionHandler cb, void *closure) { - if (nc == NULL) + if (nc == NULL || (nc->opts == NULL) || (nc->opts->mu == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); - natsConn_Lock(nc); + natsMutex_Lock(nc->opts->mu); nc->opts->closedCb = cb; nc->opts->closedCbClosure = closure; - natsConn_Unlock(nc); + natsMutex_Unlock(nc->opts->mu); return NATS_OK; } @@ -4493,13 +4493,13 @@ natsConn_setClosedCallback(natsConnection *nc, natsConnectionHandler cb, void *c natsStatus natsConn_getDisconnectedCallback(natsConnectionHandler *cb, void **closure, natsConnection *nc) { - if ((nc == NULL) || (cb == NULL) || (closure == NULL)) + if ((nc == NULL) || (nc->opts == NULL) || (nc->opts->mu == NULL) || (cb == NULL) || (closure == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); - natsConn_Lock(nc); + natsMutex_Lock(nc->opts->mu); *cb = nc->opts->disconnectedCb; *closure = nc->opts->disconnectedCbClosure; - natsConn_Unlock(nc); + natsMutex_Unlock(nc->opts->mu); return NATS_OK; } @@ -4507,13 +4507,13 @@ natsConn_getDisconnectedCallback(natsConnectionHandler *cb, void **closure, nats natsStatus natsConn_setDisconnectedCallback(natsConnection *nc, natsConnectionHandler cb, void *closure) { - if (nc == NULL) + if (nc == NULL || (nc->opts == NULL) || (nc->opts->mu == NULL)) return nats_setDefaultError(NATS_INVALID_ARG); - natsConn_Lock(nc); + natsMutex_Lock(nc->opts->mu); nc->opts->disconnectedCb = cb; nc->opts->disconnectedCbClosure = closure; - natsConn_Unlock(nc); + natsMutex_Unlock(nc->opts->mu); return NATS_OK; } diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 8bd39c08c..16fa87a53 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -37,8 +37,6 @@ add_internal_handler(microService *m, const char *verb, const char *kind, const static microError * add_verb_handlers(microService *m, const char *verb, natsMsgHandler handler); static microError * -new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); -static microError * new_dotted_subject(char **new_subject, int count, ...); microError * @@ -187,8 +185,8 @@ new_dotted_subject(char **new_subject, int count, ...) return NULL; } -static microError * -new_control_subject(char **newSubject, const char *verb, const char *name, const char *id) +microError * +micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id) { if (nats_IsStringEmpty(name) && !nats_IsStringEmpty(id)) { @@ -215,7 +213,7 @@ add_internal_handler(microService *m, const char *verb, const char *kind, if (m->monitoring_subs_len >= MICRO_MONITORING_SUBS_CAP) return micro_Errorf(500, "too many monitoring subscriptions (max: %d)", MICRO_MONITORING_SUBS_CAP); - err = new_control_subject(&subj, verb, kind, id); + err = micro_new_control_subject(&subj, verb, kind, id); if (err != NULL) return err; diff --git a/src/microp.h b/src/microp.h index b0a38f4d6..a4b3f0490 100644 --- a/src/microp.h +++ b/src/microp.h @@ -117,6 +117,8 @@ microError *micro_start_endpoint(microEndpoint *ep); microError *micro_stop_endpoint(microEndpoint *ep); microError *micro_stop_and_destroy_endpoint(microEndpoint *ep); void micro_update_last_error(microEndpoint *ep, microError *err); +microError *micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); + static inline microError *micro_strdup(char **ptr, const char *str) { diff --git a/src/util.c b/src/util.c index f39e4d755..140011fbf 100644 --- a/src/util.c +++ b/src/util.c @@ -2372,7 +2372,7 @@ nats_marshalDuration(natsBuffer *out_buf, bool comma, const char *field_name, in IFOK(s, natsBuf_Append(out_buf, field_name, -1)); IFOK(s, natsBuf_Append(out_buf, "\":\"", -1)); IFOK(s, natsBuf_Append(out_buf, buf + w, sizeof(buf) - w)); - IFOK(s, natsBuf_Append(out_buf, "\":\"", -1)); + IFOK(s, natsBuf_Append(out_buf, "\"", -1)); return NATS_UPDATE_ERR_STACK(s); } diff --git a/test/list.txt b/test/list.txt index 4818fa4f8..7e701ac90 100644 --- a/test/list.txt +++ b/test/list.txt @@ -260,6 +260,7 @@ KeyValueRePublish KeyValueMirrorDirectGet KeyValueMirrorCrossDomains MicroMatchEndpointSubject +MicroBasics MicroServiceStopsOnClosedConn MicroServiceStopsWhenServerStops StanPBufAllocator diff --git a/test/test.c b/test/test.c index ce374d3a2..fa0db8423 100644 --- a/test/test.c +++ b/test/test.c @@ -32214,6 +32214,235 @@ test_MicroMatchEndpointSubject(void) } } +static void +micro_basics_handle_request(microRequest *req) +{ + microError *err = NULL; + if ((rand() % 10) == 0) + { + err = micro_Errorf(500, "Unexpected error!"); + microRequest_Respond(req, &err, NULL, 0); + return; + } + + // Happy Path. + // Random delay between 5-10ms + nats_Sleep(5 + (rand() % 5)); + + if ((err = microRequest_Respond(req, NULL, "42", 2)) != NULL) + { + microRequest_Respond(req, &err, NULL, 0); + FAIL("failed to respond, then failed to respond with error!") + return; + } +} + +#define NUM_BASIC_MICRO_SERVICES 5 + +static void +test_MicroBasics(void) +{ + natsStatus s = NATS_OK; + microError *err = NULL; + struct threadArg arg; + natsOptions *opts = NULL; + natsConnection *nc = NULL; + natsPid serverPid = NATS_INVALID_PID; + microService **svcs = NATS_CALLOC(NUM_BASIC_MICRO_SERVICES, sizeof(microService *)); + microEndpointConfig ep_cfg = { + .name = "do", + .subject = "svc.do", + .handler = micro_basics_handle_request, + }; + microServiceConfig cfg = { + .version = "1.0.0", + .name = "CoolService", + .description = "returns 42", + .endpoint = &ep_cfg, + }; + natsMsg *reply = NULL; + microServiceInfo *info = NATS_CALLOC(1, sizeof(microServiceInfo)); + int i; + char buf[256]; + char *subject = NULL; + natsInbox *inbox = NULL; + natsSubscription *sub = NULL; + nats_JSON *js = NULL; + int num_requests = 0; + int num_errors = 0; + int n; + nats_JSON **array; + int array_len; + + srand((unsigned int)nats_NowInNanoSeconds()); + + s = _createDefaultThreadArgsForCbTests(&arg); + if (s == NATS_OK) + opts = _createReconnectOptions(); + if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) + { + FAIL("Unable to setup test for MicroConnectionEvents!"); + } + + serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); + CHECK_SERVER_STARTED(serverPid); + + test("Connect to server: "); + testCond(NATS_OK == natsConnection_Connect(&nc, opts)); + + // start 5 instances of the basic service. + for (i = 0; i < NUM_BASIC_MICRO_SERVICES; i++) + { + snprintf(buf, sizeof(buf), "Start microservice #%d: ", i); + test(buf); + testCond(NULL == micro_AddService(&svcs[i], nc, &cfg)); + } + + // Now send 50 requests. + test("Send 50 requests: "); + for (i = 0; i < 50; i++) + { + s = natsConnection_Request(&reply, nc, "svc.do", NULL, 0, 1000); + if (NATS_OK != s) + FAIL("Unable to send request"); + } + testCond(NATS_OK == s); + + // Make sure we can request valid info with local API. + for (i = 0; i < NUM_BASIC_MICRO_SERVICES; i++) + { + snprintf(buf, sizeof(buf), "Check local info #%d: ", i); + test(buf); + err = microService_GetInfo(&info, svcs[i]); + testCond((err == NULL) && + (strcmp(info->name, "CoolService") == 0) && + (strlen(info->description) > 0) && + (strlen(info->version) > 0)); + } + + // Make sure we can request valid info with $SVC.INFO request. + test("Create INFO inbox: "); + testCond(NATS_OK == natsInbox_Create(&inbox)); + micro_new_control_subject(&subject, MICRO_INFO_VERB, "CoolService", NULL); + test("Subscribe to INFO inbox: "); + testCond(NATS_OK == natsConnection_SubscribeSync(&sub, nc, inbox)); + test("Publish INFO request: "); + testCond(NATS_OK == natsConnection_PublishRequest(nc, subject, inbox, NULL, 0)); + for (i = 0;; i++) + { + snprintf(buf, sizeof(buf), "Receive INFO response #%d: ", i); + test(buf); + s = natsSubscription_NextMsg(&reply, sub, 250); + if (s == NATS_TIMEOUT) + { + testCond(i == NUM_BASIC_MICRO_SERVICES); + break; + } + testCond(NATS_OK == s); + snprintf(buf, sizeof(buf), "Validate INFO response #%d: ", i); + test(buf); + testCond(strnstr(reply->data, "\"name\":\"CoolService\"", reply->dataLen) != NULL); + natsMsg_Destroy(reply); + } + natsSubscription_Destroy(sub); + natsInbox_Destroy(inbox); + NATS_FREE(subject); + + // Make sure we can request valid info with $SVC.INFO request. + test("Create PING inbox: "); + testCond(NATS_OK == natsInbox_Create(&inbox)); + micro_new_control_subject(&subject, MICRO_PING_VERB, "CoolService", NULL); + test("Subscribe to PING inbox: "); + testCond(NATS_OK == natsConnection_SubscribeSync(&sub, nc, inbox)); + test("Publish PING request: "); + testCond(NATS_OK == natsConnection_PublishRequest(nc, subject, inbox, NULL, 0)); + for (i = 0;; i++) + { + snprintf(buf, sizeof(buf), "Receive PING response #%d: ", i); + test(buf); + s = natsSubscription_NextMsg(&reply, sub, 250); + if (s == NATS_TIMEOUT) + { + testCond(i == NUM_BASIC_MICRO_SERVICES); + break; + } + testCond(NATS_OK == s); + snprintf(buf, sizeof(buf), "Validate PING response #%d: ", i); + test(buf); + testCond(strnstr(reply->data, "\"name\":\"CoolService\"", reply->dataLen) != NULL); + natsMsg_Destroy(reply); + } + natsSubscription_Destroy(sub); + natsInbox_Destroy(inbox); + NATS_FREE(subject); + + // Get and validate stats from all service instances. + test("Create STATS inbox: "); + testCond(NATS_OK == natsInbox_Create(&inbox)); + micro_new_control_subject(&subject, MICRO_STATS_VERB, "CoolService", NULL); + test("Subscribe to STATS inbox: "); + testCond(NATS_OK == natsConnection_SubscribeSync(&sub, nc, inbox)); + test("Publish STATS request: "); + testCond(NATS_OK == natsConnection_PublishRequest(nc, subject, inbox, NULL, 0)); + + for (i = 0;; i++) + { + snprintf(buf, sizeof(buf), "Receive STATS response #%d: ", i); + test(buf); + s = natsSubscription_NextMsg(&reply, sub, 250); + if (s == NATS_TIMEOUT) + { + testCond(i == NUM_BASIC_MICRO_SERVICES); + break; + } + testCond(NATS_OK == s); + test("Parse STATS response: "); + s = nats_JSONParse(&js, reply->data, reply->dataLen); + if ((NATS_OK != s) || (js == NULL)) + FAIL("Unable to parse JSON response"); + + s = nats_JSONGetArrayObject(js, "endpoints", &array, &array_len); + if ((NATS_OK != s) || (array == NULL) || (array_len != 1)) + FAIL("Invalid 'endpoints', expected exactly 1"); + + n = 0; + s = nats_JSONGetInt(array[0], "num_requests", &n); + if (NATS_OK != s) + FAIL("no 'num_requests' in endpoint stats"); + num_requests += n; + + n = 0; + s = nats_JSONGetInt(array[0], "num_errors", &n); + if (NATS_OK != s) + FAIL("no 'num_errors' in endpoint stats"); + num_errors += n; + + nats_JSONDestroy(js); + natsMsg_Destroy(reply); + } + test("Check that STATS total request counts add up (50): "); + testCond(num_requests == 50); + test("Check that STATS total error count is positive, depends on how many instances: "); + testCond(num_errors > 0); + + natsSubscription_Destroy(sub); + natsInbox_Destroy(inbox); + NATS_FREE(subject); + + // Destroy the services in the reverse order to properly unwind the + // callbacks. At some point this needs to be fixed so that the services + // exclude themselves from a chain, rather than setting previous, etc. + for (i = NUM_BASIC_MICRO_SERVICES - 1; i >= 0; i--) + { + microService_Destroy(svcs[i]); + } + + natsConnection_Destroy(nc); + natsOptions_Destroy(opts); + _destroyDefaultThreadArgs(&arg); + _stopServer(serverPid); +} + static void test_MicroServiceStopsOnClosedConn(void) { @@ -32270,25 +32499,11 @@ test_MicroServiceStopsOnClosedConn(void) test("Start microservice again: "); testCond(NULL == micro_AddService(&m, nc, &cfg)); - // void *p; - // natsHashIter iter; - // test("Check subs count AFTER START: "); - // natsMutex_Lock(nc->subsMu); - // printf("<>/<> actual count: %d\n", natsHash_Count(nc->subs)); - // p = NULL; - // natsHashIter_Init(&iter, nc->subs); - // while (natsHashIter_Next(&iter, NULL, &p)) - // { - // printf("<>/<> subject: '%s'\n", ((natsSubscription *)p)->subject); - // } - // natsHashIter_Done(&iter); - // natsMutex_Unlock(nc->subsMu); - test("Close the connection: "); testCond(NATS_OK == natsConnection_Drain(nc)); natsConnection_Close(nc); - test("<>/<> Wait for the service to stop: "); + test("Wait for the service to stop: "); testCond((nats_Sleep(100), true)); test("Test microservice is stopped: "); @@ -32339,7 +32554,7 @@ test_MicroServiceStopsWhenServerStops(void) test("Stop the server: "); testCond((_stopServer(serverPid), true)); - test("<>/<> Wait for the service to stop: "); + test("Wait for the service to stop: "); testCond((nats_Sleep(100), true)); test("Test microservice is not running: "); @@ -34917,8 +35132,10 @@ static testInfo allTests[] = {"KeyValueMirrorCrossDomains", test_KeyValueMirrorCrossDomains}, {"MicroMatchEndpointSubject", test_MicroMatchEndpointSubject}, + {"MicroBasics", test_MicroBasics}, {"MicroServiceStopsOnClosedConn", test_MicroServiceStopsOnClosedConn}, {"MicroServiceStopsWhenServerStops", test_MicroServiceStopsWhenServerStops}, + #if defined(NATS_HAS_STREAMING) {"StanPBufAllocator", test_StanPBufAllocator}, {"StanConnOptions", test_StanConnOptions}, From 0f5b888d690217bf052a54a497ddb977192db031 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Wed, 26 Apr 2023 03:10:43 -0700 Subject: [PATCH 15/85] more tests from go, fixes --- src/micro.c | 7 +- src/micro_endpoint.c | 73 ++++---- src/micro_monitoring.c | 1 + src/microp.h | 7 +- src/nats.c | 4 +- src/util.c | 2 + test/list.txt | 2 + test/test.c | 388 +++++++++++++++++++++++++++++++++++++++-- 8 files changed, 420 insertions(+), 64 deletions(-) diff --git a/src/micro.c b/src/micro.c index d91defa69..539acfada 100644 --- a/src/micro.c +++ b/src/micro.c @@ -11,7 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// TODO <>/<> review includes #include "micro.h" #include "microp.h" #include "conn.h" @@ -38,7 +37,7 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c microError *err = NULL; microService *m = NULL; - if ((new_m == NULL) || (nc == NULL) || (cfg == NULL)) + if ((new_m == NULL) || (nc == NULL) || (cfg == NULL) || !micro_is_valid_name(cfg->name) || nats_IsStringEmpty(cfg->version)) return micro_ErrorInvalidArg; // Make a microservice object, with a reference to a natsConnection. @@ -192,7 +191,7 @@ add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microE // see if we already have an endpoint with this name for (int i = 0; i < m->endpoints_len; i++) { - if (strcmp(m->endpoints[i]->config->name, cfg->name) == 0) + if (strcmp(m->endpoints[i]->name, ep->name) == 0) { index = i; break; @@ -338,7 +337,7 @@ microService_GetStats(microServiceStats **new_stats, microService *m) // copy the entire struct, including the last error buffer. stats->endpoints[len] = ep->stats; - stats->endpoints[len].name = ep->config->name; + stats->endpoints[len].name = ep->name; stats->endpoints[len].subject = ep->subject; avg = (long double)ep->stats.processing_time_s * 1000000000.0 + (long double)ep->stats.processing_time_ns; avg = avg / (long double)ep->stats.num_requests; diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index b318d16d0..e9b447c4e 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -17,61 +17,55 @@ #include "microp.h" #include "mem.h" -static bool -is_valid_name(const char *name); -static bool -is_valid_subject(const char *subject); static void handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); static microError *new_endpoint(microEndpoint **ptr); +static microError *dup_with_prefix(char **dst, const char *prefix, const char *src) +{ + int len = (int)strlen(src) + 1; + char *p; + + if (!nats_IsStringEmpty(prefix)) + len += (int)strlen(prefix) + 1; + + *dst = NATS_CALLOC(1, len); + if (*dst == NULL) + return micro_ErrorOutOfMemory; + + p = *dst; + if (prefix != NULL) + { + len = strlen(prefix); + memcpy(p, prefix, len); + p[len] = '.'; + p += len + 1; + } + memcpy(p, src, strlen(src) + 1); + return NULL; +} + microError * micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg) { microError *err = NULL; microEndpoint *ep = NULL; const char *subj; - char *effective_subj; - char *p; - int len; - if (!is_valid_name(cfg->name) || (cfg->handler == NULL)) + if (!micro_is_valid_name(cfg->name) || (cfg->handler == NULL)) return micro_ErrorInvalidArg; - if ((cfg->subject != NULL) && !is_valid_subject(cfg->subject)) + if ((cfg->subject != NULL) && !micro_is_valid_subject(cfg->subject)) return micro_ErrorInvalidArg; + subj = nats_IsStringEmpty(cfg->subject) ? cfg->name : cfg->subject; + MICRO_CALL(err, new_endpoint(&ep)); MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->mu))); MICRO_CALL(err, micro_clone_endpoint_config(&ep->config, cfg)); - - if (err == NULL) - { - subj = nats_IsStringEmpty(cfg->subject) ? cfg->name : cfg->subject; - len = (int)strlen(subj) + 1; - if (prefix != NULL) - len += (int)strlen(prefix) + 1; - effective_subj = NATS_CALLOC(1, len); - if (effective_subj != NULL) - { - p = effective_subj; - if (prefix != NULL) - { - len = strlen(prefix); - memcpy(p, prefix, len); - p[len] = '.'; - p += len + 1; - } - memcpy(p, subj, strlen(subj) + 1); - ep->subject = effective_subj; - } - else - { - err = micro_ErrorOutOfMemory; - } - } - + MICRO_CALL(err, dup_with_prefix(&ep->name, prefix, cfg->name)); + MICRO_CALL(err, dup_with_prefix(&ep->subject, prefix, subj)); if (err != NULL) { micro_stop_and_destroy_endpoint(ep); @@ -117,6 +111,7 @@ micro_stop_and_destroy_endpoint(microEndpoint *ep) return micro_ErrorFromStatus(s); } + NATS_FREE(ep->name); NATS_FREE(ep->subject); natsSubscription_Destroy(ep->sub); natsMutex_Destroy(ep->mu); @@ -185,8 +180,7 @@ void micro_update_last_error(microEndpoint *ep, microError *err) natsMutex_Unlock(ep->mu); } -static bool -is_valid_name(const char *name) +bool micro_is_valid_name(const char *name) { int i; int len; @@ -206,8 +200,7 @@ is_valid_name(const char *name) return true; } -static bool -is_valid_subject(const char *subject) +bool micro_is_valid_subject(const char *subject) { int i; int len; diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 16fa87a53..05b8e6cc6 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -96,6 +96,7 @@ handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closu // respond for both success and error cases. microRequest_Respond(req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); + micro_destroy_request(req); natsBuf_Destroy(buf); natsMsg_Destroy(msg); microServiceInfo_Destroy(info); diff --git a/src/microp.h b/src/microp.h index a4b3f0490..ba3863b8b 100644 --- a/src/microp.h +++ b/src/microp.h @@ -39,8 +39,9 @@ struct micro_client_s struct micro_endpoint_s { - // The subject that the endpoint is listening on (may be different from - // one specified in config). + // The name and subject that the endpoint is listening on (may be different + // from one specified in config). + char *name; char *subject; // References to other entities. @@ -118,6 +119,8 @@ microError *micro_stop_endpoint(microEndpoint *ep); microError *micro_stop_and_destroy_endpoint(microEndpoint *ep); void micro_update_last_error(microEndpoint *ep, microError *err); microError *micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); +bool micro_is_valid_name(const char *name); +bool micro_is_valid_subject(const char *subject); static inline microError *micro_strdup(char **ptr, const char *str) diff --git a/src/nats.c b/src/nats.c index c13726a2b..64c45628b 100644 --- a/src/nats.c +++ b/src/nats.c @@ -771,10 +771,10 @@ _asyncCbsThread(void *arg) #define __set_handler(_h, _cb, _cl) \ { \ - natsMutex_Lock(nc->mu); \ + natsMutex_Lock(nc->opts->mu); \ _h = nc->opts->_cb; \ cbClosure = nc->opts->_cl; \ - natsMutex_Unlock(nc->mu); \ + natsMutex_Unlock(nc->opts->mu); \ } switch (cb->type) diff --git a/src/util.c b/src/util.c index 140011fbf..a235c9580 100644 --- a/src/util.c +++ b/src/util.c @@ -1486,6 +1486,7 @@ nats_JSONArrayAsStrings(nats_JSONArray *arr, char ***array, int *arraySize) if (values == NULL) return nats_setDefaultError(NATS_NO_MEMORY); + printf("<>/<> nats_JSONArrayAsStrings: allocated %d %d-byte values=%p\n", arr->size, arr->eltSize, values); for (i=0; isize; i++) { values[i] = NATS_STRDUP((char*)(arr->values[i])); @@ -1494,6 +1495,7 @@ nats_JSONArrayAsStrings(nats_JSONArray *arr, char ***array, int *arraySize) s = nats_setDefaultError(NATS_NO_MEMORY); break; } + printf("<>/<> nats_JSONArrayAsStrings: allocated value %d values=%p\n", i, values[i]); } if (s != NATS_OK) { diff --git a/test/list.txt b/test/list.txt index 7e701ac90..d3dc96e05 100644 --- a/test/list.txt +++ b/test/list.txt @@ -260,6 +260,8 @@ KeyValueRePublish KeyValueMirrorDirectGet KeyValueMirrorCrossDomains MicroMatchEndpointSubject +MicroAddService +MicroGroups MicroBasics MicroServiceStopsOnClosedConn MicroServiceStopsWhenServerStops diff --git a/test/test.c b/test/test.c index fa0db8423..94e65f1eb 100644 --- a/test/test.c +++ b/test/test.c @@ -32202,14 +32202,14 @@ test_MicroMatchEndpointSubject(void) const char *actual_subject; bool expected_match; - for (i=0; i<(int) (sizeof(test_cases)/sizeof(const char*)); i=i+3) + for (i = 0; i < (int)(sizeof(test_cases) / sizeof(const char *)); i = i + 3) { ep_subject = test_cases[i]; - actual_subject = test_cases[i+1]; - expected_match = (test_cases[i+2] != NULL); - + actual_subject = test_cases[i + 1]; + expected_match = (test_cases[i + 2] != NULL); + snprintf(buf, sizeof(buf), "endpoint subject '%s', actual subject: '%s': ", ep_subject, actual_subject); - test(buf) + test(buf); testCond(micro_match_endpoint_subject(ep_subject, actual_subject) == expected_match); } } @@ -32237,6 +32237,347 @@ micro_basics_handle_request(microRequest *req) } } +typedef struct +{ + const char *name; + microServiceConfig *cfg; + microEndpointConfig **endpoints; + int num_endpoints; + bool null_nc; + bool null_receiver; + const char *expected_err; + int expected_num_subjects; +} add_service_test_case_t; + +static void +test_MicroAddService(void) +{ + natsStatus s = NATS_OK; + microError *err = NULL; + struct threadArg arg; + natsOptions *opts = NULL; + natsConnection *nc = NULL; + natsPid serverPid = NATS_INVALID_PID; + microService *m = NULL; + microServiceInfo *info = NULL; + natsMsg *reply = NULL; + int i, j, n; + char buf[1024]; + char *subject = NULL; + nats_JSON *js = NULL; + char **array; + int array_len; + const char *str; + + microEndpointConfig default_ep_cfg = { + .name = "default", + .handler = micro_basics_handle_request, + }; + microEndpointConfig ep1_cfg = { + .name = "ep1", + .handler = micro_basics_handle_request, + }; + microEndpointConfig ep2_cfg = { + .name = "ep2", + .subject = "different-from-name", + .handler = micro_basics_handle_request, + }; + microEndpointConfig ep3_cfg = { + .name = "ep3", + .handler = micro_basics_handle_request, + }; + microEndpointConfig *all_ep_cfgs[] = {&ep1_cfg, &ep2_cfg, &ep3_cfg}; + + microServiceConfig minimal_cfg = { + .version = "1.0.0", + .name = "minimal", + }; + microServiceConfig full_cfg = { + .version = "1.0.0", + .name = "full", + .endpoint = &default_ep_cfg, + .description = "fully declared microservice", + }; + microServiceConfig err_no_name_cfg = { + .version = "1.0.0", + }; + microServiceConfig err_no_version_cfg = { + .name = "minimal", + }; + microServiceConfig err_invalid_version_cfg = { + .version = "BLAH42", + .name = "minimal", + }; + + add_service_test_case_t tcs[] = { + { + .name = "Minimal", + .cfg = &minimal_cfg, + }, + { + .name = "Full", + .cfg = &full_cfg, + .expected_num_subjects = 1, + }, + { + .name = "Full-with-endpoints", + .cfg = &full_cfg, + .endpoints = all_ep_cfgs, + .num_endpoints = sizeof(all_ep_cfgs) / sizeof(all_ep_cfgs[0]), + .expected_num_subjects = 4, + }, + { + .name = "Err-null-connection", + .cfg = &minimal_cfg, + .null_nc = true, + .expected_err = "16:400: Invalid function argument", + }, + { + .name = "Err-null-receiver", + .cfg = &minimal_cfg, + .null_receiver = true, + .expected_err = "16:400: Invalid function argument", + }, + { + .name = "Err-no-name", + .cfg = &err_no_name_cfg, + .expected_err = "16:400: Invalid function argument", + }, + { + .name = "Err-no-version", + .cfg = &err_no_version_cfg, + .expected_err = "16:400: Invalid function argument", + }, + { + .name = "Err-invalid-version", + .cfg = &err_invalid_version_cfg, + // TODO: validate the version format. + }, + }; + add_service_test_case_t tc; + + srand((unsigned int)nats_NowInNanoSeconds()); + + s = _createDefaultThreadArgsForCbTests(&arg); + if (s == NATS_OK) + opts = _createReconnectOptions(); + if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) + { + FAIL("Unable to setup test for MicroConnectionEvents!"); + } + + serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); + CHECK_SERVER_STARTED(serverPid); + + test("Connect to server: "); + testCond(NATS_OK == natsConnection_Connect(&nc, opts)); + + for (n = 0; n < (int)(sizeof(tcs) / sizeof(tcs[0])); n++) + { + tc = tcs[n]; + + snprintf(buf, sizeof(buf), "%s: AddService: ", tc.name); + test(buf); + err = micro_AddService( + tc.null_receiver ? NULL : &m, + tc.null_nc ? NULL : nc, + tc.cfg); + if (nats_IsStringEmpty(tc.expected_err)) + { + testCond(err == NULL); + } + else + { + if (strcmp(tc.expected_err, microError_String(err, buf, sizeof(buf))) != 0) + { + char buf2[2*1024]; + snprintf(buf2, sizeof(buf2), "Expected error '%s', got '%s'", tc.expected_err, buf); + FAIL(buf2); + } + testCond(true); + microError_Destroy(err); + continue; + } + + for (i = 0; i < tc.num_endpoints; i++) + { + snprintf(buf, sizeof(buf), "%s: AddEndpoint '%s': ", tc.name, tc.endpoints[i]->name); + test(buf); + testCond(NULL == microService_AddEndpoint(m, tc.endpoints[i])); + } + + err = microService_GetInfo(&info, m); + if (err != NULL) + FAIL("failed to get service info!") + + // run through PING and INFO subject variations: 0: all, 1: .name, 2: .name.id + for (j = 0; j < 3; j++) + { + err = micro_new_control_subject(&subject, MICRO_PING_VERB, + (j > 0 ? tc.cfg->name : NULL), + (j > 1 ? info->id : NULL)); + if (err != NULL) + FAIL("failed to generate PING subject!"); + + snprintf(buf, sizeof(buf), "%s: Verify PING subject %s: ", tc.name, subject); + test(buf); + s = natsConnection_Request(&reply, nc, subject, NULL, 0, 1000); + IFOK(s, nats_JSONParse(&js, natsMsg_GetData(reply), natsMsg_GetDataLength(reply))); + IFOK(s, nats_JSONGetStrPtr(js, "id", &str)); + IFOK(s, (strcmp(str, info->id) == 0) ? NATS_OK : NATS_ERR); + IFOK(s, nats_JSONGetStrPtr(js, "name", &str)); + IFOK(s, (strcmp(str, tc.cfg->name) == 0) ? NATS_OK : NATS_ERR); + IFOK(s, nats_JSONGetStrPtr(js, "version", &str)); + IFOK(s, (strcmp(str, tc.cfg->version) == 0) ? NATS_OK : NATS_ERR); + IFOK(s, nats_JSONGetStrPtr(js, "type", &str)); + IFOK(s, (strcmp(str, MICRO_PING_RESPONSE_TYPE) == 0) ? NATS_OK : NATS_ERR); + testCond(s == NATS_OK); + nats_JSONDestroy(js); + natsMsg_Destroy(reply); + NATS_FREE(subject); + + err = micro_new_control_subject(&subject, MICRO_INFO_VERB, + (j > 0 ? tc.cfg->name : NULL), + (j > 1 ? info->id : NULL)); + if (err != NULL) + FAIL("failed to generate INFO subject!"); + + snprintf(buf, sizeof(buf), "%s: Verify INFO subject %s: ", tc.name, subject); + test(buf); + s = natsConnection_Request(&reply, nc, subject, NULL, 0, 1000); + + IFOK(s, nats_JSONParse(&js, natsMsg_GetData(reply), natsMsg_GetDataLength(reply))); + IFOK(s, nats_JSONGetStrPtr(js, "id", &str)); + IFOK(s, (strcmp(str, info->id) == 0) ? NATS_OK : NATS_ERR); + IFOK(s, nats_JSONGetStrPtr(js, "name", &str)); + IFOK(s, (strcmp(str, tc.cfg->name) == 0) ? NATS_OK : NATS_ERR); + IFOK(s, nats_JSONGetStrPtr(js, "description", &str)); + IFOK(s, (!tc.cfg->description || strcmp(str, tc.cfg->description) == 0) ? NATS_OK : NATS_ERR); + IFOK(s, nats_JSONGetStrPtr(js, "type", &str)); + IFOK(s, (strcmp(str, MICRO_INFO_RESPONSE_TYPE) == 0) ? NATS_OK : NATS_ERR); + array = NULL; + array_len = 0; + IFOK(s, nats_JSONGetArrayStr(js, "subjects", &array, &array_len)); + IFOK(s, (array_len == tc.expected_num_subjects) ? NATS_OK : NATS_ERR); + testCond(s == NATS_OK); + for (int ia=0; iasubjects_len == expected_num_subjects); + + test("Verify subjects: "); + for (i = 0; i < info->subjects_len; i++) + { + if (strcmp(info->subjects[i], expected_subjects[i]) != 0) { + char buf[1024]; + snprintf(buf, sizeof(buf), "expected %s, got %s", expected_subjects[i], info->subjects[i]); + FAIL(buf); + } + } + testCond(true); + + microServiceInfo_Destroy(info); + microService_Destroy(m); + natsConnection_Destroy(nc); + natsOptions_Destroy(opts); + _destroyDefaultThreadArgs(&arg); + _stopServer(serverPid); +} + #define NUM_BASIC_MICRO_SERVICES 5 static void @@ -32261,7 +32602,7 @@ test_MicroBasics(void) .endpoint = &ep_cfg, }; natsMsg *reply = NULL; - microServiceInfo *info = NATS_CALLOC(1, sizeof(microServiceInfo)); + microServiceInfo *info = NULL; int i; char buf[256]; char *subject = NULL; @@ -32273,6 +32614,7 @@ test_MicroBasics(void) int n; nats_JSON **array; int array_len; + const char *str; srand((unsigned int)nats_NowInNanoSeconds()); @@ -32305,6 +32647,7 @@ test_MicroBasics(void) s = natsConnection_Request(&reply, nc, "svc.do", NULL, 0, 1000); if (NATS_OK != s) FAIL("Unable to send request"); + natsMsg_Destroy(reply); } testCond(NATS_OK == s); @@ -32318,6 +32661,7 @@ test_MicroBasics(void) (strcmp(info->name, "CoolService") == 0) && (strlen(info->description) > 0) && (strlen(info->version) > 0)); + microServiceInfo_Destroy(info); } // Make sure we can request valid info with $SVC.INFO request. @@ -32341,14 +32685,17 @@ test_MicroBasics(void) testCond(NATS_OK == s); snprintf(buf, sizeof(buf), "Validate INFO response #%d: ", i); test(buf); - testCond(strnstr(reply->data, "\"name\":\"CoolService\"", reply->dataLen) != NULL); + testCond((NATS_OK == nats_JSONParse(&js, reply->data, reply->dataLen)) && + (NATS_OK == nats_JSONGetStrPtr(js, "name", &str)) && + (strcmp(str, "CoolService") == 0)); + nats_JSONDestroy(js); natsMsg_Destroy(reply); } natsSubscription_Destroy(sub); natsInbox_Destroy(inbox); NATS_FREE(subject); - // Make sure we can request valid info with $SVC.INFO request. + // Make sure we can request SVC.PING. test("Create PING inbox: "); testCond(NATS_OK == natsInbox_Create(&inbox)); micro_new_control_subject(&subject, MICRO_PING_VERB, "CoolService", NULL); @@ -32369,14 +32716,17 @@ test_MicroBasics(void) testCond(NATS_OK == s); snprintf(buf, sizeof(buf), "Validate PING response #%d: ", i); test(buf); - testCond(strnstr(reply->data, "\"name\":\"CoolService\"", reply->dataLen) != NULL); + testCond((NATS_OK == nats_JSONParse(&js, reply->data, reply->dataLen)) && + (NATS_OK == nats_JSONGetStrPtr(js, "name", &str)) && + (strcmp(str, "CoolService") == 0)); + nats_JSONDestroy(js); natsMsg_Destroy(reply); } natsSubscription_Destroy(sub); natsInbox_Destroy(inbox); NATS_FREE(subject); - // Get and validate stats from all service instances. + // Get and validate $SVC.STATS from all service instances. test("Create STATS inbox: "); testCond(NATS_OK == natsInbox_Create(&inbox)); micro_new_control_subject(&subject, MICRO_STATS_VERB, "CoolService", NULL); @@ -32401,6 +32751,8 @@ test_MicroBasics(void) if ((NATS_OK != s) || (js == NULL)) FAIL("Unable to parse JSON response"); + array = NULL; + array_len = 0; s = nats_JSONGetArrayObject(js, "endpoints", &array, &array_len); if ((NATS_OK != s) || (array == NULL) || (array_len != 1)) FAIL("Invalid 'endpoints', expected exactly 1"); @@ -32417,6 +32769,7 @@ test_MicroBasics(void) FAIL("no 'num_errors' in endpoint stats"); num_errors += n; + NATS_FREE(array); nats_JSONDestroy(js); natsMsg_Destroy(reply); } @@ -32436,6 +32789,7 @@ test_MicroBasics(void) { microService_Destroy(svcs[i]); } + NATS_FREE(svcs); natsConnection_Destroy(nc); natsOptions_Destroy(opts); @@ -32479,7 +32833,7 @@ test_MicroServiceStopsOnClosedConn(void) test("Test microservice is running: "); testCond(!microService_IsStopped(m)) - test("Test microservice is responding to PING: "); + test("Test microservice is responding to PING: "); testCond(NATS_OK == natsConnection_RequestString(&reply, nc, "$SRV.PING.test", "", 500)); natsMsg_Destroy(reply); reply = NULL; @@ -32487,16 +32841,16 @@ test_MicroServiceStopsOnClosedConn(void) test("Stop microservice: "); testCond(NULL == microService_Stop(m)) - test("Test microservice is not running: "); + test("Test microservice is not running: "); testCond(microService_IsStopped(m)) - test("Test microservice is not responding to PING: "); + test("Test microservice is not responding to PING: "); testCond(NATS_OK != natsConnection_RequestString(&reply, nc, "$SRV.PING.test", "", 500)); test("Destroy microservice (cleanup): "); testCond(NULL == microService_Destroy(m)) - test("Start microservice again: "); + test("Start microservice again: "); testCond(NULL == micro_AddService(&m, nc, &cfg)); test("Close the connection: "); @@ -32551,7 +32905,7 @@ test_MicroServiceStopsWhenServerStops(void) test("Test microservice is running: "); testCond(!microService_IsStopped(m)) - test("Stop the server: "); + test("Stop the server: "); testCond((_stopServer(serverPid), true)); test("Wait for the service to stop: "); @@ -32560,7 +32914,7 @@ test_MicroServiceStopsWhenServerStops(void) test("Test microservice is not running: "); testCond(microService_IsStopped(m)) - microService_Destroy(m); + microService_Destroy(m); natsOptions_Destroy(opts); natsConnection_Destroy(nc); _destroyDefaultThreadArgs(&arg); @@ -35132,6 +35486,8 @@ static testInfo allTests[] = {"KeyValueMirrorCrossDomains", test_KeyValueMirrorCrossDomains}, {"MicroMatchEndpointSubject", test_MicroMatchEndpointSubject}, + {"MicroAddService", test_MicroAddService}, + {"MicroGroups", test_MicroGroups}, {"MicroBasics", test_MicroBasics}, {"MicroServiceStopsOnClosedConn", test_MicroServiceStopsOnClosedConn}, {"MicroServiceStopsWhenServerStops", test_MicroServiceStopsWhenServerStops}, From 95388fb07e13b2d8187b3a5a6987adb3e949dd16 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Thu, 4 May 2023 08:12:39 -0700 Subject: [PATCH 16/85] Docs (sans HTML) and some PR feedback (names) --- examples/examples.h | 1 - examples/micro-arithmetics.c | 50 +- examples/micro-func.c | 60 +- examples/micro-hello.c | 17 +- examples/micro-sequence.c | 50 +- examples/micro-stats.c | 54 +- src/micro.c | 76 +-- src/micro.h | 245 -------- src/micro_args.c | 1 - src/micro_args.h | 2 - src/micro_client.c | 3 +- src/micro_endpoint.c | 37 +- src/micro_error.c | 1 - src/micro_monitoring.c | 59 +- src/micro_request.c | 41 +- src/microp.h | 13 +- src/nats.h | 1146 ++++++++++++++++++++++++++++++++++ src/util.c | 2 - test/test.c | 115 ++-- 19 files changed, 1448 insertions(+), 525 deletions(-) delete mode 100644 src/micro.h diff --git a/examples/examples.h b/examples/examples.h index d321bb465..edf83881c 100644 --- a/examples/examples.h +++ b/examples/examples.h @@ -15,7 +15,6 @@ #define EXAMPLES_H_ #include -#include #include #include #include diff --git a/examples/micro-arithmetics.c b/examples/micro-arithmetics.c index ebe17c20f..17bf44dfd 100644 --- a/examples/micro-arithmetics.c +++ b/examples/micro-arithmetics.c @@ -36,16 +36,19 @@ handle_arithmetics_op(microRequest *req, arithmeticsOP op) char buf[1024]; int len = 0; - MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); + err = micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req)); if ((err == NULL) && (microArgs_Count(args) != 2)) { err = micro_Errorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args)); } - - MICRO_CALL(err, microArgs_GetFloat(&a1, args, 0)); - MICRO_CALL(err, microArgs_GetFloat(&a2, args, 1)); - MICRO_CALL(err, op(&result, a1, a2)); - MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf", result)); + if (err == NULL) + err = microArgs_GetFloat(&a1, args, 0); + if (err == NULL) + err = microArgs_GetFloat(&a2, args, 1); + if (err == NULL) + err = op(&result, a1, a2); + if (err == NULL) + len = snprintf(buf, sizeof(buf), "%Lf", result); microRequest_Respond(req, &err, buf, len); microArgs_Destroy(args); @@ -88,21 +91,21 @@ int main(int argc, char **argv) char errorbuf[1024]; microServiceConfig cfg = { - .description = "Arithmetic operations - NATS microservice example in C", - .name = "c-arithmetics", - .version = "1.0.0", + .Description = "Arithmetic operations - NATS microservice example in C", + .Name = "c-arithmetics", + .Version = "1.0.0", }; microEndpointConfig add_cfg = { - .name = "add", - .handler = handle_add, + .Name = "add", + .Handler = handle_add, }; microEndpointConfig divide_cfg = { - .name = "divide", - .handler = handle_divide, + .Name = "divide", + .Handler = handle_divide, }; microEndpointConfig multiply_cfg = { - .name = "multiply", - .handler = handle_multiply, + .Name = "multiply", + .Handler = handle_multiply, }; // Connect to NATS server @@ -116,16 +119,21 @@ int main(int argc, char **argv) } // Create the Microservice that listens on nc. - MICRO_CALL(err, micro_AddService(&m, conn, &cfg)); + err = micro_AddService(&m, conn, &cfg); // Add the endpoints for the functions. - MICRO_CALL(err, microService_AddGroup(&g, m, "op")); - MICRO_CALL(err, microGroup_AddEndpoint(g, &add_cfg)); - MICRO_CALL(err, microGroup_AddEndpoint(g, &multiply_cfg)); - MICRO_CALL(err, microGroup_AddEndpoint(g, ÷_cfg)); + if (err == NULL) + microService_AddGroup(&g, m, "op"); + if (err == NULL) + err = microGroup_AddEndpoint(g, &add_cfg); + if (err == NULL) + err = microGroup_AddEndpoint(g, &multiply_cfg); + if (err == NULL) + err = microGroup_AddEndpoint(g, ÷_cfg); // Run the service, until stopped. - MICRO_CALL(err, microService_Run(m)); + if (err == NULL) + err = microService_Run(m); // Cleanup. microService_Destroy(m); diff --git a/examples/micro-func.c b/examples/micro-func.c index e3fc45c71..59b682ff2 100644 --- a/examples/micro-func.c +++ b/examples/micro-func.c @@ -38,11 +38,15 @@ call_arithmetics(long double *result, natsConnection *nc, const char *subject, l char buf[1024]; int len; - MICRO_CALL(err, micro_NewClient(&client, nc, NULL)); - MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf %Lf", a1, a2)); - MICRO_CALL(err, microClient_DoRequest(&response, client, subject, buf, len)); - MICRO_CALL(err, micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response))); - MICRO_CALL(err, microArgs_GetFloat(result, args, 0)); + err = micro_NewClient(&client, nc, NULL); + if (err == NULL) + len = snprintf(buf, sizeof(buf), "%Lf %Lf", a1, a2); + if (err == NULL) + err = microClient_DoRequest(&response, client, subject, buf, len); + if (err == NULL) + err = micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response)); + if (err == NULL) + err = microArgs_GetFloat(result, args, 0); microClient_Destroy(client); natsMsg_Destroy(response); @@ -131,15 +135,18 @@ handle_function_op(microRequest *req, functionHandler op) char buf[1024]; int len = 0; - MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); + err = micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req)); if ((err == NULL) && (microArgs_Count(args) != 1)) { err = micro_Errorf(400, "Invalid number of arguments, expected 1 got %d", microArgs_Count(args)); } - MICRO_CALL(err, microArgs_GetInt(&n, args, 0)); - MICRO_CALL(err, op(&result, microRequest_GetConnection(req), n)); - MICRO_DO(err, len = snprintf(buf, sizeof(buf), "%Lf", result)); + if (err == NULL) + microArgs_GetInt(&n, args, 0); + if (err == NULL) + err = op(&result, microRequest_GetConnection(req), n); + if (err == NULL) + len = snprintf(buf, sizeof(buf), "%Lf", result); microRequest_Respond(req, &err, buf, len); microArgs_Destroy(args); @@ -162,21 +169,21 @@ int main(int argc, char **argv) char errorbuf[1024]; microServiceConfig cfg = { - .description = "Functions - NATS microservice example in C", - .name = "c-functions", - .version = "1.0.0", + .Description = "Functions - NATS microservice example in C", + .Name = "c-functions", + .Version = "1.0.0", }; microEndpointConfig factorial_cfg = { - .name = "factorial", - .handler = handle_factorial, + .Name = "factorial", + .Handler = handle_factorial, }; microEndpointConfig fibonacci_cfg = { - .name = "fibonacci", - .handler = handle_fibonacci, + .Name = "fibonacci", + .Handler = handle_fibonacci, }; microEndpointConfig power2_cfg = { - .name = "power2", - .handler = handle_power2, + .Name = "power2", + .Handler = handle_power2, }; // Connect to NATS server @@ -190,16 +197,21 @@ int main(int argc, char **argv) } // Create the Microservice that listens on nc. - MICRO_CALL(err, micro_AddService(&m, conn, &cfg)); + err = micro_AddService(&m, conn, &cfg); // Add the endpoints for the functions. - MICRO_CALL(err, microService_AddGroup(&g, m, "f")); - MICRO_CALL(err, microGroup_AddEndpoint(g, &factorial_cfg)); - MICRO_CALL(err, microGroup_AddEndpoint(g, &fibonacci_cfg)); - MICRO_CALL(err, microGroup_AddEndpoint(g, &power2_cfg)); + if (err == NULL) + err = microService_AddGroup(&g, m, "f"); + if (err == NULL) + err = microGroup_AddEndpoint(g, &factorial_cfg); + if (err == NULL) + err = microGroup_AddEndpoint(g, &fibonacci_cfg); + if (err == NULL) + err = microGroup_AddEndpoint(g, &power2_cfg); // Run the service, until stopped. - MICRO_CALL(err, microService_Run(m)); + if (err == NULL) + err = microService_Run(m); // Cleanup. microService_Destroy(m); diff --git a/examples/micro-hello.c b/examples/micro-hello.c index 028b4ab6f..10b06863d 100644 --- a/examples/micro-hello.c +++ b/examples/micro-hello.c @@ -55,14 +55,14 @@ int main(int argc, char **argv) char errorbuf[1024]; microEndpointConfig hello_cfg = { - .name = "hello", - .handler = handle, + .Name = "hello", + .Handler = handle, }; microServiceConfig cfg = { - .description = "Hello World! - NATS microservice example in C", - .name = "c-hello", - .version = "1.0.0", - .endpoint = &hello_cfg, + .Description = "Hello World! - NATS microservice example in C", + .Name = "c-hello", + .Version = "1.0.0", + .Endpoint = &hello_cfg, }; // Connect and start the services @@ -74,8 +74,9 @@ int main(int argc, char **argv) nats_PrintLastErrorStack(stderr); return 1; } - MICRO_CALL(err, micro_AddService(&m, conn, &cfg)); - MICRO_CALL(err, microService_Run(m)); + err = micro_AddService(&m, conn, &cfg); + if (err == NULL) + err = microService_Run(m); microService_Destroy(m); if (err != NULL) diff --git a/examples/micro-sequence.c b/examples/micro-sequence.c index 9583d5ca3..faa39f8ea 100644 --- a/examples/micro-sequence.c +++ b/examples/micro-sequence.c @@ -69,10 +69,13 @@ call_function(long double *result, natsConnection *nc, const char *subject, int len = snprintf(buf, sizeof(buf), "%d", n); snprintf(sbuf, sizeof(sbuf), "f.%s", subject); - MICRO_CALL(err, micro_NewClient(&client, nc, NULL)); - MICRO_CALL(err, microClient_DoRequest(&response, client, sbuf, buf, len)); - MICRO_CALL(err, micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response))); - MICRO_CALL(err, microArgs_GetFloat(result, args, 0)); + err = micro_NewClient(&client, nc, NULL); + if (err == NULL) + err = microClient_DoRequest(&response, client, sbuf, buf, len); + if (err == NULL) + err = micro_ParseArgs(&args, natsMsg_GetData(response), natsMsg_GetDataLength(response)); + if (err == NULL) + err = microArgs_GetFloat(result, args, 0); microClient_Destroy(client); natsMsg_Destroy(response); @@ -97,13 +100,14 @@ static void handle_sequence(microRequest *req) char result[64]; int result_len = 0; - MICRO_CALL(err, micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req))); + err = micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req)); if ((err == NULL) && (microArgs_Count(args) != 2)) { err = micro_Errorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args)); } - MICRO_CALL(err, microArgs_GetString(&function, args, 0)); + if (err == NULL) + err = microArgs_GetString(&function, args, 0); if ((err == NULL) && (strcmp(function, "factorial") != 0) && (strcmp(function, "power2") != 0) && @@ -111,8 +115,8 @@ static void handle_sequence(microRequest *req) { err = micro_Errorf(400, "Invalid function name '%s', must be 'factorial', 'power2', or 'fibonacci'", function); } - - MICRO_CALL(err, microArgs_GetInt(&n, args, 1)); + if (err == NULL) + err = microArgs_GetInt(&n, args, 1); if ((err == NULL) && (n < 1)) { err = micro_Errorf(400, "Invalid number of iterations %d, must be at least 1", n); @@ -120,15 +124,18 @@ static void handle_sequence(microRequest *req) for (i = 1; (err == NULL) && (i <= n); i++) { - MICRO_CALL(err, call_function(&denominator, nc, function, i)); + if (err == NULL) + err = call_function(&denominator, nc, function, i); if ((err == NULL) && (denominator == 0)) { err = micro_Errorf(500, "division by zero at step %d", i); } - MICRO_DO(err, value = value + initialValue / denominator); + if (err == NULL) + value = value + initialValue / denominator; } - MICRO_DO(err, result_len = snprintf(result, sizeof(result), "%Lf", value)); + if (err == NULL) + result_len = snprintf(result, sizeof(result), "%Lf", value); microRequest_Respond(req, &err, result, result_len); microArgs_Destroy(args); @@ -144,16 +151,16 @@ int main(int argc, char **argv) char errorbuf[1024]; microEndpointConfig sequence_cfg = { - .subject = "sequence", - .name = "sequence-service", - .handler = handle_sequence, - .schema = NULL, + .Subject = "sequence", + .Name = "sequence-service", + .Handler = handle_sequence, + .Schema = NULL, }; microServiceConfig cfg = { - .description = "Sequence adder - NATS microservice example in C", - .name = "c-sequence", - .version = "1.0.0", - .endpoint = &sequence_cfg, + .Description = "Sequence adder - NATS microservice example in C", + .Name = "c-sequence", + .Version = "1.0.0", + .Endpoint = &sequence_cfg, }; opts = parseArgs(argc, argv, ""); @@ -165,8 +172,9 @@ int main(int argc, char **argv) return 1; } - MICRO_CALL(err, micro_AddService(&m, conn, &cfg)); - MICRO_CALL(err, microService_Run(m)); + err = micro_AddService(&m, conn, &cfg); + if (err == NULL) + err = microService_Run(m); microService_Destroy(m); if (err != NULL) diff --git a/examples/micro-stats.c b/examples/micro-stats.c index ec6a950ca..67bec2d31 100644 --- a/examples/micro-stats.c +++ b/examples/micro-stats.c @@ -51,14 +51,14 @@ static void handle_stats(microRequest *req) service_state_t *service_state = microRequest_GetServiceState(req); int total, custom, len; - err = microService_GetStats(&stats, req->service); + err = microService_GetStats(&stats, req->Service); if (err != NULL) { microRequest_Respond(req, &err, NULL, 0); return; } - total = stats->endpoints[0].num_requests; + total = stats->Endpoints[0].num_requests; custom = service_state->odd_count; len = snprintf(buf, sizeof(buf), "{\"total\":%d,\"odd\":%d}", total, custom); @@ -75,36 +75,36 @@ run_example(natsConnection *conn, microRequestHandler stats_handler, char *buf, .odd_count = 0, }; microEndpointConfig default_cfg = { - .name = "default", - .handler = handle_default, + .Name = "default", + .Handler = handle_default, }; microServiceConfig cfg = { - .name = "c-stats", - .description = "NATS microservice in C with a custom stats handler", - .version = "1.0.0", - .endpoint = &default_cfg, - .stats_handler = stats_handler, - .state = &service_state, + .Name = "c-stats", + .Description = "NATS microservice in C with a custom stats handler", + .Version = "1.0.0", + .Endpoint = &default_cfg, + .StatsHandler = stats_handler, + .State = &service_state, }; int i; int len; natsMsg *resp = NULL; natsMsg *stats_resp = NULL; - MICRO_CALL(err, micro_AddService(&m, conn, &cfg)); - MICRO_CALL(err, micro_NewClient(&c, conn, NULL)); - + err = micro_AddService(&m, conn, &cfg); if (err == NULL) + err = micro_NewClient(&c, conn, NULL); + for (i = 0; (err == NULL) && (i < 10); i++) { - for (i = 0; i < 10; i++) - { - len = snprintf(buf, buf_cap, "%d", i); - MICRO_CALL(err, microClient_DoRequest(&resp, c, "default", buf, len)); - MICRO_DO(err, natsMsg_Destroy(resp)); - } + len = snprintf(buf, buf_cap, "%d", i); + if (err == NULL) + err = microClient_DoRequest(&resp, c, "default", buf, len); + if (err == NULL) + natsMsg_Destroy(resp); } - MICRO_CALL(err, microClient_DoRequest(&stats_resp, c, "$SRV.STATS.c-stats", "", 0)); + if (err == NULL) + err = microClient_DoRequest(&stats_resp, c, "$SRV.STATS.c-stats", "", 0); if (err == NULL) { len = natsMsg_GetDataLength(stats_resp); @@ -130,13 +130,17 @@ int main(int argc, char **argv) natsConnection *conn = NULL; char buf[2048]; - MICRO_CALL(err, micro_ErrorFromStatus(natsConnection_Connect(&conn, opts))); + err = micro_ErrorFromStatus(natsConnection_Connect(&conn, opts)); - MICRO_CALL(err, run_example(conn, NULL, buf, sizeof(buf))); - MICRO_DO(err, printf("Default stats response:\n----\n%s\n----\n\n", buf)); + if (err == NULL) + err = run_example(conn, NULL, buf, sizeof(buf)); + if (err == NULL) + printf("Default stats response:\n----\n%s\n----\n\n", buf); - MICRO_CALL(err, run_example(conn, handle_stats, buf, sizeof(buf))); - MICRO_DO(err, printf("Custom stats response:\n----\n%s\n----\n\n", buf)); + if (err == NULL) + err = run_example(conn, handle_stats, buf, sizeof(buf)); + if (err == NULL) + printf("Custom stats response:\n----\n%s\n----\n\n", buf); if (err != NULL) { diff --git a/src/micro.c b/src/micro.c index 539acfada..58961d970 100644 --- a/src/micro.c +++ b/src/micro.c @@ -11,7 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "micro.h" #include "microp.h" #include "conn.h" #include "mem.h" @@ -37,7 +36,7 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c microError *err = NULL; microService *m = NULL; - if ((new_m == NULL) || (nc == NULL) || (cfg == NULL) || !micro_is_valid_name(cfg->name) || nats_IsStringEmpty(cfg->version)) + if ((new_m == NULL) || (nc == NULL) || (cfg == NULL) || !micro_is_valid_name(cfg->Name) || nats_IsStringEmpty(cfg->Version)) return micro_ErrorInvalidArg; // Make a microservice object, with a reference to a natsConnection. @@ -60,7 +59,7 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c MICRO_CALL(err, wrap_connection_event_callbacks(m)) // Add the endpoints and monitoring subscriptions. - MICRO_CALL(err, microService_AddEndpoint(m, cfg->endpoint)); + MICRO_CALL(err, microService_AddEndpoint(m, cfg->Endpoint)); MICRO_CALL(err, micro_init_monitoring(m)); if (err != NULL) @@ -258,14 +257,14 @@ microService_GetInfo(microServiceInfo **new_info, microService *m) if (info == NULL) return micro_ErrorOutOfMemory; - info->name = m->cfg->name; - info->version = m->cfg->version; - info->description = m->cfg->description; - info->id = m->id; - info->type = MICRO_INFO_RESPONSE_TYPE; + info->Name = m->cfg->Name; + info->Version = m->cfg->Version; + info->Description = m->cfg->Description; + info->Id = m->id; + info->Type = MICRO_INFO_RESPONSE_TYPE; - info->subjects = NATS_CALLOC(m->endpoints_len, sizeof(char *)); - if (info->subjects == NULL) + info->Subjects = NATS_CALLOC(m->endpoints_len, sizeof(char *)); + if (info->Subjects == NULL) { NATS_FREE(info); return micro_ErrorOutOfMemory; @@ -277,11 +276,11 @@ microService_GetInfo(microServiceInfo **new_info, microService *m) microEndpoint *ep = m->endpoints[i]; if ((ep != NULL) && (ep->subject != NULL)) { - info->subjects[len] = ep->subject; + info->Subjects[len] = ep->subject; len++; } } - info->subjects_len = len; + info->SubjectsLen = len; natsMutex_Unlock(m->mu); *new_info = info; @@ -294,7 +293,7 @@ void microServiceInfo_Destroy(microServiceInfo *info) return; // subjects themselves must not be freed, just the collection. - NATS_FREE(info->subjects); + NATS_FREE(info->Subjects); NATS_FREE(info); } @@ -313,15 +312,15 @@ microService_GetStats(microServiceStats **new_stats, microService *m) if (stats == NULL) return micro_ErrorOutOfMemory; - stats->name = m->cfg->name; - stats->version = m->cfg->version; - stats->id = m->id; - stats->started = m->started; - stats->type = MICRO_STATS_RESPONSE_TYPE; + stats->Name = m->cfg->Name; + stats->Version = m->cfg->Version; + stats->Id = m->id; + stats->Started = m->started; + stats->Type = MICRO_STATS_RESPONSE_TYPE; // allocate the actual structs, not pointers. - stats->endpoints = NATS_CALLOC(m->endpoints_len, sizeof(microEndpointStats)); - if (stats->endpoints == NULL) + stats->Endpoints = NATS_CALLOC(m->endpoints_len, sizeof(microEndpointStats)); + if (stats->Endpoints == NULL) { NATS_FREE(stats); return micro_ErrorOutOfMemory; @@ -335,30 +334,30 @@ microService_GetStats(microServiceStats **new_stats, microService *m) { natsMutex_Lock(ep->mu); // copy the entire struct, including the last error buffer. - stats->endpoints[len] = ep->stats; + stats->Endpoints[len] = ep->stats; - stats->endpoints[len].name = ep->name; - stats->endpoints[len].subject = ep->subject; + stats->Endpoints[len].Name = ep->name; + stats->Endpoints[len].Subject = ep->subject; avg = (long double)ep->stats.processing_time_s * 1000000000.0 + (long double)ep->stats.processing_time_ns; avg = avg / (long double)ep->stats.num_requests; - stats->endpoints[len].average_processing_time_ns = (int64_t)avg; + stats->Endpoints[len].average_processing_time_ns = (int64_t)avg; len++; natsMutex_Unlock(ep->mu); } } natsMutex_Unlock(m->mu); - stats->endpoints_len = len; + stats->EndpointsLen = len; *new_stats = stats; return NULL; } -void natsMicroserviceStats_Destroy(microServiceStats *stats) +void microServiceStats_Destroy(microServiceStats *stats) { if (stats == NULL) return; - NATS_FREE(stats->endpoints); + NATS_FREE(stats->Endpoints); NATS_FREE(stats); } @@ -418,10 +417,10 @@ micro_clone_service_config(microServiceConfig **out, microServiceConfig *cfg) } // the strings are declared const for the public, but in a clone these need // to be duplicated. - MICRO_CALL(err, micro_strdup((char **)&new_cfg->name, cfg->name)); - MICRO_CALL(err, micro_strdup((char **)&new_cfg->version, cfg->version)); - MICRO_CALL(err, micro_strdup((char **)&new_cfg->description, cfg->description)); - MICRO_CALL(err, micro_clone_endpoint_config(&new_cfg->endpoint, cfg->endpoint)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Version, cfg->Version)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Description, cfg->Description)); + MICRO_CALL(err, micro_clone_endpoint_config(&new_cfg->Endpoint, cfg->Endpoint)); if (err != NULL) { micro_destroy_cloned_service_config(new_cfg); @@ -439,10 +438,10 @@ void micro_destroy_cloned_service_config(microServiceConfig *cfg) // the strings are declared const for the public, but in a clone these need // to be freed. - NATS_FREE((char *)cfg->name); - NATS_FREE((char *)cfg->version); - NATS_FREE((char *)cfg->description); - micro_destroy_cloned_endpoint_config(cfg->endpoint); + NATS_FREE((char *)cfg->Name); + NATS_FREE((char *)cfg->Version); + NATS_FREE((char *)cfg->Description); + micro_destroy_cloned_endpoint_config(cfg->Endpoint); NATS_FREE(cfg); } @@ -511,9 +510,9 @@ on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *closure) if (our_subject) { - if (m->cfg->err_handler != NULL) + if (m->cfg->ErrHandler != NULL) { - (*m->cfg->err_handler)(m, ep, s); + (*m->cfg->ErrHandler)(m, ep, s); } if (ep != NULL) @@ -573,7 +572,8 @@ microService_AddGroup(microGroup **new_group, microService *m, const char *prefi if ((m == NULL) || (new_group == NULL) || (prefix == NULL)) return micro_ErrorInvalidArg; - *new_group = NATS_CALLOC(1, sizeof(microGroup) + strlen(prefix) + 1); + *new_group = NATS_CALLOC(1, sizeof(microGroup) + + strlen(prefix) + 1); // "prefix."" if (new_group == NULL) { return micro_ErrorOutOfMemory; diff --git a/src/micro.h b/src/micro.h deleted file mode 100644 index 22061f238..000000000 --- a/src/micro.h +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright 2015-2018 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef MICRO_H_ -#define MICRO_H_ - -#include "nats.h" - -#define MICRO_API_PREFIX "$SRV" - -#define MICRO_INFO_RESPONSE_TYPE "io.nats.micro.v1.info_response" -#define MICRO_PING_RESPONSE_TYPE "io.nats.micro.v1.ping_response" -#define MICRO_STATS_RESPONSE_TYPE "io.nats.micro.v1.stats_response" -#define MICRO_STATS_SCHEMA_TYPE "io.nats.micro.v1.schema_response" - -#define MICRO_PING_VERB "PING" -#define MICRO_STATS_VERB "STATS" -#define MICRO_INFO_VERB "INFO" -#define MICRO_SCHEMA_VERB "SCHEMA" - -#define MICRO_STATUS_HDR "Nats-Status" -#define MICRO_ERROR_HDR "Nats-Service-Error" -#define MICRO_ERROR_CODE_HDR "Nats-Service-Error-Code" - -#define MICRO_CALL(__err, __call) \ - if ((__err) == NULL) \ - { \ - __err = (__call); \ - } - -#define MICRO_DO(__err, __block) \ - if ((__err) == NULL) \ - __block; - -/** - * The Microservice object. Create and start with #microService_Create. - */ -typedef struct micro_service_s microService; - -/** - * The Microservice endpoint object. - * TODO document the interface. - */ -typedef struct micro_endpoint_s microEndpoint; - -typedef struct micro_group_s microGroup; - -/** - * The Microservice request object. - */ -typedef struct microRequest -{ - natsMsg *message; - - // service is guaranteed to be set to the microservice processing the - // request; endpoint may be NULL for requests on internal (monitoring) - // subjects. - microService *service; - microEndpoint *endpoint; -} microRequest; - -/** \brief Callback type for request processing. - * - * This is the callback that one provides when creating a microservice endpoint. - * The library will invoke this callback for each message arriving to the - * specified subject. - * - * @see microService_AddEndpoint() - */ -typedef void (*microRequestHandler)(microRequest *req); - -typedef void (*microErrorHandler)(microService *m, microEndpoint *ep, natsStatus s); - -/** - * The Microservice configuration object. The service holds on to it, so it must - * be constant for the lifetime of the service. - */ -typedef struct microServiceConfig -{ - const char *name; - const char *version; - const char *description; - struct microEndpointConfig *endpoint; - microRequestHandler stats_handler; - microErrorHandler err_handler; - void *state; -} microServiceConfig; - -typedef struct microServiceInfo -{ - const char *type; - const char *name; - const char *version; - const char *description; - const char *id; - const char **subjects; - int subjects_len; -} microServiceInfo; - -/** - * The Microservice endpoint configuration object. - */ -typedef struct microEndpointConfig -{ - const char *name; - microRequestHandler handler; - void *state; - const char *subject; - struct microSchema *schema; -} microEndpointConfig; - -/** - * The Microservice endpoint stats struct. - */ -typedef struct microEndpointStats -{ - const char *name; - const char *subject; - int64_t num_requests; - int64_t num_errors; - int64_t processing_time_s; - int64_t processing_time_ns; - int64_t average_processing_time_ns; - char last_error_string[2048]; -} microEndpointStats; - -/** - * The Microservice stats struct. - */ -typedef struct microServiceStats -{ - const char *type; - const char *name; - const char *version; - const char *id; - int64_t started; - int endpoints_len; - microEndpointStats *endpoints; -} microServiceStats; - -/** - * The Microservice endpoint schema object. - */ -typedef struct microSchema -{ - const char *request; - const char *response; -} microSchema; - -/** - * The Microservice client. Initialize with #microClient_Create. - */ -typedef struct micro_client_s microClient; - -/** - * The Microservice configuration object. - */ -typedef struct microClientConfig microClientConfig; - -typedef struct micro_error_s microError; - -// -// microService methods. - -NATS_EXTERN microError *micro_AddService(microService **new_microservice, natsConnection *nc, microServiceConfig *cfg); -NATS_EXTERN microError *microService_AddEndpoint(microService *m, microEndpointConfig *cfg); -NATS_EXTERN microError *microService_AddGroup(microGroup **new_group, microService *m, const char *prefix); -NATS_EXTERN microError *microService_Destroy(microService *m); -NATS_EXTERN natsConnection *microService_GetConnection(microService *m); -NATS_EXTERN bool microService_IsStopped(microService *m); -NATS_EXTERN microError *microService_Run(microService *m); -NATS_EXTERN microError *microService_Stop(microService *m); - -// -// microGroup methods. - -NATS_EXTERN microError *microGroup_AddGroup(microGroup **new_group, microGroup *parent, const char *prefix); -NATS_EXTERN microError *microGroup_AddEndpoint(microGroup *g, microEndpointConfig *cfg); - -// -// microRequest methods. - -NATS_EXTERN microError *microRequest_AddHeader(microRequest *req, const char *key, const char *value); -NATS_EXTERN microError *microRequest_DeleteHeader(microRequest *req, const char *key); -NATS_EXTERN natsConnection *microRequest_GetConnection(microRequest *req); -NATS_EXTERN const char *microRequest_GetData(microRequest *req); -NATS_EXTERN int microRequest_GetDataLength(microRequest *req); -NATS_EXTERN microEndpoint *microRequest_GetEndpoint(microRequest *req); -NATS_EXTERN void *microRequest_GetEndpointState(microRequest *req); -NATS_EXTERN microError *microRequest_GetHeaderKeys(microRequest *req, const char ***keys, int *count); -NATS_EXTERN microError *microRequest_GetHeaderValue(microRequest *req, const char *key, const char **value); -NATS_EXTERN microError *microRequest_GetHeaderValues(microRequest *req, const char *key, const char ***values, int *count); -NATS_EXTERN natsMsg *microRequest_GetMsg(microRequest *req); -NATS_EXTERN const char *microRequest_GetReply(microRequest *req); -NATS_EXTERN microService *microRequest_GetService(microRequest *req); -NATS_EXTERN void *microRequest_GetServiceState(microRequest *req); -NATS_EXTERN uint64_t microRequest_GetSequence(microRequest *req); -NATS_EXTERN const char *microRequest_GetSubject(microRequest *req); -NATS_EXTERN int64_t microRequest_GetTime(microRequest *req); -NATS_EXTERN microError *microRequest_Respond(microRequest *req, microError **err_will_free, const char *data, size_t len); -NATS_EXTERN microError *microRequest_SetHeader(microRequest *req, const char *key, const char *value); - -// -// microError methods. - -NATS_EXTERN microError *micro_Errorf(int code, const char *format, ...); -NATS_EXTERN microError *micro_ErrorFromResponse(natsStatus s, natsMsg *msg); -NATS_EXTERN microError *micro_ErrorFromStatus(natsStatus s); -NATS_EXTERN int microError_Code(microError *err); -NATS_EXTERN void microError_Destroy(microError *err); -NATS_EXTERN natsStatus microError_Status(microError *err); -NATS_EXTERN const char *microError_String(microError *err, char *buf, int len); -NATS_EXTERN microError *microError_Wrapf(microError *err, const char *format, ...); - -// -// microClient methods. -microError *micro_NewClient(microClient **new_client, natsConnection *nc, microClientConfig *cfg); -void microClient_Destroy(microClient *client); -microError *microClient_DoRequest(natsMsg **reply, microClient *client, const char *subject, const char *data, int data_len); - -// -// microServiceInfo methods. - -NATS_EXTERN microError *microService_GetInfo(microServiceInfo **new_info, microService *m); -NATS_EXTERN void microServiceInfo_Destroy(microServiceInfo *info); - -// -// microServiceStats methods. - -NATS_EXTERN microError *microService_GetStats(microServiceStats **new_stats, microService *m); -NATS_EXTERN void natsMicroserviceStats_Destroy(microServiceStats *stats); - -/** @} */ // end of microserviceGroup - -#endif /* MICRO_H_ */ diff --git a/src/micro_args.c b/src/micro_args.c index 917399616..2423c70de 100644 --- a/src/micro_args.c +++ b/src/micro_args.c @@ -11,7 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "micro.h" #include "microp.h" #include "micro_args.h" #include "mem.h" diff --git a/src/micro_args.h b/src/micro_args.h index e4373ac44..8d6d0a345 100644 --- a/src/micro_args.h +++ b/src/micro_args.h @@ -14,8 +14,6 @@ #ifndef MICRO_ARGS_H_ #define MICRO_ARGS_H_ -#include "micro.h" - /** * Request unmarshaled as "arguments", a space-separated list of numbers and strings. * TODO document the interface. diff --git a/src/micro_client.c b/src/micro_client.c index 2636edae3..8cc75ac0b 100644 --- a/src/micro_client.c +++ b/src/micro_client.c @@ -11,7 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "micro.h" #include "microp.h" #include "mem.h" #include "conn.h" @@ -24,7 +23,7 @@ micro_NewClient(microClient **new_client, natsConnection *nc, microClientConfig if (new_client == NULL) return micro_ErrorInvalidArg; - client = NATS_CALLOC(1, sizeof(struct micro_client_s)); + client = NATS_CALLOC(1, sizeof(microClient)); if (client == NULL) return micro_ErrorOutOfMemory; diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index e9b447c4e..09fdb9af5 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -13,7 +13,6 @@ #include -#include "micro.h" #include "microp.h" #include "mem.h" @@ -53,18 +52,18 @@ micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpoint *ep = NULL; const char *subj; - if (!micro_is_valid_name(cfg->name) || (cfg->handler == NULL)) + if (!micro_is_valid_name(cfg->Name) || (cfg->Handler == NULL)) return micro_ErrorInvalidArg; - if ((cfg->subject != NULL) && !micro_is_valid_subject(cfg->subject)) + if ((cfg->Subject != NULL) && !micro_is_valid_subject(cfg->Subject)) return micro_ErrorInvalidArg; - subj = nats_IsStringEmpty(cfg->subject) ? cfg->name : cfg->subject; + subj = nats_IsStringEmpty(cfg->Subject) ? cfg->Name : cfg->Subject; MICRO_CALL(err, new_endpoint(&ep)); MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->mu))); MICRO_CALL(err, micro_clone_endpoint_config(&ep->config, cfg)); - MICRO_CALL(err, dup_with_prefix(&ep->name, prefix, cfg->name)); + MICRO_CALL(err, dup_with_prefix(&ep->name, prefix, cfg->Name)); MICRO_CALL(err, dup_with_prefix(&ep->subject, prefix, subj)); if (err != NULL) { @@ -81,7 +80,7 @@ microError * micro_start_endpoint(microEndpoint *ep) { natsStatus s = NATS_OK; - if ((ep->subject == NULL) || (ep->config == NULL) || (ep->config->handler == NULL)) + if ((ep->subject == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) // nothing to do return NULL; @@ -132,7 +131,7 @@ handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *cl microRequest *req = NULL; int64_t start, elapsed_ns, full_s; - if (ep == NULL || ep->config == NULL || ep->config->handler == NULL) + if (ep == NULL || ep->config == NULL || ep->config->Handler == NULL) { // This is a bug, we should not have received a message on this // subscription. @@ -140,7 +139,7 @@ handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *cl return; } stats = &ep->stats; - handler = ep->config->handler; + handler = ep->config->Handler; err = micro_new_request(&req, ep->m, ep, msg); if (err != NULL) @@ -148,7 +147,7 @@ handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *cl natsMsg_Destroy(msg); return; } - req->endpoint = ep; + req->Endpoint = ep; // handle the request. start = nats_NowInNanoSeconds(); @@ -230,8 +229,8 @@ destroy_schema(microSchema *schema) if (schema == NULL) return; - NATS_FREE((char *)schema->request); - NATS_FREE((char *)schema->response); + NATS_FREE((char *)schema->Request); + NATS_FREE((char *)schema->Response); NATS_FREE(schema); } @@ -249,8 +248,8 @@ clone_schema(microSchema **to, const microSchema *from) if (*to == NULL) return micro_ErrorOutOfMemory; - MICRO_CALL(err, micro_strdup((char **)&((*to)->request), from->request)); - MICRO_CALL(err, micro_strdup((char **)&((*to)->response), from->response)); + MICRO_CALL(err, micro_strdup((char **)&((*to)->Request), from->Request)); + MICRO_CALL(err, micro_strdup((char **)&((*to)->Response), from->Response)); if (err != NULL) { @@ -297,9 +296,9 @@ micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) memcpy(new_cfg, cfg, sizeof(microEndpointConfig)); } - MICRO_CALL(err, micro_strdup((char **)&new_cfg->name, cfg->name)); - MICRO_CALL(err, micro_strdup((char **)&new_cfg->subject, cfg->subject)); - MICRO_CALL(err, clone_schema(&new_cfg->schema, cfg->schema)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Subject, cfg->Subject)); + MICRO_CALL(err, clone_schema(&new_cfg->Schema, cfg->Schema)); if (err != NULL) { @@ -318,10 +317,10 @@ void micro_destroy_cloned_endpoint_config(microEndpointConfig *cfg) // the strings are declared const for the public, but in a clone these need // to be freed. - NATS_FREE((char *)cfg->name); - NATS_FREE((char *)cfg->subject); + NATS_FREE((char *)cfg->Name); + NATS_FREE((char *)cfg->Subject); - destroy_schema(cfg->schema); + destroy_schema(cfg->Schema); NATS_FREE(cfg); } diff --git a/src/micro_error.c b/src/micro_error.c index 57448a46a..950c697de 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -13,7 +13,6 @@ #include -#include "micro.h" #include "microp.h" static microError * diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 05b8e6cc6..dbd728c2e 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -13,7 +13,6 @@ #include -#include "micro.h" #include "microp.h" #include "util.h" @@ -109,17 +108,17 @@ handle_stats_internal(microRequest *req) microServiceStats *stats = NULL; natsBuffer *buf = NULL; - if ((req == NULL) || (req->service == NULL) || (req->service->cfg == NULL)) + if ((req == NULL) || (req->Service == NULL) || (req->Service->cfg == NULL)) return; // Should not happen - MICRO_CALL(err, microService_GetStats(&stats, req->service)); + MICRO_CALL(err, microService_GetStats(&stats, req->Service)); MICRO_CALL(err, marshal_stats(&buf, stats)); // respond for both success and error cases. microRequest_Respond(req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); natsBuf_Destroy(buf); - natsMicroserviceStats_Destroy(stats); + microServiceStats_Destroy(stats); } static void @@ -133,8 +132,8 @@ handle_stats(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *clos if ((m == NULL) || (m->cfg == NULL)) return; // Should not happen - if (m->cfg->stats_handler != NULL) - h = m->cfg->stats_handler; + if (m->cfg->StatsHandler != NULL) + h = m->cfg->StatsHandler; MICRO_CALL(err, micro_new_request(&req, m, NULL, msg)); MICRO_DO(err, h(req)); @@ -247,11 +246,11 @@ add_verb_handlers(microService *m, const char *verb, natsMsgHandler handler) if (err == NULL) { snprintf(name, sizeof(name), "%s-kind", verb); - err = add_internal_handler(m, verb, m->cfg->name, "", name, handler); + err = add_internal_handler(m, verb, m->cfg->Name, "", name, handler); } if (err == NULL) { - err = add_internal_handler(m, verb, m->cfg->name, m->id, verb, handler); + err = add_internal_handler(m, verb, m->cfg->Name, m->id, verb, handler); } return err; } @@ -272,8 +271,8 @@ marshal_ping(natsBuffer **new_buf, microService *m) if (s == NATS_OK) { s = natsBuf_Append(buf, "{", -1); - IFOK_attr("name", m->cfg->name, ","); - IFOK_attr("version", m->cfg->version, ","); + IFOK_attr("name", m->cfg->Name, ","); + IFOK_attr("version", m->cfg->Version, ","); IFOK_attr("id", m->id, ","); IFOK_attr("type", MICRO_PING_RESPONSE_TYPE, ""); IFOK(s, natsBuf_AppendByte(buf, '}')); @@ -297,25 +296,25 @@ marshal_info(natsBuffer **new_buf, microServiceInfo *info) s = natsBuf_Create(&buf, 4096); IFOK(s, natsBuf_Append(buf, "{", -1)); - IFOK_attr("description", info->description, ","); - IFOK_attr("id", info->id, ","); - IFOK_attr("name", info->name, ","); - IFOK_attr("type", info->type, ","); - if ((s == NATS_OK) && (info->subjects_len > 0)) + IFOK_attr("description", info->Description, ","); + IFOK_attr("id", info->Id, ","); + IFOK_attr("name", info->Name, ","); + IFOK_attr("type", info->Type, ","); + if ((s == NATS_OK) && (info->SubjectsLen > 0)) { int i; IFOK(s, natsBuf_Append(buf, "\"subjects\":[", -1)); - for (i = 0; i < info->subjects_len; i++) + for (i = 0; i < info->SubjectsLen; i++) { IFOK(s, natsBuf_Append(buf, "\"", -1)); - IFOK(s, natsBuf_Append(buf, info->subjects[i], -1)); + IFOK(s, natsBuf_Append(buf, info->Subjects[i], -1)); IFOK(s, natsBuf_Append(buf, "\"", -1)); - if (i < (info->subjects_len - 1)) + if (i < (info->SubjectsLen - 1)) IFOK(s, natsBuf_Append(buf, ",", -1)); } IFOK(s, natsBuf_Append(buf, "],", -1)); } - IFOK_attr("version", info->version, ""); + IFOK_attr("version", info->Version, ""); IFOK(s, natsBuf_AppendByte(buf, '}')); if (s != NATS_OK) @@ -338,21 +337,21 @@ marshal_stats(natsBuffer **new_buf, microServiceStats *stats) s = natsBuf_Create(&buf, 8 * 1024); IFOK(s, natsBuf_AppendByte(buf, '{')); - IFOK_attr("id", stats->id, ","); - IFOK_attr("name", stats->name, ","); - IFOK_attr("type", stats->type, ","); - IFOK(s, nats_EncodeTimeUTC(timebuf, sizeof(timebuf), stats->started)); + IFOK_attr("id", stats->Id, ","); + IFOK_attr("name", stats->Name, ","); + IFOK_attr("type", stats->Type, ","); + IFOK(s, nats_EncodeTimeUTC(timebuf, sizeof(timebuf), stats->Started)); IFOK_attr("started", timebuf, ","); - if ((s == NATS_OK) && (stats->endpoints_len > 0)) + if ((s == NATS_OK) && (stats->EndpointsLen > 0)) { IFOK(s, natsBuf_Append(buf, "\"endpoints\":[", -1)); - for (i = 0; i < stats->endpoints_len; i++) + for (i = 0; i < stats->EndpointsLen; i++) { - ep = &stats->endpoints[i]; + ep = &stats->Endpoints[i]; IFOK(s, natsBuf_AppendByte(buf, '{')); - IFOK_attr("name", ep->name, ","); - IFOK_attr("subject", ep->subject, ","); + IFOK_attr("name", ep->Name, ","); + IFOK_attr("subject", ep->Subject, ","); IFOK(s, nats_marshalLong(buf, false, "num_requests", ep->num_requests)); IFOK(s, nats_marshalLong(buf, true, "num_errors", ep->num_errors)); IFOK(s, nats_marshalDuration(buf, true, "average_processing_time", ep->average_processing_time_ns)); @@ -360,13 +359,13 @@ marshal_stats(natsBuffer **new_buf, microServiceStats *stats) IFOK_attr("last_error", ep->last_error_string, ""); IFOK(s, natsBuf_Append(buf, "}", -1)); - if (i < (stats->endpoints_len - 1)) + if (i < (stats->EndpointsLen - 1)) IFOK(s, natsBuf_Append(buf, ",", -1)); } IFOK(s, natsBuf_Append(buf, "],", -1)); } - IFOK_attr("version", stats->version, ""); + IFOK_attr("version", stats->Version, ""); IFOK(s, natsBuf_AppendByte(buf, '}')); if (s == NATS_OK) diff --git a/src/micro_request.c b/src/micro_request.c index fcea5eaa9..c41bf97da 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -11,7 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "micro.h" #include "microp.h" #include "mem.h" @@ -23,16 +22,16 @@ microRequest_Respond(microRequest *req, microError **err_will_free, const char * natsStatus s = NATS_OK; char buf[64]; - if ((req == NULL) || (req->message == NULL) || (req->message->sub == NULL) || (req->message->sub->conn == NULL)) + if ((req == NULL) || (req->Message == NULL) || (req->Message->sub == NULL) || (req->Message->sub->conn == NULL)) { return micro_ErrorInvalidArg; } - IFOK(s, natsMsg_Create(&msg, natsMsg_GetReply(req->message), NULL, data, len)); + IFOK(s, natsMsg_Create(&msg, natsMsg_GetReply(req->Message), NULL, data, len)); if ((s == NATS_OK) && (err_will_free != NULL) && (*err_will_free != NULL)) { err = *err_will_free; - micro_update_last_error(req->endpoint, err); + micro_update_last_error(req->Endpoint, err); if ((s == NATS_OK) && (err->status != NATS_OK)) { s = natsMsgHeader_Set(msg, MICRO_STATUS_HDR, natsStatus_GetText(err->status)); @@ -47,7 +46,7 @@ microRequest_Respond(microRequest *req, microError **err_will_free, const char * s = natsMsgHeader_Set(msg, MICRO_ERROR_CODE_HDR, buf); } } - IFOK(s, natsConnection_PublishMsg(req->message->sub->conn, msg)); + IFOK(s, natsConnection_PublishMsg(req->Message->sub->conn, msg)); natsMsg_Destroy(msg); if (err_will_free != NULL) @@ -77,7 +76,7 @@ microRequest_DeleteHeader(microRequest *req, const char *key) natsConnection * microRequest_GetConnection(microRequest *req) { - return ((req != NULL) && (req->service != NULL)) ? req->service->nc : NULL; + return ((req != NULL) && (req->Service != NULL)) ? req->Service->nc : NULL; } const char * @@ -94,7 +93,7 @@ int microRequest_GetDataLength(microRequest *req) microEndpoint * microRequest_GetEndpoint(microRequest *req) { - return (req != NULL) ? req->endpoint : NULL; + return (req != NULL) ? req->Endpoint : NULL; } microError * @@ -121,7 +120,7 @@ microRequest_GetHeaderValues(microRequest *req, const char *key, const char ***v natsMsg * microRequest_GetMsg(microRequest *req) { - return (req != NULL) ? req->message : NULL; + return (req != NULL) ? req->Message : NULL; } const char * @@ -130,11 +129,6 @@ microRequest_GetReply(microRequest *req) return natsMsg_GetReply(microRequest_GetMsg(req)); } -uint64_t microRequest_GetSequence(microRequest *req) -{ - return natsMsg_GetSequence(microRequest_GetMsg(req)); -} - const char *microRequest_GetSubject(microRequest *req) { return natsMsg_GetSubject(microRequest_GetMsg(req)); @@ -142,25 +136,20 @@ const char *microRequest_GetSubject(microRequest *req) void *microRequest_GetServiceState(microRequest *req) { - if ((req == NULL) || (req->service == NULL) || (req->service->cfg == NULL)) + if ((req == NULL) || (req->Service == NULL) || (req->Service->cfg == NULL)) { return NULL; } - return req->service->cfg->state; + return req->Service->cfg->State; } void *microRequest_GetEndpointState(microRequest *req) { - if ((req == NULL) || (req->endpoint == NULL) || (req->endpoint->config == NULL)) + if ((req == NULL) || (req->Endpoint == NULL) || (req->Endpoint->config == NULL)) { return NULL; } - return req->endpoint->config->state; -} - -int64_t microRequest_GetTime(microRequest *req) -{ - return natsMsg_GetTime(microRequest_GetMsg(req)); + return req->Endpoint->config->State; } microError * @@ -173,7 +162,7 @@ microRequest_SetHeader(microRequest *req, const char *key, const char *value) microService * microRequest_GetService(microRequest *req) { - return (req != NULL) ? req->service : NULL; + return (req != NULL) ? req->Service : NULL; } void micro_destroy_request(microRequest *req) @@ -194,9 +183,9 @@ micro_new_request(microRequest **new_request, microService *m, microEndpoint *ep if (req == NULL) return micro_ErrorOutOfMemory; - req->message = msg; - req->service = m; - req->endpoint = ep; + req->Message = msg; + req->Service = m; + req->Endpoint = ep; *new_request = req; return NULL; } diff --git a/src/microp.h b/src/microp.h index ba3863b8b..63c61f792 100644 --- a/src/microp.h +++ b/src/microp.h @@ -17,6 +17,17 @@ #include "natsp.h" #include "mem.h" +#define MICRO_CALL(__err, __call) \ + if ((__err) == NULL) \ + { \ + __err = (__call); \ + } + +#define MICRO_DO(__err, __block) \ + if ((__err) == NULL) \ + __block; + + #define MICRO_QUEUE_GROUP "q" #define MICRO_DEFAULT_ENDPOINT_NAME "default" @@ -70,7 +81,7 @@ struct micro_service_s { // these are set at initialization time time and do not change. natsConnection *nc; - struct microServiceConfig *cfg; + microServiceConfig *cfg; char id[NUID_BUFFER_LEN + 1]; // groups are just convenient wrappers to make "prefixed" endpoints with diff --git a/src/nats.h b/src/nats.h index 5b53a29a5..dea0809ca 100644 --- a/src/nats.h +++ b/src/nats.h @@ -7104,6 +7104,1152 @@ kvStatus_Destroy(kvStatus *sts); /** @} */ // end of funcGroup +// +// Microservices. +// + +/** \defgroup microGroup EXPERIMENTAL - Microservices + * + * \warning EXPERIMENTAL FEATURE! We reserve the right to change the API without + * necessarily bumping the major version of the library. + * + * ### NATS Microservices. + * + * Microservices can expose one or more request-response endpoints that process + * incoming NATS messages. + * + * Microservices are created by calling micro_AddService, and configured by + * passing a microServiceConfig to it. Many microservices can share a single + * connection to a NATS server. + * + * Once created, a microservice will subscribe to all endpoints' subjects and + * associate them with the configured handlers. It will also subscribe to and + * service monitoring subjects for service-specific pings, metadata, and + * statistics requests. The endpoint subscriptions are created with a queue + * group, so that incoming requests are automatically load-balanced across all + * running instances of a microservice. The monitoring subscriptions are not + * groupped, each service instance receives and responds to all monitoring + * requests. + * + * Once created, the microservice is asyncronous, message handlers and other + * callbacks will be invoked in separate threads. No further action is needed. + * Your program can use microService_Stop, microService_IsStopped to control the + * execution of the service. + * + * @{ + */ + +/** \defgroup microTypes Types + * + * Microservice types. + * + * @{ + */ + +/** + * @brief The Microservice client. + * + * Initialize with #micro_NewClient and destroy with #microClient_Destroy. + * + * @see micro_NewClient, microClient_Destroy + */ +typedef struct micro_client_s microClient; + +/** + * @brief The Microservice configuration object. For forward compatibility only. + */ +typedef struct __for_forward_compatibility_only microClientConfig; + +/** + * @brief `microEndpoint` represents a microservice endpoint. + * + * The only place where this struct is used by the user is in callbacks, to + * identify which endpoint was called, or caused an error. + * + * @see microRequestHandler, microErrorHandler, microServiceConfig, + * microEndpointConfig + */ +typedef struct micro_endpoint_s microEndpoint; + +/** + * @brief The Microservice endpoint configuration object. + * + * @see micro_endpoint_config_s for descriptions of the fields, + * micro_service_config_s, microServiceConfig, microService_AddEndpoint, + * microGroup_AddEndpoint + */ +typedef struct micro_endpoint_config_s microEndpointConfig; + +/** + * @brief The Microservice endpoint-level stats struct. + * + * Returned as part of microEndpointStats. See micro_endpoint_stats_s for + * descriptions of the fields. + * + * @see micro_endpoint_stats_s, microServiceStats, microService_GetStats + */ +typedef struct micro_endpoint_stats_s microEndpointStats; + +/** + * @brief the Microservice error object. + * + * This error type is returned by most microservice functions. You can create + * your own custom errors by using #micro_Errorf, extract errors from response + * messages using #micro_ErrorFromResponse, and wrap existing errors using + * #microError_Wrapf. Errors are heap-allocated and must be freed with either + * #microError_Destroy or by passing it into #microRequest_Respond. + * + * There are no public fields in this struct, use #microError_Code, + * #microError_Status, and #microError_String to get more information about the + * error. + */ +typedef struct micro_error_s microError; + +/** + * @brief a collection of endpoints and other groups, with a + * common prefix to their subjects and names. + * + * It has no other purpose than + * convenience, for organizing endpoint subject space. + */ +typedef struct micro_group_s microGroup; + +/** + * @brief a request received by a microservice endpoint. + * + * @see micro_request_s for descriptions of the fields. + */ +typedef struct micro_request_s microRequest; + +/** + * The Microservice endpoint schema object. + */ +typedef struct micro_schema_s microSchema; + +/** + * @brief the main object for a configured microservice. + * + * It can be created with #micro_AddService, and configured by passing a + * microServiceConfig to it. Once no longer needed, a microservice should be + * destroyed with microService_Destroy. + * + * @see micro_AddService, microServiceConfig, microEndpointConfig, + * microService_Destroy, microService_Stop, microService_IsStopped, + * microService_Run + */ +typedef struct micro_service_s microService; + +/** + * @brief The microservice configuration object. + * + * The service is created with a clone of the config and all of its values, so + * the original can be freed or modified after calling #micro_AddService. See + * micro_service_config_s for descriptions of the fields. + * + * @see micro_service_config_s + */ +typedef struct micro_service_config_s microServiceConfig; + +/** + * @brief information about a running microservice. + * + * microServiceInfo is the struct returned by microService_GetInfo function. It + * is also accessible by sending a `$SRV.INFO.[.]` request to + * the service. See micro_service_info_s for descriptions of the fields. + * + * @see micro_service_info_s, microService_GetInfo + */ +typedef struct micro_service_info_s microServiceInfo; + +/** + * @brief The Microservice service-level stats struct. + * + * @see micro_service_stats_s for descriptions of the fields, + * microService_GetStats + */ +typedef struct micro_service_stats_s microServiceStats; + +/** @} */ // end of microTypes + +/** \defgroup microCallbacks Callbacks + * + * Microservice callbacks. + * @{ + */ + +/** + * @brief Callback type for request processing. + * + * This is the callback that one provides when creating a microservice endpoint. + * The library will invoke this callback for each message arriving to the + * specified subject. + * + * @param req The request object, containing the message and other relevant + * references. + * + * @see microEndpointConfig, micro_endpoint_config_s. + */ +typedef void (*microRequestHandler)(microRequest *req); + +/** + * @brief Callback type for async error notifications. + * + * If specified in microServiceConfig, this callback is invoked for internal + * errors (e.g. message delivery failures) related to a microservice. If the + * error is associated with an endpoint, the ep parameter points at the + * endpoint. However, this handler may be invoked for errors happening in + * monitoring subjects, in which case ep is NULL. + * + * The error handler is invoked asynchronously, in a separate theread. + * + * The error handler is not invoked for microservice-level errors that are sent + * back to the client as responses. Note that the error counts in + * microEndpointStats include both internal and service-level errors. + * + * @param m The microservice object. + * @param ep The endpoint object, or NULL if the error is not associated with an + * endpoint. + * @param s The NATS status for the error. + * + * @see microServiceConfig, micro_service_config_s. + */ +typedef void (*microErrorHandler)(microService *m, microEndpoint *ep, natsStatus s); + +/** @} */ // end of microCallbacks + +/** \defgroup microStructs Public structs + * + * Microservice public structs. + * + * @{ + */ + +/** + * The Microservice endpoint configuration object. + */ +struct micro_endpoint_config_s +{ + /** + * @brief The name of the endpoint. + * + * Used in the service stats to list endpoints by name. Must not be empty. + */ + const char *Name; + + /** + * @brief The NATS subject the endpoint will listen on. + * + * Wildcards are allowed. If `Subject` is empty, it attempts to default to + * `Name`, provided it is a valid subject. + * + * For endpoints added to a group, the subject is automatically prefixed + * with the group's prefix. + */ + const char *Subject; + + /** + * @brief The endpoint schema. + */ + microSchema *Schema; + + /** + * @brief The request handler for the endpoint. + */ + microRequestHandler Handler; + + /** + * @brief A user-provided pointer to store with the endpoint + * (state/closure). + */ + void *State; +}; + +/** + * The Microservice endpoint stats struct. + */ +struct micro_endpoint_stats_s +{ + const char *Name; + const char *Subject; + + /** + * @brief The number of requests received by the endpoint. + */ + int64_t num_requests; + + /** + * @brief The number of errors, service-level and internal, associated with + * the endpoint. + */ + int64_t num_errors; + + /** + * @brief total request processing time (the seconds part). + */ + int64_t processing_time_s; + + /** + * @brief total request processing time (the nanoseconds part). + */ + int64_t processing_time_ns; + + /** + * @brief average request processing time, in ns. + */ + int64_t average_processing_time_ns; + + /** + * @brief a copy of the last error message. + */ + char last_error_string[2048]; +}; + +/** + * A microservice request. + * + * microRequest represents a request received by a microservice endpoint. + */ +struct micro_request_s +{ + /** + * @brief The NATS message underlying the request. + */ + natsMsg *Message; + + /** + * @brief A reference to the service that received the request. + */ + microService *Service; + + /** + * @brief A reference to the service that received the request. + */ + microEndpoint *Endpoint; +}; + +/** + * @brief The Microservice endpoint schema object. + */ +struct micro_schema_s +{ + const char *Request; + const char *Response; +}; + +/** + * @brief The Microservice top-level configuration object. + * + * The service is created with a clone of the config and all of its values, so + * the original can be freed or modified after calling micro_AddService. + */ +struct micro_service_config_s +{ + /** + * @brief The name of the service. + * + * It can be used to compose monitoring messages specific to this service. + */ + const char *Name; + + /** + * @brief The (semantic) version of the service. + */ + const char *Version; + + /** + * @brief The description of the service. + */ + const char *Description; + + /** + * @brief The "main" (aka default) endpoint configuration. + * + * It is the default in that it does not require calling + * microService_AddEndpoint, it is added automatically when creating the + * service. + */ + microEndpointConfig *Endpoint; + + /** + * @brief A custom stats handler. + * + * It will be called to output the service's stats. It replaces the default + * stats handler but can pull the service stats using microService_GetStats + * function, then marshal them itself, as appropriate. + */ + microRequestHandler StatsHandler; + + /** + * @brief An error notification handler. + * + * It will be called asynchonously upon internal errors. It does not get + * called for application-level errors, successfully sent out by the + * microservice. + */ + microErrorHandler ErrHandler; + + /** + * @brief A user-provided pointer to state data. + * + * A closure that is accessible from the request, stats, and internal event + * handlers. Please note that handlers are invoked on separate threads, + * consider thread-safe mechanisms of accessing the data. + */ + void *State; +}; + +/** + * microServiceInfo is the struct returned by microService_GetInfo function. It + * is also accessible by sending a `$SRV.INFO.[.]` request to + * the service. + */ +struct micro_service_info_s +{ + /** + * @brief Response type. Always `"io.nats.micro.v1.info_response"`. + */ + const char *Type; + + /** + * @brief The name of the service. + */ + const char *Name; + + /** + * @brief The semantic version of the service. + */ + const char *Version; + + /** + * @brief The description of the service. + */ + const char *Description; + + /** + * @brief The ID of the service instance responding to the request. + */ + const char *Id; + + /** + * @brief All endpoint subjects the service is listening on. + */ + const char **Subjects; + + /** + * @brief The number of subjects in the `subjects` array. + */ + int SubjectsLen; +}; + +/** + * The Microservice stats struct. + */ +struct micro_service_stats_s +{ + /** + * @brief Response type. Always `"io.nats.micro.v1.stats_response"`. + */ + const char *Type; + + /** + * @brief The name of the service. + */ + const char *Name; + + /** + * @brief The semantic version of the service. + */ + const char *Version; + + /** + * @brief The ID of the service instance responding to the request. + */ + const char *Id; + + /** + * @brief The timestamp of when the service was started. + */ + int64_t Started; + + /** + * @brief The stats for each endpoint of the service. + */ + microEndpointStats *Endpoints; + + /** + * @brief The number of endpoints in the `endpoints` array. + */ + int EndpointsLen; +}; + +/** @} */ // end of microStructs + +/** \defgroup microConstants Public constants + * + * Microservice public constants. + * @{ + */ + +/** + * @brief The prefix for all microservice monitoring subjects. + * + * For example, `"$SRV.PING"`. + */ +#define MICRO_API_PREFIX "$SRV" + +/** + * @brief The `type` set in the `$SRV.INFO` responses. + */ +#define MICRO_INFO_RESPONSE_TYPE "io.nats.micro.v1.info_response" + +/** + * @brief For `$SRV.INFO.*` subjects. + */ +#define MICRO_INFO_VERB "INFO" + +/** + * @brief The `type` set in the `$SRV.PING` response. + */ +#define MICRO_PING_RESPONSE_TYPE "io.nats.micro.v1.ping_response" + +/** + * @brief For `$SRV.PING` subjects. + */ +#define MICRO_PING_VERB "PING" + +/** + * @brief The `type` set in the `$SRV.SCHEMA` response. + */ +#define MICRO_STATS_SCHEMA_TYPE "io.nats.micro.v1.schema_response" + +/** + * @brief For `$SRV.SCHEMA` subjects. + */ +#define MICRO_SCHEMA_VERB "SCHEMA" + +/** + * @brief The `type` set in the `STATS` response. + */ +#define MICRO_STATS_RESPONSE_TYPE "io.nats.micro.v1.stats_response" + +/** + * @brief The "verb" used in `$SRV.STATS` subjects. + */ +#define MICRO_STATS_VERB "STATS" + +/** + * @brief The response message header used to communicate an erroneous NATS + * status back to the requestor. + */ +#define MICRO_STATUS_HDR "Nats-Status" + +/** + * @brief The response message header used to communicate an error message back + * to the requestor. + */ +#define MICRO_ERROR_HDR "Nats-Service-Error" + +/** + * @brief The response message header used to communicate an integer error code + * back to the requestor. + */ +#define MICRO_ERROR_CODE_HDR "Nats-Service-Error-Code" + +/** @} */ // end of microConstants + +/** \defgroup microFunctions Functions + * + * Microservice functions. + * @{ + */ + +/** \defgroup microServiceFunctions microService + * + * Functions that operate with #microService. + * @{ + */ + +/** @brief Creates and starts a new microservice. + * + * @note The microservice should be destroyed to clean up using + * #microService_Destroy. + * + * @param new_microservice the location where to store the pointer to the new + * #microService object. + * @param nc the #natsConnection the service will use to receive and respond to + * requests. + * @param config a #microServiceConfig object with the configuration of the + * service. See #micro_service_config_s for descriptions of the fields. + * + * @return a #microError if an error occurred. + * + * @see #microService_Destroy, #microService_AddEndpoint, #microServiceConfig, + * #microEndpointConfig + */ +NATS_EXTERN microError * +micro_AddService(microService **new_microservice, natsConnection *nc, microServiceConfig *config); + +/** @brief Adds an endpoint to a microservice and starts listening for messages. + * + * Endpoints are currently destroyed when the service is stopped, there is no + * present way to remove or stop individual endpoints. + * + * @param m the #microService that the endpoint will be added to. + * @param config a #microEndpointConfig object with the configuration of the + * endpoint. See #micro_endpoint_config_s for descriptions of the fields. + * + * @return a #microError if an error occurred. + * + * @see #microService_Destroy, #microEndpointConfig + */ +NATS_EXTERN microError * +microService_AddEndpoint(microService *m, microEndpointConfig *config); + +/** @brief Adds an group (prefix) to a microservice. + * + * Groups are associated with a service, and are destroyed when the service is + * destroyed. + * + * @param new_group the location where to store the pointer to the new + * #microGroup object. + * @param m the #microService that the group will be added to. + * @param prefix a prefix to use on names and subjects of all endpoints in the + * group. + * + * @return a #microError if an error occurred. + * + * @see #microGroup_AddGroup, #microGroup_AddEndpoint + */ +NATS_EXTERN microError * +microService_AddGroup(microGroup **new_group, microService *m, const char *prefix); + +/** @brief Destroys a microservice, stopping it first if needed. + * + * @note This function may fail while stopping the service, do not assume + * unconditional success if clean up is important. + * + * @param m the #microService to stop and destroy. + * + * @return a #microError if an error occurred. + * + * @see #microService_Stop, #microService_Run + */ +NATS_EXTERN microError * +microService_Destroy(microService *m); + +/** @brief Returns the connection associated with the service. If the service + * was successfully started, it is safe to assume it's not NULL, however it may + * already have been disconnected or closed. + * + * @param m the #microService. + * + * @return a #natsConnection associated with the service. + */ +NATS_EXTERN natsConnection * +microService_GetConnection(microService *m); + +/** @brief Returns a #microServiceInfo for a microservice. + * + * @note the #microServiceInfo struct returned by this call should be freed with + * #microServiceInfo_Destroy. + * + * @param new_info receives the pointer to the #microServiceInfo. + * @param m the #microService to query. + * + * @return a #microError if an error occurred. + * + * @see #microServiceInfo, #micro_service_info_s + */ +NATS_EXTERN microError * +microService_GetInfo(microServiceInfo **new_info, microService *m); + +/** @brief Returns run-time statistics for a microservice. + * + * @note the #microServiceStats struct returned by this call should be freed + * with #microServiceStats_Destroy. + * + * @param new_stats receives the pointer to the #microServiceStats. + * @param m the #microService to query. + * + * @return a #microError if an error occurred. + * + * @see #microServiceStats, #micro_service_stats_s + */ +NATS_EXTERN microError * +microService_GetStats(microServiceStats **new_stats, microService *m); + +/** @brief Checks if the service is stopped. + * + * @param m the #microService. + * + * @return `true` if stopped, otherwise `false`. + * + * @see #micro_AddService, #microService_Stop, #microService_Run + */ +NATS_EXTERN bool +microService_IsStopped(microService *m); + +/** @brief Waits for a microservice to stop. + * + * #micro_AddService starts the service with async subscriptions. + * #microService_Run waits for the service to stop. + * + * @param m the #microService. + * + * @return a #microError for invalid arguments, otherwise always succeeds. + * + * @see #micro_AddService, #microService_Stop + */ +NATS_EXTERN microError * +microService_Run(microService *m); + +/** @brief Stops a running microservice. + * + * Drains and closes the all subscriptions (endpoints and monitoring), resets + * the stats, and posts the async `Done` callback for the service, so it can do + * its own clean up if needed. + * + * @param m the #microService. + * + * @return a #microError if an error occurred. + * + * @see #micro_AddService, #microService_Run + */ +NATS_EXTERN microError *microService_Stop(microService *m); + +/** @} */ // end of microServiceFunctions + +/** \defgroup microGroupFunctions microGroup + * + * Functions that operate with #microGroup. + * @{ + */ + +/** @brief Adds a sub-group to #microGroup. + * + * The new subgroup will be prefixed as "parent_prefix.prefix.". Groups are + * associated with a service, and are destroyed when the service is destroyed. + * + * @param new_group the location where to store the pointer to the new + * #microGroup object. + * @param parent the #microGroup that the new group will be added to. + * @param prefix a prefix to use on names and subjects of all endpoints in the + * group. + * + * @return a #microError if an error occurred. + * + * @see #microGroup_AddGroup, #microGroup_AddEndpoint + */ +NATS_EXTERN microError * +microGroup_AddGroup(microGroup **new_group, microGroup *parent, const char *prefix); + +/** @brief Adds an endpoint to a #microGroup and starts listening for messages. + * + * This is a convenience method to add an endpoint with its `Subject` and `Name` + * prefixed with the group's prefix. + * + * @param g the #microGroup that the endpoint will be added to. + * @param config a #microEndpointConfig object with the configuration of the + * endpoint. See #micro_endpoint_config_s for descriptions of the fields. + * + * @return a #microError if an error occurred. + * + * @see #microService_Destroy, #microService_AddEndpoint, #microEndpointConfig + */ +NATS_EXTERN microError * +microGroup_AddEndpoint(microGroup *g, microEndpointConfig *config); + +/** @} */ // end of microGroupFunctions + +/** \defgroup microRequestFunctions microRequest + * + * Functions that operate with #microRequest. + * @{ + */ + +/** @brief Adds a header to the underlying NATS request message. + * + * @param req the request. + * @param key the key under which the `value` will be stored. It can't ne `NULL` + * or empty. + * @param value the string to add to the values associated with the given `key`. + * The value can be `NULL` or empty string. + * + * @return a #microError if an error occurred. + * + * @see #natsMsgHeader_Add, #microRequest_DeleteHeader, #microRequest_GetHeaderKeys, + * #microRequest_GetHeaderValue, #microRequest_GetHeaderValues + */ +NATS_EXTERN microError * +microRequest_AddHeader(microRequest *req, const char *key, const char *value); + +/** @brief Deletes a header from the underlying NATS request message. + * + * @param req the request. + * @param key the key to delete from the headers map. + * + * @return a #microError if an error occurred. + * + * @see #natsMsgHeader_Delete, #microRequest_AddHeader + */ +NATS_EXTERN microError * +microRequest_DeleteHeader(microRequest *req, const char *key); + +/** @brief Returns the connection associated with the request. + * + * @param req the request. + * + * @return a #natsConnection associated with the request. In fact, it's the + * connection associated with the service, so by the time the request handler is + * invoked the connection state may be different from when it was received. + */ +NATS_EXTERN natsConnection * +microRequest_GetConnection(microRequest *req); + +/** @brief Returns the data in the the request, as a byte array. + * + * @note The request owns the data, so it should not be freed other than with + * `microRequest_Destroy`. + * @note The data is not `NULL` terminated. Use `microRequest_GetDataLength` to + * obtain the number of bytes. + * + * @param req the request. + * + * @return a pointer to the request's data. + * + * @see #natsMsg_GetData, #microRequest_GetDataLength + */ +NATS_EXTERN const char * +microRequest_GetData(microRequest *req); + +/** @brief Returns the number of data bytes in the the request. + * + * @param req the request. + * + * @return the number of data bytes in the request. + * + * @see #natsMsg_GetDataLength, #microRequest_GetData + */ +NATS_EXTERN int +microRequest_GetDataLength(microRequest *req); + +/** \brief Returns the pointer to the user-provided endpoint state, if + * the request is associated with an endpoint. + * + * @param req the request. + * + * @return the `state` pointer provided in microEndpointConfig. + * + * @see #microEndpointConfig, #micro_endpoint_config_s + */ +NATS_EXTERN void * +microRequest_GetEndpointState(microRequest *req); + +/** @brief Gets the list of all header keys in the NATS message underlying the + * request. + * + * The returned strings are own by the library and MUST not be freed or altered. + * However, the returned array `keys` MUST be freed by the user. + * + * @param req the request. + * @param keys the memory location where the library will store the pointer to + * the array of keys. + * @param count the memory location where the library will store the number of + * keys returned. + * + * @return a #microError if an error occurred. + * + * @see #natsMsgHeader_Keys, #microRequest_GetHeaderValue, #microRequest_GetHeaderValues + */ +NATS_EXTERN microError * +microRequest_GetHeaderKeys(microRequest *req, const char ***keys, int *count); + +/** @brief Get the header entry associated with `key` from the NATS message underlying the request. + * + * @param req the request. + * @param key the key for which the value is requested. + * @param value the memory location where the library will store the pointer to the first + * value (if more than one is found) associated with the `key`. + * + * @return a #microError if an error occurred. + * + * @see #natsMsgHeader_Get, #microRequest_GetHeaderValue, #microRequest_GetHeaderValues + */ +NATS_EXTERN microError * +microRequest_GetHeaderValue(microRequest *req, const char *key, const char **value); + +/** @brief Get all header values associated with `key` from the NATS message + * underlying the request. + * + * @param req the request. + * @param key the key for which the values are requested. + * @param values the memory location where the library will store the pointer to + * the array value (if more than one is found) associated with the `key`. + * @param count the memory location where the library will store the number of + * values returned. + * + * @return a #microError if an error occurred. + * + * @see #natsMsgHeader_Values, #microRequest_GetHeaderValue, + * #microRequest_GetHeaderKeys + */ +NATS_EXTERN microError * +microRequest_GetHeaderValues(microRequest *req, const char *key, const char ***values, int *count); + +/** @brief Get the NATS message underlying the request. + * + * @param req the request. + * + * @return the pointer to #natsMsg. + */ +NATS_EXTERN natsMsg * +microRequest_GetMsg(microRequest *req); + +/** @brief Returns the reply subject set in this message. + * + * Returns the reply, possibly `NULL`. + * + * @warning The string belongs to the message and must not be freed. + * Copy it if needed. + * + * @param req the request. + */ +NATS_EXTERN const char * +microRequest_GetReply(microRequest *req); + +/** @brief Returns the pointer to the microservice associated with the request. + * + * @param req the request. + * + * @return the microservice pointer. + */ +NATS_EXTERN microService * +microRequest_GetService(microRequest *req); + +/** @brief Returns the pointer to the user-provided service state. + * + * @param req the request. + * + * @return the `state` pointer provided in microServiceConfig. + * + * @see #microServiceConfig, #micro_service_config_s + */ +NATS_EXTERN void * +microRequest_GetServiceState(microRequest *req); + +/** @brief Returns the subject of the request message. + * + * @warning The string belongs to the message and must not be freed. + * Copy it if needed. + * + * @param req the request. + */ +NATS_EXTERN const char * +microRequest_GetSubject(microRequest *req); + +/** + * @brief Respond to a request, on the same NATS connection. + * + * @param req the request. + * @param data the response data. + * @param len the length of the response data. + * + * @return an error, if any. + */ +NATS_EXTERN microError * +microRequest_Respond(microRequest *req, microError **err_will_free, const char *data, size_t len); + +/** @brief Add `value` to the header associated with `key` in the NATS message + * underlying the request. + * + * @param req the request. + * @param key the key under which the `value` will be stored. It can't ne `NULL` + * or empty. + * @param value the string to store under the given `key`. The value can be + * `NULL` or empty string. + * + * @return a #microError if an error occurred. + * + * @see #natsMsgHeader_Set + */ +NATS_EXTERN microError * +microRequest_SetHeader(microRequest *req, const char *key, const char *value); + +/** @} */ // end of microRequestFunctions + +/** \defgroup microErrorFunctions microError + * + * Functions that create and manipulate #microError. + * @{ + */ + +/** @brief creates a new #microError, with a printf-like formatted message. + * + * @note Errors must be freed with #microError_Destroy, but often they are + * simply returned up the call stack. + * + * @param code an `int` code, loosely modeled after the HTTP status code. + * @param format printf-like format. + * + * @return a new #microError or micro_ErrorOutOfMemory. + */ +NATS_EXTERN microError * +micro_Errorf(int code, const char *format, ...); + +/** @brief Checks if microservice response is an error. + * + * @note <>/<> TODO does this need to be public? + * + * If the response is an error, the function returns a #microError composed of + * the code and error message contained in the response header. It also accepts + * a NATS status code, usually one that came from #natsConnection_Request, and + * makes it into a #microError if it is not a NATS_OK. + * + * @param s NATS status of receiving the response. + * @param msg NATS message to check the header of. + * + * @return a new #microError or `NULL` if no error if found. + */ +NATS_EXTERN microError * +micro_ErrorFromResponse(natsStatus s, natsMsg *msg); + +/** @brief Wraps a NATS status into a #microError, if not a NATS_OK. + * + * @param s NATS status of receiving the response. + * + * @return a new #microError or `NULL` if no error if found. + */ +NATS_EXTERN microError * +micro_ErrorFromStatus(natsStatus s); + +/** @brief returns the int code of the error. + * + * @param err the error. + * + * @return the int code. + */ +NATS_EXTERN int +microError_Code(microError *err); + +/** @brief destroys a #microError. + * + * @param err the error. + */ +NATS_EXTERN void +microError_Destroy(microError *err); + +/** + * @brief Returns the NATS status associated with the error. + * + * @param err + * + * @return the status + */ +NATS_EXTERN natsStatus +microError_Status(microError *err); + +/** + * @brief Returns a printable string with the error message. + * + * This function outputs into a user-provided buffer, and returns a "`%s`-able" + * pointer to it. + * + * @param err the error. + * @param buf the output buffer. + * @param len the capacity of the output buffer. + * @return `buf` + */ +NATS_EXTERN const char * +microError_String(microError *err, char *buf, int len); + +/** + * @brief Wraps an exising #microError with a higher printf-like formatted + * message. + * + * @warning The original error may be freed and should not be refered to after + * having been wrapped. + * + * @param err the original error + * @param format the new message to prepend to the original error message. + * @param ... + * + * @return a new, wrapped, error. + */ +NATS_EXTERN microError * +microError_Wrapf(microError *err, const char *format, ...); + +/** @} */ // end of microErrorFunctions + +/** \defgroup microClientFunctions microClient + * + * @{ + */ + +/** + * @brief Creates a new microservice client. + * + * @param new_client received the pointer to the new client. + * @param nc a NATS connection. + * @param cfg for future use, use NULL for now. + * + * @return a #microError if an error occurred. + */ +NATS_EXTERN microError * +micro_NewClient(microClient **new_client, natsConnection *nc, microClientConfig *cfg); + +/** + * @brief Destroys a microservice client. + * + * @param client the client to destroy. + */ +NATS_EXTERN void +microClient_Destroy(microClient *client); + +/** + * @brief Sends a request to a microservice and receives the response. + * + * @param reply receives the pointer to the response NATS message. `reply` must + * be freed with #natsMsg_Destroy. + * @param client the client to use. + * @param subject the subject to send the request on. + * @param data the request data. + * @param data_len the request data length. + * + * @return a #microError if an error occurred. + */ +NATS_EXTERN microError * +microClient_DoRequest(natsMsg **reply, microClient *client, const char *subject, const char *data, int data_len); + +/** @} */ // end of microClientFunctions + +/** \defgroup microCleanupFunctions Miscellaneous + * + * Functions to destroy miscellaneous objects. + * @{ + */ + +/** + * @brief Destroys a #microServiceInfo object. + * + * @param info the object to destroy. + */ +NATS_EXTERN void +microServiceInfo_Destroy(microServiceInfo *info); + +/** + * @brief Destroys a #microServiceStats object. + * + * @param stats the object to destroy. + */ +NATS_EXTERN void +microServiceStats_Destroy(microServiceStats *stats); + +/** @} */ // end of microCleanupFunctions + +/** @} */ // end of microFunctions + +/** @} */ // end of microGroup + /** \defgroup wildcardsGroup Wildcards * @{ * Use of wildcards. There are two type of wildcards: `*` for partial, diff --git a/src/util.c b/src/util.c index a235c9580..140011fbf 100644 --- a/src/util.c +++ b/src/util.c @@ -1486,7 +1486,6 @@ nats_JSONArrayAsStrings(nats_JSONArray *arr, char ***array, int *arraySize) if (values == NULL) return nats_setDefaultError(NATS_NO_MEMORY); - printf("<>/<> nats_JSONArrayAsStrings: allocated %d %d-byte values=%p\n", arr->size, arr->eltSize, values); for (i=0; isize; i++) { values[i] = NATS_STRDUP((char*)(arr->values[i])); @@ -1495,7 +1494,6 @@ nats_JSONArrayAsStrings(nats_JSONArray *arr, char ***array, int *arraySize) s = nats_setDefaultError(NATS_NO_MEMORY); break; } - printf("<>/<> nats_JSONArrayAsStrings: allocated value %d values=%p\n", i, values[i]); } if (s != NATS_OK) { diff --git a/test/test.c b/test/test.c index 94e65f1eb..3c1b63646 100644 --- a/test/test.c +++ b/test/test.c @@ -38,7 +38,6 @@ #include "parser.h" #include "js.h" #include "kv.h" -#include "micro.h" #include "microp.h" #if defined(NATS_HAS_STREAMING) #include "stan/conn.h" @@ -32270,43 +32269,43 @@ test_MicroAddService(void) const char *str; microEndpointConfig default_ep_cfg = { - .name = "default", - .handler = micro_basics_handle_request, + .Name = "default", + .Handler = micro_basics_handle_request, }; microEndpointConfig ep1_cfg = { - .name = "ep1", - .handler = micro_basics_handle_request, + .Name = "ep1", + .Handler = micro_basics_handle_request, }; microEndpointConfig ep2_cfg = { - .name = "ep2", - .subject = "different-from-name", - .handler = micro_basics_handle_request, + .Name = "ep2", + .Subject = "different-from-name", + .Handler = micro_basics_handle_request, }; microEndpointConfig ep3_cfg = { - .name = "ep3", - .handler = micro_basics_handle_request, + .Name = "ep3", + .Handler = micro_basics_handle_request, }; microEndpointConfig *all_ep_cfgs[] = {&ep1_cfg, &ep2_cfg, &ep3_cfg}; microServiceConfig minimal_cfg = { - .version = "1.0.0", - .name = "minimal", + .Version = "1.0.0", + .Name = "minimal", }; microServiceConfig full_cfg = { - .version = "1.0.0", - .name = "full", - .endpoint = &default_ep_cfg, - .description = "fully declared microservice", + .Version = "1.0.0", + .Name = "full", + .Endpoint = &default_ep_cfg, + .Description = "fully declared microservice", }; microServiceConfig err_no_name_cfg = { - .version = "1.0.0", + .Version = "1.0.0", }; microServiceConfig err_no_version_cfg = { - .name = "minimal", + .Name = "minimal", }; microServiceConfig err_invalid_version_cfg = { - .version = "BLAH42", - .name = "minimal", + .Version = "BLAH42", + .Name = "minimal", }; add_service_test_case_t tcs[] = { @@ -32401,7 +32400,7 @@ test_MicroAddService(void) for (i = 0; i < tc.num_endpoints; i++) { - snprintf(buf, sizeof(buf), "%s: AddEndpoint '%s': ", tc.name, tc.endpoints[i]->name); + snprintf(buf, sizeof(buf), "%s: AddEndpoint '%s': ", tc.name, tc.endpoints[i]->Name); test(buf); testCond(NULL == microService_AddEndpoint(m, tc.endpoints[i])); } @@ -32414,8 +32413,8 @@ test_MicroAddService(void) for (j = 0; j < 3; j++) { err = micro_new_control_subject(&subject, MICRO_PING_VERB, - (j > 0 ? tc.cfg->name : NULL), - (j > 1 ? info->id : NULL)); + (j > 0 ? tc.cfg->Name : NULL), + (j > 1 ? info->Id : NULL)); if (err != NULL) FAIL("failed to generate PING subject!"); @@ -32424,11 +32423,11 @@ test_MicroAddService(void) s = natsConnection_Request(&reply, nc, subject, NULL, 0, 1000); IFOK(s, nats_JSONParse(&js, natsMsg_GetData(reply), natsMsg_GetDataLength(reply))); IFOK(s, nats_JSONGetStrPtr(js, "id", &str)); - IFOK(s, (strcmp(str, info->id) == 0) ? NATS_OK : NATS_ERR); + IFOK(s, (strcmp(str, info->Id) == 0) ? NATS_OK : NATS_ERR); IFOK(s, nats_JSONGetStrPtr(js, "name", &str)); - IFOK(s, (strcmp(str, tc.cfg->name) == 0) ? NATS_OK : NATS_ERR); + IFOK(s, (strcmp(str, tc.cfg->Name) == 0) ? NATS_OK : NATS_ERR); IFOK(s, nats_JSONGetStrPtr(js, "version", &str)); - IFOK(s, (strcmp(str, tc.cfg->version) == 0) ? NATS_OK : NATS_ERR); + IFOK(s, (strcmp(str, tc.cfg->Version) == 0) ? NATS_OK : NATS_ERR); IFOK(s, nats_JSONGetStrPtr(js, "type", &str)); IFOK(s, (strcmp(str, MICRO_PING_RESPONSE_TYPE) == 0) ? NATS_OK : NATS_ERR); testCond(s == NATS_OK); @@ -32437,8 +32436,8 @@ test_MicroAddService(void) NATS_FREE(subject); err = micro_new_control_subject(&subject, MICRO_INFO_VERB, - (j > 0 ? tc.cfg->name : NULL), - (j > 1 ? info->id : NULL)); + (j > 0 ? tc.cfg->Name : NULL), + (j > 1 ? info->Id : NULL)); if (err != NULL) FAIL("failed to generate INFO subject!"); @@ -32448,11 +32447,11 @@ test_MicroAddService(void) IFOK(s, nats_JSONParse(&js, natsMsg_GetData(reply), natsMsg_GetDataLength(reply))); IFOK(s, nats_JSONGetStrPtr(js, "id", &str)); - IFOK(s, (strcmp(str, info->id) == 0) ? NATS_OK : NATS_ERR); + IFOK(s, (strcmp(str, info->Id) == 0) ? NATS_OK : NATS_ERR); IFOK(s, nats_JSONGetStrPtr(js, "name", &str)); - IFOK(s, (strcmp(str, tc.cfg->name) == 0) ? NATS_OK : NATS_ERR); + IFOK(s, (strcmp(str, tc.cfg->Name) == 0) ? NATS_OK : NATS_ERR); IFOK(s, nats_JSONGetStrPtr(js, "description", &str)); - IFOK(s, (!tc.cfg->description || strcmp(str, tc.cfg->description) == 0) ? NATS_OK : NATS_ERR); + IFOK(s, (!tc.cfg->Description || strcmp(str, tc.cfg->Description) == 0) ? NATS_OK : NATS_ERR); IFOK(s, nats_JSONGetStrPtr(js, "type", &str)); IFOK(s, (strcmp(str, MICRO_INFO_RESPONSE_TYPE) == 0) ? NATS_OK : NATS_ERR); array = NULL; @@ -32493,16 +32492,16 @@ test_MicroGroups(void) int i; microEndpointConfig ep1_cfg = { - .name = "ep1", - .handler = micro_basics_handle_request, + .Name = "ep1", + .Handler = micro_basics_handle_request, }; microEndpointConfig ep2_cfg = { - .name = "ep2", - .handler = micro_basics_handle_request, + .Name = "ep2", + .Handler = micro_basics_handle_request, }; microServiceConfig cfg = { - .version = "1.0.0", - .name = "with-groups", + .Version = "1.0.0", + .Name = "with-groups", }; const char* expected_subjects[] = { @@ -32557,14 +32556,14 @@ test_MicroGroups(void) FAIL("failed to get service info!") test("Verify number of subjects: "); - testCond(info->subjects_len == expected_num_subjects); + testCond(info->SubjectsLen == expected_num_subjects); test("Verify subjects: "); - for (i = 0; i < info->subjects_len; i++) + for (i = 0; i < info->SubjectsLen; i++) { - if (strcmp(info->subjects[i], expected_subjects[i]) != 0) { + if (strcmp(info->Subjects[i], expected_subjects[i]) != 0) { char buf[1024]; - snprintf(buf, sizeof(buf), "expected %s, got %s", expected_subjects[i], info->subjects[i]); + snprintf(buf, sizeof(buf), "expected %s, got %s", expected_subjects[i], info->Subjects[i]); FAIL(buf); } } @@ -32591,15 +32590,15 @@ test_MicroBasics(void) natsPid serverPid = NATS_INVALID_PID; microService **svcs = NATS_CALLOC(NUM_BASIC_MICRO_SERVICES, sizeof(microService *)); microEndpointConfig ep_cfg = { - .name = "do", - .subject = "svc.do", - .handler = micro_basics_handle_request, + .Name = "do", + .Subject = "svc.do", + .Handler = micro_basics_handle_request, }; microServiceConfig cfg = { - .version = "1.0.0", - .name = "CoolService", - .description = "returns 42", - .endpoint = &ep_cfg, + .Version = "1.0.0", + .Name = "CoolService", + .Description = "returns 42", + .Endpoint = &ep_cfg, }; natsMsg *reply = NULL; microServiceInfo *info = NULL; @@ -32658,13 +32657,13 @@ test_MicroBasics(void) test(buf); err = microService_GetInfo(&info, svcs[i]); testCond((err == NULL) && - (strcmp(info->name, "CoolService") == 0) && - (strlen(info->description) > 0) && - (strlen(info->version) > 0)); + (strcmp(info->Name, "CoolService") == 0) && + (strlen(info->Description) > 0) && + (strlen(info->Version) > 0)); microServiceInfo_Destroy(info); } - // Make sure we can request valid info with $SVC.INFO request. + // Make sure we can request valid info with $SRV.INFO request. test("Create INFO inbox: "); testCond(NATS_OK == natsInbox_Create(&inbox)); micro_new_control_subject(&subject, MICRO_INFO_VERB, "CoolService", NULL); @@ -32695,7 +32694,7 @@ test_MicroBasics(void) natsInbox_Destroy(inbox); NATS_FREE(subject); - // Make sure we can request SVC.PING. + // Make sure we can request SRV.PING. test("Create PING inbox: "); testCond(NATS_OK == natsInbox_Create(&inbox)); micro_new_control_subject(&subject, MICRO_PING_VERB, "CoolService", NULL); @@ -32726,7 +32725,7 @@ test_MicroBasics(void) natsInbox_Destroy(inbox); NATS_FREE(subject); - // Get and validate $SVC.STATS from all service instances. + // Get and validate $SRV.STATS from all service instances. test("Create STATS inbox: "); testCond(NATS_OK == natsInbox_Create(&inbox)); micro_new_control_subject(&subject, MICRO_STATS_VERB, "CoolService", NULL); @@ -32807,8 +32806,8 @@ test_MicroServiceStopsOnClosedConn(void) struct threadArg arg; microService *m = NULL; microServiceConfig cfg = { - .name = "test", - .version = "1.0.0", + .Name = "test", + .Version = "1.0.0", }; natsMsg *reply = NULL; @@ -32880,8 +32879,8 @@ test_MicroServiceStopsWhenServerStops(void) struct threadArg arg; microService *m = NULL; microServiceConfig cfg = { - .name = "test", - .version = "1.0.0", + .Name = "test", + .Version = "1.0.0", }; s = _createDefaultThreadArgsForCbTests(&arg); From 06ffeac7ec35bc19dead11682873e85876de1692 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 8 May 2023 14:04:07 -0700 Subject: [PATCH 17/85] request handler returns err instead of calling Respond(Error) --- examples/micro-arithmetics.c | 29 +++-- examples/micro-func.c | 21 ++-- examples/micro-hello.c | 4 +- examples/micro-sequence.c | 16 +-- examples/micro-stats.c | 18 ++- src/micro_args.c | 29 ++--- src/micro_client.c | 2 +- src/micro_endpoint.c | 52 ++++++--- src/micro_error.c | 205 ++++++++++++++++++----------------- src/micro_monitoring.c | 29 +++-- src/micro_request.c | 49 +++++---- src/microp.h | 29 ++++- src/nats.h | 117 +++++++++++++------- test/test.c | 36 +++--- 14 files changed, 358 insertions(+), 278 deletions(-) diff --git a/examples/micro-arithmetics.c b/examples/micro-arithmetics.c index 17bf44dfd..1799b240b 100644 --- a/examples/micro-arithmetics.c +++ b/examples/micro-arithmetics.c @@ -23,11 +23,11 @@ // arithmeticsOp is a type for a C function that implements am operation: add, // multiply, divide. -typedef microError *(*arithmeticsOP)(long double *result, long double a1, long double a2); +typedef void (*arithmeticsOP)(long double *result, long double a1, long double a2); // handle_arithmetics_op is a helper function that wraps an implementation of an // operation into a request handler. -static void +static microError * handle_arithmetics_op(microRequest *req, arithmeticsOP op) { microError *err = NULL; @@ -39,45 +39,42 @@ handle_arithmetics_op(microRequest *req, arithmeticsOP op) err = micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req)); if ((err == NULL) && (microArgs_Count(args) != 2)) { - err = micro_Errorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args)); + err = micro_Errorf("invalid number of arguments, expected 2 got %d", microArgs_Count(args)); } if (err == NULL) err = microArgs_GetFloat(&a1, args, 0); if (err == NULL) err = microArgs_GetFloat(&a2, args, 1); if (err == NULL) - err = op(&result, a1, a2); + op(&result, a1, a2); if (err == NULL) len = snprintf(buf, sizeof(buf), "%Lf", result); + if (err == NULL) + err = microRequest_Respond(req, buf, len); - microRequest_Respond(req, &err, buf, len); microArgs_Destroy(args); + return microError_Wrapf(err, "failed to handle arithmetics operation"); } -static microError * -add(long double *result, long double a1, long double a2) +static void add(long double *result, long double a1, long double a2) { *result = a1 + a2; - return NULL; } -static microError * -divide(long double *result, long double a1, long double a2) +static void divide(long double *result, long double a1, long double a2) { *result = a1 / a2; - return NULL; } -static microError *multiply(long double *result, long double a1, long double a2) +static void multiply(long double *result, long double a1, long double a2) { *result = a1 * a2; - return NULL; } // request handlers for each operation. -static void handle_add(microRequest *req) { handle_arithmetics_op(req, add); } -static void handle_divide(microRequest *req) { handle_arithmetics_op(req, divide); } -static void handle_multiply(microRequest *req) { handle_arithmetics_op(req, multiply); } +static microError *handle_add(microRequest *req) { return handle_arithmetics_op(req, add); } +static microError *handle_divide(microRequest *req) { return handle_arithmetics_op(req, divide); } +static microError *handle_multiply(microRequest *req) { return handle_arithmetics_op(req, multiply); } // main is the main entry point for the microservice. int main(int argc, char **argv) diff --git a/examples/micro-func.c b/examples/micro-func.c index 59b682ff2..0dbb0cced 100644 --- a/examples/micro-func.c +++ b/examples/micro-func.c @@ -62,7 +62,7 @@ factorial(long double *result, natsConnection *nc, int n) int i; if (n < 1) - err = micro_Errorf(400, "n=%d. must be greater than 0", n); + err = micro_Errorf("n=%d. must be greater than 0", n); *result = 1; for (i = 1; i <= n; i++) @@ -84,7 +84,7 @@ fibonacci(long double *result, natsConnection *nc, int n) long double n1, n2; if (n < 0) - err = micro_Errorf(400, "n=%d. must be non-negative", n); + err = micro_Errorf("n=%d. must be non-negative", n); if (n < 2) { @@ -111,7 +111,7 @@ static microError *power2(long double *result, natsConnection *nc, int n) int i; if (n < 1) - return micro_Errorf(400, "n=%d. must be greater than 0", n); + return micro_Errorf("n=%d. must be greater than 0", n); *result = 1; for (i = 1; i <= n; i++) @@ -125,7 +125,7 @@ static microError *power2(long double *result, natsConnection *nc, int n) // handle_function_op is a helper function that wraps an implementation function // like factorial, fibonacci, etc. into a request handler. -static void +static microError * handle_function_op(microRequest *req, functionHandler op) { microError *err = NULL; @@ -138,24 +138,25 @@ handle_function_op(microRequest *req, functionHandler op) err = micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req)); if ((err == NULL) && (microArgs_Count(args) != 1)) { - err = micro_Errorf(400, "Invalid number of arguments, expected 1 got %d", microArgs_Count(args)); + err = micro_Errorf("Invalid number of arguments, expected 1 got %d", microArgs_Count(args)); } - if (err == NULL) microArgs_GetInt(&n, args, 0); if (err == NULL) err = op(&result, microRequest_GetConnection(req), n); if (err == NULL) len = snprintf(buf, sizeof(buf), "%Lf", result); + if (err == NULL) + err = microRequest_Respond(req, buf, len); - microRequest_Respond(req, &err, buf, len); microArgs_Destroy(args); + return err; } // handle_... are the request handlers for each function. -static void handle_factorial(microRequest *req) { handle_function_op(req, factorial); } -static void handle_fibonacci(microRequest *req) { handle_function_op(req, fibonacci); } -static void handle_power2(microRequest *req) { handle_function_op(req, power2); } +static microError *handle_factorial(microRequest *req) { return handle_function_op(req, factorial); } +static microError *handle_fibonacci(microRequest *req) { return handle_function_op(req, fibonacci); } +static microError *handle_power2(microRequest *req) { return handle_function_op(req, power2); } // main is the main entry point for the microservice. int main(int argc, char **argv) diff --git a/examples/micro-hello.c b/examples/micro-hello.c index 10b06863d..a4612d5ce 100644 --- a/examples/micro-hello.c +++ b/examples/micro-hello.c @@ -39,10 +39,10 @@ #define HELLO "Hello, World!" -static void +static microError * handle(microRequest *req) { - microRequest_Respond(req, NULL, HELLO, sizeof(HELLO)); + return microRequest_Respond(req, HELLO, sizeof(HELLO)); } int main(int argc, char **argv) diff --git a/examples/micro-sequence.c b/examples/micro-sequence.c index faa39f8ea..c836791e1 100644 --- a/examples/micro-sequence.c +++ b/examples/micro-sequence.c @@ -86,7 +86,7 @@ call_function(long double *result, natsConnection *nc, const char *subject, int // (float), f name (string), and N (int). E.g.: '10.0 "power2" 10' will // calculate 10/2 + 10/4 + 10/8 + 10/16 + 10/32 + 10/64 + 10/128 + 10/256 + // 10/512 + 10/1024 = 20.998046875 -static void handle_sequence(microRequest *req) +static microError *handle_sequence(microRequest *req) { microError *err = NULL; natsConnection *nc = microRequest_GetConnection(req); @@ -103,7 +103,7 @@ static void handle_sequence(microRequest *req) err = micro_ParseArgs(&args, microRequest_GetData(req), microRequest_GetDataLength(req)); if ((err == NULL) && (microArgs_Count(args) != 2)) { - err = micro_Errorf(400, "Invalid number of arguments, expected 2 got %d", microArgs_Count(args)); + err = micro_Errorf("Invalid number of arguments, expected 2 got %d", microArgs_Count(args)); } if (err == NULL) @@ -113,13 +113,13 @@ static void handle_sequence(microRequest *req) (strcmp(function, "power2") != 0) && (strcmp(function, "fibonacci") != 0)) { - err = micro_Errorf(400, "Invalid function name '%s', must be 'factorial', 'power2', or 'fibonacci'", function); + err = micro_Errorf("Invalid function name '%s', must be 'factorial', 'power2', or 'fibonacci'", function); } if (err == NULL) err = microArgs_GetInt(&n, args, 1); if ((err == NULL) && (n < 1)) { - err = micro_Errorf(400, "Invalid number of iterations %d, must be at least 1", n); + err = micro_Errorf("Invalid number of iterations %d, must be at least 1", n); } for (i = 1; (err == NULL) && (i <= n); i++) @@ -128,7 +128,7 @@ static void handle_sequence(microRequest *req) err = call_function(&denominator, nc, function, i); if ((err == NULL) && (denominator == 0)) { - err = micro_Errorf(500, "division by zero at step %d", i); + err = micro_Errorf("division by zero at step %d", i); } if (err == NULL) value = value + initialValue / denominator; @@ -136,9 +136,11 @@ static void handle_sequence(microRequest *req) if (err == NULL) result_len = snprintf(result, sizeof(result), "%Lf", value); - - microRequest_Respond(req, &err, result, result_len); + if (err == NULL) + err = microRequest_Respond(req, result, result_len); + microArgs_Destroy(args); + return err; } int main(int argc, char **argv) diff --git a/examples/micro-stats.c b/examples/micro-stats.c index 67bec2d31..8e0a77b7d 100644 --- a/examples/micro-stats.c +++ b/examples/micro-stats.c @@ -23,7 +23,8 @@ typedef struct service_state_s int odd_count; } service_state_t; -static void handle_default(microRequest *req) +static microError * +handle_default(microRequest *req) { char buf[64]; const char *response = "odd"; @@ -39,11 +40,11 @@ static void handle_default(microRequest *req) response = "even"; } - microRequest_Respond(req, NULL, response, strlen(response)); - return; + return microRequest_Respond(req, response, strlen(response)); } -static void handle_stats(microRequest *req) +static microError * +handle_stats(microRequest *req) { microError *err = NULL; microServiceStats *stats = NULL; @@ -51,18 +52,15 @@ static void handle_stats(microRequest *req) service_state_t *service_state = microRequest_GetServiceState(req); int total, custom, len; - err = microService_GetStats(&stats, req->Service); + err = microService_GetStats(&stats, microRequest_GetService(req)); if (err != NULL) - { - microRequest_Respond(req, &err, NULL, 0); - return; - } + return err; total = stats->Endpoints[0].num_requests; custom = service_state->odd_count; len = snprintf(buf, sizeof(buf), "{\"total\":%d,\"odd\":%d}", total, custom); - microRequest_Respond(req, NULL, buf, len); + return microRequest_Respond(req, buf, len); } static microError * diff --git a/src/micro_args.c b/src/micro_args.c index 2423c70de..61799f02b 100644 --- a/src/micro_args.c +++ b/src/micro_args.c @@ -48,7 +48,7 @@ micro_ParseArgs(microArgs **ptr, const char *data, int data_len) int n; if ((ptr == NULL) || (data == NULL) || (data_len < 0)) - return micro_Errorf(500, "invalid function argument"); + return microError_Wrapf(micro_ErrorInvalidArg, "failed to parse args"); MICRO_CALL(err, parse(NULL, &n, data, data_len)); MICRO_CALL(err, new_args(&args, n)); @@ -57,7 +57,7 @@ micro_ParseArgs(microArgs **ptr, const char *data, int data_len) if (err != NULL) { microArgs_Destroy(args); - return err; + return microError_Wrapf(err, "failed to parse args"); } *ptr = args; return NULL; @@ -116,14 +116,9 @@ microArgs_GetString(const char **val, microArgs *args, int index) return NULL; } -/// @brief decodes the rest of a string into a pre-allocated buffer of -/// sufficient length, or just calculates the needed buffer size. The opening -/// quote must have been already processed by the caller (parse). -/// -/// @param dup receives the parsed string, must be freed by the caller. pass NULL to just get the length. -/// @param data raw message data -/// @param data_len length of data -/// @return error in case the string is not properly terminated. +// decodes the rest of a string into a pre-allocated buffer of sufficient +// length, or just calculates the needed buffer size. The opening quote must +// have been already processed by the caller (parse). static microError * decode_rest_of_string(char *dup, int *decoded_len, int *i, const char *data, int data_len) { @@ -184,19 +179,13 @@ decode_rest_of_string(char *dup, int *decoded_len, int *i, const char *data, int } if (!terminated) { - micro_Errorf(400, "a quoted string is not properly terminated"); + return micro_Errorf("a quoted string is not properly terminated"); } *decoded_len = len; return NULL; } -/// @brief decodes and duplicates the rest of a string, as the name says. -/// @param dup receives the parsed string, must be freed by the caller. pass -/// NULL to just get the length. -/// @param data raw message data -/// @param data_len length of data -/// @return error. static microError * decode_and_dupe_rest_of_string(char **dup, int *i, const char *data, int data_len) { @@ -294,7 +283,7 @@ parse(void **args, int *args_len, const char *data, int data_len) break; default: - return micro_Errorf(400, "unexpected '%c', an argument must be a number or a quoted string", c); + return micro_Errorf("unexpected '%c', an argument must be a number or a quoted string", c); } break; @@ -353,12 +342,12 @@ parse(void **args, int *args_len, const char *data, int data_len) break; default: - return micro_Errorf(400, "unexpected '%c', a number must be followed by a space", c); + return micro_Errorf("unexpected '%c', a number must be followed by a space", c); } break; default: - return micro_Errorf(500, "unreachable: wrong state for a ' ', expected NewArg or NumberArg, got %d", state); + return micro_Errorf("unreachable: wrong state for a ' ', expected NewArg or NumberArg, got %d", state); } } diff --git a/src/micro_client.c b/src/micro_client.c index 8cc75ac0b..e29abbb24 100644 --- a/src/micro_client.c +++ b/src/micro_client.c @@ -58,7 +58,7 @@ microClient_DoRequest(natsMsg **reply, microClient *client, const char *subject, return microError_Wrapf(micro_ErrorFromStatus(s), "request failed"); } - err = micro_ErrorFromResponse(s, msg); + err = micro_is_error_message(s, msg); if (err == NULL) { *reply = msg; diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index 09fdb9af5..139c942e5 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -120,50 +120,67 @@ micro_stop_and_destroy_endpoint(microEndpoint *ep) return NULL; } +static void update_last_error(microEndpoint *ep, microError *err) +{ + ep->stats.num_errors++; + microError_String(err, ep->stats.last_error_string, sizeof(ep->stats.last_error_string)); +} + static void handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { // natsStatus s = NATS_OK; microError *err = NULL; + microError *service_err = NULL; microEndpoint *ep = (microEndpoint *)closure; - microEndpointStats *stats; + microEndpointStats *stats = NULL; microRequestHandler handler; microRequest *req = NULL; - int64_t start, elapsed_ns, full_s; + int64_t start, elapsed_ns = 0, full_s; - if (ep == NULL || ep->config == NULL || ep->config->Handler == NULL) + if ((ep == NULL) || (ep->mu == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) { - // This is a bug, we should not have received a message on this + // This would be a bug, we should not have received a message on this // subscription. - natsMsg_Destroy(msg); return; } + stats = &ep->stats; handler = ep->config->Handler; err = micro_new_request(&req, ep->m, ep, msg); - if (err != NULL) + if (err == NULL) { - natsMsg_Destroy(msg); - return; - } - req->Endpoint = ep; + // handle the request. + start = nats_NowInNanoSeconds(); - // handle the request. - start = nats_NowInNanoSeconds(); - handler(req); - elapsed_ns = nats_NowInNanoSeconds() - start; + service_err = handler(req); + if (service_err != NULL) + { + // if the handler returned an error, we attempt to respond with it. + // Note that if the handler chose to do its own RespondError which + // fails, and then the handler returns its error - we'll try to + // RespondError again, double-counting the error. + err = microRequest_RespondError(req, service_err); + } - // Update stats. Note that unlike in the go client, the error stats are - // handled by req.Respond function. + elapsed_ns = nats_NowInNanoSeconds() - start; + } + + // Update stats. natsMutex_Lock(ep->mu); + stats->num_requests++; stats->processing_time_ns += elapsed_ns; full_s = stats->processing_time_ns / 1000000000; stats->processing_time_s += full_s; stats->processing_time_ns -= full_s * 1000000000; + + update_last_error(ep, err); + natsMutex_Unlock(ep->mu); + microError_Destroy(err); micro_destroy_request(req); natsMsg_Destroy(msg); } @@ -174,8 +191,7 @@ void micro_update_last_error(microEndpoint *ep, microError *err) return; natsMutex_Lock(ep->mu); - ep->stats.num_errors++; - microError_String(err, ep->stats.last_error_string, sizeof(ep->stats.last_error_string)); + update_last_error(ep, err); natsMutex_Unlock(ep->mu); } diff --git a/src/micro_error.c b/src/micro_error.c index 950c697de..1a42dca37 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -15,25 +15,19 @@ #include "microp.h" -static microError * -new_error(natsStatus s, int code, char *description); - static microError _errorOutOfMemory = { .status = NATS_NO_MEMORY, - .code = 500, - .description = "Out of memory", + .message = (char *)"out of memory", }; static microError _errorInvalidArg = { .status = NATS_INVALID_ARG, - .code = 400, - .description = "Invalid function argument", + .message = (char *)"invalid function argument", }; static microError _errorInvalidFormat = { .status = NATS_INVALID_ARG, - .code = 400, - .description = "Invalid error format string", + .message = (char *)"invalid format string", }; static microError *knownErrors[] = { @@ -46,143 +40,168 @@ static microError *knownErrors[] = { microError *micro_ErrorOutOfMemory = &_errorOutOfMemory; microError *micro_ErrorInvalidArg = &_errorInvalidArg; -microError * -micro_Errorf(int code, const char *format, ...) +static microError * +verrorf(natsStatus s, int code, const char *format, va_list args) { - va_list args1, args2; - char *buf = NULL; - int len = 0; + microError *err = NULL; + int message_len = 0; - if ((code == 0) && nats_IsStringEmpty(format)) - return NULL; + va_list args2; + va_copy(args2, args); - va_start(args1, format); - va_copy(args2, args1); + if (format == NULL) + format = ""; - len = vsnprintf(NULL, 0, format, args1); - va_end(args1); - if (len < 0) + message_len = vsnprintf(NULL, 0, format, args); + if (message_len < 0) { va_end(args2); return &_errorInvalidFormat; } - buf = NATS_CALLOC(1, len + 1); - if (buf == NULL) + + err = NATS_CALLOC(1, sizeof(microError) + message_len + 1); + if (err == NULL) { va_end(args2); return &_errorOutOfMemory; } - vsnprintf(buf, len + 1, format, args2); + err->code = code; + err->status = s; + err->message = (char *)(err + 1); + vsnprintf(err->message, message_len + 1, format, args2); va_end(args2); + return err; +} + +microError * +micro_Errorf(const char *format, ...) +{ + microError *err = NULL; + va_list args; + + va_start(args, format); + err = verrorf(NATS_OK, 0, format, args); + va_end(args); + return err; +} - return new_error(NATS_ERR, code, buf); +microError * +micro_ErrorfCode(int code, const char *format, ...) +{ + microError *err = NULL; + va_list args; + + va_start(args, format); + err = verrorf(NATS_OK, code, format, args); + va_end(args); + return err; } microError * micro_ErrorFromStatus(natsStatus s) { - char *dup = NULL; + microError *err = NULL; + const char *message = natsStatus_GetText(s); + int message_len = strlen(message); if (s == NATS_OK) return NULL; - dup = NATS_STRDUP(natsStatus_GetText(s)); - if (dup == NULL) + err = NATS_CALLOC(1, sizeof(microError) + message_len + 1); + if (err == NULL) return &_errorOutOfMemory; - return new_error(s, 0, dup); + err->status = s; + err->message = (char *)(err + 1); + memcpy(err->message, message, message_len + 1); + return err; } microError * -micro_ErrorFromResponse(natsStatus status, natsMsg *msg) +micro_is_error_message(natsStatus status, natsMsg *msg) { microError *err = NULL; const char *c = NULL, *d = NULL; - bool is_error; + bool is_service_error; + bool is_nats_error = (status != NATS_OK); + int code = 0; if (msg != NULL) { natsMsgHeader_Get(msg, MICRO_ERROR_CODE_HDR, &c); natsMsgHeader_Get(msg, MICRO_ERROR_HDR, &d); } - - is_error = (status != NATS_OK) || !nats_IsStringEmpty(c) || !nats_IsStringEmpty(d); - if (!is_error) - return NULL; - - err = microError_Wrapf(micro_ErrorFromStatus(status), d); - if (!nats_IsStringEmpty(c) && (err != NULL)) + if (!nats_IsStringEmpty(c)) { - err->code = atoi(c); + code = atoi(c); } - return err; + is_service_error = (code != 0) || !nats_IsStringEmpty(d); + + if (is_service_error && !is_nats_error) + { + return micro_ErrorfCode(code, d); + } + else if (!is_service_error && is_nats_error) + { + return micro_ErrorFromStatus(status); + } + else if (is_service_error && is_nats_error) + { + err = microError_Wrapf(micro_ErrorFromStatus(status), d); + err->code = code; + return err; + } + + return NULL; } microError * microError_Wrapf(microError *err, const char *format, ...) { va_list args; - char *buf = NULL; - int len1 = 0, len2 = 0; - natsStatus s; - int code; + microError *new_err = NULL; if (err == NULL) return NULL; - if (nats_IsStringEmpty(format)) - return err; va_start(args, format); - len1 = vsnprintf(NULL, 0, format, args); + new_err = verrorf(NATS_OK, 0, format, args); va_end(args); - if (len1 < 0) - { - return &_errorInvalidFormat; - } - if (!nats_IsStringEmpty(err->description)) - { - len2 = strlen(err->description) + 2; // ": " - } - buf = NATS_CALLOC(1, len1 + len2 + 1); - if (buf == NULL) - { - return &_errorOutOfMemory; - } - - va_start(args, format); - vsnprintf(buf, len1 + 1, format, args); - va_end(args); - if (!nats_IsStringEmpty(err->description)) - { - buf[len1] = ':'; - buf[len1 + 1] = ' '; - memcpy(buf + len1 + 2, err->description, len2 - 2); - } - buf[len1 + len2] = '\0'; - code = err->code; - s = err->status; - microError_Destroy(err); - return new_error(s, code, buf); + new_err->cause = err; + return new_err; } const char * microError_String(microError *err, char *buf, int size) { + int used = 0; + const char *caused; + if (buf == NULL) return ""; if (err == NULL) { snprintf(buf, size, "null"); + return buf; } - else if (err->status == NATS_OK) + + if (err->status != NATS_OK) { - snprintf(buf, size, "%d: %s", err->code, err->description); + used += snprintf(buf + used, size - used, "status %d: ", err->status); } - else + if (err->code != 0) { - snprintf(buf, size, "%d:%d: %s", err->status, err->code, err->description); + used += snprintf(buf + used, size - used, "code %d: ", err->code); + } + used += snprintf(buf + used, size - used, "%s", err->message); + + if (err->cause != NULL) + { + used += snprintf(buf + used, size - used, ": "); + caused = microError_String(err->cause, buf + used, size - used); + used += strlen(caused); } return buf; } @@ -190,7 +209,13 @@ microError_String(microError *err, char *buf, int size) natsStatus microError_Status(microError *err) { - return (err != NULL) ? err->status : NATS_OK; + if (err == NULL) + return NATS_OK; + + if (err->status != NATS_OK) + return err->status; + + return microError_Status(err->cause); } void microError_Destroy(microError *err) @@ -206,24 +231,6 @@ void microError_Destroy(microError *err) return; } - // description is declared const for the users, but is strdup-ed on - // creation. - NATS_FREE((void *)err->description); + microError_Destroy(err->cause); NATS_FREE(err); } - -static microError * -new_error(natsStatus s, int code, char *description) -{ - microError *err = NULL; - - err = NATS_CALLOC(1, sizeof(microError)); - if (err == NULL) - return &_errorOutOfMemory; - - err->status = s; - err->code = code; - err->description = description; - - return err; -} diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index dbd728c2e..edf509619 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -68,8 +68,10 @@ handle_ping(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closu MICRO_CALL(err, marshal_ping(&buf, m)); MICRO_CALL(err, micro_new_request(&req, m, NULL, msg)); - // respond for both success and error cases. - microRequest_Respond(req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); + if (err == NULL) + microRequest_Respond(req, natsBuf_Data(buf), natsBuf_Len(buf)); + else + microRequest_RespondError(req, err); // will destroy err micro_destroy_request(req); natsMsg_Destroy(msg); @@ -92,8 +94,10 @@ handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closu MICRO_CALL(err, marshal_info(&buf, info)); MICRO_CALL(err, micro_new_request(&req, m, NULL, msg)); - // respond for both success and error cases. - microRequest_Respond(req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); + if (err == NULL) + microRequest_Respond(req, natsBuf_Data(buf), natsBuf_Len(buf)); + else + microRequest_RespondError(req, err); // will destroy err micro_destroy_request(req); natsBuf_Destroy(buf); @@ -101,7 +105,7 @@ handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closu microServiceInfo_Destroy(info); } -static void +static microError * handle_stats_internal(microRequest *req) { microError *err = NULL; @@ -109,16 +113,17 @@ handle_stats_internal(microRequest *req) natsBuffer *buf = NULL; if ((req == NULL) || (req->Service == NULL) || (req->Service->cfg == NULL)) - return; // Should not happen + return micro_ErrorInvalidArg; // Should not happen MICRO_CALL(err, microService_GetStats(&stats, req->Service)); MICRO_CALL(err, marshal_stats(&buf, stats)); - // respond for both success and error cases. - microRequest_Respond(req, &err, natsBuf_Data(buf), natsBuf_Len(buf)); + if (err == NULL) + microRequest_Respond(req, natsBuf_Data(buf), natsBuf_Len(buf)); natsBuf_Destroy(buf); microServiceStats_Destroy(stats); + return err; } static void @@ -137,7 +142,9 @@ handle_stats(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *clos MICRO_CALL(err, micro_new_request(&req, m, NULL, msg)); MICRO_DO(err, h(req)); - MICRO_DO(err, micro_destroy_request(req)); + + micro_destroy_request(req); + microError_Destroy(err); natsMsg_Destroy(msg); } @@ -190,7 +197,7 @@ micro_new_control_subject(char **newSubject, const char *verb, const char *name, { if (nats_IsStringEmpty(name) && !nats_IsStringEmpty(id)) { - return micro_Errorf(400, "service name is required when id is provided: %s", id); + return micro_Errorf("service name is required when id is provided: %s", id); } else if (nats_IsStringEmpty(name) && nats_IsStringEmpty(id)) @@ -211,7 +218,7 @@ add_internal_handler(microService *m, const char *verb, const char *kind, char *subj = NULL; if (m->monitoring_subs_len >= MICRO_MONITORING_SUBS_CAP) - return micro_Errorf(500, "too many monitoring subscriptions (max: %d)", MICRO_MONITORING_SUBS_CAP); + return micro_Errorf("too many monitoring subscriptions (max: %d)", MICRO_MONITORING_SUBS_CAP); err = micro_new_control_subject(&subj, verb, kind, id); if (err != NULL) diff --git a/src/micro_request.c b/src/micro_request.c index c41bf97da..147a0a763 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -15,48 +15,59 @@ #include "mem.h" microError * -microRequest_Respond(microRequest *req, microError **err_will_free, const char *data, size_t len) +microRequest_Respond(microRequest *req, const char *data, size_t len) +{ + return microRequest_RespondCustom(req, NULL, data, len); +} + +microError * +microRequest_RespondError(microRequest *req, microError *err) +{ + return microRequest_RespondCustom(req, err, NULL, 0); +} + +microError * +microRequest_RespondCustom(microRequest *req, microError *service_error, const char *data, size_t len) { natsMsg *msg = NULL; - microError *err = NULL; natsStatus s = NATS_OK; char buf[64]; if ((req == NULL) || (req->Message == NULL) || (req->Message->sub == NULL) || (req->Message->sub->conn == NULL)) { - return micro_ErrorInvalidArg; + s = NATS_INVALID_ARG; } - - IFOK(s, natsMsg_Create(&msg, natsMsg_GetReply(req->Message), NULL, data, len)); - if ((s == NATS_OK) && (err_will_free != NULL) && (*err_will_free != NULL)) + if (s == NATS_OK) + { + s = natsMsg_Create(&msg, natsMsg_GetReply(req->Message), NULL, data, len); + } + if ((s == NATS_OK) && (service_error != NULL)) { - err = *err_will_free; - micro_update_last_error(req->Endpoint, err); - if ((s == NATS_OK) && (err->status != NATS_OK)) + micro_update_last_error(req->Endpoint, service_error); + if (service_error->status != NATS_OK) { - s = natsMsgHeader_Set(msg, MICRO_STATUS_HDR, natsStatus_GetText(err->status)); + s = natsMsgHeader_Set(msg, MICRO_STATUS_HDR, natsStatus_GetText(service_error->status)); } if (s == NATS_OK) { - s = natsMsgHeader_Set(msg, MICRO_ERROR_HDR, err->description); + s = natsMsgHeader_Set(msg, MICRO_ERROR_HDR, service_error->message); } if (s == NATS_OK) { - snprintf(buf, sizeof(buf), "%d", err->code); + snprintf(buf, sizeof(buf), "%d", service_error->code); s = natsMsgHeader_Set(msg, MICRO_ERROR_CODE_HDR, buf); } } - IFOK(s, natsConnection_PublishMsg(req->Message->sub->conn, msg)); - - natsMsg_Destroy(msg); - if (err_will_free != NULL) + if (s == NATS_OK) { - microError_Destroy(*err_will_free); - *err_will_free = NULL; + s = natsConnection_PublishMsg(req->Message->sub->conn, msg); } + + microError_Destroy(service_error); + natsMsg_Destroy(msg); return microError_Wrapf( micro_ErrorFromStatus(s), - "failed to respond to a message with an error"); + "microRequest_RespondErrorWithData failed"); } microError * diff --git a/src/microp.h b/src/microp.h index 63c61f792..f1dd205f5 100644 --- a/src/microp.h +++ b/src/microp.h @@ -38,9 +38,10 @@ struct micro_error_s { + struct micro_error_s *cause; natsStatus status; int code; - const char *description; + char *message; }; struct micro_client_s @@ -113,6 +114,29 @@ struct micro_service_s bool is_stopping; }; +/** + * A microservice request. + * + * microRequest represents a request received by a microservice endpoint. + */ +struct micro_request_s +{ + /** + * @brief The NATS message underlying the request. + */ + natsMsg *Message; + + /** + * @brief A reference to the service that received the request. + */ + microService *Service; + + /** + * @brief A reference to the service that received the request. + */ + microEndpoint *Endpoint; +}; + extern microError *micro_ErrorOutOfMemory; extern microError *micro_ErrorInvalidArg; @@ -133,6 +157,9 @@ microError *micro_new_control_subject(char **newSubject, const char *verb, const bool micro_is_valid_name(const char *name); bool micro_is_valid_subject(const char *subject); +microError *micro_is_error_message(natsStatus s, natsMsg *msg); + + static inline microError *micro_strdup(char **ptr, const char *str) { diff --git a/src/nats.h b/src/nats.h index dea0809ca..af2d34109 100644 --- a/src/nats.h +++ b/src/nats.h @@ -7194,8 +7194,7 @@ typedef struct micro_endpoint_stats_s microEndpointStats; * @brief the Microservice error object. * * This error type is returned by most microservice functions. You can create - * your own custom errors by using #micro_Errorf, extract errors from response - * messages using #micro_ErrorFromResponse, and wrap existing errors using + * your own custom errors by using #micro_Errorf and wrap existing errors using * #microError_Wrapf. Errors are heap-allocated and must be freed with either * #microError_Destroy or by passing it into #microRequest_Respond. * @@ -7289,7 +7288,7 @@ typedef struct micro_service_stats_s microServiceStats; * * @see microEndpointConfig, micro_endpoint_config_s. */ -typedef void (*microRequestHandler)(microRequest *req); +typedef microError *(*microRequestHandler)(microRequest *req); /** * @brief Callback type for async error notifications. @@ -7404,29 +7403,6 @@ struct micro_endpoint_stats_s char last_error_string[2048]; }; -/** - * A microservice request. - * - * microRequest represents a request received by a microservice endpoint. - */ -struct micro_request_s -{ - /** - * @brief The NATS message underlying the request. - */ - natsMsg *Message; - - /** - * @brief A reference to the service that received the request. - */ - microService *Service; - - /** - * @brief A reference to the service that received the request. - */ - microEndpoint *Endpoint; -}; - /** * @brief The Microservice endpoint schema object. */ @@ -8049,15 +8025,78 @@ microRequest_GetSubject(microRequest *req); /** * @brief Respond to a request, on the same NATS connection. - * + * * @param req the request. * @param data the response data. * @param len the length of the response data. + * + * @return an error, if any. + */ +NATS_EXTERN microError * +microRequest_Respond(microRequest *req, const char *data, size_t len); + +/** + * @brief Respond to a request with a simple error. + * + * If err is NULL, `RespondError` does nothing. * + * @note microRequest_RespondError is called automatially if the handler returns + * an error. Usually, there is no need for a handler to use this function + * directly. If the request + * + * @param req the request. + * @param err the error to include in the response header. If `NULL`, no error. + * * @return an error, if any. */ NATS_EXTERN microError * -microRequest_Respond(microRequest *req, microError **err_will_free, const char *data, size_t len); +microRequest_RespondError(microRequest *req, microError *err); + +/** + * @brief Respond to a message, with an OK or an error. + * + * If err is NULL, `RespondErrorWithData` is equivalent to `Respond`. If err is + * not NULL, the response will include the error in the response header, and err + * will be freed. + * + * The following example illustrates idiomatic usage in a request handler. Since + * this handler handles its own error responses, the only error it might return + * would be a failure to send the response. + * + * \code{.c} + * err = somefunc(); + * if (err != NULL) { + * return microRequest_RespondCustom(req, err, error_data, data_len); + * } + * ... + * \endcode + * + * Or, if the request handler has its own cleanup logic: + * + * \code{.c} + * if (err = somefunc(), err != NULL) + * goto CLEANUP; + * ... + * + * CLEANUP: + * if (err != NULL) { + * return microRequest_RespondCustom(req, err, error_data, data_len); + * } + * return NULL; + * \endcode + * + * @param req the request. + * @param err the error to include in the response header. If `NULL`, no error. + * @param data the response data. + * @param len the length of the response data. + * + * @note + * + * + * @return an error, if any. + */ +NATS_EXTERN microError * +microRequest_RespondCustom(microRequest *req, microError *err, const char *data, size_t len); /** @brief Add `value` to the header associated with `key` in the NATS message * underlying the request. @@ -8088,30 +8127,26 @@ microRequest_SetHeader(microRequest *req, const char *key, const char *value); * @note Errors must be freed with #microError_Destroy, but often they are * simply returned up the call stack. * - * @param code an `int` code, loosely modeled after the HTTP status code. * @param format printf-like format. * * @return a new #microError or micro_ErrorOutOfMemory. */ NATS_EXTERN microError * -micro_Errorf(int code, const char *format, ...); +micro_Errorf(const char *format, ...); -/** @brief Checks if microservice response is an error. +/** @brief creates a new #microError, with a code and a printf-like formatted + * message. * - * @note <>/<> TODO does this need to be public? - * - * If the response is an error, the function returns a #microError composed of - * the code and error message contained in the response header. It also accepts - * a NATS status code, usually one that came from #natsConnection_Request, and - * makes it into a #microError if it is not a NATS_OK. + * @note Errors must be freed with #microError_Destroy, but often they are + * simply returned up the call stack. * - * @param s NATS status of receiving the response. - * @param msg NATS message to check the header of. + * @param code an `int` code, loosely modeled after the HTTP status code. + * @param format printf-like format. * - * @return a new #microError or `NULL` if no error if found. + * @return a new #microError or micro_ErrorOutOfMemory. */ NATS_EXTERN microError * -micro_ErrorFromResponse(natsStatus s, natsMsg *msg); +micro_ErrorfCode(int code, const char *format, ...); /** @brief Wraps a NATS status into a #microError, if not a NATS_OK. * diff --git a/test/test.c b/test/test.c index 3c1b63646..1c8c06286 100644 --- a/test/test.c +++ b/test/test.c @@ -32213,27 +32213,17 @@ test_MicroMatchEndpointSubject(void) } } -static void +static microError * micro_basics_handle_request(microRequest *req) { - microError *err = NULL; if ((rand() % 10) == 0) - { - err = micro_Errorf(500, "Unexpected error!"); - microRequest_Respond(req, &err, NULL, 0); - return; - } + return micro_Errorf("Unexpected error!"); // Happy Path. // Random delay between 5-10ms nats_Sleep(5 + (rand() % 5)); - if ((err = microRequest_Respond(req, NULL, "42", 2)) != NULL) - { - microRequest_Respond(req, &err, NULL, 0); - FAIL("failed to respond, then failed to respond with error!") - return; - } + return microRequest_Respond(req, "42", 2); } typedef struct @@ -32329,23 +32319,23 @@ test_MicroAddService(void) .name = "Err-null-connection", .cfg = &minimal_cfg, .null_nc = true, - .expected_err = "16:400: Invalid function argument", + .expected_err = "status 16: invalid function argument", }, { .name = "Err-null-receiver", .cfg = &minimal_cfg, .null_receiver = true, - .expected_err = "16:400: Invalid function argument", + .expected_err = "status 16: invalid function argument", }, { .name = "Err-no-name", .cfg = &err_no_name_cfg, - .expected_err = "16:400: Invalid function argument", + .expected_err = "status 16: invalid function argument", }, { .name = "Err-no-version", .cfg = &err_no_version_cfg, - .expected_err = "16:400: Invalid function argument", + .expected_err = "status 16: invalid function argument", }, { .name = "Err-invalid-version", @@ -32832,7 +32822,7 @@ test_MicroServiceStopsOnClosedConn(void) test("Test microservice is running: "); testCond(!microService_IsStopped(m)) - test("Test microservice is responding to PING: "); + test("Test microservice is responding to PING: "); testCond(NATS_OK == natsConnection_RequestString(&reply, nc, "$SRV.PING.test", "", 500)); natsMsg_Destroy(reply); reply = NULL; @@ -32840,16 +32830,16 @@ test_MicroServiceStopsOnClosedConn(void) test("Stop microservice: "); testCond(NULL == microService_Stop(m)) - test("Test microservice is not running: "); + test("Test microservice is not running: "); testCond(microService_IsStopped(m)) - test("Test microservice is not responding to PING: "); + test("Test microservice is not responding to PING: "); testCond(NATS_OK != natsConnection_RequestString(&reply, nc, "$SRV.PING.test", "", 500)); test("Destroy microservice (cleanup): "); testCond(NULL == microService_Destroy(m)) - test("Start microservice again: "); + test("Start microservice again: "); testCond(NULL == micro_AddService(&m, nc, &cfg)); test("Close the connection: "); @@ -32904,7 +32894,7 @@ test_MicroServiceStopsWhenServerStops(void) test("Test microservice is running: "); testCond(!microService_IsStopped(m)) - test("Stop the server: "); + test("Stop the server: "); testCond((_stopServer(serverPid), true)); test("Wait for the service to stop: "); @@ -32913,7 +32903,7 @@ test_MicroServiceStopsWhenServerStops(void) test("Test microservice is not running: "); testCond(microService_IsStopped(m)) - microService_Destroy(m); + microService_Destroy(m); natsOptions_Destroy(opts); natsConnection_Destroy(nc); _destroyDefaultThreadArgs(&arg); From 9d7a54326229ae1ab8e511f83fed6fabc339a2b3 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Thu, 11 May 2023 13:17:59 -0700 Subject: [PATCH 18/85] Reworked how endpoints are stores in servbice, refcounting --- src/micro.c | 595 ++++++++++++++++++++++------------------- src/micro_endpoint.c | 200 +++++++++----- src/micro_monitoring.c | 126 +++------ src/micro_request.c | 2 +- src/microp.h | 60 +++-- test/list.txt | 1 + test/test.c | 100 ++++++- 7 files changed, 631 insertions(+), 453 deletions(-) diff --git a/src/micro.c b/src/micro.c index 58961d970..ba231a1a1 100644 --- a/src/micro.c +++ b/src/micro.c @@ -15,19 +15,10 @@ #include "conn.h" #include "mem.h" -static microError * -stop_and_destroy_microservice(microService *m); -static microError * -wrap_connection_event_callbacks(microService *m); -static microError * -unwrap_connection_event_callbacks(microService *m); - -static microError * -new_service(microService **ptr) -{ - *ptr = NATS_CALLOC(1, sizeof(microService)); - return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; -} +static microError *new_service(microService **ptr, natsConnection *nc); +static void free_service(microService *m); +static microError *wrap_connection_event_callbacks(microService *m); +static microError *unwrap_connection_event_callbacks(microService *m); microError * micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *cfg) @@ -40,16 +31,11 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c return micro_ErrorInvalidArg; // Make a microservice object, with a reference to a natsConnection. - MICRO_CALL(err, new_service(&m)); + err = new_service(&m, nc); if (err != NULL) return err; - natsConn_retain(nc); - m->nc = nc; - m->refs = 1; - m->started = nats_Now() * 1000000; - - IFOK(s, natsMutex_Create(&m->mu)); + IFOK(s, natsMutex_Create(&m->service_mu)); IFOK(s, natsNUID_Next(m->id, sizeof(m->id))); err = micro_ErrorFromStatus(s); @@ -57,15 +43,13 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c // Wrap the connection callbacks before we subscribe to anything. MICRO_CALL(err, wrap_connection_event_callbacks(m)) - - // Add the endpoints and monitoring subscriptions. - MICRO_CALL(err, microService_AddEndpoint(m, cfg->Endpoint)); MICRO_CALL(err, micro_init_monitoring(m)); + MICRO_CALL(err, microService_AddEndpoint(m, cfg->Endpoint)); if (err != NULL) { - microService_Destroy(m); - return microError_Wrapf(micro_ErrorFromStatus(s), "failed to add microservice"); + micro_release_service(m); + return microError_Wrapf(err, "failed to add microservice %s", cfg->Name); } *new_m = m; @@ -73,306 +57,272 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c } microError * -microService_Stop(microService *m) +micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal) { microError *err = NULL; - natsStatus s = NATS_OK; - int i; - void *free_eps, *free_subs; + microEndpoint *ptr = NULL; + microEndpoint *prev_ptr = NULL; + microEndpoint *ep = NULL; + microEndpoint *prev_ep = NULL; if (m == NULL) return micro_ErrorInvalidArg; - - natsMutex_Lock(m->mu); - if (m->is_stopping || m->is_stopped) - { - natsMutex_Unlock(m->mu); + if (cfg == NULL) return NULL; - } - m->is_stopping = true; - natsMutex_Unlock(m->mu); - err = unwrap_connection_event_callbacks(m); + err = micro_new_endpoint(&ep, m, prefix, cfg, is_internal); if (err != NULL) - return err; + return microError_Wrapf(err, "failed to create endpoint %s", cfg->Name); + + micro_lock_service(m); - for (i = 0; i < m->endpoints_len; i++) + if (m->first_ep != NULL) { - if (err = micro_stop_and_destroy_endpoint(m->endpoints[i]), err != NULL) + if (strcmp(m->first_ep->name, ep->name) == 0) { - return microError_Wrapf(err, "failed to stop endpoint"); + ep->next = m->first_ep->next; + prev_ep = m->first_ep; + m->first_ep = ep; } - } - for (i = 0; i < m->monitoring_subs_len; i++) - { - if (!natsConnection_IsClosed(m->nc)) + else { - s = natsSubscription_Drain(m->monitoring_subs[i]); - if (s != NATS_OK) - return microError_Wrapf(micro_ErrorFromStatus(s), "failed to drain monitoring subscription"); + prev_ptr = m->first_ep; + for (ptr = m->first_ep->next; ptr != NULL; prev_ptr = ptr, ptr = ptr->next) + { + if (strcmp(ptr->name, ep->name) == 0) + { + ep->next = ptr->next; + prev_ptr->next = ep; + prev_ep = ptr; + break; + } + } + if (prev_ep == NULL) + { + prev_ptr->next = ep; + } } - natsSubscription_Destroy(m->monitoring_subs[i]); + } + else + { + m->first_ep = ep; } - // probably being paranoid here, there should be no need to lock. - free_eps = m->endpoints; - free_subs = m->monitoring_subs; + // Increment the number of endpoints, unless we are replacing an existing + // one. + if (prev_ep == NULL) + { + m->num_eps++; + } - natsMutex_Lock(m->mu); - m->is_stopped = true; - m->is_stopping = false; - m->started = 0; - m->monitoring_subs_len = 0; - m->monitoring_subs = NULL; - m->endpoints_len = 0; - m->endpoints = NULL; - natsMutex_Unlock(m->mu); + micro_unlock_service(m); - NATS_FREE(free_eps); - NATS_FREE(free_subs); + if (prev_ep != NULL) + { + // Rid of the previous endpoint with the same name, if any. If this + // fails we can return the error, leave the newly added endpoint in the + // list, not started. A retry with the same name will clean it up. + if (err = micro_destroy_endpoint(prev_ep), err != NULL) + return err; + } + if (err = micro_start_endpoint(ep), err != NULL) + { + // Best effort, leave the new endpoint in the list, as is. A retry with + // the same name will clean it up. + return microError_Wrapf(err, "failed to start endpoint %s", ep->name); + } + + if (new_ep != NULL) + *new_ep = ep; return NULL; } -bool microService_IsStopped(microService *m) +static void +_remove_endpoint(microService *m, microEndpoint *to_remove) { - bool is_stopped; + microEndpoint *ptr = NULL; + microEndpoint *prev_ptr = NULL; - if ((m == NULL) || (m->mu == NULL)) - return true; + if (m->first_ep == NULL) + return; - natsMutex_Lock(m->mu); - is_stopped = m->is_stopped; - natsMutex_Unlock(m->mu); + if (m->first_ep == to_remove) + { + m->first_ep = m->first_ep->next; + return; + } - return is_stopped; + prev_ptr = m->first_ep; + for (ptr = m->first_ep->next; ptr != NULL; ptr = ptr->next) + { + if (ptr == to_remove) + { + prev_ptr->next = ptr->next; + return; + } + } } -microError * -microService_Run(microService *m) +void micro_unlink_endpoint_from_service(microService *m, microEndpoint *to_remove) { - if ((m == NULL) || (m->mu == NULL)) - return micro_ErrorInvalidArg; + if ((m == NULL) || to_remove == NULL) + return; - while (!microService_IsStopped(m)) - { - nats_Sleep(50); - } + micro_lock_service(m); + _remove_endpoint(m, to_remove); + micro_unlock_service(m); +} - return NULL; +microError * +microService_AddEndpoint(microService *m, microEndpointConfig *cfg) +{ + return micro_add_endpoint(NULL, m, NULL, cfg, false); } -natsConnection * -microService_GetConnection(microService *m) +microError * +microGroup_AddEndpoint(microGroup *g, microEndpointConfig *cfg) { - if (m == NULL) - return NULL; - return m->nc; + if (g == NULL) + return micro_ErrorInvalidArg; + + return micro_add_endpoint(NULL, g->m, g->prefix, cfg, false); } -static microError * -add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg) +microError * +microService_Stop(microService *m) { microError *err = NULL; - int index = -1; - int new_cap; microEndpoint *ep = NULL; if (m == NULL) return micro_ErrorInvalidArg; - if (cfg == NULL) - return NULL; - err = micro_new_endpoint(&ep, m, prefix, cfg); - if (err != NULL) - return microError_Wrapf(err, "failed to create endpoint"); + // Stop is a rare call, it's ok to lock the service for the duration. + micro_lock_service(m); - // see if we already have an endpoint with this name - for (int i = 0; i < m->endpoints_len; i++) + if (m->is_stopped) { - if (strcmp(m->endpoints[i]->name, ep->name) == 0) - { - index = i; - break; - } + micro_unlock_service(m); + return NULL; } - if (index == -1) + err = unwrap_connection_event_callbacks(m); + for (ep = m->first_ep; (err == NULL) && (ep != NULL); ep = m->first_ep) { - // if not, grow the array as needed. - if (m->endpoints_len == m->endpoints_cap) + m->first_ep = ep->next; + m->num_eps--; + + if (err = micro_destroy_endpoint(ep), err != NULL) { - new_cap = m->endpoints_cap * 2; - if (new_cap == 0) - new_cap = 4; - microEndpoint **new_eps = (microEndpoint **)NATS_CALLOC(new_cap, sizeof(microEndpoint *)); - if (new_eps == NULL) - return micro_ErrorOutOfMemory; - for (int i = 0; i < m->endpoints_len; i++) - new_eps[i] = m->endpoints[i]; - NATS_FREE(m->endpoints); - m->endpoints = new_eps; - m->endpoints_cap = new_cap; + err = microError_Wrapf(err, "failed to stop endpoint %s", ep->name); } - index = m->endpoints_len; - m->endpoints_len++; - } - else - { - // stop and destroy the existing endpoint. - micro_stop_and_destroy_endpoint(m->endpoints[index]); } - err = micro_start_endpoint(ep); - if (err != NULL) + if (err == NULL) { - micro_stop_and_destroy_endpoint(ep); - return microError_Wrapf(err, "failed to start endpoint"); + m->is_stopped = true; + m->started = 0; + m->num_eps = 0; } - // add the endpoint. - m->endpoints[index] = ep; - if (new_ep != NULL) - *new_ep = ep; - return NULL; + micro_unlock_service(m); + return err; } -microError * -microService_AddEndpoint(microService *m, microEndpointConfig *cfg) +bool microService_IsStopped(microService *m) { - return add_endpoint(NULL, m, NULL, cfg); + bool is_stopped; + + if ((m == NULL) || (m->service_mu == NULL)) + return true; + + micro_lock_service(m); + is_stopped = m->is_stopped; + micro_unlock_service(m); + + return is_stopped; } microError * -microService_GetInfo(microServiceInfo **new_info, microService *m) +microService_Destroy(microService *m) { - microServiceInfo *info = NULL; - int i; - int len = 0; - - if ((new_info == NULL) || (m == NULL) || (m->mu == NULL)) - return micro_ErrorInvalidArg; + microError *err = NULL; - info = NATS_CALLOC(1, sizeof(microServiceInfo)); - if (info == NULL) - return micro_ErrorOutOfMemory; + err = microService_Stop(m); + if (err != NULL) + return err; - info->Name = m->cfg->Name; - info->Version = m->cfg->Version; - info->Description = m->cfg->Description; - info->Id = m->id; - info->Type = MICRO_INFO_RESPONSE_TYPE; + micro_release_service(m); + return NULL; +} - info->Subjects = NATS_CALLOC(m->endpoints_len, sizeof(char *)); - if (info->Subjects == NULL) - { - NATS_FREE(info); - return micro_ErrorOutOfMemory; - } +microError * +microService_Run(microService *m) +{ + if ((m == NULL) || (m->service_mu == NULL)) + return micro_ErrorInvalidArg; - natsMutex_Lock(m->mu); - for (i = 0; i < m->endpoints_len; i++) + while (!microService_IsStopped(m)) { - microEndpoint *ep = m->endpoints[i]; - if ((ep != NULL) && (ep->subject != NULL)) - { - info->Subjects[len] = ep->subject; - len++; - } + nats_Sleep(50); } - info->SubjectsLen = len; - natsMutex_Unlock(m->mu); - *new_info = info; return NULL; } -void microServiceInfo_Destroy(microServiceInfo *info) +static microError * +new_service(microService **ptr, natsConnection *nc) { - if (info == NULL) - return; + *ptr = NATS_CALLOC(1, sizeof(microService)); + if (*ptr == NULL) + return micro_ErrorOutOfMemory; - // subjects themselves must not be freed, just the collection. - NATS_FREE(info->Subjects); - NATS_FREE(info); + natsConn_retain(nc); + (*ptr)->refs = 1; + (*ptr)->nc = nc; + (*ptr)->started = nats_Now() * 1000000; + return NULL; } -microError * -microService_GetStats(microServiceStats **new_stats, microService *m) +microService * +micro_retain_service(microService *m) { - microServiceStats *stats = NULL; - int i; - int len = 0; - long double avg = 0.0; - - if ((new_stats == NULL) || (m == NULL) || (m->mu == NULL)) - return micro_ErrorInvalidArg; - - stats = NATS_CALLOC(1, sizeof(microServiceStats)); - if (stats == NULL) - return micro_ErrorOutOfMemory; - - stats->Name = m->cfg->Name; - stats->Version = m->cfg->Version; - stats->Id = m->id; - stats->Started = m->started; - stats->Type = MICRO_STATS_RESPONSE_TYPE; + if (m == NULL) + return NULL; - // allocate the actual structs, not pointers. - stats->Endpoints = NATS_CALLOC(m->endpoints_len, sizeof(microEndpointStats)); - if (stats->Endpoints == NULL) - { - NATS_FREE(stats); - return micro_ErrorOutOfMemory; - } + micro_lock_service(m); - natsMutex_Lock(m->mu); - for (i = 0; i < m->endpoints_len; i++) - { - microEndpoint *ep = m->endpoints[i]; - if ((ep != NULL) && (ep->mu != NULL)) - { - natsMutex_Lock(ep->mu); - // copy the entire struct, including the last error buffer. - stats->Endpoints[len] = ep->stats; + m->refs++; - stats->Endpoints[len].Name = ep->name; - stats->Endpoints[len].Subject = ep->subject; - avg = (long double)ep->stats.processing_time_s * 1000000000.0 + (long double)ep->stats.processing_time_ns; - avg = avg / (long double)ep->stats.num_requests; - stats->Endpoints[len].average_processing_time_ns = (int64_t)avg; - len++; - natsMutex_Unlock(ep->mu); - } - } - natsMutex_Unlock(m->mu); - stats->EndpointsLen = len; + micro_unlock_service(m); - *new_stats = stats; - return NULL; + return m; } -void microServiceStats_Destroy(microServiceStats *stats) +void micro_release_service(microService *m) { - if (stats == NULL) + int refs = 0; + + if (m == NULL) return; - NATS_FREE(stats->Endpoints); - NATS_FREE(stats); + micro_lock_service(m); + + refs = --(m->refs); + + micro_unlock_service(m); + + if (refs == 0) + free_service(m); } -microError * -microService_Destroy(microService *m) +static void free_service(microService *m) { - microError *err = NULL; microGroup *next = NULL; if (m == NULL) - return NULL; - - err = microService_Stop(m); - if (err != NULL) - return err; + return; // destroy all groups. if (m->groups != NULL) @@ -386,13 +336,10 @@ microService_Destroy(microService *m) } } - micro_destroy_cloned_service_config(m->cfg); + micro_free_cloned_service_config(m->cfg); natsConn_release(m->nc); - natsMutex_Destroy(m->mu); - NATS_FREE(m->endpoints); - NATS_FREE(m->monitoring_subs); + natsMutex_Destroy(m->service_mu); NATS_FREE(m); - return NULL; } static inline microError *new_service_config(microServiceConfig **ptr) @@ -423,7 +370,7 @@ micro_clone_service_config(microServiceConfig **out, microServiceConfig *cfg) MICRO_CALL(err, micro_clone_endpoint_config(&new_cfg->Endpoint, cfg->Endpoint)); if (err != NULL) { - micro_destroy_cloned_service_config(new_cfg); + micro_free_cloned_service_config(new_cfg); return err; } @@ -431,7 +378,7 @@ micro_clone_service_config(microServiceConfig **out, microServiceConfig *cfg) return NULL; } -void micro_destroy_cloned_service_config(microServiceConfig *cfg) +void micro_free_cloned_service_config(microServiceConfig *cfg) { if (cfg == NULL) return; @@ -441,7 +388,7 @@ void micro_destroy_cloned_service_config(microServiceConfig *cfg) NATS_FREE((char *)cfg->Name); NATS_FREE((char *)cfg->Version); NATS_FREE((char *)cfg->Description); - micro_destroy_cloned_endpoint_config(cfg->Endpoint); + micro_free_cloned_endpoint_config(cfg->Endpoint); NATS_FREE(cfg); } @@ -468,6 +415,8 @@ on_connection_disconnected(natsConnection *nc, void *closure) if (m == NULL) return; + // <>/<> TODO: Should we stop the service? Not 100% how the Go client does + // it. microService_Stop(m); if (m->prev_on_connection_disconnected != NULL) @@ -483,52 +432,39 @@ on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *closure) microEndpoint *ep = NULL; microError *err = NULL; bool our_subject = false; - int i; if ((m == NULL) || (sub == NULL)) return; - for (i = 0; !our_subject && (i < m->monitoring_subs_len); i++) + micro_lock_service(m); + for (ep = m->first_ep; (!our_subject) && (ep != NULL); ep = ep->next) { - if (strcmp(m->monitoring_subs[i]->subject, sub->subject) == 0) + if (micro_match_endpoint_subject(ep->subject, sub->subject)) { - our_subject = true; + break; } } + micro_unlock_service(m); - for (i = 0; !our_subject && i < m->endpoints_len; i++) + if (m->cfg->ErrHandler != NULL) { - ep = m->endpoints[i]; - if (ep == NULL) - continue; // shouldn't happen - - if (micro_match_endpoint_subject(ep->subject, sub->subject)) - { - our_subject = true; - } + (*m->cfg->ErrHandler)(m, ep, s); } - if (our_subject) + if (ep != NULL) { - if (m->cfg->ErrHandler != NULL) - { - (*m->cfg->ErrHandler)(m, ep, s); - } - - if (ep != NULL) - { - err = micro_ErrorFromStatus(s); - micro_update_last_error(ep, err); - microError_Destroy(err); - } + err = microError_Wrapf(micro_ErrorFromStatus(s), "NATS error on endpoint %s", ep->name); + micro_update_last_error(ep, err); + microError_Destroy(err); } + // <>/<> TODO: Should we stop the service? The Go client does. + microService_Stop(m); + if (m->prev_on_error != NULL) { (*m->prev_on_error)(nc, sub, s, m->prev_on_error_closure); } - - microService_Stop(m); } static microError * @@ -617,11 +553,128 @@ microGroup_AddGroup(microGroup **new_group, microGroup *parent, const char *pref return NULL; } +natsConnection * +microService_GetConnection(microService *m) +{ + if (m == NULL) + return NULL; + return m->nc; +} + microError * -microGroup_AddEndpoint(microGroup *g, microEndpointConfig *cfg) +microService_GetInfo(microServiceInfo **new_info, microService *m) { - if (g == NULL) + microServiceInfo *info = NULL; + microEndpoint *ep = NULL; + int len = 0; + + if ((new_info == NULL) || (m == NULL) || (m->service_mu == NULL)) return micro_ErrorInvalidArg; - return add_endpoint(NULL, g->m, g->prefix, cfg); + info = NATS_CALLOC(1, sizeof(microServiceInfo)); + if (info == NULL) + return micro_ErrorOutOfMemory; + + info->Name = m->cfg->Name; + info->Version = m->cfg->Version; + info->Description = m->cfg->Description; + info->Id = m->id; + info->Type = MICRO_INFO_RESPONSE_TYPE; + + // Overallocate subjects, will filter out internal ones. + info->Subjects = NATS_CALLOC(m->num_eps, sizeof(char *)); + if (info->Subjects == NULL) + { + NATS_FREE(info); + return micro_ErrorOutOfMemory; + } + + micro_lock_service(m); + for (ep = m->first_ep; ep != NULL; ep = ep->next) + { + if ((!ep->is_monitoring_endpoint) && (ep->subject != NULL)) + { + info->Subjects[len] = ep->subject; + len++; + } + } + info->SubjectsLen = len; + micro_unlock_service(m); + + *new_info = info; + return NULL; +} + +void microServiceInfo_Destroy(microServiceInfo *info) +{ + if (info == NULL) + return; + + // subjects themselves must not be freed, just the collection. + NATS_FREE(info->Subjects); + NATS_FREE(info); +} + +microError * +microService_GetStats(microServiceStats **new_stats, microService *m) +{ + microServiceStats *stats = NULL; + microEndpoint *ep = NULL; + int len = 0; + long double avg = 0.0; + + if ((new_stats == NULL) || (m == NULL) || (m->service_mu == NULL)) + return micro_ErrorInvalidArg; + + stats = NATS_CALLOC(1, sizeof(microServiceStats)); + if (stats == NULL) + return micro_ErrorOutOfMemory; + + stats->Name = m->cfg->Name; + stats->Version = m->cfg->Version; + stats->Id = m->id; + stats->Started = m->started; + stats->Type = MICRO_STATS_RESPONSE_TYPE; + + // Allocate the actual structs, not pointers. Overallocate for the internal + // endpoints even though they are filtered out. + stats->Endpoints = NATS_CALLOC(m->num_eps, sizeof(microEndpointStats)); + if (stats->Endpoints == NULL) + { + NATS_FREE(stats); + return micro_ErrorOutOfMemory; + } + + micro_lock_service(m); + for (ep = m->first_ep; ep != NULL; ep = ep->next) + { + if ((ep != NULL) && (!ep->is_monitoring_endpoint) && (ep->endpoint_mu != NULL)) + { + micro_lock_endpoint(ep); + // copy the entire struct, including the last error buffer. + stats->Endpoints[len] = ep->stats; + + stats->Endpoints[len].Name = ep->name; + stats->Endpoints[len].Subject = ep->subject; + avg = (long double)ep->stats.processing_time_s * 1000000000.0 + (long double)ep->stats.processing_time_ns; + avg = avg / (long double)ep->stats.num_requests; + stats->Endpoints[len].average_processing_time_ns = (int64_t)avg; + len++; + micro_unlock_endpoint(ep); + } + } + micro_unlock_service(m); + stats->EndpointsLen = len; + + *new_stats = stats; + return NULL; +} + +void microServiceStats_Destroy(microServiceStats *stats) +{ + if (stats == NULL) + return; + + NATS_FREE(stats->Endpoints); + NATS_FREE(stats); } diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index 139c942e5..79d85aae6 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -16,37 +16,16 @@ #include "microp.h" #include "mem.h" -static void -handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); - -static microError *new_endpoint(microEndpoint **ptr); - -static microError *dup_with_prefix(char **dst, const char *prefix, const char *src) -{ - int len = (int)strlen(src) + 1; - char *p; - - if (!nats_IsStringEmpty(prefix)) - len += (int)strlen(prefix) + 1; - - *dst = NATS_CALLOC(1, len); - if (*dst == NULL) - return micro_ErrorOutOfMemory; - - p = *dst; - if (prefix != NULL) - { - len = strlen(prefix); - memcpy(p, prefix, len); - p[len] = '.'; - p += len + 1; - } - memcpy(p, src, strlen(src) + 1); - return NULL; -} +static microError *dup_with_prefix(char **dst, const char *prefix, const char *src); +static void free_endpoint(microEndpoint *ep); +static void handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); +static microError *new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); +static void on_drain_complete(void *closure); +static void release_endpoint(microEndpoint *ep); +static microEndpoint *retain_endpoint(microEndpoint *ep); microError * -micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg) +micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal) { microError *err = NULL; microEndpoint *ep = NULL; @@ -60,18 +39,23 @@ micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, subj = nats_IsStringEmpty(cfg->Subject) ? cfg->Name : cfg->Subject; - MICRO_CALL(err, new_endpoint(&ep)); - MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->mu))); + ep = NATS_CALLOC(1, sizeof(microEndpoint)); + if (ep == NULL) + return micro_ErrorOutOfMemory; + ep->refs = 1; + ep->is_monitoring_endpoint = is_internal; + ep->m = micro_retain_service(m); + + MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->endpoint_mu))); MICRO_CALL(err, micro_clone_endpoint_config(&ep->config, cfg)); MICRO_CALL(err, dup_with_prefix(&ep->name, prefix, cfg->Name)); MICRO_CALL(err, dup_with_prefix(&ep->subject, prefix, subj)); if (err != NULL) { - micro_stop_and_destroy_endpoint(ep); + free_endpoint(ep); return err; } - ep->m = m; *new_ep = ep; return NULL; } @@ -87,37 +71,111 @@ micro_start_endpoint(microEndpoint *ep) // reset the stats. memset(&ep->stats, 0, sizeof(ep->stats)); - s = natsConnection_QueueSubscribe(&ep->sub, ep->m->nc, ep->subject, - MICRO_QUEUE_GROUP, handle_request, ep); - if (s != NATS_OK) - return micro_ErrorFromStatus(s); + if (ep->is_monitoring_endpoint) + s = natsConnection_Subscribe(&ep->sub, ep->m->nc, ep->subject, handle_request, ep); + else + s = natsConnection_QueueSubscribe(&ep->sub, ep->m->nc, ep->subject, MICRO_QUEUE_GROUP, handle_request, ep); - return NULL; + IFOK(s, natsSubscription_SetOnCompleteCB(ep->sub, on_drain_complete, ep)); + + return micro_ErrorFromStatus(s); } microError * -micro_stop_and_destroy_endpoint(microEndpoint *ep) +micro_stop_endpoint(microEndpoint *ep) { natsStatus s = NATS_OK; if ((ep == NULL) || (ep->sub == NULL)) return NULL; - if (!natsConnection_IsClosed(ep->m->nc)) + if (natsConnection_IsClosed(ep->m->nc) || !natsSubscription_IsValid(ep->sub)) { - s = natsSubscription_Drain(ep->sub); - if (s != NATS_OK) - return micro_ErrorFromStatus(s); + // <>/<> does the subscription need to be closed? It will be Destroy-ed + // when the endpoint is destroyed. + return NULL; } + // Initiate draining the subscription. Do not release the endpoint until + // the on_drain_complete. + retain_endpoint(ep); + s = natsSubscription_Drain(ep->sub); + if (s != NATS_OK) + { + release_endpoint(ep); + return microError_Wrapf(micro_ErrorFromStatus(s), + "failed to stop endpoint %s: failed to drain subscription", ep->name); + } + return NULL; +} + +microError * +micro_destroy_endpoint(microEndpoint *ep) +{ + microError *err = NULL; + err = micro_stop_endpoint(ep); + if (err != NULL) + return err; + + release_endpoint(ep); + return NULL; +} + +static void on_drain_complete(void *closure) +{ + if (closure == NULL) + return; + + microEndpoint *ep =(microEndpoint *)closure; + + micro_unlink_endpoint_from_service(ep->m, ep); + release_endpoint((microEndpoint *)closure); +} + +microEndpoint * +retain_endpoint(microEndpoint *ep) +{ + if (ep == NULL) + return ep; + + micro_lock_endpoint(ep); + + ep->refs++; + + micro_unlock_endpoint(ep); + + return ep; +} + +void release_endpoint(microEndpoint *ep) +{ + int refs = 0; + + if (ep == NULL) + return; + + micro_lock_endpoint(ep); + + refs = --(ep->refs); + + micro_unlock_endpoint(ep); + + if (refs == 0) + free_endpoint(ep); +} + +void free_endpoint(microEndpoint *ep) +{ + if (ep == NULL) + return; + + micro_release_service(ep->m); NATS_FREE(ep->name); NATS_FREE(ep->subject); natsSubscription_Destroy(ep->sub); - natsMutex_Destroy(ep->mu); - micro_destroy_cloned_endpoint_config(ep->config); + natsMutex_Destroy(ep->endpoint_mu); + micro_free_cloned_endpoint_config(ep->config); NATS_FREE(ep); - - return NULL; } static void update_last_error(microEndpoint *ep, microError *err) @@ -138,7 +196,7 @@ handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *cl microRequest *req = NULL; int64_t start, elapsed_ns = 0, full_s; - if ((ep == NULL) || (ep->mu == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) + if ((ep == NULL) || (ep->endpoint_mu == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) { // This would be a bug, we should not have received a message on this // subscription. @@ -168,20 +226,19 @@ handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *cl } // Update stats. - natsMutex_Lock(ep->mu); + micro_lock_endpoint(ep); stats->num_requests++; stats->processing_time_ns += elapsed_ns; full_s = stats->processing_time_ns / 1000000000; stats->processing_time_s += full_s; stats->processing_time_ns -= full_s * 1000000000; - update_last_error(ep, err); - natsMutex_Unlock(ep->mu); + micro_unlock_endpoint(ep); microError_Destroy(err); - micro_destroy_request(req); + micro_free_request(req); natsMsg_Destroy(msg); } @@ -190,9 +247,9 @@ void micro_update_last_error(microEndpoint *ep, microError *err) if (err == NULL || ep == NULL) return; - natsMutex_Lock(ep->mu); + micro_lock_endpoint(ep); update_last_error(ep, err); - natsMutex_Unlock(ep->mu); + micro_unlock_endpoint(ep); } bool micro_is_valid_name(const char *name) @@ -277,13 +334,6 @@ clone_schema(microSchema **to, const microSchema *from) return NULL; } -static microError * -new_endpoint(microEndpoint **ptr) -{ - *ptr = NATS_CALLOC(1, sizeof(microEndpoint)); - return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; -} - static inline microError * new_endpoint_config(microEndpointConfig **ptr) { @@ -318,7 +368,7 @@ micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) if (err != NULL) { - micro_destroy_cloned_endpoint_config(new_cfg); + micro_free_cloned_endpoint_config(new_cfg); return err; } @@ -326,7 +376,7 @@ micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) return NULL; } -void micro_destroy_cloned_endpoint_config(microEndpointConfig *cfg) +void micro_free_cloned_endpoint_config(microEndpointConfig *cfg) { if (cfg == NULL) return; @@ -395,3 +445,27 @@ bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_sub } } } + +static microError *dup_with_prefix(char **dst, const char *prefix, const char *src) +{ + int len = (int)strlen(src) + 1; + char *p; + + if (!nats_IsStringEmpty(prefix)) + len += (int)strlen(prefix) + 1; + + *dst = NATS_CALLOC(1, len); + if (*dst == NULL) + return micro_ErrorOutOfMemory; + + p = *dst; + if (!nats_IsStringEmpty(prefix)) + { + len = strlen(prefix); + memcpy(p, prefix, len); + p[len] = '.'; + p += len + 1; + } + memcpy(p, src, strlen(src) + 1); + return NULL; +} diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index edf509619..76b4ed288 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -16,25 +16,17 @@ #include "microp.h" #include "util.h" -static microError * -marshal_ping(natsBuffer **new_buf, microService *m); -static void -handle_ping(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); - -static microError * -marshal_info(natsBuffer **new_buf, microServiceInfo *info); -static void -handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); - -static microError * -marshal_stats(natsBuffer **new_buf, microServiceStats *stats); -static void -handle_stats(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); +static microError *marshal_ping(natsBuffer **new_buf, microService *m); +static microError *handle_ping(microRequest *req); +static microError *marshal_info(natsBuffer **new_buf, microServiceInfo *info); +static microError *handle_info(microRequest *req); +static microError * marshal_stats(natsBuffer **new_buf, microServiceStats *stats); +static microError * handle_stats(microRequest *req); static microError * -add_internal_handler(microService *m, const char *verb, const char *kind, const char *id, const char *name, natsMsgHandler handler); +add_internal_handler(microService *m, const char *verb, const char *kind, const char *id, const char *name, microRequestHandler handler); static microError * -add_verb_handlers(microService *m, const char *verb, natsMsgHandler handler); +add_verb_handlers(microService *m, const char *verb, microRequestHandler handler); static microError * new_dotted_subject(char **new_subject, int count, ...); @@ -42,110 +34,81 @@ microError * micro_init_monitoring(microService *m) { microError *err = NULL; - - m->monitoring_subs = NATS_CALLOC(MICRO_MONITORING_SUBS_CAP, sizeof(natsSubscription *)); - if (m->monitoring_subs == NULL) - return micro_ErrorOutOfMemory; - m->monitoring_subs_len = 0; - MICRO_CALL(err, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); return err; } -static void -handle_ping(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +static microError * +handle_ping(microRequest *req) { microError *err = NULL; - microService *m = (microService *)closure; - microRequest *req = NULL; + microService *m = microRequest_GetService(req); natsBuffer *buf = NULL; if ((m == NULL) || (m->cfg == NULL)) - return; // Should not happen + return micro_ErrorInvalidArg; // Should not happen MICRO_CALL(err, marshal_ping(&buf, m)); - MICRO_CALL(err, micro_new_request(&req, m, NULL, msg)); + MICRO_CALL(err, microRequest_Respond(req, natsBuf_Data(buf), natsBuf_Len(buf))); - if (err == NULL) - microRequest_Respond(req, natsBuf_Data(buf), natsBuf_Len(buf)); - else - microRequest_RespondError(req, err); // will destroy err - - micro_destroy_request(req); - natsMsg_Destroy(msg); natsBuf_Destroy(buf); + return err; } -static void -handle_info(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +static microError * +handle_info(microRequest *req) { microError *err = NULL; - microService *m = (microService *)closure; - microRequest *req = NULL; + microService *m = microRequest_GetService(req); microServiceInfo *info = NULL; natsBuffer *buf = NULL; if ((m == NULL) || (m->cfg == NULL)) - return; // Should not happen + return micro_ErrorInvalidArg; // Should not happen MICRO_CALL(err, microService_GetInfo(&info, m)); MICRO_CALL(err, marshal_info(&buf, info)); - MICRO_CALL(err, micro_new_request(&req, m, NULL, msg)); - - if (err == NULL) - microRequest_Respond(req, natsBuf_Data(buf), natsBuf_Len(buf)); - else - microRequest_RespondError(req, err); // will destroy err + MICRO_CALL(err, microRequest_Respond(req, natsBuf_Data(buf), natsBuf_Len(buf))); - micro_destroy_request(req); natsBuf_Destroy(buf); - natsMsg_Destroy(msg); microServiceInfo_Destroy(info); + return err; } static microError * handle_stats_internal(microRequest *req) { microError *err = NULL; + microService *m = microRequest_GetService(req); microServiceStats *stats = NULL; natsBuffer *buf = NULL; - if ((req == NULL) || (req->Service == NULL) || (req->Service->cfg == NULL)) + if ((m == NULL) || (m == NULL)) return micro_ErrorInvalidArg; // Should not happen MICRO_CALL(err, microService_GetStats(&stats, req->Service)); MICRO_CALL(err, marshal_stats(&buf, stats)); - - if (err == NULL) - microRequest_Respond(req, natsBuf_Data(buf), natsBuf_Len(buf)); + MICRO_CALL(err, microRequest_Respond(req, natsBuf_Data(buf), natsBuf_Len(buf))); natsBuf_Destroy(buf); microServiceStats_Destroy(stats); return err; } -static void -handle_stats(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +static microError * +handle_stats(microRequest *req) { - microError *err = NULL; - microService *m = (microService *)closure; - microRequest *req = NULL; - microRequestHandler h = handle_stats_internal; + microService *m = microRequest_GetService(req); if ((m == NULL) || (m->cfg == NULL)) - return; // Should not happen - - if (m->cfg->StatsHandler != NULL) - h = m->cfg->StatsHandler; - - MICRO_CALL(err, micro_new_request(&req, m, NULL, msg)); - MICRO_DO(err, h(req)); + return micro_ErrorInvalidArg; // Should not happen - micro_destroy_request(req); - microError_Destroy(err); - natsMsg_Destroy(msg); + if (m->cfg->StatsHandler != NULL) + return m->cfg->StatsHandler(req); + else + return handle_stats_internal(req); } static microError * @@ -210,32 +173,23 @@ micro_new_control_subject(char **newSubject, const char *verb, const char *name, static microError * add_internal_handler(microService *m, const char *verb, const char *kind, - const char *id, const char *name, natsMsgHandler handler) + const char *id, const char *name, microRequestHandler handler) { microError *err = NULL; - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; char *subj = NULL; - if (m->monitoring_subs_len >= MICRO_MONITORING_SUBS_CAP) - return micro_Errorf("too many monitoring subscriptions (max: %d)", MICRO_MONITORING_SUBS_CAP); - err = micro_new_control_subject(&subj, verb, kind, id); if (err != NULL) return err; - s = natsConnection_Subscribe(&sub, m->nc, subj, handler, m); + microEndpointConfig cfg = { + .Subject = subj, + .Name = name, + .Handler = handler, + }; + err = micro_add_endpoint(NULL, m, "", &cfg, true); NATS_FREE(subj); - if (s != NATS_OK) - { - microService_Stop(m); - return micro_ErrorFromStatus(s); - } - - m->monitoring_subs[m->monitoring_subs_len] = sub; - m->monitoring_subs_len++; - - return NULL; + return err; } // __verbHandlers generates control handlers for a specific verb. Each request @@ -243,7 +197,7 @@ add_internal_handler(microService *m, const char *verb, const char *kind, // written with the framework, one that handles all services of a particular // kind, and finally a specific service instance. static microError * -add_verb_handlers(microService *m, const char *verb, natsMsgHandler handler) +add_verb_handlers(microService *m, const char *verb, microRequestHandler handler) { microError *err = NULL; char name[1024]; diff --git a/src/micro_request.c b/src/micro_request.c index 147a0a763..af94b1406 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -176,7 +176,7 @@ microRequest_GetService(microRequest *req) return (req != NULL) ? req->Service : NULL; } -void micro_destroy_request(microRequest *req) +void micro_free_request(microRequest *req) { NATS_FREE(req); } diff --git a/src/microp.h b/src/microp.h index f1dd205f5..5e9861b6e 100644 --- a/src/microp.h +++ b/src/microp.h @@ -27,7 +27,6 @@ if ((__err) == NULL) \ __block; - #define MICRO_QUEUE_GROUP "q" #define MICRO_DEFAULT_ENDPOINT_NAME "default" @@ -60,8 +59,21 @@ struct micro_endpoint_s microService *m; microEndpointConfig *config; + // Monitoring endpoints are different in a few ways. For now, express it as + // a single flag but consider unbundling: + // - use_queue_group: Service endpoints use a queue group, monitoring + // endpoints don't. + // - forward_response_errors_to_async_handler: Service endpoints handle + // respond errors themselves, standard monitoring endpoints don't, so + // send the errors to the service async handler, if exists. + // - gather_stats: Monitoring endpoints don't need stats. + // - include_in_info: Monitoring endpoints are not listed in INFO + // responses. + bool is_monitoring_endpoint; + // Mutex for starting/stopping the endpoint, and for updating the stats. - natsMutex *mu; + natsMutex *endpoint_mu; + int refs; // The subscription for the endpoint. If NULL, the endpoint is stopped. natsSubscription *sub; @@ -69,6 +81,8 @@ struct micro_endpoint_s // Endpoint stats. These are initialized only for running endpoints, and are // cleared if the endpoint is stopped. microEndpointStats stats; + + microEndpoint *next; }; struct micro_group_s @@ -91,16 +105,11 @@ struct micro_service_s // these are are updated concurrently with access as the service runs, so // need to be protected by mutex. - natsMutex *mu; - + natsMutex *service_mu; int refs; - struct micro_endpoint_s **endpoints; - int endpoints_len; - int endpoints_cap; - - natsSubscription **monitoring_subs; - int monitoring_subs_len; + struct micro_endpoint_s *first_ep; + int num_eps; natsConnectionHandler prev_on_connection_closed; void *prev_on_connection_closed_closure; @@ -111,7 +120,6 @@ struct micro_service_s int64_t started; // UTC time expressed as number of nanoseconds since epoch. bool is_stopped; - bool is_stopping; }; /** @@ -131,7 +139,7 @@ struct micro_request_s */ microService *Service; - /** + /** * @brief A reference to the service that received the request. */ microEndpoint *Endpoint; @@ -140,26 +148,32 @@ struct micro_request_s extern microError *micro_ErrorOutOfMemory; extern microError *micro_ErrorInvalidArg; +microError *micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); microError *micro_clone_endpoint_config(microEndpointConfig **new_cfg, microEndpointConfig *cfg); microError *micro_clone_service_config(microServiceConfig **new_cfg, microServiceConfig *cfg); -void micro_destroy_cloned_endpoint_config(microEndpointConfig *cfg); -void micro_destroy_cloned_service_config(microServiceConfig *cfg); -void micro_destroy_request(microRequest *req); +void micro_free_cloned_endpoint_config(microEndpointConfig *cfg); +void micro_free_cloned_service_config(microServiceConfig *cfg); +void micro_free_request(microRequest *req); microError *micro_init_monitoring(microService *m); +microError *micro_is_error_message(natsStatus s, natsMsg *msg); +bool micro_is_valid_name(const char *name); +bool micro_is_valid_subject(const char *subject); bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject); -microError *micro_new_endpoint(microEndpoint **new_endpoint, microService *m, const char *prefix, microEndpointConfig *cfg); +microError *micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); +microError *micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); microError *micro_new_request(microRequest **new_request, microService *m, microEndpoint *ep, natsMsg *msg); +void micro_release_service(microService *m); +microService *micro_retain_service(microService *m); microError *micro_start_endpoint(microEndpoint *ep); +microError *micro_destroy_endpoint(microEndpoint *ep); microError *micro_stop_endpoint(microEndpoint *ep); -microError *micro_stop_and_destroy_endpoint(microEndpoint *ep); +void micro_unlink_endpoint_from_service(microService *m, microEndpoint *to_remove); void micro_update_last_error(microEndpoint *ep, microError *err); -microError *micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); -bool micro_is_valid_name(const char *name); -bool micro_is_valid_subject(const char *subject); - -microError *micro_is_error_message(natsStatus s, natsMsg *msg); - +static inline void micro_lock_service(microService *m) { natsMutex_Lock(m->service_mu); } +static inline void micro_unlock_service(microService *m) { natsMutex_Unlock(m->service_mu); } +static inline void micro_lock_endpoint(microEndpoint *ep) { natsMutex_Lock(ep->endpoint_mu); } +static inline void micro_unlock_endpoint(microEndpoint *ep) { natsMutex_Unlock(ep->endpoint_mu); } static inline microError *micro_strdup(char **ptr, const char *str) { diff --git a/test/list.txt b/test/list.txt index d3dc96e05..e4a138d60 100644 --- a/test/list.txt +++ b/test/list.txt @@ -263,6 +263,7 @@ MicroMatchEndpointSubject MicroAddService MicroGroups MicroBasics +MicroStartStop MicroServiceStopsOnClosedConn MicroServiceStopsWhenServerStops StanPBufAllocator diff --git a/test/test.c b/test/test.c index 1c8c06286..9886d1ae7 100644 --- a/test/test.c +++ b/test/test.c @@ -32593,7 +32593,7 @@ test_MicroBasics(void) natsMsg *reply = NULL; microServiceInfo *info = NULL; int i; - char buf[256]; + char buf[1024]; char *subject = NULL; natsInbox *inbox = NULL; natsSubscription *sub = NULL; @@ -32665,6 +32665,7 @@ test_MicroBasics(void) { snprintf(buf, sizeof(buf), "Receive INFO response #%d: ", i); test(buf); + reply = NULL; s = natsSubscription_NextMsg(&reply, sub, 250); if (s == NATS_TIMEOUT) { @@ -32674,6 +32675,7 @@ test_MicroBasics(void) testCond(NATS_OK == s); snprintf(buf, sizeof(buf), "Validate INFO response #%d: ", i); test(buf); + js = NULL; testCond((NATS_OK == nats_JSONParse(&js, reply->data, reply->dataLen)) && (NATS_OK == nats_JSONGetStrPtr(js, "name", &str)) && (strcmp(str, "CoolService") == 0)); @@ -32696,6 +32698,7 @@ test_MicroBasics(void) { snprintf(buf, sizeof(buf), "Receive PING response #%d: ", i); test(buf); + reply = NULL; s = natsSubscription_NextMsg(&reply, sub, 250); if (s == NATS_TIMEOUT) { @@ -32705,6 +32708,7 @@ test_MicroBasics(void) testCond(NATS_OK == s); snprintf(buf, sizeof(buf), "Validate PING response #%d: ", i); test(buf); + js = NULL; testCond((NATS_OK == nats_JSONParse(&js, reply->data, reply->dataLen)) && (NATS_OK == nats_JSONGetStrPtr(js, "name", &str)) && (strcmp(str, "CoolService") == 0)); @@ -32728,6 +32732,7 @@ test_MicroBasics(void) { snprintf(buf, sizeof(buf), "Receive STATS response #%d: ", i); test(buf); + reply = NULL; s = natsSubscription_NextMsg(&reply, sub, 250); if (s == NATS_TIMEOUT) { @@ -32735,27 +32740,28 @@ test_MicroBasics(void) break; } testCond(NATS_OK == s); + test("Parse STATS response: "); + js = NULL; s = nats_JSONParse(&js, reply->data, reply->dataLen); - if ((NATS_OK != s) || (js == NULL)) - FAIL("Unable to parse JSON response"); + testCond((NATS_OK == s) && (js != NULL)); + test("Ensure STATS has one endpoint: "); array = NULL; array_len = 0; s = nats_JSONGetArrayObject(js, "endpoints", &array, &array_len); - if ((NATS_OK != s) || (array == NULL) || (array_len != 1)) - FAIL("Invalid 'endpoints', expected exactly 1"); + testCond((NATS_OK == s) && (array != NULL) && (array_len == 1)) + test("Ensure endpoint has num_requests: "); n = 0; s = nats_JSONGetInt(array[0], "num_requests", &n); - if (NATS_OK != s) - FAIL("no 'num_requests' in endpoint stats"); + testCond(NATS_OK == s); num_requests += n; + test("Ensure endpoint has num_errors: "); n = 0; s = nats_JSONGetInt(array[0], "num_errors", &n); - if (NATS_OK != s) - FAIL("no 'num_errors' in endpoint stats"); + testCond(NATS_OK == s); num_errors += n; NATS_FREE(array); @@ -32786,6 +32792,81 @@ test_MicroBasics(void) _stopServer(serverPid); } +static void +test_MicroStartStop(void) +{ + natsStatus s = NATS_OK; + struct threadArg arg; + natsOptions *opts = NULL; + natsConnection *nc = NULL; + natsPid serverPid = NATS_INVALID_PID; + microService **svcs = NATS_CALLOC(NUM_BASIC_MICRO_SERVICES, sizeof(microService *)); + microEndpointConfig ep_cfg = { + .Name = "do", + .Subject = "svc.do", + .Handler = micro_basics_handle_request, + }; + microServiceConfig cfg = { + .Version = "1.0.0", + .Name = "CoolService", + .Description = "returns 42", + .Endpoint = &ep_cfg, + }; + natsMsg *reply = NULL; + int i; + char buf[256]; + + srand((unsigned int)nats_NowInNanoSeconds()); + + s = _createDefaultThreadArgsForCbTests(&arg); + if (s == NATS_OK) + opts = _createReconnectOptions(); + if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) + { + FAIL("Unable to setup test for MicroConnectionEvents!"); + } + + serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); + CHECK_SERVER_STARTED(serverPid); + + test("Connect to server: "); + testCond(NATS_OK == natsConnection_Connect(&nc, opts)); + + // start 5 instances of the basic service. + for (i = 0; i < NUM_BASIC_MICRO_SERVICES; i++) + { + snprintf(buf, sizeof(buf), "Start microservice #%d: ", i); + test(buf); + testCond(NULL == micro_AddService(&svcs[i], nc, &cfg)); + } + + // Now send 50 requests. + test("Send 50 requests: "); + for (i = 0; i < 50; i++) + { + s = natsConnection_Request(&reply, nc, "svc.do", NULL, 0, 1000); + if (NATS_OK != s) + FAIL("Unable to send request"); + natsMsg_Destroy(reply); + } + testCond(NATS_OK == s); + + // Destroy the services in the reverse order to properly unwind the + // callbacks. At some point this needs to be fixed so that the services + // exclude themselves from a chain, rather than setting previous, etc. + for (i = NUM_BASIC_MICRO_SERVICES - 1; i >= 0; i--) + { + + microService_Destroy(svcs[i]); + } + NATS_FREE(svcs); + + natsConnection_Destroy(nc); + natsOptions_Destroy(opts); + _destroyDefaultThreadArgs(&arg); + _stopServer(serverPid); +} + static void test_MicroServiceStopsOnClosedConn(void) { @@ -35478,6 +35559,7 @@ static testInfo allTests[] = {"MicroAddService", test_MicroAddService}, {"MicroGroups", test_MicroGroups}, {"MicroBasics", test_MicroBasics}, + {"MicroStartStop", test_MicroStartStop}, {"MicroServiceStopsOnClosedConn", test_MicroServiceStopsOnClosedConn}, {"MicroServiceStopsWhenServerStops", test_MicroServiceStopsWhenServerStops}, From 74c70c7b9109d6d881968c655851647b8650b7ec Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Fri, 12 May 2023 08:56:28 -0700 Subject: [PATCH 19/85] fixed copyright messages --- examples/examples.h | 2 +- examples/micro-arithmetics.c | 2 +- examples/micro-func.c | 2 +- examples/micro-hello.c | 2 +- examples/micro-sequence.c | 2 +- examples/micro-stats.c | 2 +- src/conn.c | 2 +- src/conn.h | 2 +- src/crypto.c | 2 +- src/micro.c | 2 +- src/micro_args.c | 2 +- src/micro_args.h | 2 +- src/micro_client.c | 2 +- src/micro_endpoint.c | 2 +- src/micro_error.c | 2 +- src/micro_monitoring.c | 2 +- src/micro_request.c | 2 +- src/microp.h | 2 +- src/nats.c | 2 +- src/nats.h | 2 +- src/util.c | 2 +- src/util.h | 2 +- test/test.c | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/examples/examples.h b/examples/examples.h index edf83881c..e92e65eb9 100644 --- a/examples/examples.h +++ b/examples/examples.h @@ -1,4 +1,4 @@ -// Copyright 2015-2018 The NATS Authors +// Copyright 2015-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/examples/micro-arithmetics.c b/examples/micro-arithmetics.c index 1799b240b..1a286bb99 100644 --- a/examples/micro-arithmetics.c +++ b/examples/micro-arithmetics.c @@ -1,4 +1,4 @@ -// Copyright 2021 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/examples/micro-func.c b/examples/micro-func.c index 0dbb0cced..5a8cd80ad 100644 --- a/examples/micro-func.c +++ b/examples/micro-func.c @@ -1,4 +1,4 @@ -// Copyright 2021 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/examples/micro-hello.c b/examples/micro-hello.c index a4612d5ce..6ffb982d9 100644 --- a/examples/micro-hello.c +++ b/examples/micro-hello.c @@ -1,4 +1,4 @@ -// Copyright 2021 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/examples/micro-sequence.c b/examples/micro-sequence.c index c836791e1..2eb1a7f0f 100644 --- a/examples/micro-sequence.c +++ b/examples/micro-sequence.c @@ -1,4 +1,4 @@ -// Copyright 2021 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/examples/micro-stats.c b/examples/micro-stats.c index 8e0a77b7d..de7506056 100644 --- a/examples/micro-stats.c +++ b/examples/micro-stats.c @@ -1,4 +1,4 @@ -// Copyright 2021 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/conn.c b/src/conn.c index bb7d13f02..b1c9446df 100644 --- a/src/conn.c +++ b/src/conn.c @@ -1,4 +1,4 @@ -// Copyright 2015-2021 The NATS Authors +// Copyright 2015-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/conn.h b/src/conn.h index 7538aa921..8f297b348 100644 --- a/src/conn.h +++ b/src/conn.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 The NATS Authors +// Copyright 2015-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/crypto.c b/src/crypto.c index caff4dd3f..655f0e938 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1,4 +1,4 @@ -// Copyright 2019 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/micro.c b/src/micro.c index ba231a1a1..f59f0a904 100644 --- a/src/micro.c +++ b/src/micro.c @@ -1,4 +1,4 @@ -// Copyright 2021-2023 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/micro_args.c b/src/micro_args.c index 61799f02b..be8995896 100644 --- a/src/micro_args.c +++ b/src/micro_args.c @@ -1,4 +1,4 @@ -// Copyright 2021-2023 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/micro_args.h b/src/micro_args.h index 8d6d0a345..fa3efa40a 100644 --- a/src/micro_args.h +++ b/src/micro_args.h @@ -1,4 +1,4 @@ -// Copyright 2015-2018 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/micro_client.c b/src/micro_client.c index e29abbb24..012c64b33 100644 --- a/src/micro_client.c +++ b/src/micro_client.c @@ -1,4 +1,4 @@ -// Copyright 2015-2018 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index 79d85aae6..ca8c24ec7 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -1,4 +1,4 @@ -// Copyright 2021-2023 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/micro_error.c b/src/micro_error.c index 1a42dca37..1b66b4298 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -1,4 +1,4 @@ -// Copyright 2015-2018 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 76b4ed288..cddb91704 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -1,4 +1,4 @@ -// Copyright 2021-2023 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/micro_request.c b/src/micro_request.c index af94b1406..31532b918 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -1,4 +1,4 @@ -// Copyright 2015-2018 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/microp.h b/src/microp.h index 5e9861b6e..a959d8721 100644 --- a/src/microp.h +++ b/src/microp.h @@ -1,4 +1,4 @@ -// Copyright 2015-2018 The NATS Authors +// Copyright 2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/nats.c b/src/nats.c index 64c45628b..f7f284d0e 100644 --- a/src/nats.c +++ b/src/nats.c @@ -1,4 +1,4 @@ -// Copyright 2015-2021 The NATS Authors +// Copyright 2015-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/nats.h b/src/nats.h index af2d34109..e93653bce 100644 --- a/src/nats.h +++ b/src/nats.h @@ -1,4 +1,4 @@ -// Copyright 2015-2022 The NATS Authors +// Copyright 2015-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/util.c b/src/util.c index 140011fbf..be0a84275 100644 --- a/src/util.c +++ b/src/util.c @@ -1,4 +1,4 @@ -// Copyright 2015-2021 The NATS Authors +// Copyright 2015-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/util.h b/src/util.h index d2a6ce963..b7c156da6 100644 --- a/src/util.h +++ b/src/util.h @@ -1,4 +1,4 @@ -// Copyright 2015-2021 The NATS Authors +// Copyright 2015-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/test/test.c b/test/test.c index 9886d1ae7..3e3c6dae3 100644 --- a/test/test.c +++ b/test/test.c @@ -1,4 +1,4 @@ -// Copyright 2015-2022 The NATS Authors +// Copyright 2015-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at From 3b9157f775e17104a18adb010dcd9d040b7194ed Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Fri, 12 May 2023 08:59:00 -0700 Subject: [PATCH 20/85] PR feedback: fixes in examples --- examples/micro-arithmetics.c | 5 ++++- examples/micro-func.c | 11 +++++++---- examples/micro-hello.c | 4 ++++ examples/micro-sequence.c | 5 ++++- examples/micro-stats.c | 6 ++++-- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/examples/micro-arithmetics.c b/examples/micro-arithmetics.c index 1a286bb99..9e2eb53f1 100644 --- a/examples/micro-arithmetics.c +++ b/examples/micro-arithmetics.c @@ -15,7 +15,7 @@ // Sequence NATS microservice example. // -// This example illustrated multiple NATS microservices communicating with each +// This example illustrates multiple NATS microservices communicating with each // other. Please see the main microservice, micro-sequence.c for a more detailed // explanation. // @@ -112,6 +112,7 @@ int main(int argc, char **argv) { printf("Error: %u - %s\n", s, natsStatus_GetText(s)); nats_PrintLastErrorStack(stderr); + natsOptions_Destroy(opts); return 1; } @@ -134,6 +135,8 @@ int main(int argc, char **argv) // Cleanup. microService_Destroy(m); + natsOptions_Destroy(opts); + natsConnection_Destroy(conn); if (err != NULL) { printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); diff --git a/examples/micro-func.c b/examples/micro-func.c index 5a8cd80ad..0f62cb85a 100644 --- a/examples/micro-func.c +++ b/examples/micro-func.c @@ -15,7 +15,7 @@ // Sequence NATS microservice example. // -// This example illustrated multiple NATS microservices communicating with each +// This example illustrates multiple NATS microservices communicating with each // other. Please see the main microservice, micro-sequence.c for a more detailed // explanation. // @@ -62,7 +62,7 @@ factorial(long double *result, natsConnection *nc, int n) int i; if (n < 1) - err = micro_Errorf("n=%d. must be greater than 0", n); + return micro_Errorf("n=%d. must be greater than 0", n); *result = 1; for (i = 1; i <= n; i++) @@ -84,7 +84,7 @@ fibonacci(long double *result, natsConnection *nc, int n) long double n1, n2; if (n < 0) - err = micro_Errorf("n=%d. must be non-negative", n); + return micro_Errorf("n=%d. must be non-negative", n); if (n < 2) { @@ -141,7 +141,7 @@ handle_function_op(microRequest *req, functionHandler op) err = micro_Errorf("Invalid number of arguments, expected 1 got %d", microArgs_Count(args)); } if (err == NULL) - microArgs_GetInt(&n, args, 0); + err = microArgs_GetInt(&n, args, 0); if (err == NULL) err = op(&result, microRequest_GetConnection(req), n); if (err == NULL) @@ -194,6 +194,7 @@ int main(int argc, char **argv) { printf("Error: %u - %s\n", s, natsStatus_GetText(s)); nats_PrintLastErrorStack(stderr); + natsOptions_Destroy(opts); return 1; } @@ -216,6 +217,8 @@ int main(int argc, char **argv) // Cleanup. microService_Destroy(m); + natsOptions_Destroy(opts); + natsConnection_Destroy(conn); if (err != NULL) { printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); diff --git a/examples/micro-hello.c b/examples/micro-hello.c index 6ffb982d9..6eedbbf70 100644 --- a/examples/micro-hello.c +++ b/examples/micro-hello.c @@ -72,13 +72,17 @@ int main(int argc, char **argv) { printf("Error: %u - %s\n", s, natsStatus_GetText(s)); nats_PrintLastErrorStack(stderr); + natsOptions_Destroy(opts); return 1; } + err = micro_AddService(&m, conn, &cfg); if (err == NULL) err = microService_Run(m); microService_Destroy(m); + natsOptions_Destroy(opts); + natsConnection_Destroy(conn); if (err != NULL) { printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); diff --git a/examples/micro-sequence.c b/examples/micro-sequence.c index 2eb1a7f0f..057934c20 100644 --- a/examples/micro-sequence.c +++ b/examples/micro-sequence.c @@ -15,7 +15,7 @@ // Sequence NATS microservice example. // -// This example illustrated multiple NATS microservices communicating with each +// This example illustrates multiple NATS microservices communicating with each // other. // // The main service (c-sequence) calculates the sum of 1/f(1) + 1/f(2)... up to @@ -171,6 +171,7 @@ int main(int argc, char **argv) { printf("Error: %u - %s\n", s, natsStatus_GetText(s)); nats_PrintLastErrorStack(stderr); + natsOptions_Destroy(opts); return 1; } @@ -179,6 +180,8 @@ int main(int argc, char **argv) err = microService_Run(m); microService_Destroy(m); + natsOptions_Destroy(opts); + natsConnection_Destroy(conn); if (err != NULL) { printf("Error: %s\n", microError_String(err, errorbuf, sizeof(errorbuf))); diff --git a/examples/micro-stats.c b/examples/micro-stats.c index de7506056..abcc7c749 100644 --- a/examples/micro-stats.c +++ b/examples/micro-stats.c @@ -128,8 +128,8 @@ int main(int argc, char **argv) natsConnection *conn = NULL; char buf[2048]; - err = micro_ErrorFromStatus(natsConnection_Connect(&conn, opts)); - + err = micro_ErrorFromStatus( + natsConnection_Connect(&conn, opts)); if (err == NULL) err = run_example(conn, NULL, buf, sizeof(buf)); if (err == NULL) @@ -145,6 +145,8 @@ int main(int argc, char **argv) fprintf(stderr, "Error: %s\n", microError_String(err, buf, sizeof(buf))); } + natsOptions_Destroy(opts); + natsConnection_Destroy(conn); microError_Destroy(err); return err == NULL ? 0 : 1; } From 3f8321d5ee3fa5ce8e0db222d2328f1f5e6fb486 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Fri, 12 May 2023 10:41:21 -0700 Subject: [PATCH 21/85] PR feedback: removed redundant include mem.h --- src/micro_args.c | 1 - src/micro_client.c | 1 - src/micro_endpoint.c | 1 - src/micro_request.c | 1 - 4 files changed, 4 deletions(-) diff --git a/src/micro_args.c b/src/micro_args.c index be8995896..7c6eafbe6 100644 --- a/src/micro_args.c +++ b/src/micro_args.c @@ -13,7 +13,6 @@ #include "microp.h" #include "micro_args.h" -#include "mem.h" struct args_s { diff --git a/src/micro_client.c b/src/micro_client.c index 012c64b33..efb658571 100644 --- a/src/micro_client.c +++ b/src/micro_client.c @@ -12,7 +12,6 @@ // limitations under the License. #include "microp.h" -#include "mem.h" #include "conn.h" microError * diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index ca8c24ec7..dbe37c773 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -14,7 +14,6 @@ #include #include "microp.h" -#include "mem.h" static microError *dup_with_prefix(char **dst, const char *prefix, const char *src); static void free_endpoint(microEndpoint *ep); diff --git a/src/micro_request.c b/src/micro_request.c index 31532b918..30b6b8218 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -12,7 +12,6 @@ // limitations under the License. #include "microp.h" -#include "mem.h" microError * microRequest_Respond(microRequest *req, const char *data, size_t len) From af31cd92fcf7f246904a4df8a31ad39cc8785ad9 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Fri, 12 May 2023 11:14:42 -0700 Subject: [PATCH 22/85] PR Feedback: use `size_t` and `natsSubscription_GetSubject` --- src/micro.c | 9 ++++++--- src/micro_endpoint.c | 5 ++--- src/micro_error.c | 4 ++-- src/micro_monitoring.c | 3 ++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/micro.c b/src/micro.c index f59f0a904..07093ca1d 100644 --- a/src/micro.c +++ b/src/micro.c @@ -432,14 +432,17 @@ on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *closure) microEndpoint *ep = NULL; microError *err = NULL; bool our_subject = false; + const char *subject = NULL; if ((m == NULL) || (sub == NULL)) return; + subject = natsSubscription_GetSubject(sub); + micro_lock_service(m); for (ep = m->first_ep; (!our_subject) && (ep != NULL); ep = ep->next) { - if (micro_match_endpoint_subject(ep->subject, sub->subject)) + if (micro_match_endpoint_subject(ep->subject, subject)) { break; } @@ -509,7 +512,7 @@ microService_AddGroup(microGroup **new_group, microService *m, const char *prefi return micro_ErrorInvalidArg; *new_group = NATS_CALLOC(1, sizeof(microGroup) + - strlen(prefix) + 1); // "prefix."" + strlen(prefix) + 1); // "prefix\0" if (new_group == NULL) { return micro_ErrorOutOfMemory; @@ -527,7 +530,7 @@ microError * microGroup_AddGroup(microGroup **new_group, microGroup *parent, const char *prefix) { char *p; - int len; + size_t len; if ((parent == NULL) || (new_group == NULL) || (prefix == NULL)) return micro_ErrorInvalidArg; diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index dbe37c773..a61917461 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -186,7 +186,6 @@ static void update_last_error(microEndpoint *ep, microError *err) static void handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) { - // natsStatus s = NATS_OK; microError *err = NULL; microError *service_err = NULL; microEndpoint *ep = (microEndpoint *)closure; @@ -447,11 +446,11 @@ bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_sub static microError *dup_with_prefix(char **dst, const char *prefix, const char *src) { - int len = (int)strlen(src) + 1; + size_t len = strlen(src) + 1; char *p; if (!nats_IsStringEmpty(prefix)) - len += (int)strlen(prefix) + 1; + len += strlen(prefix) + 1; *dst = NATS_CALLOC(1, len); if (*dst == NULL) diff --git a/src/micro_error.c b/src/micro_error.c index 1b66b4298..91cd2ca7c 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -103,7 +103,7 @@ micro_ErrorFromStatus(natsStatus s) { microError *err = NULL; const char *message = natsStatus_GetText(s); - int message_len = strlen(message); + size_t message_len = strlen(message); if (s == NATS_OK) return NULL; @@ -176,7 +176,7 @@ microError_Wrapf(microError *err, const char *format, ...) const char * microError_String(microError *err, char *buf, int size) { - int used = 0; + size_t used = 0; const char *caused; if (buf == NULL) diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index cddb91704..0f26b4f91 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -115,7 +115,8 @@ static microError * new_dotted_subject(char **new_subject, int count, ...) { va_list args; - int i, len, n; + int i; + size_t len, n; char *result, *p; va_start(args, count); From 373d5dd348d615e241ccdd87fc0f0dd200f8db8d Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Fri, 12 May 2023 12:26:32 -0700 Subject: [PATCH 23/85] PR feedback: initialize decoded_len --- src/micro_args.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/micro_args.c b/src/micro_args.c index 7c6eafbe6..4d04a770c 100644 --- a/src/micro_args.c +++ b/src/micro_args.c @@ -190,7 +190,7 @@ decode_and_dupe_rest_of_string(char **dup, int *i, const char *data, int data_le { microError *err = NULL; int start = *i; - int decoded_len; + int decoded_len = 0; err = decode_rest_of_string(NULL, &decoded_len, i, data, data_len); if (err != NULL) From 84dffc2af1e98e2f359fbe3d418e4952598b8ea6 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Fri, 12 May 2023 12:32:56 -0700 Subject: [PATCH 24/85] PR feedback: removed err->message silliness --- src/micro_error.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/micro_error.c b/src/micro_error.c index 91cd2ca7c..e3cbfac3a 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -113,7 +113,6 @@ micro_ErrorFromStatus(natsStatus s) return &_errorOutOfMemory; err->status = s; - err->message = (char *)(err + 1); memcpy(err->message, message, message_len + 1); return err; } From cd5fae806d46051cdeb84cf073bbe11ae5646ff3 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Fri, 12 May 2023 12:38:44 -0700 Subject: [PATCH 25/85] PR feedback: cast size_t to int calling natsMsg_Create --- src/micro_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/micro_request.c b/src/micro_request.c index 30b6b8218..9f608ad75 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -38,7 +38,7 @@ microRequest_RespondCustom(microRequest *req, microError *service_error, const c } if (s == NATS_OK) { - s = natsMsg_Create(&msg, natsMsg_GetReply(req->Message), NULL, data, len); + s = natsMsg_Create(&msg, natsMsg_GetReply(req->Message), NULL, data, (int)len); } if ((s == NATS_OK) && (service_error != NULL)) { From a346df6dbbd59d7b690b6690217354bd28ca06a5 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 15 May 2023 10:31:42 -0700 Subject: [PATCH 26/85] Ref counting fixes --- src/micro.c | 94 ++++++++++++-------------------------------- src/micro_endpoint.c | 87 +++++++++++++++++++++------------------- src/microp.h | 7 ++-- test/test.c | 34 +++++++--------- 4 files changed, 90 insertions(+), 132 deletions(-) diff --git a/src/micro.c b/src/micro.c index 07093ca1d..fdd75f33d 100644 --- a/src/micro.c +++ b/src/micro.c @@ -41,6 +41,11 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c MICRO_CALL(err, micro_clone_service_config(&m->cfg, cfg)); + if (err == NULL) + { + m->is_running = true; + } + // Wrap the connection callbacks before we subscribe to anything. MICRO_CALL(err, wrap_connection_event_callbacks(m)) MICRO_CALL(err, micro_init_monitoring(m)); @@ -138,42 +143,6 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, return NULL; } -static void -_remove_endpoint(microService *m, microEndpoint *to_remove) -{ - microEndpoint *ptr = NULL; - microEndpoint *prev_ptr = NULL; - - if (m->first_ep == NULL) - return; - - if (m->first_ep == to_remove) - { - m->first_ep = m->first_ep->next; - return; - } - - prev_ptr = m->first_ep; - for (ptr = m->first_ep->next; ptr != NULL; ptr = ptr->next) - { - if (ptr == to_remove) - { - prev_ptr->next = ptr->next; - return; - } - } -} - -void micro_unlink_endpoint_from_service(microService *m, microEndpoint *to_remove) -{ - if ((m == NULL) || to_remove == NULL) - return; - - micro_lock_service(m); - _remove_endpoint(m, to_remove); - micro_unlock_service(m); -} - microError * microService_AddEndpoint(microService *m, microEndpointConfig *cfg) { @@ -195,24 +164,32 @@ microService_Stop(microService *m) microError *err = NULL; microEndpoint *ep = NULL; + bool is_running = false; + microEndpoint *first_ep = NULL; + if (m == NULL) return micro_ErrorInvalidArg; - // Stop is a rare call, it's ok to lock the service for the duration. micro_lock_service(m); + is_running = m->is_running; + first_ep = m->first_ep; + micro_unlock_service(m); - if (m->is_stopped) - { - micro_unlock_service(m); + if (!is_running) return NULL; - } err = unwrap_connection_event_callbacks(m); - for (ep = m->first_ep; (err == NULL) && (ep != NULL); ep = m->first_ep) + + for (ep = first_ep; (err == NULL) && (ep != NULL); ep = first_ep) { - m->first_ep = ep->next; + micro_lock_service(m); + first_ep = ep->next; + m->first_ep = first_ep; m->num_eps--; + micro_unlock_service(m); + // micro_destroy_endpoint may release the service, locking it in the + // process, so we need to avoid a race. if (err = micro_destroy_endpoint(ep), err != NULL) { err = microError_Wrapf(err, "failed to stop endpoint %s", ep->name); @@ -221,12 +198,14 @@ microService_Stop(microService *m) if (err == NULL) { - m->is_stopped = true; + micro_lock_service(m); + m->is_running = false; m->started = 0; m->num_eps = 0; + m->first_ep = NULL; + micro_unlock_service(m); } - micro_unlock_service(m); return err; } @@ -238,7 +217,7 @@ bool microService_IsStopped(microService *m) return true; micro_lock_service(m); - is_stopped = m->is_stopped; + is_stopped = !m->is_running; micro_unlock_service(m); return is_stopped; @@ -407,24 +386,6 @@ on_connection_closed(natsConnection *nc, void *closure) } } -static void -on_connection_disconnected(natsConnection *nc, void *closure) -{ - microService *m = (microService *)closure; - - if (m == NULL) - return; - - // <>/<> TODO: Should we stop the service? Not 100% how the Go client does - // it. - microService_Stop(m); - - if (m->prev_on_connection_disconnected != NULL) - { - (*m->prev_on_connection_closed)(nc, m->prev_on_connection_disconnected_closure); - } -} - static void on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *closure) { @@ -438,7 +399,7 @@ on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *closure) return; subject = natsSubscription_GetSubject(sub); - + micro_lock_service(m); for (ep = m->first_ep; (!our_subject) && (ep != NULL); ep = ep->next) { @@ -481,9 +442,6 @@ wrap_connection_event_callbacks(microService *m) IFOK(s, natsConn_getClosedCallback(&m->prev_on_connection_closed, &m->prev_on_connection_closed_closure, m->nc)); IFOK(s, natsConn_setClosedCallback(m->nc, on_connection_closed, m)); - IFOK(s, natsConn_getDisconnectedCallback(&m->prev_on_connection_disconnected, &m->prev_on_connection_disconnected_closure, m->nc)); - IFOK(s, natsConn_setDisconnectedCallback(m->nc, on_connection_disconnected, m)); - IFOK(s, natsConn_getErrorCallback(&m->prev_on_error, &m->prev_on_error_closure, m->nc)); IFOK(s, natsConn_setErrorCallback(m->nc, on_error, m)); diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index a61917461..c68e50941 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -19,7 +19,6 @@ static microError *dup_with_prefix(char **dst, const char *prefix, const char *s static void free_endpoint(microEndpoint *ep); static void handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); static microError *new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); -static void on_drain_complete(void *closure); static void release_endpoint(microEndpoint *ep); static microEndpoint *retain_endpoint(microEndpoint *ep); @@ -59,10 +58,17 @@ micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, return NULL; } +static void _release_on_complete(void *closure) +{ + release_endpoint((microEndpoint *)closure); +} + microError * micro_start_endpoint(microEndpoint *ep) { natsStatus s = NATS_OK; + natsSubscription *sub = NULL; + if ((ep->subject == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) // nothing to do return NULL; @@ -70,67 +76,68 @@ micro_start_endpoint(microEndpoint *ep) // reset the stats. memset(&ep->stats, 0, sizeof(ep->stats)); + // extra retain before subscribing since we'll need to hold it until + // on_complete on the subscription. + retain_endpoint(ep); + if (ep->is_monitoring_endpoint) - s = natsConnection_Subscribe(&ep->sub, ep->m->nc, ep->subject, handle_request, ep); + s = natsConnection_Subscribe(&sub, ep->m->nc, ep->subject, handle_request, ep); else - s = natsConnection_QueueSubscribe(&ep->sub, ep->m->nc, ep->subject, MICRO_QUEUE_GROUP, handle_request, ep); + s = natsConnection_QueueSubscribe(&sub, ep->m->nc, ep->subject, MICRO_QUEUE_GROUP, handle_request, ep); - IFOK(s, natsSubscription_SetOnCompleteCB(ep->sub, on_drain_complete, ep)); + if (s == NATS_OK) + { + natsSubscription_SetOnCompleteCB(sub, _release_on_complete, ep); + + micro_lock_endpoint(ep); + ep->sub = sub; + ep->is_running = true; + micro_unlock_endpoint(ep); + } + else + { + natsSubscription_Destroy(sub); // likely always a no-op. + release_endpoint(ep); // to compensate for the extra retain above. + } return micro_ErrorFromStatus(s); } microError * -micro_stop_endpoint(microEndpoint *ep) +micro_destroy_endpoint(microEndpoint *ep) { natsStatus s = NATS_OK; + natsSubscription *sub = NULL; + bool is_running = false; - if ((ep == NULL) || (ep->sub == NULL)) + if (ep == NULL) return NULL; - if (natsConnection_IsClosed(ep->m->nc) || !natsSubscription_IsValid(ep->sub)) - { - // <>/<> does the subscription need to be closed? It will be Destroy-ed - // when the endpoint is destroyed. + micro_lock_endpoint(ep); + sub = ep->sub; + is_running = ep->is_running; + micro_unlock_endpoint(ep); + + if (!is_running) return NULL; - } - // Initiate draining the subscription. Do not release the endpoint until - // the on_drain_complete. - retain_endpoint(ep); - s = natsSubscription_Drain(ep->sub); - if (s != NATS_OK) + if (!natsConnection_IsClosed(ep->m->nc) && natsSubscription_IsValid(sub)) { - release_endpoint(ep); - return microError_Wrapf(micro_ErrorFromStatus(s), - "failed to stop endpoint %s: failed to drain subscription", ep->name); + // When the drain is complete, will release the final ref on ep. + s = natsSubscription_Drain(sub); + if (s != NATS_OK) + { + return microError_Wrapf(micro_ErrorFromStatus(s), + "failed to stop endpoint %s: failed to drain subscription", ep->name); + } } - return NULL; -} - -microError * -micro_destroy_endpoint(microEndpoint *ep) -{ - microError *err = NULL; - err = micro_stop_endpoint(ep); - if (err != NULL) - return err; + // Release ep since it's no longer running (to compensate for the retain in + // micro_start_endpoint). release_endpoint(ep); return NULL; } -static void on_drain_complete(void *closure) -{ - if (closure == NULL) - return; - - microEndpoint *ep =(microEndpoint *)closure; - - micro_unlink_endpoint_from_service(ep->m, ep); - release_endpoint((microEndpoint *)closure); -} - microEndpoint * retain_endpoint(microEndpoint *ep) { diff --git a/src/microp.h b/src/microp.h index a959d8721..dec9c365c 100644 --- a/src/microp.h +++ b/src/microp.h @@ -74,6 +74,7 @@ struct micro_endpoint_s // Mutex for starting/stopping the endpoint, and for updating the stats. natsMutex *endpoint_mu; int refs; + bool is_running; // The subscription for the endpoint. If NULL, the endpoint is stopped. natsSubscription *sub; @@ -119,7 +120,7 @@ struct micro_service_s void *prev_on_error_closure; int64_t started; // UTC time expressed as number of nanoseconds since epoch. - bool is_stopped; + bool is_running; }; /** @@ -151,6 +152,7 @@ extern microError *micro_ErrorInvalidArg; microError *micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); microError *micro_clone_endpoint_config(microEndpointConfig **new_cfg, microEndpointConfig *cfg); microError *micro_clone_service_config(microServiceConfig **new_cfg, microServiceConfig *cfg); +microError *micro_destroy_endpoint(microEndpoint *ep); void micro_free_cloned_endpoint_config(microEndpointConfig *cfg); void micro_free_cloned_service_config(microServiceConfig *cfg); void micro_free_request(microRequest *req); @@ -165,9 +167,6 @@ microError *micro_new_request(microRequest **new_request, microService *m, micro void micro_release_service(microService *m); microService *micro_retain_service(microService *m); microError *micro_start_endpoint(microEndpoint *ep); -microError *micro_destroy_endpoint(microEndpoint *ep); -microError *micro_stop_endpoint(microEndpoint *ep); -void micro_unlink_endpoint_from_service(microService *m, microEndpoint *to_remove); void micro_update_last_error(microEndpoint *ep, microError *err); static inline void micro_lock_service(microService *m) { natsMutex_Lock(m->service_mu); } diff --git a/test/test.c b/test/test.c index 3e3c6dae3..d59f4d1ec 100644 --- a/test/test.c +++ b/test/test.c @@ -32821,7 +32821,9 @@ test_MicroStartStop(void) s = _createDefaultThreadArgsForCbTests(&arg); if (s == NATS_OK) opts = _createReconnectOptions(); - if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) + if ((opts == NULL) || + (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) || + (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) { FAIL("Unable to setup test for MicroConnectionEvents!"); } @@ -32856,7 +32858,6 @@ test_MicroStartStop(void) // exclude themselves from a chain, rather than setting previous, etc. for (i = NUM_BASIC_MICRO_SERVICES - 1; i >= 0; i--) { - microService_Destroy(svcs[i]); } NATS_FREE(svcs); @@ -32886,7 +32887,9 @@ test_MicroServiceStopsOnClosedConn(void) if (s == NATS_OK) opts = _createReconnectOptions(); - if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) + if ((opts == NULL) || + (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) || + (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) { FAIL("Unable to setup test for MicroConnectionEvents!"); } @@ -32908,24 +32911,11 @@ test_MicroServiceStopsOnClosedConn(void) natsMsg_Destroy(reply); reply = NULL; - test("Stop microservice: "); - testCond(NULL == microService_Stop(m)) - - test("Test microservice is not running: "); - testCond(microService_IsStopped(m)) - - test("Test microservice is not responding to PING: "); - testCond(NATS_OK != natsConnection_RequestString(&reply, nc, "$SRV.PING.test", "", 500)); - - test("Destroy microservice (cleanup): "); - testCond(NULL == microService_Destroy(m)) - - test("Start microservice again: "); - testCond(NULL == micro_AddService(&m, nc, &cfg)); - test("Close the connection: "); testCond(NATS_OK == natsConnection_Drain(nc)); natsConnection_Close(nc); + test("Ensure the connection has closed: "); + testCond(natsConnection_IsClosed(nc)); test("Wait for the service to stop: "); testCond((nats_Sleep(100), true)); @@ -32933,7 +32923,9 @@ test_MicroServiceStopsOnClosedConn(void) test("Test microservice is stopped: "); testCond(microService_IsStopped(m)); - microService_Destroy(m); + test("Destroy microservice (final): "); + testCond(NULL == microService_Destroy(m)) + natsOptions_Destroy(opts); natsConnection_Destroy(nc); _destroyDefaultThreadArgs(&arg); @@ -32958,7 +32950,9 @@ test_MicroServiceStopsWhenServerStops(void) if (s == NATS_OK) opts = _createReconnectOptions(); - if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) + if ((opts == NULL) || + (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) || + (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) { FAIL("Unable to setup test for MicroConnectionEvents!"); } From 99965158fc9685f3e63549945835f8885949a07f Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 15 May 2023 17:10:43 -0700 Subject: [PATCH 27/85] PR feedback: strdup-ed strings in Info and Stats --- src/micro.c | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/micro.c b/src/micro.c index fdd75f33d..4e4d20c51 100644 --- a/src/micro.c +++ b/src/micro.c @@ -536,12 +536,14 @@ microService_GetInfo(microServiceInfo **new_info, microService *m) if (info == NULL) return micro_ErrorOutOfMemory; - info->Name = m->cfg->Name; - info->Version = m->cfg->Version; - info->Description = m->cfg->Description; - info->Id = m->id; + info->Name = NATS_STRDUP(m->cfg->Name); + info->Version = NATS_STRDUP(m->cfg->Version); + info->Description = NATS_STRDUP(m->cfg->Description); + info->Id = NATS_STRDUP(m->id); info->Type = MICRO_INFO_RESPONSE_TYPE; + micro_lock_service(m); + // Overallocate subjects, will filter out internal ones. info->Subjects = NATS_CALLOC(m->num_eps, sizeof(char *)); if (info->Subjects == NULL) @@ -550,12 +552,11 @@ microService_GetInfo(microServiceInfo **new_info, microService *m) return micro_ErrorOutOfMemory; } - micro_lock_service(m); for (ep = m->first_ep; ep != NULL; ep = ep->next) { if ((!ep->is_monitoring_endpoint) && (ep->subject != NULL)) { - info->Subjects[len] = ep->subject; + info->Subjects[len] = NATS_STRDUP(ep->subject); len++; } } @@ -568,11 +569,18 @@ microService_GetInfo(microServiceInfo **new_info, microService *m) void microServiceInfo_Destroy(microServiceInfo *info) { + int i; + if (info == NULL) return; - // subjects themselves must not be freed, just the collection. + for(i = 0; i < info->SubjectsLen; i++) + NATS_FREE((char *)info->Subjects[i]); NATS_FREE(info->Subjects); + NATS_FREE((char *)info->Name); + NATS_FREE((char *)info->Version); + NATS_FREE((char *)info->Description); + NATS_FREE((char *)info->Id); NATS_FREE(info); } @@ -591,12 +599,14 @@ microService_GetStats(microServiceStats **new_stats, microService *m) if (stats == NULL) return micro_ErrorOutOfMemory; - stats->Name = m->cfg->Name; - stats->Version = m->cfg->Version; - stats->Id = m->id; + stats->Name = NATS_STRDUP(m->cfg->Name); + stats->Version = NATS_STRDUP(m->cfg->Version); + stats->Id = NATS_STRDUP(m->id); stats->Started = m->started; stats->Type = MICRO_STATS_RESPONSE_TYPE; + micro_lock_service(m); + // Allocate the actual structs, not pointers. Overallocate for the internal // endpoints even though they are filtered out. stats->Endpoints = NATS_CALLOC(m->num_eps, sizeof(microEndpointStats)); @@ -606,7 +616,6 @@ microService_GetStats(microServiceStats **new_stats, microService *m) return micro_ErrorOutOfMemory; } - micro_lock_service(m); for (ep = m->first_ep; ep != NULL; ep = ep->next) { if ((ep != NULL) && (!ep->is_monitoring_endpoint) && (ep->endpoint_mu != NULL)) @@ -615,8 +624,8 @@ microService_GetStats(microServiceStats **new_stats, microService *m) // copy the entire struct, including the last error buffer. stats->Endpoints[len] = ep->stats; - stats->Endpoints[len].Name = ep->name; - stats->Endpoints[len].Subject = ep->subject; + stats->Endpoints[len].Name = NATS_STRDUP(ep->name); + stats->Endpoints[len].Subject = NATS_STRDUP(ep->subject); avg = (long double)ep->stats.processing_time_s * 1000000000.0 + (long double)ep->stats.processing_time_ns; avg = avg / (long double)ep->stats.num_requests; stats->Endpoints[len].average_processing_time_ns = (int64_t)avg; @@ -624,6 +633,7 @@ microService_GetStats(microServiceStats **new_stats, microService *m) micro_unlock_endpoint(ep); } } + micro_unlock_service(m); stats->EndpointsLen = len; @@ -633,9 +643,19 @@ microService_GetStats(microServiceStats **new_stats, microService *m) void microServiceStats_Destroy(microServiceStats *stats) { + int i; + if (stats == NULL) return; + for (i=0; i < stats->EndpointsLen; i++) + { + NATS_FREE((char *)stats->Endpoints[i].Name); + NATS_FREE((char *)stats->Endpoints[i].Subject); + } NATS_FREE(stats->Endpoints); + NATS_FREE((char *)stats->Name); + NATS_FREE((char *)stats->Version); + NATS_FREE((char *)stats->Id); NATS_FREE(stats); } From 1aecdd454f648678db029098665ed7aeded47a1e Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 15 May 2023 17:24:46 -0700 Subject: [PATCH 28/85] Fixed NULL pointer crashes in the previous commit --- src/micro.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/micro.c b/src/micro.c index 4e4d20c51..37cdb5614 100644 --- a/src/micro.c +++ b/src/micro.c @@ -536,10 +536,10 @@ microService_GetInfo(microServiceInfo **new_info, microService *m) if (info == NULL) return micro_ErrorOutOfMemory; - info->Name = NATS_STRDUP(m->cfg->Name); - info->Version = NATS_STRDUP(m->cfg->Version); - info->Description = NATS_STRDUP(m->cfg->Description); - info->Id = NATS_STRDUP(m->id); + micro_strdup((char **)&info->Name, m->cfg->Name); + micro_strdup((char **)&info->Version, m->cfg->Version); + micro_strdup((char **)&info->Description, m->cfg->Description); + micro_strdup((char **)&info->Id, m->id); info->Type = MICRO_INFO_RESPONSE_TYPE; micro_lock_service(m); @@ -556,7 +556,7 @@ microService_GetInfo(microServiceInfo **new_info, microService *m) { if ((!ep->is_monitoring_endpoint) && (ep->subject != NULL)) { - info->Subjects[len] = NATS_STRDUP(ep->subject); + micro_strdup((char **)&info->Subjects[len], ep->subject); len++; } } @@ -599,9 +599,9 @@ microService_GetStats(microServiceStats **new_stats, microService *m) if (stats == NULL) return micro_ErrorOutOfMemory; - stats->Name = NATS_STRDUP(m->cfg->Name); - stats->Version = NATS_STRDUP(m->cfg->Version); - stats->Id = NATS_STRDUP(m->id); + micro_strdup((char **)&stats->Name, m->cfg->Name); + micro_strdup((char **)&stats->Version, m->cfg->Version); + micro_strdup((char **)&stats->Id, m->id); stats->Started = m->started; stats->Type = MICRO_STATS_RESPONSE_TYPE; @@ -624,8 +624,8 @@ microService_GetStats(microServiceStats **new_stats, microService *m) // copy the entire struct, including the last error buffer. stats->Endpoints[len] = ep->stats; - stats->Endpoints[len].Name = NATS_STRDUP(ep->name); - stats->Endpoints[len].Subject = NATS_STRDUP(ep->subject); + micro_strdup((char **)&stats->Endpoints[len].Name, ep->name); + micro_strdup((char **)&stats->Endpoints[len].Subject, ep->subject); avg = (long double)ep->stats.processing_time_s * 1000000000.0 + (long double)ep->stats.processing_time_ns; avg = avg / (long double)ep->stats.num_requests; stats->Endpoints[len].average_processing_time_ns = (int64_t)avg; From e071af1fdcea9e609d0eaccb7cabb67c3c6a59b5 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 15 May 2023 17:25:59 -0700 Subject: [PATCH 29/85] PR feedback: micro_new_endpoint: check if endpoint config is NULL --- src/micro_endpoint.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index c68e50941..69a1a58fd 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -29,8 +29,12 @@ micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpoint *ep = NULL; const char *subj; - if (!micro_is_valid_name(cfg->Name) || (cfg->Handler == NULL)) - return micro_ErrorInvalidArg; + if (cfg == NULL) + return microError_Wrapf(micro_ErrorInvalidArg, "NULL endpoint config"); + if (!micro_is_valid_name(cfg->Name)) + return microError_Wrapf(micro_ErrorInvalidArg, "invalid endpoint name %s", cfg->Name); + if (cfg->Handler == NULL) + return microError_Wrapf(micro_ErrorInvalidArg, "NULL endpoint request handler for %s", cfg->Name); if ((cfg->Subject != NULL) && !micro_is_valid_subject(cfg->Subject)) return micro_ErrorInvalidArg; From 996803393bfc8561bf95b211d3fc323a9021cb85 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 15 May 2023 17:39:21 -0700 Subject: [PATCH 30/85] PR feedback: missing unlocks --- src/micro.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/micro.c b/src/micro.c index 37cdb5614..d6facfe76 100644 --- a/src/micro.c +++ b/src/micro.c @@ -548,6 +548,7 @@ microService_GetInfo(microServiceInfo **new_info, microService *m) info->Subjects = NATS_CALLOC(m->num_eps, sizeof(char *)); if (info->Subjects == NULL) { + micro_unlock_service(m); NATS_FREE(info); return micro_ErrorOutOfMemory; } @@ -612,6 +613,7 @@ microService_GetStats(microServiceStats **new_stats, microService *m) stats->Endpoints = NATS_CALLOC(m->num_eps, sizeof(microEndpointStats)); if (stats->Endpoints == NULL) { + micro_unlock_service(m); NATS_FREE(stats); return micro_ErrorOutOfMemory; } From 0b1acae7629ddb09ea49672eda495897857243ae Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 15 May 2023 17:41:06 -0700 Subject: [PATCH 31/85] PR feedback: use natsBuf_AppendByte where appropriate --- src/micro_monitoring.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 0f26b4f91..3a40db31f 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -232,7 +232,7 @@ marshal_ping(natsBuffer **new_buf, microService *m) s = natsBuf_Create(&buf, 1024); if (s == NATS_OK) { - s = natsBuf_Append(buf, "{", -1); + s = natsBuf_AppendByte(buf, '{'); IFOK_attr("name", m->cfg->Name, ","); IFOK_attr("version", m->cfg->Version, ","); IFOK_attr("id", m->id, ","); @@ -257,7 +257,7 @@ marshal_info(natsBuffer **new_buf, microServiceInfo *info) natsStatus s; s = natsBuf_Create(&buf, 4096); - IFOK(s, natsBuf_Append(buf, "{", -1)); + IFOK(s, natsBuf_AppendByte(buf, '{')); IFOK_attr("description", info->Description, ","); IFOK_attr("id", info->Id, ","); IFOK_attr("name", info->Name, ","); @@ -268,13 +268,13 @@ marshal_info(natsBuffer **new_buf, microServiceInfo *info) IFOK(s, natsBuf_Append(buf, "\"subjects\":[", -1)); for (i = 0; i < info->SubjectsLen; i++) { - IFOK(s, natsBuf_Append(buf, "\"", -1)); + IFOK(s, natsBuf_AppendByte(buf, '"')); IFOK(s, natsBuf_Append(buf, info->Subjects[i], -1)); - IFOK(s, natsBuf_Append(buf, "\"", -1)); + IFOK(s, natsBuf_AppendByte(buf, '"')); if (i < (info->SubjectsLen - 1)) - IFOK(s, natsBuf_Append(buf, ",", -1)); + IFOK(s, natsBuf_AppendByte(buf, ',')); } - IFOK(s, natsBuf_Append(buf, "],", -1)); + IFOK(s, natsBuf_Append(buf, "],", 2)); } IFOK_attr("version", info->Version, ""); IFOK(s, natsBuf_AppendByte(buf, '}')); @@ -319,12 +319,12 @@ marshal_stats(natsBuffer **new_buf, microServiceStats *stats) IFOK(s, nats_marshalDuration(buf, true, "average_processing_time", ep->average_processing_time_ns)); IFOK(s, natsBuf_AppendByte(buf, ',')); IFOK_attr("last_error", ep->last_error_string, ""); - IFOK(s, natsBuf_Append(buf, "}", -1)); + IFOK(s, natsBuf_AppendByte(buf, '}')); if (i < (stats->EndpointsLen - 1)) - IFOK(s, natsBuf_Append(buf, ",", -1)); + IFOK(s, natsBuf_AppendByte(buf, ',')); } - IFOK(s, natsBuf_Append(buf, "],", -1)); + IFOK(s, natsBuf_Append(buf, "],", 2)); } IFOK_attr("version", stats->Version, ""); From d3816fc67640f3e8ab38ff81f10e9a9eff88e38f Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 15 May 2023 17:52:18 -0700 Subject: [PATCH 32/85] PR feedback: CamelCased remaining public fields --- examples/micro-stats.c | 2 +- src/micro.c | 12 ++++++------ src/micro_endpoint.c | 16 ++++++++-------- src/micro_monitoring.c | 14 +++++++------- src/nats.h | 12 ++++++------ 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/examples/micro-stats.c b/examples/micro-stats.c index abcc7c749..ce53b8d29 100644 --- a/examples/micro-stats.c +++ b/examples/micro-stats.c @@ -56,7 +56,7 @@ handle_stats(microRequest *req) if (err != NULL) return err; - total = stats->Endpoints[0].num_requests; + total = stats->Endpoints[0].NumRequests; custom = service_state->odd_count; len = snprintf(buf, sizeof(buf), "{\"total\":%d,\"odd\":%d}", total, custom); diff --git a/src/micro.c b/src/micro.c index d6facfe76..b67fdc4c9 100644 --- a/src/micro.c +++ b/src/micro.c @@ -575,7 +575,7 @@ void microServiceInfo_Destroy(microServiceInfo *info) if (info == NULL) return; - for(i = 0; i < info->SubjectsLen; i++) + for (i = 0; i < info->SubjectsLen; i++) NATS_FREE((char *)info->Subjects[i]); NATS_FREE(info->Subjects); NATS_FREE((char *)info->Name); @@ -628,14 +628,14 @@ microService_GetStats(microServiceStats **new_stats, microService *m) micro_strdup((char **)&stats->Endpoints[len].Name, ep->name); micro_strdup((char **)&stats->Endpoints[len].Subject, ep->subject); - avg = (long double)ep->stats.processing_time_s * 1000000000.0 + (long double)ep->stats.processing_time_ns; - avg = avg / (long double)ep->stats.num_requests; - stats->Endpoints[len].average_processing_time_ns = (int64_t)avg; + avg = (long double)ep->stats.ProcessingTimeSeconds * 1000000000.0 + (long double)ep->stats.ProcessingTimeNanoseconds; + avg = avg / (long double)ep->stats.NumRequests; + stats->Endpoints[len].AverageProcessingTimeNanoseconds = (int64_t)avg; len++; micro_unlock_endpoint(ep); } } - + micro_unlock_service(m); stats->EndpointsLen = len; @@ -650,7 +650,7 @@ void microServiceStats_Destroy(microServiceStats *stats) if (stats == NULL) return; - for (i=0; i < stats->EndpointsLen; i++) + for (i = 0; i < stats->EndpointsLen; i++) { NATS_FREE((char *)stats->Endpoints[i].Name); NATS_FREE((char *)stats->Endpoints[i].Subject); diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index 69a1a58fd..731bc5eb0 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -101,7 +101,7 @@ micro_start_endpoint(microEndpoint *ep) else { natsSubscription_Destroy(sub); // likely always a no-op. - release_endpoint(ep); // to compensate for the extra retain above. + release_endpoint(ep); // to compensate for the extra retain above. } return micro_ErrorFromStatus(s); @@ -190,8 +190,8 @@ void free_endpoint(microEndpoint *ep) static void update_last_error(microEndpoint *ep, microError *err) { - ep->stats.num_errors++; - microError_String(err, ep->stats.last_error_string, sizeof(ep->stats.last_error_string)); + ep->stats.NumErrors++; + microError_String(err, ep->stats.LastErrorString, sizeof(ep->stats.LastErrorString)); } static void @@ -237,11 +237,11 @@ handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *cl // Update stats. micro_lock_endpoint(ep); - stats->num_requests++; - stats->processing_time_ns += elapsed_ns; - full_s = stats->processing_time_ns / 1000000000; - stats->processing_time_s += full_s; - stats->processing_time_ns -= full_s * 1000000000; + stats->NumRequests++; + stats->ProcessingTimeNanoseconds += elapsed_ns; + full_s = stats->ProcessingTimeNanoseconds / 1000000000; + stats->ProcessingTimeSeconds += full_s; + stats->ProcessingTimeNanoseconds -= full_s * 1000000000; update_last_error(ep, err); micro_unlock_endpoint(ep); diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 3a40db31f..b79b53377 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -20,8 +20,8 @@ static microError *marshal_ping(natsBuffer **new_buf, microService *m); static microError *handle_ping(microRequest *req); static microError *marshal_info(natsBuffer **new_buf, microServiceInfo *info); static microError *handle_info(microRequest *req); -static microError * marshal_stats(natsBuffer **new_buf, microServiceStats *stats); -static microError * handle_stats(microRequest *req); +static microError *marshal_stats(natsBuffer **new_buf, microServiceStats *stats); +static microError *handle_stats(microRequest *req); static microError * add_internal_handler(microService *m, const char *verb, const char *kind, const char *id, const char *name, microRequestHandler handler); @@ -105,7 +105,7 @@ handle_stats(microRequest *req) if ((m == NULL) || (m->cfg == NULL)) return micro_ErrorInvalidArg; // Should not happen - if (m->cfg->StatsHandler != NULL) + if (m->cfg->StatsHandler != NULL) return m->cfg->StatsHandler(req); else return handle_stats_internal(req); @@ -314,11 +314,11 @@ marshal_stats(natsBuffer **new_buf, microServiceStats *stats) IFOK(s, natsBuf_AppendByte(buf, '{')); IFOK_attr("name", ep->Name, ","); IFOK_attr("subject", ep->Subject, ","); - IFOK(s, nats_marshalLong(buf, false, "num_requests", ep->num_requests)); - IFOK(s, nats_marshalLong(buf, true, "num_errors", ep->num_errors)); - IFOK(s, nats_marshalDuration(buf, true, "average_processing_time", ep->average_processing_time_ns)); + IFOK(s, nats_marshalLong(buf, false, "num_requests", ep->NumRequests)); + IFOK(s, nats_marshalLong(buf, true, "num_errors", ep->NumErrors)); + IFOK(s, nats_marshalDuration(buf, true, "average_processing_time", ep->AverageProcessingTimeNanoseconds)); IFOK(s, natsBuf_AppendByte(buf, ',')); - IFOK_attr("last_error", ep->last_error_string, ""); + IFOK_attr("last_error", ep->LastErrorString, ""); IFOK(s, natsBuf_AppendByte(buf, '}')); if (i < (stats->EndpointsLen - 1)) diff --git a/src/nats.h b/src/nats.h index e93653bce..8150bbec3 100644 --- a/src/nats.h +++ b/src/nats.h @@ -7374,33 +7374,33 @@ struct micro_endpoint_stats_s /** * @brief The number of requests received by the endpoint. */ - int64_t num_requests; + int64_t NumRequests; /** * @brief The number of errors, service-level and internal, associated with * the endpoint. */ - int64_t num_errors; + int64_t NumErrors; /** * @brief total request processing time (the seconds part). */ - int64_t processing_time_s; + int64_t ProcessingTimeSeconds; /** * @brief total request processing time (the nanoseconds part). */ - int64_t processing_time_ns; + int64_t ProcessingTimeNanoseconds; /** * @brief average request processing time, in ns. */ - int64_t average_processing_time_ns; + int64_t AverageProcessingTimeNanoseconds; /** * @brief a copy of the last error message. */ - char last_error_string[2048]; + char LastErrorString[2048]; }; /** From 4745e19834bf4a2df18baef697b35301ef9776f4 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Fri, 19 May 2023 06:44:25 -0700 Subject: [PATCH 33/85] Added support for multiple handlers for ConnectionClosed and AsyncError callback events. Added a nats_CallbackList internal type, use it for closed and error cases. Removed prior code for natsConn_get/setXXXCallback. Cleaned up access to other callbacks in conn.c (lock opts). --- src/conn.c | 136 ++++++++------------------- src/conn.h | 19 ---- src/micro.c | 40 +++----- src/microp.h | 7 -- src/nats.c | 145 ++++++++++++++++++---------- src/natsp.h | 25 ++++- src/opts.c | 256 +++++++++++++++++++++++++++++++++++++++++++++++--- src/opts.h | 22 +++++ test/list.txt | 5 +- test/test.c | 118 ++++++++++++++++------- 10 files changed, 521 insertions(+), 252 deletions(-) diff --git a/src/conn.c b/src/conn.c index b1c9446df..9309993b5 100644 --- a/src/conn.c +++ b/src/conn.c @@ -526,10 +526,17 @@ _processInfo(natsConnection *nc, char *info, int len) { natsStatus s = NATS_OK; nats_JSON *json = NULL; + bool postDiscoveredServersCb = false; + bool postLameDuckCb = false; if (info == NULL) return NATS_OK; + natsMutex_Lock(nc->opts->mu); + postDiscoveredServersCb = (nc->opts->discoveredServersCb != NULL); + postLameDuckCb = (nc->opts->lameDuckCb != NULL); + natsMutex_Unlock(nc->opts->mu); + _clearServerInfo(&(nc->info)); s = nats_JSONParse(&json, info, len); @@ -574,13 +581,13 @@ _processInfo(natsConnection *nc, char *info, int len) nc->info.connectURLsCount, tlsName, &added); - if ((s == NATS_OK) && added && !nc->initc && (nc->opts->discoveredServersCb != NULL)) + if ((s == NATS_OK) && added && !nc->initc && postDiscoveredServersCb) natsAsyncCb_PostConnHandler(nc, ASYNC_DISCOVERED_SERVERS); } // Process the LDM callback after the above. It will cover cases where // we have connect URLs and invoke discovered server callback, and case // where we don't. - if ((s == NATS_OK) && nc->info.lameDuckMode && (nc->opts->lameDuckCb != NULL)) + if ((s == NATS_OK) && nc->info.lameDuckMode && postLameDuckCb) natsAsyncCb_PostConnHandler(nc, ASYNC_LAME_DUCK_MODE); if (s != NATS_OK) @@ -1523,6 +1530,15 @@ _doReconnect(void *arg) int i = 0; natsCustomReconnectDelayHandler crd = NULL; void *crdClosure = NULL; + bool postDisconnectedCb = false; + bool postReconnectedCb = false; + bool postConnectedCb = false; + + natsMutex_Lock(nc->opts->mu); + postDisconnectedCb = (nc->opts->disconnectedCb != NULL); + postReconnectedCb = (nc->opts->reconnectedCb != NULL); + postConnectedCb = (nc->opts->connectedCb != NULL); + natsMutex_Unlock(nc->opts->mu); natsConn_Lock(nc); @@ -1542,7 +1558,7 @@ _doReconnect(void *arg) // Perform appropriate callback if needed for a disconnect. // (do not do this if we are here on initial connect failure) - if (!nc->initc && (nc->opts->disconnectedCb != NULL)) + if (!nc->initc && postDisconnectedCb) natsAsyncCb_PostConnHandler(nc, ASYNC_DISCONNECTED); crd = nc->opts->customReconnectDelayCB; @@ -1706,7 +1722,7 @@ _doReconnect(void *arg) // This was the initial connect. Set this to false. nc->initc = false; // Invoke the callback. - if (nc->opts->connectedCb != NULL) + if (postConnectedCb) natsAsyncCb_PostConnHandler(nc, ASYNC_CONNECTED); } else @@ -1714,7 +1730,7 @@ _doReconnect(void *arg) // Call reconnectedCB if appropriate. Since we are in a separate // thread, we could invoke the callback directly, however, we // still post it so all callbacks from a connection are serialized. - if (nc->opts->reconnectedCb != NULL) + if (postReconnectedCb) natsAsyncCb_PostConnHandler(nc, ASYNC_RECONNECTED); } @@ -1998,13 +2014,20 @@ _connect(natsConnection *nc) int max = 0; int64_t wtime = 0; bool retry = false; + bool retryOnFailedConnect = false; + bool hasConnectedCb = false; + + natsMutex_Lock(nc->opts->mu); + hasConnectedCb = (nc->opts->connectedCb != NULL); + retryOnFailedConnect = nc->opts->retryOnFailedConnect; + natsMutex_Unlock(nc->opts->mu); natsConn_Lock(nc); nc->initc = true; pool = nc->srvPool; - if ((nc->opts->retryOnFailedConnect) && (nc->opts->connectedCb == NULL)) + if ((retryOnFailedConnect) && !hasConnectedCb) { retry = true; max = nc->opts->maxReconnect; @@ -2057,6 +2080,7 @@ _connect(natsConnection *nc) retSts = NATS_OK; } } + if (!retry) break; @@ -2070,8 +2094,8 @@ _connect(natsConnection *nc) // If not connected and retry asynchronously on failed connect if ((nc->status != NATS_CONN_STATUS_CONNECTED) - && nc->opts->retryOnFailedConnect - && (nc->opts->connectedCb != NULL)) + && retryOnFailedConnect + && hasConnectedCb) { natsConn_Unlock(nc); @@ -2472,8 +2496,15 @@ _close(natsConnection *nc, natsConnStatus status, bool fromPublicClose, bool doC struct threadsToJoin ttj; bool sockWasActive = false; bool detach = false; + bool postClosedCb = false; + bool postDisconnectedCb = false; natsSubscription *sub = NULL; + natsMutex_Lock(nc->opts->mu); + postClosedCb = (nc->opts->closedCb != NULL); + postDisconnectedCb = (nc->opts->disconnectedCb != NULL); + natsMutex_Unlock(nc->opts->mu); + natsConn_lockAndRetain(nc); // If invoked from the public Close() call, attempt to flush @@ -2547,7 +2578,7 @@ _close(natsConnection *nc, natsConnStatus status, bool fromPublicClose, bool doC // Perform appropriate callback if needed for a disconnect. // Do not invoke if we were disconnected and failed to reconnect (since // it has already been invoked in doReconnect). - if (doCBs && !nc->rle && (nc->opts->disconnectedCb != NULL) && sockWasActive) + if (doCBs && !nc->rle && postDisconnectedCb && sockWasActive) natsAsyncCb_PostConnHandler(nc, ASYNC_DISCONNECTED); sub = nc->respMux; @@ -2563,7 +2594,7 @@ _close(natsConnection *nc, natsConnStatus status, bool fromPublicClose, bool doC natsConn_Lock(nc); // Perform appropriate callback if needed for a connection closed. - if (doCBs && (nc->opts->closedCb != NULL)) + if (doCBs && postClosedCb) natsAsyncCb_PostConnHandler(nc, ASYNC_CLOSED); nc->status = status; @@ -4432,88 +4463,3 @@ natsConn_defaultErrHandler(natsConnection *nc, natsSubscription *sub, natsStatus } fflush(stderr); } - -natsStatus -natsConn_getErrorCallback(natsErrHandler *cb, void **closure, natsConnection *nc) -{ - if ((nc == NULL) || (nc->opts == NULL) || (nc->opts->mu == NULL) || (cb == NULL) || (closure == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsMutex_Lock(nc->opts->mu); - *cb = nc->opts->asyncErrCb; - *closure = nc->opts->asyncErrCbClosure; - natsMutex_Unlock(nc->opts->mu); - - return NATS_OK; -} - -natsStatus -natsConn_setErrorCallback(natsConnection *nc, natsErrHandler cb, void *closure) -{ - // The error callback must not be NULL, other code may rely on it. - if ((nc == NULL) || (nc->opts == NULL) || (nc->opts->mu == NULL) || (cb == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsMutex_Lock(nc->opts->mu); - nc->opts->asyncErrCb = cb; - nc->opts->asyncErrCbClosure = closure; - natsMutex_Unlock(nc->opts->mu); - - return NATS_OK; -} - -natsStatus -natsConn_getClosedCallback(natsConnectionHandler *cb, void **closure, natsConnection *nc) -{ - if ((nc == NULL) || (nc->opts == NULL) || (nc->opts->mu == NULL) || (cb == NULL) || (closure == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsMutex_Lock(nc->opts->mu); - *cb = nc->opts->closedCb; - *closure = nc->opts->closedCbClosure; - natsMutex_Unlock(nc->opts->mu); - - return NATS_OK; -} - -natsStatus -natsConn_setClosedCallback(natsConnection *nc, natsConnectionHandler cb, void *closure) -{ - if (nc == NULL || (nc->opts == NULL) || (nc->opts->mu == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsMutex_Lock(nc->opts->mu); - nc->opts->closedCb = cb; - nc->opts->closedCbClosure = closure; - natsMutex_Unlock(nc->opts->mu); - - return NATS_OK; -} - -natsStatus -natsConn_getDisconnectedCallback(natsConnectionHandler *cb, void **closure, natsConnection *nc) -{ - if ((nc == NULL) || (nc->opts == NULL) || (nc->opts->mu == NULL) || (cb == NULL) || (closure == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsMutex_Lock(nc->opts->mu); - *cb = nc->opts->disconnectedCb; - *closure = nc->opts->disconnectedCbClosure; - natsMutex_Unlock(nc->opts->mu); - - return NATS_OK; -} - -natsStatus -natsConn_setDisconnectedCallback(natsConnection *nc, natsConnectionHandler cb, void *closure) -{ - if (nc == NULL || (nc->opts == NULL) || (nc->opts->mu == NULL)) - return nats_setDefaultError(NATS_INVALID_ARG); - - natsMutex_Lock(nc->opts->mu); - nc->opts->disconnectedCb = cb; - nc->opts->disconnectedCbClosure = closure; - natsMutex_Unlock(nc->opts->mu); - - return NATS_OK; -} diff --git a/src/conn.h b/src/conn.h index 8f297b348..6321f9193 100644 --- a/src/conn.h +++ b/src/conn.h @@ -160,23 +160,4 @@ natsConn_close(natsConnection *nc); void natsConn_destroy(natsConnection *nc, bool fromPublicDestroy); -natsStatus -natsConn_setErrorCallback(natsConnection *nc, natsErrHandler cb, void *closure); - -natsStatus -natsConn_getErrorCallback(natsErrHandler *cb, void **closure, natsConnection *nc); - -natsStatus -natsConn_setClosedCallback(natsConnection *nc, natsConnectionHandler cb, void *closure); - -natsStatus -natsConn_getClosedCallback(natsConnectionHandler *cb, void **closure, natsConnection *nc); - -natsStatus -natsConn_setDisconnectedCallback(natsConnection *nc, natsConnectionHandler cb, void *closure); - -natsStatus -natsConn_getDisconnectedCallback(natsConnectionHandler *cb, void **closure, natsConnection *nc); - - #endif /* CONN_H_ */ diff --git a/src/micro.c b/src/micro.c index b67fdc4c9..39d9fe2e4 100644 --- a/src/micro.c +++ b/src/micro.c @@ -13,12 +13,12 @@ #include "microp.h" #include "conn.h" -#include "mem.h" +#include "opts.h" static microError *new_service(microService **ptr, natsConnection *nc); static void free_service(microService *m); static microError *wrap_connection_event_callbacks(microService *m); -static microError *unwrap_connection_event_callbacks(microService *m); +static void unwrap_connection_event_callbacks(microService *m); microError * micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *cfg) @@ -178,7 +178,7 @@ microService_Stop(microService *m) if (!is_running) return NULL; - err = unwrap_connection_event_callbacks(m); + unwrap_connection_event_callbacks(m); for (ep = first_ep; (err == NULL) && (ep != NULL); ep = first_ep) { @@ -379,11 +379,6 @@ on_connection_closed(natsConnection *nc, void *closure) return; microService_Stop(m); - - if (m->prev_on_connection_closed != NULL) - { - (*m->prev_on_connection_closed)(nc, m->prev_on_connection_closed_closure); - } } static void @@ -424,11 +419,6 @@ on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *closure) // <>/<> TODO: Should we stop the service? The Go client does. microService_Stop(m); - - if (m->prev_on_error != NULL) - { - (*m->prev_on_error)(nc, sub, s, m->prev_on_error_closure); - } } static microError * @@ -436,31 +426,23 @@ wrap_connection_event_callbacks(microService *m) { natsStatus s = NATS_OK; - if (m == NULL) + if ((m == NULL) || (m->nc == NULL) || (m->nc->opts == NULL)) return micro_ErrorInvalidArg; - IFOK(s, natsConn_getClosedCallback(&m->prev_on_connection_closed, &m->prev_on_connection_closed_closure, m->nc)); - IFOK(s, natsConn_setClosedCallback(m->nc, on_connection_closed, m)); - - IFOK(s, natsConn_getErrorCallback(&m->prev_on_error, &m->prev_on_error_closure, m->nc)); - IFOK(s, natsConn_setErrorCallback(m->nc, on_error, m)); + IFOK(s, natsOptions_addConnectionClosedCallback(m->nc->opts,on_connection_closed, m)); + IFOK(s, natsOptions_addErrorCallback(m->nc->opts, on_error, m)); return microError_Wrapf(micro_ErrorFromStatus(s), "failed to wrap connection event callbacks"); } -static microError * +static void unwrap_connection_event_callbacks(microService *m) { - natsStatus s = NATS_OK; - - if (m == NULL) - return micro_ErrorInvalidArg; - - IFOK(s, natsConn_setClosedCallback(m->nc, m->prev_on_connection_closed, m->prev_on_connection_closed_closure)); - IFOK(s, natsConn_setDisconnectedCallback(m->nc, m->prev_on_connection_disconnected, m->prev_on_connection_disconnected_closure)); - IFOK(s, natsConn_setErrorCallback(m->nc, m->prev_on_error, m->prev_on_error_closure)); + if ((m == NULL) || (m->nc == NULL) || (m->nc->opts == NULL)) + return; - return microError_Wrapf(micro_ErrorFromStatus(s), "failed to unwrap connection event callbacks"); + natsOptions_removeConnectionClosedCallback(m->nc->opts, on_connection_closed, m); + natsOptions_removeErrorCallback(m->nc->opts, on_error, m); } microError * diff --git a/src/microp.h b/src/microp.h index dec9c365c..d6a64588a 100644 --- a/src/microp.h +++ b/src/microp.h @@ -112,13 +112,6 @@ struct micro_service_s struct micro_endpoint_s *first_ep; int num_eps; - natsConnectionHandler prev_on_connection_closed; - void *prev_on_connection_closed_closure; - natsConnectionHandler prev_on_connection_disconnected; - void *prev_on_connection_disconnected_closure; - natsErrHandler prev_on_error; - void *prev_on_error_closure; - int64_t started; // UTC time expressed as number of nanoseconds since epoch. bool is_running; }; diff --git a/src/nats.c b/src/nats.c index f7f284d0e..996a593f1 100644 --- a/src/nats.c +++ b/src/nats.c @@ -31,6 +31,7 @@ #include "sub.h" #include "nkeys.h" #include "crypto.h" +#include "opts.h" #define WAIT_LIB_INITIALIZED \ natsMutex_Lock(gLib.lock); \ @@ -723,15 +724,42 @@ _timerThread(void *arg) natsLib_Release(); } +// _dup_callback_list clones cb. However, for a single callback it avoids an +// allocation by copying it into a pre-allocated `single`. In this case `clone` +// is set to NULL and single is returned. If cb is a list, then the entire list +// is cloned, placed into `clone`, and returned. +static nats_CallbackList* +_dup_callback_list(nats_CallbackList *single, nats_CallbackList **clone, nats_CallbackList *cb) +{ + static nats_CallbackList empty = {.next = NULL}; + + *single = empty; + *clone = NULL; + + if (cb == NULL) + return NULL; + + if (cb->next == NULL) + { + *single = *cb; + return single; + } + + // Ignore the status, return NULL on errors. + natsOptions_cloneCallbackList(clone, cb); + return *clone; +} + + static void _asyncCbsThread(void *arg) { natsLibAsyncCbs *asyncCbs = &(gLib.asyncCbs); natsAsyncCbInfo *cb = NULL; natsConnection *nc = NULL; - natsConnectionHandler cbHandler = NULL; - natsErrHandler errHandler = NULL; - void *cbClosure = NULL; + nats_CallbackList *call = NULL; + nats_CallbackList *clone = NULL; + nats_CallbackList single = {.next=NULL}; #if defined(NATS_HAS_STREAMING) stanConnection *sc = NULL; #endif @@ -759,66 +787,87 @@ _asyncCbsThread(void *arg) natsMutex_Unlock(asyncCbs->lock); nc = cb->nc; + #if defined(NATS_HAS_STREAMING) sc = cb->sc; + + // handle streaming connection callbacks + if (cb->type == ASYNC_STAN_CONN_LOST) + { + (*(sc->opts->connectionLostCB))(sc, sc->connLostErrTxt, sc->opts->connectionLostCBClosure); + } + else + { + // handle all other callbacks #endif - // callback handlers can be updated on a live connection, so we need to + // Default to a single natsConnectionHandler callback. Duplicated + // callbacks (closed, error) will update the type. + call = &single; + single.type = CALLBACK_TYPE_CONN; + + // Callback handlers can be updated on a live connection, so we need to // lock. - cbHandler = NULL; - errHandler = NULL; - cbClosure = NULL; - -#define __set_handler(_h, _cb, _cl) \ - { \ - natsMutex_Lock(nc->opts->mu); \ - _h = nc->opts->_cb; \ - cbClosure = nc->opts->_cl; \ - natsMutex_Unlock(nc->opts->mu); \ + natsMutex_Lock(nc->opts->mu); + switch (cb->type) + { + case ASYNC_CLOSED: + call = _dup_callback_list(&single, &clone, nc->opts->closedCb); + break; + case ASYNC_DISCONNECTED: + call->f.conn = nc->opts->disconnectedCb; + call->closure = nc->opts->disconnectedCbClosure; + break; + case ASYNC_RECONNECTED: + call->f.conn = nc->opts->reconnectedCb; + call->closure = nc->opts->reconnectedCbClosure; + break; + case ASYNC_CONNECTED: + call->f.conn = nc->opts->connectedCb; + call->closure = nc->opts->connectedCbClosure; + break; + case ASYNC_DISCOVERED_SERVERS: + call->f.conn = nc->opts->discoveredServersCb; + call->closure = nc->opts->discoveredServersClosure; + break; + case ASYNC_LAME_DUCK_MODE: + call->f.conn = nc->opts->lameDuckCb; + call->closure = nc->opts->lameDuckClosure; + break; + case ASYNC_ERROR: + call = _dup_callback_list(&single, &clone, nc->opts->asyncErrCb); + break; + default: + call = NULL; + break; } + natsMutex_Unlock(nc->opts->mu); - switch (cb->type) + // Invoke the callbacks + for (; call != NULL; call = call->next) { - case ASYNC_CLOSED: - __set_handler(cbHandler, closedCb, closedCbClosure); - break; - case ASYNC_DISCONNECTED: - __set_handler(cbHandler, disconnectedCb, disconnectedCbClosure); - break; - case ASYNC_RECONNECTED: - __set_handler(cbHandler, reconnectedCb, reconnectedCbClosure); - break; - case ASYNC_CONNECTED: - __set_handler(cbHandler, connectedCb, connectedCbClosure); - break; - case ASYNC_DISCOVERED_SERVERS: - __set_handler(cbHandler, discoveredServersCb, discoveredServersClosure); - break; - case ASYNC_LAME_DUCK_MODE: - __set_handler(cbHandler, lameDuckCb, lameDuckClosure); + switch (call->type) + { + case CALLBACK_TYPE_ERROR: + if (cb->errTxt != NULL) + nats_setErrStatusAndTxt(cb->err, cb->errTxt); + (*call->f.err)(nc, cb->sub, cb->err, call->closure); break; - case ASYNC_ERROR: - __set_handler(errHandler, asyncErrCb, asyncErrCbClosure); + + case CALLBACK_TYPE_CONN: + (*call->f.conn)(nc, call->closure); break; + default: break; + } } - // Invoke the callback - if (cbHandler != NULL) - { - (*(cbHandler))(nc, cbClosure); - } - else if (errHandler != NULL) - { - if (cb->errTxt != NULL) - nats_setErrStatusAndTxt(cb->err, cb->errTxt); - (*(errHandler))(nc, cb->sub, cb->err, cbClosure); - } + // Free anything that might have been cloned. + natsOptions_freeCallbackList(clone); + clone = NULL; + #if defined(NATS_HAS_STREAMING) - else if (cb->type == ASYNC_STAN_CONN_LOST) - { - (*(sc->opts->connectionLostCB))(sc, sc->connLostErrTxt, sc->opts->connectionLostCBClosure); } #endif diff --git a/src/natsp.h b/src/natsp.h index c98bbb283..f2836eb66 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -196,6 +196,24 @@ typedef struct __userCreds } userCreds; +typedef enum +{ + CALLBACK_TYPE_CONN = 0, + CALLBACK_TYPE_ERROR, +} nats_CallbackType; + +typedef struct __nats_CallbackList +{ + nats_CallbackType type; + union + { + natsConnectionHandler conn; + natsErrHandler err; + } f; + void *closure; + struct __nats_CallbackList *next; +} nats_CallbackList; + struct __natsOptions { // This field must be the first (see natsOptions_clone, same if you add @@ -225,8 +243,8 @@ struct __natsOptions natsTokenHandler tokenCb; void *tokenCbClosure; - natsConnectionHandler closedCb; - void *closedCbClosure; + nats_CallbackList *closedCb; + nats_CallbackList *asyncErrCb; natsConnectionHandler disconnectedCb; void *disconnectedCbClosure; @@ -244,9 +262,6 @@ struct __natsOptions natsConnectionHandler lameDuckCb; void *lameDuckClosure; - natsErrHandler asyncErrCb; - void *asyncErrCbClosure; - int64_t pingInterval; int maxPingsOut; int maxPendingMsgs; diff --git a/src/opts.c b/src/opts.c index ec730fbb0..bdfd9a7f3 100644 --- a/src/opts.c +++ b/src/opts.c @@ -883,17 +883,190 @@ natsOptions_SetMaxPendingMsgs(natsOptions *opts, int maxPending) return NATS_OK; } +void +natsOptions_unlinkCallback(nats_CallbackList **prior_cb_removed, nats_CallbackList **headptr, void(*f)(void), void *closure) +{ + nats_CallbackList *this, *prev; + + if (f == NULL) + return; + + for (this = *headptr, prev = NULL; this != NULL; prev = this, this = this->next) + { + if (((void(*)(void))(this->f.conn) == f) && (this->closure == closure)) + { + *prior_cb_removed = this; + if (prev == NULL) + { + *headptr = this->next; + } + else + { + prev->next = this->next; + } + + return; + } + } +} + +void +natsOptions_freeCallbackList(nats_CallbackList *cb) +{ + nats_CallbackList *next = NULL; + + while (cb != NULL) + { + next = cb->next; + NATS_FREE(cb); + cb = next; + } +} + +natsStatus +natsOptions_cloneCallbackList(nats_CallbackList **clone, nats_CallbackList *this) +{ + nats_CallbackList *prev = NULL; + nats_CallbackList *head = NULL; + nats_CallbackList *tail = NULL; + + for (; this != NULL; this = this->next) + { + tail = NATS_CALLOC(1, sizeof(nats_CallbackList)); + if (tail == NULL) + { + natsOptions_freeCallbackList(head); + return nats_setDefaultError(NATS_NO_MEMORY); + } + *tail = *this; + tail->next = NULL; + + if (prev == NULL) + { + head = tail; + } + else + { + prev->next = tail; + } + prev = tail; + } + + *clone = head; + return NATS_OK; +} + +natsStatus +natsOptions_addConnectionClosedCallback(natsOptions *opts, natsConnectionHandler f, void *closure) +{ + natsStatus s = NATS_OK; + nats_CallbackList *cb = NULL; + nats_CallbackList *replaced = NULL; + + cb = NATS_CALLOC(1, sizeof(nats_CallbackList)); + if (cb == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + cb->type = CALLBACK_TYPE_CONN; + cb->f.conn = f; + cb->closure = closure; + + LOCK_AND_CHECK_OPTIONS(opts, 0); + + // unlink if already in the list. + natsOptions_unlinkCallback(&replaced, &opts->closedCb, (void(*)(void))f, closure); + + // add at the head. + cb->next = opts->closedCb; + opts->closedCb = cb; + + UNLOCK_OPTS(opts); + + NATS_FREE(replaced); + + return s; +} + +natsStatus +natsOptions_addErrorCallback(natsOptions *opts, natsErrHandler f, void *closure) +{ + natsStatus s = NATS_OK; + nats_CallbackList *cb = NULL; + nats_CallbackList *replaced = NULL; + + cb = NATS_CALLOC(1, sizeof(nats_CallbackList)); + if (cb == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + cb->type = CALLBACK_TYPE_ERROR; + cb->f.err = f; + cb->closure = closure; + + LOCK_AND_CHECK_OPTIONS(opts, 0); + + // unlink if already in the list. + natsOptions_unlinkCallback(&replaced, &opts->asyncErrCb, (void(*)(void))f, closure); + + // add at the head. + cb->next = opts->asyncErrCb; + opts->asyncErrCb = cb; + + UNLOCK_OPTS(opts); + + return s; +} + +void natsOptions_removeConnectionClosedCallback(natsOptions *opts, natsConnectionHandler f, void *closure) +{ + nats_CallbackList *removed = NULL; + + natsMutex_Lock(opts->mu); + + natsOptions_unlinkCallback(&removed, &opts->closedCb, (void(*)(void))f, closure); + + UNLOCK_OPTS(opts); + + NATS_FREE(removed); +} + +void natsOptions_removeErrorCallback(natsOptions *opts, natsErrHandler f, void *closure) +{ + nats_CallbackList *removed = NULL; + + natsMutex_Lock(opts->mu); + + natsOptions_unlinkCallback(&removed, &opts->asyncErrCb, (void(*)(void))f, closure); + + UNLOCK_OPTS(opts); + + NATS_FREE(removed); +} + natsStatus natsOptions_SetErrorHandler(natsOptions *opts, natsErrHandler errHandler, void *closure) { - LOCK_AND_CHECK_OPTIONS(opts, 0); + if (errHandler == NULL) + { + errHandler = natsConn_defaultErrHandler; + closure = NULL; + } - opts->asyncErrCb = errHandler; - opts->asyncErrCbClosure = closure; + LOCK_AND_CHECK_OPTIONS(opts, 0); if (opts->asyncErrCb == NULL) - opts->asyncErrCb = natsConn_defaultErrHandler; + { + UNLOCK_OPTS(opts); + return natsOptions_addErrorCallback(opts, errHandler, closure); + } + if (opts->asyncErrCb->next != NULL) + { + // can't allow overriding a list of callbacks with a single one. + UNLOCK_OPTS(opts); + return nats_setDefaultError(NATS_ILLEGAL_STATE); + } + + opts->asyncErrCb->type = CALLBACK_TYPE_ERROR; + opts->asyncErrCb->f.err = errHandler; + opts->asyncErrCb->closure = closure; UNLOCK_OPTS(opts); @@ -904,13 +1077,37 @@ natsStatus natsOptions_SetClosedCB(natsOptions *opts, natsConnectionHandler closedCb, void *closure) { + nats_CallbackList *to_free = NULL; + LOCK_AND_CHECK_OPTIONS(opts, 0); - opts->closedCb = closedCb; - opts->closedCbClosure = closure; + if ((opts->closedCb != NULL) && (opts->closedCb->next != NULL)) + { + // can't allow overriding a list of callbacks with a single one. + UNLOCK_OPTS(opts); + return nats_setDefaultError(NATS_ILLEGAL_STATE); + } + + if (closedCb == NULL) + { + to_free = opts->closedCb; + opts->closedCb = NULL; + } + else + { + if (opts->closedCb == NULL) + { + UNLOCK_OPTS(opts); + return natsOptions_addConnectionClosedCallback(opts, closedCb, closure); + } + opts->closedCb->type = CALLBACK_TYPE_CONN; + opts->closedCb->f.conn = closedCb; + opts->closedCb->closure = closure; + } UNLOCK_OPTS(opts); + NATS_FREE(to_free); return NATS_OK; } @@ -934,7 +1131,7 @@ natsOptions_SetReconnectedCB(natsOptions *opts, natsConnectionHandler reconnectedCb, void *closure) { - LOCK_AND_CHECK_OPTIONS(opts, 0); +LOCK_AND_CHECK_OPTIONS(opts, 0); opts->reconnectedCb = reconnectedCb; opts->reconnectedCbClosure = closure; @@ -1077,7 +1274,7 @@ natsOptions_SetRetryOnFailedConnect(natsOptions *opts, bool retry, { opts->connectedCb = connectedCb; opts->connectedCbClosure = closure; - } + } UNLOCK_OPTS(opts); return NATS_OK; @@ -1461,6 +1658,9 @@ _freeOptions(natsOptions *opts) if (opts == NULL) return; + natsOptions_freeCallbackList(opts->closedCb); + natsOptions_freeCallbackList(opts->asyncErrCb); + NATS_FREE(opts->url); NATS_FREE(opts->name); _freeServers(opts); @@ -1475,11 +1675,11 @@ _freeOptions(natsOptions *opts) NATS_FREE(opts); } -natsStatus -natsOptions_Create(natsOptions **newOpts) +static natsStatus +_create_options(natsOptions **newOpts) { - natsStatus s; - natsOptions *opts = NULL; + natsStatus s = NATS_OK; + natsOptions *opts = NULL; // Ensure the library is loaded s = nats_Open(-1); @@ -1496,6 +1696,27 @@ natsOptions_Create(natsOptions **newOpts) return NATS_UPDATE_ERR_STACK(NATS_NO_MEMORY); } + *newOpts = opts; + return NATS_OK; +} + +natsStatus +natsOptions_Create(natsOptions **newOpts) +{ + natsStatus s = NATS_OK; + natsOptions *opts = NULL; + nats_CallbackList *defaultErrorCb = NULL; + + defaultErrorCb = (nats_CallbackList*) NATS_CALLOC(1, sizeof(nats_CallbackList)); + if (defaultErrorCb == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + defaultErrorCb->type = CALLBACK_TYPE_ERROR; + defaultErrorCb->f.err = natsConn_defaultErrHandler; + + s = _create_options(&opts); + if (s != NATS_OK) + return s; + opts->allowReconnect = true; opts->secure = false; opts->maxReconnect = NATS_OPTS_DEFAULT_MAX_RECONNECT; @@ -1510,7 +1731,7 @@ natsOptions_Create(natsOptions **newOpts) opts->reconnectBufSize = NATS_OPTS_DEFAULT_RECONNECT_BUF_SIZE; opts->reconnectJitter = NATS_OPTS_DEFAULT_RECONNECT_JITTER; opts->reconnectJitterTLS = NATS_OPTS_DEFAULT_RECONNECT_JITTER_TLS; - opts->asyncErrCb = natsConn_defaultErrHandler; + opts->asyncErrCb = defaultErrorCb; *newOpts = opts; @@ -1524,7 +1745,7 @@ natsOptions_clone(natsOptions *opts) natsOptions *cloned = NULL; int muSize; - if ((s = natsOptions_Create(&cloned)) != NATS_OK) + if ((s = _create_options(&cloned)) != NATS_OK) { NATS_UPDATE_ERR_STACK(s); return NULL; @@ -1551,12 +1772,17 @@ natsOptions_clone(natsOptions *opts) cloned->nkey = NULL; cloned->userCreds = NULL; cloned->inboxPfx = NULL; + cloned->closedCb = NULL; + cloned->asyncErrCb = NULL; + + IFOK(s, natsOptions_cloneCallbackList(&(cloned->closedCb), opts->closedCb)); + IFOK(s, natsOptions_cloneCallbackList(&(cloned->asyncErrCb), opts->asyncErrCb)); // Also, set the number of servers count to 0, until we update // it (if necessary) when calling SetServers. cloned->serversCount = 0; - if (opts->name != NULL) + if ((s == NATS_OK) && (opts->name != NULL)) s = natsOptions_SetName(cloned, opts->name); if ((s == NATS_OK) && (opts->url != NULL)) diff --git a/src/opts.h b/src/opts.h index 3ee87e3e2..25d4ececf 100644 --- a/src/opts.h +++ b/src/opts.h @@ -38,4 +38,26 @@ natsOptions* natsOptions_clone(natsOptions *opts); +natsStatus +natsOptions_addConnectionClosedCallback(natsOptions *opts, natsConnectionHandler f, void *closure); + +void +natsOptions_removeConnectionClosedCallback(natsOptions *opts, natsConnectionHandler f, void *closure); + +natsStatus +natsOptions_addErrorCallback(natsOptions *opts, natsErrHandler f, void *closure); + +void +natsOptions_removeErrorCallback(natsOptions *opts, natsErrHandler f, void *closure); + +void +natsOptions_freeCallbackList(nats_CallbackList *cb); + +natsStatus +natsOptions_cloneCallbackList(nats_CallbackList **clone, nats_CallbackList *this); + +// for testing +void +natsOptions_unlinkCallback(nats_CallbackList **prior_cb_removed, nats_CallbackList **headptr, void (*f)(void), void *closure); + #endif /* OPTS_H_ */ diff --git a/test/list.txt b/test/list.txt index e4a138d60..8a62e5fd2 100644 --- a/test/list.txt +++ b/test/list.txt @@ -19,6 +19,7 @@ natsHash natsHashing natsStrHash natsInbox +natsOptionsUnlinkCallback natsOptions natsSock_ConnectTcp natsSock_ShuffleIPs @@ -67,7 +68,7 @@ ConnectionWithNULLOptions ConnectionToWithNullURLs ConnectionStatus ConnClosedCB -SetConnClosedCB +AddConnClosedCB CloseDisconnectedCB ServerStopDisconnectedCB ClosedConnections @@ -148,7 +149,7 @@ AsyncSubscriptionPendingDrain SyncSubscriptionPending SyncSubscriptionPendingDrain AsyncErrHandler -SetAsyncErrHandler +AddAsyncErrHandler AsyncSubscriberStarvation AsyncSubscriberOnClose NextMsgCallOnAsyncSub diff --git a/test/test.c b/test/test.c index d59f4d1ec..d50c47032 100644 --- a/test/test.c +++ b/test/test.c @@ -2554,6 +2554,55 @@ _dummySigCb(char **customErrTxt, unsigned char **psig, int *sigLen, const char* return NATS_OK; } +static void +test_natsOptionsUnlinkCallback(void) +{ + nats_CallbackList cb1 = { + .f.conn = _dummyConnHandler, + .closure = (void*)1, + }; + nats_CallbackList cb2 = { + .f.err = _dummyErrHandler, + .closure = (void*)2, + }; + nats_CallbackList cb3 = { + .f.conn = _dummyConnHandler, + .closure = (void*)3, + }; + + test("the first only one: "); + nats_CallbackList test1[3] = {cb1}; + nats_CallbackList *head = &test1[0]; + nats_CallbackList *to_remove = NULL; + natsOptions_unlinkCallback(&to_remove, &head, (void(*)(void))cb1.f.conn, cb1.closure); + testCond((to_remove == &test1[0]) + && (head == NULL)); + + test("Unlink the first out of 3: "); + nats_CallbackList test2[3] = {cb1, cb2, cb3}; + head = &test2[0]; + to_remove = NULL; + test2[0].next = &test2[1]; + test2[1].next = &test2[2]; + natsOptions_unlinkCallback(&to_remove, &head, (void(*)(void))cb1.f.err, cb1.closure); + testCond((to_remove == &test2[0]) + && (head == &test2[1]) + && (test2[1].next == &test2[2]) + && (test2[2].next == NULL)); + + test("Unlink the middle out of 3: "); + nats_CallbackList testMiddle[3] = {cb1, cb2, cb3}; + head = &testMiddle[0]; + to_remove = NULL; + testMiddle[0].next = &testMiddle[1]; + testMiddle[1].next = &testMiddle[2]; + natsOptions_unlinkCallback(&to_remove, &head, (void(*)(void))cb2.f.err, cb2.closure); + testCond((to_remove == &testMiddle[1]) + && (head == &testMiddle[0]) + && (testMiddle[1].next == &testMiddle[2]) + && (testMiddle[2].next == NULL)); +} + static void test_natsOptions(void) { @@ -2717,8 +2766,7 @@ test_natsOptions(void) s = natsOptions_SetRetryOnFailedConnect(opts, false, _dummyConnHandler, (void*)1); testCond((s == NATS_OK) && (opts->retryOnFailedConnect == false) - && (opts->connectedCb == NULL) - && (opts->connectedCbClosure == NULL)); + && (opts->connectedCb == NULL)); test("Set Secure: "); s = natsOptions_SetSecure(opts, true); @@ -2811,15 +2859,15 @@ test_natsOptions(void) test("Set Error Handler: "); s = natsOptions_SetErrorHandler(opts, _dummyErrHandler, NULL); - testCond((s == NATS_OK) && (opts->asyncErrCb == _dummyErrHandler)); + testCond((s == NATS_OK) && (opts->asyncErrCb != NULL)&& (opts->asyncErrCb->f.err == _dummyErrHandler)); test("Remove Error Handler: "); s = natsOptions_SetErrorHandler(opts, NULL, NULL); - testCond((s == NATS_OK) && (opts->asyncErrCb == natsConn_defaultErrHandler)); + testCond((s == NATS_OK) && (opts->asyncErrCb != NULL)&& (opts->asyncErrCb->f.err == natsConn_defaultErrHandler)); test("Set ClosedCB: "); s = natsOptions_SetClosedCB(opts, _dummyConnHandler, NULL); - testCond((s == NATS_OK) && (opts->closedCb == _dummyConnHandler)); + testCond((s == NATS_OK) && (opts->closedCb != NULL)&& (opts->closedCb->f.conn == _dummyConnHandler)); test("Remove ClosedCB: "); s = natsOptions_SetClosedCB(opts, NULL, NULL); @@ -3055,7 +3103,8 @@ test_natsOptions(void) if (cloned == NULL) s = NATS_NO_MEMORY; else if ((cloned->pingInterval != 3000) - || (cloned->asyncErrCb != _dummyErrHandler) + || (cloned->asyncErrCb == NULL) + || (cloned->asyncErrCb->f.err != _dummyErrHandler) || (cloned->name == NULL) || (strcmp(cloned->name, "name") != 0) || (cloned->url == NULL) @@ -3099,9 +3148,11 @@ test_natsOptions(void) s = natsOptions_Create(&opts); if (s == NATS_OK) cloned = natsOptions_clone(opts); - testCond((s == NATS_OK) && (cloned != NULL) - && (cloned->asyncErrCb == natsConn_defaultErrHandler) - && (cloned->asyncErrCbClosure == NULL)); + testCond((s == NATS_OK) + && (cloned != NULL) + && (cloned->asyncErrCb != NULL) + && (cloned->asyncErrCb->f.err == natsConn_defaultErrHandler) + && (cloned->asyncErrCb->closure == NULL)); natsOptions_Destroy(cloned); natsOptions_Destroy(opts); } @@ -8534,7 +8585,7 @@ test_ConnClosedCB(void) } static void -test_SetConnClosedCB(void) +test_AddConnClosedCB(void) { natsStatus s; natsConnection *nc = NULL; @@ -8549,20 +8600,22 @@ test_SetConnClosedCB(void) if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) { - FAIL("Unable to setup test for SetConnClosedCB!"); + FAIL("Unable to setup test for AddConnClosedCB!"); } serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); CHECK_SERVER_STARTED(serverPid); + test("connect to server: "); s = natsConnection_Connect(&nc, opts); + testCond(s == NATS_OK); - // Set the connection closed handler in-flight - IFOK(s, natsConn_setClosedCallback(nc, _closedCb, (void*) &arg)); - if (s == NATS_OK) - natsConnection_Close(nc); - + test("set the connection closed handler in-flight"); + s = natsOptions_addConnectionClosedCallback(nc->opts, _closedCb, (void*)&arg); + testCond(s == NATS_OK); + test("Test connection closed CB invoked: "); + natsConnection_Close(nc); natsMutex_Lock(arg.m); s = NATS_OK; @@ -8576,7 +8629,6 @@ test_SetConnClosedCB(void) natsConnection_Destroy(nc); _destroyDefaultThreadArgs(&arg); - _stopServer(serverPid); } @@ -14422,7 +14474,7 @@ test_AsyncErrHandler(void) } static void -test_SetAsyncErrHandler(void) +test_AddAsyncErrHandler(void) { natsStatus s; natsConnection *nc = NULL; @@ -14448,22 +14500,28 @@ test_SetAsyncErrHandler(void) serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); CHECK_SERVER_STARTED(serverPid); + test("connect: "); s = natsConnection_Connect(&nc, opts); - IFOK(s, natsConnection_Subscribe(&sub, nc, "async_test", _recvTestString, (void*) &arg)); + testCond(s == NATS_OK); + + test("subscribe: "); + s = natsConnection_Subscribe(&sub, nc, "async_test", _recvTestString, (void*) &arg); + testCond(s == NATS_OK); natsMutex_Lock(arg.m); arg.sub = sub; natsMutex_Unlock(arg.m); - // Start sending messages + test("send messages: "); for (int i=0; (s == NATS_OK) && (i < (opts->maxPendingMsgs)); i++) { s = natsConnection_PublishString(nc, "async_test", "hello"); } + testCond(s == NATS_OK); - // Set the error handler in-flight - IFOK(s, natsConn_setErrorCallback(nc, _asyncErrCb, (void*) &arg)); + test("add the error handler in-flight: ") + s = natsOptions_addErrorCallback(nc->opts, _asyncErrCb, (void*)&arg); for (int i=0; (s == NATS_OK) && (i < 100); i++) @@ -14471,6 +14529,7 @@ test_SetAsyncErrHandler(void) s = natsConnection_PublishString(nc, "async_test", "hello"); } IFOK(s, natsConnection_Flush(nc)); + testCond(s == NATS_OK); // Wait for async err callback natsMutex_Lock(arg.m); @@ -32777,10 +32836,7 @@ test_MicroBasics(void) natsInbox_Destroy(inbox); NATS_FREE(subject); - // Destroy the services in the reverse order to properly unwind the - // callbacks. At some point this needs to be fixed so that the services - // exclude themselves from a chain, rather than setting previous, etc. - for (i = NUM_BASIC_MICRO_SERVICES - 1; i >= 0; i--) + for (i = 0; i < NUM_BASIC_MICRO_SERVICES; i++) { microService_Destroy(svcs[i]); } @@ -32853,10 +32909,7 @@ test_MicroStartStop(void) } testCond(NATS_OK == s); - // Destroy the services in the reverse order to properly unwind the - // callbacks. At some point this needs to be fixed so that the services - // exclude themselves from a chain, rather than setting previous, etc. - for (i = NUM_BASIC_MICRO_SERVICES - 1; i >= 0; i--) + for (i = 0; i < NUM_BASIC_MICRO_SERVICES; i++) { microService_Destroy(svcs[i]); } @@ -35295,6 +35348,7 @@ static testInfo allTests[] = {"natsHashing", test_natsHashing}, {"natsStrHash", test_natsStrHash}, {"natsInbox", test_natsInbox}, + {"natsOptionsUnlinkCallback", test_natsOptionsUnlinkCallback}, {"natsOptions", test_natsOptions}, {"natsSock_ConnectTcp", test_natsSock_ConnectTcp}, {"natsSock_ShuffleIPs", test_natsSock_ShuffleIPs}, @@ -35349,7 +35403,7 @@ static testInfo allTests[] = {"ConnectionToWithNullURLs", test_ConnectionToWithNullURLs}, {"ConnectionStatus", test_ConnectionStatus}, {"ConnClosedCB", test_ConnClosedCB}, - {"SetConnClosedCB", test_SetConnClosedCB}, + {"AddConnClosedCB", test_AddConnClosedCB}, {"CloseDisconnectedCB", test_CloseDisconnectedCB}, {"ServerStopDisconnectedCB", test_ServerStopDisconnectedCB}, {"ClosedConnections", test_ClosedConnections}, @@ -35432,7 +35486,7 @@ static testInfo allTests[] = {"SyncSubscriptionPending", test_SyncSubscriptionPending}, {"SyncSubscriptionPendingDrain", test_SyncSubscriptionPendingDrain}, {"AsyncErrHandler", test_AsyncErrHandler}, - {"SetAsyncErrHandler", test_SetAsyncErrHandler}, + {"AddAsyncErrHandler", test_AddAsyncErrHandler}, {"AsyncSubscriberStarvation", test_AsyncSubscriberStarvation}, {"AsyncSubscriberOnClose", test_AsyncSubscriberOnClose}, {"NextMsgCallOnAsyncSub", test_NextMsgCallOnAsyncSub}, From 2048d4c2952fac4fe1c8a2ac7c2df9e2ccad91f8 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sun, 21 May 2023 09:24:21 -0700 Subject: [PATCH 34/85] added is_internal to microError to avoid freeing internal values --- src/micro_error.c | 20 ++++---------------- src/microp.h | 1 + 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/micro_error.c b/src/micro_error.c index e3cbfac3a..3066b3250 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -16,27 +16,23 @@ #include "microp.h" static microError _errorOutOfMemory = { + .is_internal = true, .status = NATS_NO_MEMORY, .message = (char *)"out of memory", }; static microError _errorInvalidArg = { + .is_internal = true, .status = NATS_INVALID_ARG, .message = (char *)"invalid function argument", }; static microError _errorInvalidFormat = { + .is_internal = true, .status = NATS_INVALID_ARG, .message = (char *)"invalid format string", }; -static microError *knownErrors[] = { - &_errorOutOfMemory, - &_errorInvalidArg, - &_errorInvalidFormat, - NULL, -}; - microError *micro_ErrorOutOfMemory = &_errorOutOfMemory; microError *micro_ErrorInvalidArg = &_errorInvalidArg; @@ -219,17 +215,9 @@ microError_Status(microError *err) void microError_Destroy(microError *err) { - int i; - - if (err == NULL) + if ((err == NULL) || err->is_internal) return; - for (i = 0; knownErrors[i] != NULL; i++) - { - if (err == knownErrors[i]) - return; - } - microError_Destroy(err->cause); NATS_FREE(err); } diff --git a/src/microp.h b/src/microp.h index dec9c365c..93285156d 100644 --- a/src/microp.h +++ b/src/microp.h @@ -37,6 +37,7 @@ struct micro_error_s { + bool is_internal; struct micro_error_s *cause; natsStatus status; int code; From 44a46520e1cd11bc421680dbc82045a9c65ccdda Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sun, 21 May 2023 09:39:13 -0700 Subject: [PATCH 35/85] PR feedback: initialize to avoid a warning --- src/micro_args.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/micro_args.c b/src/micro_args.c index 4d04a770c..7db7c7a77 100644 --- a/src/micro_args.c +++ b/src/micro_args.c @@ -44,7 +44,7 @@ micro_ParseArgs(microArgs **ptr, const char *data, int data_len) { microError *err = NULL; microArgs *args = NULL; - int n; + int n = 0; if ((ptr == NULL) || (data == NULL) || (data_len < 0)) return microError_Wrapf(micro_ErrorInvalidArg, "failed to parse args"); From 843fd8afa938618c93ffde1ac6df5703caab30e5 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 22 May 2023 05:28:34 -0700 Subject: [PATCH 36/85] Removed the never-implemented schema --- examples/micro-sequence.c | 1 - src/micro_endpoint.c | 40 --------------------------------------- src/microp.h | 4 ---- src/nats.h | 29 ---------------------------- 4 files changed, 74 deletions(-) diff --git a/examples/micro-sequence.c b/examples/micro-sequence.c index 057934c20..3655f32ea 100644 --- a/examples/micro-sequence.c +++ b/examples/micro-sequence.c @@ -156,7 +156,6 @@ int main(int argc, char **argv) .Subject = "sequence", .Name = "sequence-service", .Handler = handle_sequence, - .Schema = NULL, }; microServiceConfig cfg = { .Description = "Sequence adder - NATS microservice example in C", diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index 731bc5eb0..e5c89f9c2 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -305,44 +305,6 @@ bool micro_is_valid_subject(const char *subject) return true; } -static inline void -destroy_schema(microSchema *schema) -{ - if (schema == NULL) - return; - - NATS_FREE((char *)schema->Request); - NATS_FREE((char *)schema->Response); - NATS_FREE(schema); -} - -static microError * -clone_schema(microSchema **to, const microSchema *from) -{ - microError *err = NULL; - if (from == NULL) - { - *to = NULL; - return NULL; - } - - *to = NATS_CALLOC(1, sizeof(microSchema)); - if (*to == NULL) - return micro_ErrorOutOfMemory; - - MICRO_CALL(err, micro_strdup((char **)&((*to)->Request), from->Request)); - MICRO_CALL(err, micro_strdup((char **)&((*to)->Response), from->Response)); - - if (err != NULL) - { - destroy_schema(*to); - *to = NULL; - return err; - } - - return NULL; -} - static inline microError * new_endpoint_config(microEndpointConfig **ptr) { @@ -373,7 +335,6 @@ micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); MICRO_CALL(err, micro_strdup((char **)&new_cfg->Subject, cfg->Subject)); - MICRO_CALL(err, clone_schema(&new_cfg->Schema, cfg->Schema)); if (err != NULL) { @@ -395,7 +356,6 @@ void micro_free_cloned_endpoint_config(microEndpointConfig *cfg) NATS_FREE((char *)cfg->Name); NATS_FREE((char *)cfg->Subject); - destroy_schema(cfg->Schema); NATS_FREE(cfg); } diff --git a/src/microp.h b/src/microp.h index 93285156d..1b1549935 100644 --- a/src/microp.h +++ b/src/microp.h @@ -31,10 +31,6 @@ #define MICRO_DEFAULT_ENDPOINT_NAME "default" -// 4 verbs: INFO, STATS, PING, SCHEMA; -// 3 subjects for each verb. -#define MICRO_MONITORING_SUBS_CAP (4 * 3) - struct micro_error_s { bool is_internal; diff --git a/src/nats.h b/src/nats.h index 8150bbec3..5a13c70c8 100644 --- a/src/nats.h +++ b/src/nats.h @@ -7220,11 +7220,6 @@ typedef struct micro_group_s microGroup; */ typedef struct micro_request_s microRequest; -/** - * The Microservice endpoint schema object. - */ -typedef struct micro_schema_s microSchema; - /** * @brief the main object for a configured microservice. * @@ -7346,11 +7341,6 @@ struct micro_endpoint_config_s */ const char *Subject; - /** - * @brief The endpoint schema. - */ - microSchema *Schema; - /** * @brief The request handler for the endpoint. */ @@ -7403,15 +7393,6 @@ struct micro_endpoint_stats_s char LastErrorString[2048]; }; -/** - * @brief The Microservice endpoint schema object. - */ -struct micro_schema_s -{ - const char *Request; - const char *Response; -}; - /** * @brief The Microservice top-level configuration object. * @@ -7593,16 +7574,6 @@ struct micro_service_stats_s */ #define MICRO_PING_VERB "PING" -/** - * @brief The `type` set in the `$SRV.SCHEMA` response. - */ -#define MICRO_STATS_SCHEMA_TYPE "io.nats.micro.v1.schema_response" - -/** - * @brief For `$SRV.SCHEMA` subjects. - */ -#define MICRO_SCHEMA_VERB "SCHEMA" - /** * @brief The `type` set in the `STATS` response. */ From cc1881c7f7c56c3134245823dc8265f3e98a9561 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 22 May 2023 09:19:47 -0700 Subject: [PATCH 37/85] PR feedback: added a missing FREE(replaced) --- src/opts.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/opts.c b/src/opts.c index bdfd9a7f3..4e30f5781 100644 --- a/src/opts.c +++ b/src/opts.c @@ -1011,6 +1011,7 @@ natsOptions_addErrorCallback(natsOptions *opts, natsErrHandler f, void *closure) UNLOCK_OPTS(opts); + NATS_FREE(replaced); return s; } From dfcdf2d26add03a9ffe4a42262a29389735726fe Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 22 May 2023 09:27:17 -0700 Subject: [PATCH 38/85] PR feedback: free the replaced value in SetErrorHandler and SetClosedCB --- src/opts.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/opts.c b/src/opts.c index 4e30f5781..08902243c 100644 --- a/src/opts.c +++ b/src/opts.c @@ -1045,6 +1045,8 @@ natsStatus natsOptions_SetErrorHandler(natsOptions *opts, natsErrHandler errHandler, void *closure) { + nats_CallbackList *replaced = NULL; + if (errHandler == NULL) { errHandler = natsConn_defaultErrHandler; @@ -1058,6 +1060,8 @@ natsOptions_SetErrorHandler(natsOptions *opts, natsErrHandler errHandler, UNLOCK_OPTS(opts); return natsOptions_addErrorCallback(opts, errHandler, closure); } + + replaced = opts->asyncErrCb; if (opts->asyncErrCb->next != NULL) { // can't allow overriding a list of callbacks with a single one. @@ -1071,6 +1075,7 @@ natsOptions_SetErrorHandler(natsOptions *opts, natsErrHandler errHandler, UNLOCK_OPTS(opts); + NATS_FREE(replaced); return NATS_OK; } @@ -1101,6 +1106,7 @@ natsOptions_SetClosedCB(natsOptions *opts, natsConnectionHandler closedCb, UNLOCK_OPTS(opts); return natsOptions_addConnectionClosedCallback(opts, closedCb, closure); } + to_free = opts->closedCb; opts->closedCb->type = CALLBACK_TYPE_CONN; opts->closedCb->f.conn = closedCb; opts->closedCb->closure = closure; From 3b13ac08c68f23ed0d008f122cb470b2ff733b2f Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 22 May 2023 09:49:01 -0700 Subject: [PATCH 39/85] reversed: previous (free the replaced value...), added a test --- src/opts.c | 6 ------ test/test.c | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/opts.c b/src/opts.c index 08902243c..4e30f5781 100644 --- a/src/opts.c +++ b/src/opts.c @@ -1045,8 +1045,6 @@ natsStatus natsOptions_SetErrorHandler(natsOptions *opts, natsErrHandler errHandler, void *closure) { - nats_CallbackList *replaced = NULL; - if (errHandler == NULL) { errHandler = natsConn_defaultErrHandler; @@ -1060,8 +1058,6 @@ natsOptions_SetErrorHandler(natsOptions *opts, natsErrHandler errHandler, UNLOCK_OPTS(opts); return natsOptions_addErrorCallback(opts, errHandler, closure); } - - replaced = opts->asyncErrCb; if (opts->asyncErrCb->next != NULL) { // can't allow overriding a list of callbacks with a single one. @@ -1075,7 +1071,6 @@ natsOptions_SetErrorHandler(natsOptions *opts, natsErrHandler errHandler, UNLOCK_OPTS(opts); - NATS_FREE(replaced); return NATS_OK; } @@ -1106,7 +1101,6 @@ natsOptions_SetClosedCB(natsOptions *opts, natsConnectionHandler closedCb, UNLOCK_OPTS(opts); return natsOptions_addConnectionClosedCallback(opts, closedCb, closure); } - to_free = opts->closedCb; opts->closedCb->type = CALLBACK_TYPE_CONN; opts->closedCb->f.conn = closedCb; opts->closedCb->closure = closure; diff --git a/test/test.c b/test/test.c index d50c47032..713a1252f 100644 --- a/test/test.c +++ b/test/test.c @@ -2861,6 +2861,10 @@ test_natsOptions(void) s = natsOptions_SetErrorHandler(opts, _dummyErrHandler, NULL); testCond((s == NATS_OK) && (opts->asyncErrCb != NULL)&& (opts->asyncErrCb->f.err == _dummyErrHandler)); + test("Set Error Handler 2nd time: "); + s = natsOptions_SetErrorHandler(opts, _dummyErrHandler, NULL); + testCond((s == NATS_OK) && (opts->asyncErrCb != NULL)&& (opts->asyncErrCb->f.err == _dummyErrHandler)); + test("Remove Error Handler: "); s = natsOptions_SetErrorHandler(opts, NULL, NULL); testCond((s == NATS_OK) && (opts->asyncErrCb != NULL)&& (opts->asyncErrCb->f.err == natsConn_defaultErrHandler)); @@ -2869,6 +2873,10 @@ test_natsOptions(void) s = natsOptions_SetClosedCB(opts, _dummyConnHandler, NULL); testCond((s == NATS_OK) && (opts->closedCb != NULL)&& (opts->closedCb->f.conn == _dummyConnHandler)); + test("Set ClosedCB 2nd time: "); + s = natsOptions_SetClosedCB(opts, _dummyConnHandler, NULL); + testCond((s == NATS_OK) && (opts->closedCb != NULL)&& (opts->closedCb->f.conn == _dummyConnHandler)); + test("Remove ClosedCB: "); s = natsOptions_SetClosedCB(opts, NULL, NULL); testCond((s == NATS_OK) && (opts->closedCb == NULL)); From ff454ba5f42b4c8c34e3c28aed0feb556705b878 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 22 May 2023 09:50:44 -0700 Subject: [PATCH 40/85] PR feedback: whitespace --- src/opts.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/opts.c b/src/opts.c index 4e30f5781..500e0f9ab 100644 --- a/src/opts.c +++ b/src/opts.c @@ -1132,7 +1132,7 @@ natsOptions_SetReconnectedCB(natsOptions *opts, natsConnectionHandler reconnectedCb, void *closure) { -LOCK_AND_CHECK_OPTIONS(opts, 0); + LOCK_AND_CHECK_OPTIONS(opts, 0); opts->reconnectedCb = reconnectedCb; opts->reconnectedCbClosure = closure; @@ -1275,7 +1275,7 @@ natsOptions_SetRetryOnFailedConnect(natsOptions *opts, bool retry, { opts->connectedCb = connectedCb; opts->connectedCbClosure = closure; - } + } UNLOCK_OPTS(opts); return NATS_OK; From 59c3c4ecec0b39765fe38bd0c245d2f88d1ebe01 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 22 May 2023 10:00:01 -0700 Subject: [PATCH 41/85] PR feedback: nit - added natsOptions_(un)lock --- src/conn.c | 16 ++++++++-------- src/nats.c | 4 ++-- src/opts.c | 12 ++++++------ src/opts.h | 14 ++++++++++++-- test/test.c | 4 ++-- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/conn.c b/src/conn.c index 9309993b5..0945f6bd5 100644 --- a/src/conn.c +++ b/src/conn.c @@ -532,10 +532,10 @@ _processInfo(natsConnection *nc, char *info, int len) if (info == NULL) return NATS_OK; - natsMutex_Lock(nc->opts->mu); + natsOptions_lock(nc->opts); postDiscoveredServersCb = (nc->opts->discoveredServersCb != NULL); postLameDuckCb = (nc->opts->lameDuckCb != NULL); - natsMutex_Unlock(nc->opts->mu); + natsOptions_unlock(nc->opts); _clearServerInfo(&(nc->info)); @@ -1534,11 +1534,11 @@ _doReconnect(void *arg) bool postReconnectedCb = false; bool postConnectedCb = false; - natsMutex_Lock(nc->opts->mu); + natsOptions_lock(nc->opts); postDisconnectedCb = (nc->opts->disconnectedCb != NULL); postReconnectedCb = (nc->opts->reconnectedCb != NULL); postConnectedCb = (nc->opts->connectedCb != NULL); - natsMutex_Unlock(nc->opts->mu); + natsOptions_unlock(nc->opts); natsConn_Lock(nc); @@ -2017,10 +2017,10 @@ _connect(natsConnection *nc) bool retryOnFailedConnect = false; bool hasConnectedCb = false; - natsMutex_Lock(nc->opts->mu); + natsOptions_lock(nc->opts); hasConnectedCb = (nc->opts->connectedCb != NULL); retryOnFailedConnect = nc->opts->retryOnFailedConnect; - natsMutex_Unlock(nc->opts->mu); + natsOptions_unlock(nc->opts); natsConn_Lock(nc); nc->initc = true; @@ -2500,10 +2500,10 @@ _close(natsConnection *nc, natsConnStatus status, bool fromPublicClose, bool doC bool postDisconnectedCb = false; natsSubscription *sub = NULL; - natsMutex_Lock(nc->opts->mu); + natsOptions_lock(nc->opts); postClosedCb = (nc->opts->closedCb != NULL); postDisconnectedCb = (nc->opts->disconnectedCb != NULL); - natsMutex_Unlock(nc->opts->mu); + natsOptions_unlock(nc->opts); natsConn_lockAndRetain(nc); diff --git a/src/nats.c b/src/nats.c index 996a593f1..a943fafb1 100644 --- a/src/nats.c +++ b/src/nats.c @@ -808,7 +808,7 @@ _asyncCbsThread(void *arg) // Callback handlers can be updated on a live connection, so we need to // lock. - natsMutex_Lock(nc->opts->mu); + natsOptions_lock(nc->opts); switch (cb->type) { case ASYNC_CLOSED: @@ -841,7 +841,7 @@ _asyncCbsThread(void *arg) call = NULL; break; } - natsMutex_Unlock(nc->opts->mu); + natsOptions_unlock(nc->opts); // Invoke the callbacks for (; call != NULL; call = call->next) diff --git a/src/opts.c b/src/opts.c index 500e0f9ab..e7e5b499d 100644 --- a/src/opts.c +++ b/src/opts.c @@ -1019,11 +1019,11 @@ void natsOptions_removeConnectionClosedCallback(natsOptions *opts, natsConnectio { nats_CallbackList *removed = NULL; - natsMutex_Lock(opts->mu); + natsOptions_lock(opts); natsOptions_unlinkCallback(&removed, &opts->closedCb, (void(*)(void))f, closure); - UNLOCK_OPTS(opts); + natsOptions_unlock(opts); NATS_FREE(removed); } @@ -1032,11 +1032,11 @@ void natsOptions_removeErrorCallback(natsOptions *opts, natsErrHandler f, void * { nats_CallbackList *removed = NULL; - natsMutex_Lock(opts->mu); + natsOptions_lock(opts); natsOptions_unlinkCallback(&removed, &opts->asyncErrCb, (void(*)(void))f, closure); - UNLOCK_OPTS(opts); + natsOptions_unlock(opts); NATS_FREE(removed); } @@ -1752,7 +1752,7 @@ natsOptions_clone(natsOptions *opts) return NULL; } - natsMutex_Lock(opts->mu); + natsOptions_lock(opts); muSize = sizeof(cloned->mu); @@ -1834,7 +1834,7 @@ natsOptions_clone(natsOptions *opts) NATS_UPDATE_ERR_STACK(s); } - natsMutex_Unlock(opts->mu); + natsOptions_unlock(opts); return cloned; } diff --git a/src/opts.h b/src/opts.h index 25d4ececf..21fb08410 100644 --- a/src/opts.h +++ b/src/opts.h @@ -16,12 +16,22 @@ #include "natsp.h" +static inline void natsOptions_lock(natsOptions *opts) +{ + natsMutex_Lock(opts->mu); +} + +static inline void natsOptions_unlock(natsOptions *opts) +{ + natsMutex_Unlock(opts->mu); +} + #define LOCK_AND_CHECK_OPTIONS(o, c) \ if (((o) == NULL) || ((c))) \ return nats_setDefaultError(NATS_INVALID_ARG); \ - natsMutex_Lock((o)->mu); + natsOptions_lock(o); -#define UNLOCK_OPTS(o) natsMutex_Unlock((o)->mu) +#define UNLOCK_OPTS(o) natsOptions_unlock(o); #define NATS_OPTS_DEFAULT_MAX_RECONNECT (60) diff --git a/test/test.c b/test/test.c index 713a1252f..d3dd97572 100644 --- a/test/test.c +++ b/test/test.c @@ -16545,10 +16545,10 @@ test_ReconnectJitter(void) FAIL("Unable to setup test"); test("Default jitter values: "); - natsMutex_Lock(opts->mu); + natsOptions_lock(opts); s = (((opts->reconnectJitter == NATS_OPTS_DEFAULT_RECONNECT_JITTER) && (opts->reconnectJitterTLS == NATS_OPTS_DEFAULT_RECONNECT_JITTER_TLS)) ? NATS_OK : NATS_ERR); - natsMutex_Unlock(opts->mu); + natsOptions_unlock(opts); testCond(s == NATS_OK); s = natsOptions_SetURL(opts, "nats://127.0.0.1:4222"); From 8f169d02a13b87f3841c82b4eb639d77051d01bd Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 22 May 2023 10:25:34 -0700 Subject: [PATCH 42/85] PR feedback: hopefully fixes all windows warnings --- examples/micro-stats.c | 5 +++-- src/micro_error.c | 4 ++-- src/micro_request.c | 2 +- src/nats.h | 2 +- src/util.c | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/micro-stats.c b/examples/micro-stats.c index ce53b8d29..610c141a7 100644 --- a/examples/micro-stats.c +++ b/examples/micro-stats.c @@ -50,7 +50,8 @@ handle_stats(microRequest *req) microServiceStats *stats = NULL; char buf[2048]; service_state_t *service_state = microRequest_GetServiceState(req); - int total, custom, len; + int64_t total; + int custom, len; err = microService_GetStats(&stats, microRequest_GetService(req)); if (err != NULL) @@ -59,7 +60,7 @@ handle_stats(microRequest *req) total = stats->Endpoints[0].NumRequests; custom = service_state->odd_count; len = snprintf(buf, sizeof(buf), - "{\"total\":%d,\"odd\":%d}", total, custom); + "{\"total\":" PRId64 ",\"odd\":%d}", total, custom); return microRequest_Respond(req, buf, len); } diff --git a/src/micro_error.c b/src/micro_error.c index 3066b3250..8f2338e01 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -169,7 +169,7 @@ microError_Wrapf(microError *err, const char *format, ...) } const char * -microError_String(microError *err, char *buf, int size) +microError_String(microError *err, char *buf, size_t size) { size_t used = 0; const char *caused; @@ -184,7 +184,7 @@ microError_String(microError *err, char *buf, int size) if (err->status != NATS_OK) { - used += snprintf(buf + used, size - used, "status %d: ", err->status); + used += snprintf(buf + used, size - used, "status %u: ", err->status); } if (err->code != 0) { diff --git a/src/micro_request.c b/src/micro_request.c index 9f608ad75..8968aa3f1 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -53,7 +53,7 @@ microRequest_RespondCustom(microRequest *req, microError *service_error, const c } if (s == NATS_OK) { - snprintf(buf, sizeof(buf), "%d", service_error->code); + snprintf(buf, sizeof(buf), "%u", service_error->code); s = natsMsgHeader_Set(msg, MICRO_ERROR_CODE_HDR, buf); } } diff --git a/src/nats.h b/src/nats.h index 5a13c70c8..f4846d279 100644 --- a/src/nats.h +++ b/src/nats.h @@ -8166,7 +8166,7 @@ microError_Status(microError *err); * @return `buf` */ NATS_EXTERN const char * -microError_String(microError *err, char *buf, int len); +microError_String(microError *err, char *buf, size_t len); /** * @brief Wraps an exising #microError with a higher printf-like formatted diff --git a/src/util.c b/src/util.c index be0a84275..22544ae22 100644 --- a/src/util.c +++ b/src/util.c @@ -2295,7 +2295,7 @@ nats_marshalDuration(natsBuffer *out_buf, bool comma, const char *field_name, in const char *start = (comma ? ",\"" : "\""); if (neg) - u = -u; + u = (uint64_t) -u; if (u < 1000000000) { From 3877534b31e037d9ee7cd6a03c4ee9b7d921b88d Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 22 May 2023 10:25:34 -0700 Subject: [PATCH 43/85] PR feedback: hopefully fixes all windows warnings --- examples/micro-stats.c | 5 +++-- src/micro_error.c | 4 ++-- src/micro_request.c | 2 +- src/nats.h | 2 +- src/util.c | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/micro-stats.c b/examples/micro-stats.c index ce53b8d29..610c141a7 100644 --- a/examples/micro-stats.c +++ b/examples/micro-stats.c @@ -50,7 +50,8 @@ handle_stats(microRequest *req) microServiceStats *stats = NULL; char buf[2048]; service_state_t *service_state = microRequest_GetServiceState(req); - int total, custom, len; + int64_t total; + int custom, len; err = microService_GetStats(&stats, microRequest_GetService(req)); if (err != NULL) @@ -59,7 +60,7 @@ handle_stats(microRequest *req) total = stats->Endpoints[0].NumRequests; custom = service_state->odd_count; len = snprintf(buf, sizeof(buf), - "{\"total\":%d,\"odd\":%d}", total, custom); + "{\"total\":" PRId64 ",\"odd\":%d}", total, custom); return microRequest_Respond(req, buf, len); } diff --git a/src/micro_error.c b/src/micro_error.c index 3066b3250..8f2338e01 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -169,7 +169,7 @@ microError_Wrapf(microError *err, const char *format, ...) } const char * -microError_String(microError *err, char *buf, int size) +microError_String(microError *err, char *buf, size_t size) { size_t used = 0; const char *caused; @@ -184,7 +184,7 @@ microError_String(microError *err, char *buf, int size) if (err->status != NATS_OK) { - used += snprintf(buf + used, size - used, "status %d: ", err->status); + used += snprintf(buf + used, size - used, "status %u: ", err->status); } if (err->code != 0) { diff --git a/src/micro_request.c b/src/micro_request.c index 9f608ad75..8968aa3f1 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -53,7 +53,7 @@ microRequest_RespondCustom(microRequest *req, microError *service_error, const c } if (s == NATS_OK) { - snprintf(buf, sizeof(buf), "%d", service_error->code); + snprintf(buf, sizeof(buf), "%u", service_error->code); s = natsMsgHeader_Set(msg, MICRO_ERROR_CODE_HDR, buf); } } diff --git a/src/nats.h b/src/nats.h index 5a13c70c8..f4846d279 100644 --- a/src/nats.h +++ b/src/nats.h @@ -8166,7 +8166,7 @@ microError_Status(microError *err); * @return `buf` */ NATS_EXTERN const char * -microError_String(microError *err, char *buf, int len); +microError_String(microError *err, char *buf, size_t len); /** * @brief Wraps an exising #microError with a higher printf-like formatted diff --git a/src/util.c b/src/util.c index be0a84275..22544ae22 100644 --- a/src/util.c +++ b/src/util.c @@ -2295,7 +2295,7 @@ nats_marshalDuration(natsBuffer *out_buf, bool comma, const char *field_name, in const char *start = (comma ? ",\"" : "\""); if (neg) - u = -u; + u = (uint64_t) -u; if (u < 1000000000) { From 2b4583525bacca504fd44c176af41718efedd01a Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 22 May 2023 10:56:25 -0700 Subject: [PATCH 44/85] PR feedback: removed previous PR, wrong branch --- examples/micro-stats.c | 5 ++--- src/micro_error.c | 4 ++-- src/micro_request.c | 2 +- src/nats.h | 2 +- src/util.c | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/micro-stats.c b/examples/micro-stats.c index 610c141a7..ce53b8d29 100644 --- a/examples/micro-stats.c +++ b/examples/micro-stats.c @@ -50,8 +50,7 @@ handle_stats(microRequest *req) microServiceStats *stats = NULL; char buf[2048]; service_state_t *service_state = microRequest_GetServiceState(req); - int64_t total; - int custom, len; + int total, custom, len; err = microService_GetStats(&stats, microRequest_GetService(req)); if (err != NULL) @@ -60,7 +59,7 @@ handle_stats(microRequest *req) total = stats->Endpoints[0].NumRequests; custom = service_state->odd_count; len = snprintf(buf, sizeof(buf), - "{\"total\":" PRId64 ",\"odd\":%d}", total, custom); + "{\"total\":%d,\"odd\":%d}", total, custom); return microRequest_Respond(req, buf, len); } diff --git a/src/micro_error.c b/src/micro_error.c index 8f2338e01..3066b3250 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -169,7 +169,7 @@ microError_Wrapf(microError *err, const char *format, ...) } const char * -microError_String(microError *err, char *buf, size_t size) +microError_String(microError *err, char *buf, int size) { size_t used = 0; const char *caused; @@ -184,7 +184,7 @@ microError_String(microError *err, char *buf, size_t size) if (err->status != NATS_OK) { - used += snprintf(buf + used, size - used, "status %u: ", err->status); + used += snprintf(buf + used, size - used, "status %d: ", err->status); } if (err->code != 0) { diff --git a/src/micro_request.c b/src/micro_request.c index 8968aa3f1..9f608ad75 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -53,7 +53,7 @@ microRequest_RespondCustom(microRequest *req, microError *service_error, const c } if (s == NATS_OK) { - snprintf(buf, sizeof(buf), "%u", service_error->code); + snprintf(buf, sizeof(buf), "%d", service_error->code); s = natsMsgHeader_Set(msg, MICRO_ERROR_CODE_HDR, buf); } } diff --git a/src/nats.h b/src/nats.h index f4846d279..5a13c70c8 100644 --- a/src/nats.h +++ b/src/nats.h @@ -8166,7 +8166,7 @@ microError_Status(microError *err); * @return `buf` */ NATS_EXTERN const char * -microError_String(microError *err, char *buf, size_t len); +microError_String(microError *err, char *buf, int len); /** * @brief Wraps an exising #microError with a higher printf-like formatted diff --git a/src/util.c b/src/util.c index 22544ae22..be0a84275 100644 --- a/src/util.c +++ b/src/util.c @@ -2295,7 +2295,7 @@ nats_marshalDuration(natsBuffer *out_buf, bool comma, const char *field_name, in const char *start = (comma ? ",\"" : "\""); if (neg) - u = (uint64_t) -u; + u = -u; if (u < 1000000000) { From 24977638b69a0026cbf57ca7c54e99e79887f8e9 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 22 May 2023 10:57:03 -0700 Subject: [PATCH 45/85] restored LOCK_OPTIONS macros --- src/opts.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/opts.h b/src/opts.h index 21fb08410..f9d672cfe 100644 --- a/src/opts.h +++ b/src/opts.h @@ -29,9 +29,9 @@ static inline void natsOptions_unlock(natsOptions *opts) #define LOCK_AND_CHECK_OPTIONS(o, c) \ if (((o) == NULL) || ((c))) \ return nats_setDefaultError(NATS_INVALID_ARG); \ - natsOptions_lock(o); + natsMutex_Lock((o)->mu); -#define UNLOCK_OPTS(o) natsOptions_unlock(o); +#define UNLOCK_OPTS(o) natsMutex_Unlock((o)->mu) #define NATS_OPTS_DEFAULT_MAX_RECONNECT (60) From a0dafe84c6aae2337ba0acc61c2bec1271b9eec3 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 22 May 2023 09:16:36 -0700 Subject: [PATCH 46/85] `Done` handler --- examples/micro-stats.c | 4 ++-- src/micro.c | 12 +++++++++++ src/nats.h | 49 +++++++++++++++++++++++++++++++++++++----- test/test.c | 27 +++++++++++++++++++++-- 4 files changed, 83 insertions(+), 9 deletions(-) diff --git a/examples/micro-stats.c b/examples/micro-stats.c index 610c141a7..fdbba9bfe 100644 --- a/examples/micro-stats.c +++ b/examples/micro-stats.c @@ -51,7 +51,7 @@ handle_stats(microRequest *req) char buf[2048]; service_state_t *service_state = microRequest_GetServiceState(req); int64_t total; - int custom, len; + int custom, len; err = microService_GetStats(&stats, microRequest_GetService(req)); if (err != NULL) @@ -60,7 +60,7 @@ handle_stats(microRequest *req) total = stats->Endpoints[0].NumRequests; custom = service_state->odd_count; len = snprintf(buf, sizeof(buf), - "{\"total\":" PRId64 ",\"odd\":%d}", total, custom); + "{\"total\":%lld,\"odd\":%d}", total, custom); return microRequest_Respond(req, buf, len); } diff --git a/src/micro.c b/src/micro.c index b67fdc4c9..66d9fd104 100644 --- a/src/micro.c +++ b/src/micro.c @@ -204,6 +204,9 @@ microService_Stop(microService *m) m->num_eps = 0; m->first_ep = NULL; micro_unlock_service(m); + + if (m->cfg->DoneHandler != NULL) + m->cfg->DoneHandler(m); } return err; @@ -250,6 +253,15 @@ microService_Run(microService *m) return NULL; } +void * +microService_GetState(microService *m) +{ + if (m == NULL) + return NULL; + + return m->cfg->State; +} + static microError * new_service(microService **ptr, natsConnection *nc) { diff --git a/src/nats.h b/src/nats.h index f4846d279..f6e612548 100644 --- a/src/nats.h +++ b/src/nats.h @@ -7271,7 +7271,7 @@ typedef struct micro_service_stats_s microServiceStats; * @{ */ -/** +/** * @brief Callback type for request processing. * * This is the callback that one provides when creating a microservice endpoint. @@ -7285,7 +7285,7 @@ typedef struct micro_service_stats_s microServiceStats; */ typedef microError *(*microRequestHandler)(microRequest *req); -/** +/** * @brief Callback type for async error notifications. * * If specified in microServiceConfig, this callback is invoked for internal @@ -7309,6 +7309,19 @@ typedef microError *(*microRequestHandler)(microRequest *req); */ typedef void (*microErrorHandler)(microService *m, microEndpoint *ep, natsStatus s); +/** + * @brief Callback type for `Done` (service stopped) notifications. + * + * If specified in microServiceConfig, this callback is invoked right after the + * service stops. In the C client, this callback is invoked directly from the + * microService_Stop function, in whatever thread is executing it. + * + * @param m The microservice object. + * + * @see microServiceConfig, micro_service_config_s. + */ +typedef void (*microDoneHandler)(microService *m); + /** @} */ // end of microCallbacks /** \defgroup microStructs Public structs @@ -7445,6 +7458,16 @@ struct micro_service_config_s */ microErrorHandler ErrHandler; + /** + * @brief A callback handler for handling the final cleanup `Done` event, + * right before the service is destroyed. + * + * It will be called directly from #microService_Stop method, so it may be + * executed in any of the user threads or in the async callback thread if + * the service stops itself on connection closed or an error event. + */ + microDoneHandler DoneHandler; + /** * @brief A user-provided pointer to state data. * @@ -7710,6 +7733,18 @@ microService_GetConnection(microService *m); NATS_EXTERN microError * microService_GetInfo(microServiceInfo **new_info, microService *m); +/** @brief Returns the pointer to state data (closure). It is originally + * provided in #microServiceConfig.State. + * + * @param m the #microService. + * + * @return the state pointer. + * + * @see #microServiceConfig + */ +NATS_EXTERN void * +microService_GetState(microService *m); + /** @brief Returns run-time statistics for a microservice. * * @note the #microServiceStats struct returned by this call should be freed @@ -7753,9 +7788,13 @@ microService_Run(microService *m); /** @brief Stops a running microservice. * * Drains and closes the all subscriptions (endpoints and monitoring), resets - * the stats, and posts the async `Done` callback for the service, so it can do - * its own clean up if needed. - * + * the stats, and calls the `Done` callback for the service, so it can do its + * own clean up if needed. + * + * It is possible that this call encounters an error while stopping the service, + * in which case it aborts and returns the error. The service then may be in a + * partially stopped state, and the `Done` callback will not have been called. + * * @param m the #microService. * * @return a #microError if an error occurred. diff --git a/test/test.c b/test/test.c index d59f4d1ec..1e579b528 100644 --- a/test/test.c +++ b/test/test.c @@ -32868,6 +32868,17 @@ test_MicroStartStop(void) _stopServer(serverPid); } +void micro_service_done_handler(microService *s) +{ + struct threadArg *arg = (struct threadArg*) microService_GetState(s); + + natsMutex_Lock(arg->m); + arg->done = true; + natsCondition_Broadcast(arg->c); + natsMutex_Unlock(arg->m); + +} + static void test_MicroServiceStopsOnClosedConn(void) { @@ -32880,6 +32891,8 @@ test_MicroServiceStopsOnClosedConn(void) microServiceConfig cfg = { .Name = "test", .Version = "1.0.0", + .DoneHandler = micro_service_done_handler, + .State = &arg, }; natsMsg *reply = NULL; @@ -32918,7 +32931,11 @@ test_MicroServiceStopsOnClosedConn(void) testCond(natsConnection_IsClosed(nc)); test("Wait for the service to stop: "); - testCond((nats_Sleep(100), true)); + natsMutex_Lock(arg.m); + while ((s != NATS_TIMEOUT) && !arg.done) + s = natsCondition_TimedWait(arg.c, arg.m, 10000); + natsMutex_Unlock(arg.m); + testCond(arg.done); test("Test microservice is stopped: "); testCond(microService_IsStopped(m)); @@ -32944,6 +32961,8 @@ test_MicroServiceStopsWhenServerStops(void) microServiceConfig cfg = { .Name = "test", .Version = "1.0.0", + .DoneHandler = micro_service_done_handler, + .State = &arg, }; s = _createDefaultThreadArgsForCbTests(&arg); @@ -32973,7 +32992,11 @@ test_MicroServiceStopsWhenServerStops(void) testCond((_stopServer(serverPid), true)); test("Wait for the service to stop: "); - testCond((nats_Sleep(100), true)); + natsMutex_Lock(arg.m); + while ((s != NATS_TIMEOUT) && !arg.done) + s = natsCondition_TimedWait(arg.c, arg.m, 10000); + natsMutex_Unlock(arg.m); + testCond(arg.done); test("Test microservice is not running: "); testCond(microService_IsStopped(m)) From 9182317e5c6e009a8c0c194d17ba4a17a0ef7b41 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 23 May 2023 06:48:54 -0700 Subject: [PATCH 47/85] printf --- examples/micro-stats.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/micro-stats.c b/examples/micro-stats.c index fdbba9bfe..8a5b27203 100644 --- a/examples/micro-stats.c +++ b/examples/micro-stats.c @@ -50,17 +50,16 @@ handle_stats(microRequest *req) microServiceStats *stats = NULL; char buf[2048]; service_state_t *service_state = microRequest_GetServiceState(req); - int64_t total; - int custom, len; + int total, custom, len; err = microService_GetStats(&stats, microRequest_GetService(req)); if (err != NULL) return err; - total = stats->Endpoints[0].NumRequests; + total = (int) stats->Endpoints[0].NumRequests; custom = service_state->odd_count; len = snprintf(buf, sizeof(buf), - "{\"total\":%lld,\"odd\":%d}", total, custom); + "{\"total\":%d,\"odd\":%d}", total, custom); return microRequest_Respond(req, buf, len); } From ee020b705c085cb38b753ae105a5713836a5e1d6 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 23 May 2023 08:39:01 -0700 Subject: [PATCH 48/85] Added Test_MicroAsyncErrorHandler --- src/micro.c | 2 +- test/list.txt | 1 + test/test.c | 130 ++++++++++++++++++++++++++++++-------------------- 3 files changed, 81 insertions(+), 52 deletions(-) diff --git a/src/micro.c b/src/micro.c index 44b704c00..4fd19c809 100644 --- a/src/micro.c +++ b/src/micro.c @@ -429,7 +429,7 @@ on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *closure) microError_Destroy(err); } - // <>/<> TODO: Should we stop the service? The Go client does. + // TODO: Should we stop the service? The Go client does. microService_Stop(m); } diff --git a/test/list.txt b/test/list.txt index 8a62e5fd2..dde363ef3 100644 --- a/test/list.txt +++ b/test/list.txt @@ -267,6 +267,7 @@ MicroBasics MicroStartStop MicroServiceStopsOnClosedConn MicroServiceStopsWhenServerStops +MicroAsyncErrorHandler StanPBufAllocator StanConnOptions StanSubOptions diff --git a/test/test.c b/test/test.c index 003b3f285..131273ac3 100644 --- a/test/test.c +++ b/test/test.c @@ -32929,9 +32929,9 @@ test_MicroStartStop(void) _stopServer(serverPid); } -void micro_service_done_handler(microService *s) +void micro_service_done_handler(microService *m) { - struct threadArg *arg = (struct threadArg*) microService_GetState(s); + struct threadArg *arg = (struct threadArg*) microService_GetState(m); natsMutex_Lock(arg->m); arg->done = true; @@ -33068,70 +33068,97 @@ test_MicroServiceStopsWhenServerStops(void) _destroyDefaultThreadArgs(&arg); } -// static void -// test_AsyncErrHandler(void) -// { -// natsStatus s; -// natsConnection *nc = NULL; -// natsOptions *opts = NULL; -// natsSubscription *sub = NULL; -// natsPid serverPid = NATS_INVALID_PID; -// struct threadArg arg; +void micro_async_error_handler(microService *m, microEndpoint *ep, natsStatus s) +{ + struct threadArg *arg = (struct threadArg*) microService_GetState(m); + + natsMutex_Lock(arg->m); + arg->status = s; + natsCondition_Broadcast(arg->c); + natsMutex_Unlock(arg->m); +} + +static void +test_MicroAsyncErrorHandler(void) +{ + natsStatus s; + natsConnection *nc = NULL; + natsOptions *opts = NULL; + natsSubscription *sub = NULL; + natsPid serverPid = NATS_INVALID_PID; + struct threadArg arg; + microService *m = NULL; + microServiceConfig cfg = { + .Name = "test", + .Version = "1.0.0", + .DoneHandler = micro_service_done_handler, + .ErrHandler = micro_async_error_handler, + .State = &arg, + }; + + s = _createDefaultThreadArgsForCbTests(&arg); + if (s != NATS_OK) + FAIL("Unable to setup test!"); + + arg.status = NATS_OK; + arg.control= 7; -// s = _createDefaultThreadArgsForCbTests(&arg); -// if (s != NATS_OK) -// FAIL("Unable to setup test!"); + s = natsOptions_Create(&opts); + IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); + IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 10)); -// arg.status = NATS_OK; -// arg.control= 7; + if (s != NATS_OK) + FAIL("Unable to create options for test AsyncErrHandler"); -// s = natsOptions_Create(&opts); -// IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); -// IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 10)); -// IFOK(s, natsOptions_SetErrorHandler(opts, _asyncErrCb, (void*) &arg)); + serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); + CHECK_SERVER_STARTED(serverPid); -// if (s != NATS_OK) -// FAIL("Unable to create options for test AsyncErrHandler"); + test("Connect to NATS: "); + testCond(NATS_OK == natsConnection_Connect(&nc, opts)); -// serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); -// CHECK_SERVER_STARTED(serverPid); + test("Start microservice: "); + testCond(NULL == micro_AddService(&m, nc, &cfg)); -// s = natsConnection_Connect(&nc, opts); -// IFOK(s, natsConnection_Subscribe(&sub, nc, "async_test", _recvTestString, (void*) &arg)); + test("Test microservice is running: "); + testCond(!microService_IsStopped(m)) -// natsMutex_Lock(arg.m); -// arg.sub = sub; -// natsMutex_Unlock(arg.m); + test("Subscribe to async_test: "); + testCond(NATS_OK == natsConnection_Subscribe(&sub, nc, "async_test", _recvTestString, (void*) &arg)); -// for (int i=0; -// (s == NATS_OK) && (i < (opts->maxPendingMsgs + 100)); i++) -// { -// s = natsConnection_PublishString(nc, "async_test", "hello"); -// } -// IFOK(s, natsConnection_Flush(nc)); + natsMutex_Lock(arg.m); + arg.sub = sub; + arg.status = NATS_OK; + natsMutex_Unlock(arg.m); -// // Wait for async err callback -// natsMutex_Lock(arg.m); -// while ((s != NATS_TIMEOUT) && !arg.done) -// s = natsCondition_TimedWait(arg.c, arg.m, 2000); -// natsMutex_Unlock(arg.m); + test("Cause an error by sending too many messages: "); + for (int i=0; + (s == NATS_OK) && (i < (opts->maxPendingMsgs + 100)); i++) + { + s = natsConnection_PublishString(nc, "async_test", "hello"); + } + testCond(NATS_OK == natsConnection_Flush(nc)); -// test("Aync fired properly, and all checks are good: "); -// testCond((s == NATS_OK) -// && arg.done -// && arg.closed -// && (arg.status == NATS_OK)); + // Wait for async err callback + natsMutex_Lock(arg.m); + while ((s != NATS_TIMEOUT) && (arg.status == NATS_OK || !arg.done)) + s = natsCondition_TimedWait(arg.c, arg.m, 2000); + natsMutex_Unlock(arg.m); -// natsOptions_Destroy(opts); -// natsSubscription_Destroy(sub); -// natsConnection_Destroy(nc); + test("Aync error fired properly, with correct status, and done: "); + testCond((s == NATS_OK) && (arg.status == NATS_SLOW_CONSUMER) && arg.done); -// _destroyDefaultThreadArgs(&arg); + test("Test microservice is not running: "); + testCond(microService_IsStopped(m)) -// _stopServer(serverPid); -// } + microService_Destroy(m); + natsOptions_Destroy(opts); + natsSubscription_Destroy(sub); + natsConnection_Destroy(nc); + _destroyDefaultThreadArgs(&arg); + _stopServer(serverPid); +} #if defined(NATS_HAS_STREAMING) @@ -35641,6 +35668,7 @@ static testInfo allTests[] = {"MicroStartStop", test_MicroStartStop}, {"MicroServiceStopsOnClosedConn", test_MicroServiceStopsOnClosedConn}, {"MicroServiceStopsWhenServerStops", test_MicroServiceStopsWhenServerStops}, + {"MicroAsyncErrorHandler", test_MicroAsyncErrorHandler}, #if defined(NATS_HAS_STREAMING) {"StanPBufAllocator", test_StanPBufAllocator}, From 59c5de832d3e729b25e703f74e6fa2b4a4a25499 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 23 May 2023 08:48:25 -0700 Subject: [PATCH 49/85] PR feedback: warnings --- src/micro.c | 3 ++- src/util.c | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/micro.c b/src/micro.c index 4fd19c809..004f25dbd 100644 --- a/src/micro.c +++ b/src/micro.c @@ -569,9 +569,10 @@ void microServiceInfo_Destroy(microServiceInfo *info) if (info == NULL) return; + // casts to quiet the compiler. for (i = 0; i < info->SubjectsLen; i++) NATS_FREE((char *)info->Subjects[i]); - NATS_FREE(info->Subjects); + NATS_FREE((char *)info->Subjects); NATS_FREE((char *)info->Name); NATS_FREE((char *)info->Version); NATS_FREE((char *)info->Description); diff --git a/src/util.c b/src/util.c index 22544ae22..22eeef364 100644 --- a/src/util.c +++ b/src/util.c @@ -2288,15 +2288,12 @@ nats_marshalDuration(natsBuffer *out_buf, bool comma, const char *field_name, in // Largest time is 2540400h10m10.000000000s char buf[32]; int w = 32; - uint64_t u = d; bool neg = d < 0; + uint64_t u = (uint64_t) (neg ? -d : d); int prec; natsStatus s = NATS_OK; const char *start = (comma ? ",\"" : "\""); - if (neg) - u = (uint64_t) -u; - if (u < 1000000000) { // Special case: if duration is smaller than a second, From 36cb48134a48c5c360a88d1675b14854cb57f4a4 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 23 May 2023 13:01:00 -0700 Subject: [PATCH 50/85] Fixed AsyncErr test --- test/test.c | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/test/test.c b/test/test.c index 131273ac3..3445bcbc5 100644 --- a/test/test.c +++ b/test/test.c @@ -32994,7 +32994,7 @@ test_MicroServiceStopsOnClosedConn(void) test("Wait for the service to stop: "); natsMutex_Lock(arg.m); while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 10000); + s = natsCondition_TimedWait(arg.c, arg.m, 1000); natsMutex_Unlock(arg.m); testCond(arg.done); @@ -33055,7 +33055,7 @@ test_MicroServiceStopsWhenServerStops(void) test("Wait for the service to stop: "); natsMutex_Lock(arg.m); while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 10000); + s = natsCondition_TimedWait(arg.c, arg.m, 1000); natsMutex_Unlock(arg.m); testCond(arg.done); @@ -33073,6 +33073,12 @@ void micro_async_error_handler(microService *m, microEndpoint *ep, natsStatus s) struct threadArg *arg = (struct threadArg*) microService_GetState(m); natsMutex_Lock(arg->m); + + // unblock the blocked sub + arg->closed = true; + + // set the data to verify + arg->done = true; arg->status = s; natsCondition_Broadcast(arg->c); natsMutex_Unlock(arg->m); @@ -33091,7 +33097,6 @@ test_MicroAsyncErrorHandler(void) microServiceConfig cfg = { .Name = "test", .Version = "1.0.0", - .DoneHandler = micro_service_done_handler, .ErrHandler = micro_async_error_handler, .State = &arg, }; @@ -33100,9 +33105,6 @@ test_MicroAsyncErrorHandler(void) if (s != NATS_OK) FAIL("Unable to setup test!"); - arg.status = NATS_OK; - arg.control= 7; - s = natsOptions_Create(&opts); IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 10)); @@ -33126,8 +33128,8 @@ test_MicroAsyncErrorHandler(void) testCond(NATS_OK == natsConnection_Subscribe(&sub, nc, "async_test", _recvTestString, (void*) &arg)); natsMutex_Lock(arg.m); - arg.sub = sub; arg.status = NATS_OK; + arg.control= 7; natsMutex_Unlock(arg.m); test("Cause an error by sending too many messages: "); @@ -33136,18 +33138,19 @@ test_MicroAsyncErrorHandler(void) { s = natsConnection_PublishString(nc, "async_test", "hello"); } - testCond(NATS_OK == natsConnection_Flush(nc)); + testCond((NATS_OK == s) + && (NATS_OK == natsConnection_Flush(nc))); - // Wait for async err callback + test("Wait for async err callback: "); natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status == NATS_OK || !arg.done)) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); + while ((s != NATS_TIMEOUT) && (arg.status != NATS_SLOW_CONSUMER)) + s = natsCondition_TimedWait(arg.c, arg.m, 1000); natsMutex_Unlock(arg.m); + testCond((s == NATS_OK) + && (arg.status == NATS_SLOW_CONSUMER) + && arg.done); - test("Aync error fired properly, with correct status, and done: "); - testCond((s == NATS_OK) && (arg.status == NATS_SLOW_CONSUMER) && arg.done); - - test("Test microservice is not running: "); + test("Test microservice is not running: \n\n"); testCond(microService_IsStopped(m)) microService_Destroy(m); From 964d210ca9470934924a77680fbb4e52657adc98 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 23 May 2023 13:21:15 -0700 Subject: [PATCH 51/85] Now with a Done handler to fix the test on gcc builds --- test/test.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/test.c b/test/test.c index 3445bcbc5..6cf081bf3 100644 --- a/test/test.c +++ b/test/test.c @@ -33078,7 +33078,6 @@ void micro_async_error_handler(microService *m, microEndpoint *ep, natsStatus s) arg->closed = true; // set the data to verify - arg->done = true; arg->status = s; natsCondition_Broadcast(arg->c); natsMutex_Unlock(arg->m); @@ -33097,6 +33096,7 @@ test_MicroAsyncErrorHandler(void) microServiceConfig cfg = { .Name = "test", .Version = "1.0.0", + .DoneHandler = micro_service_done_handler, .ErrHandler = micro_async_error_handler, .State = &arg, }; @@ -33147,8 +33147,14 @@ test_MicroAsyncErrorHandler(void) s = natsCondition_TimedWait(arg.c, arg.m, 1000); natsMutex_Unlock(arg.m); testCond((s == NATS_OK) - && (arg.status == NATS_SLOW_CONSUMER) - && arg.done); + && (arg.status == NATS_SLOW_CONSUMER)); + + test("Wait for the service to stop: "); + natsMutex_Lock(arg.m); + while ((s != NATS_TIMEOUT) && !arg.done) + s = natsCondition_TimedWait(arg.c, arg.m, 1000); + natsMutex_Unlock(arg.m); + testCond((s == NATS_OK) && arg.done); test("Test microservice is not running: \n\n"); testCond(microService_IsStopped(m)) From c3677d20b19eef666a9dcdebb0ad61887dda1e86 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 23 May 2023 13:39:48 -0700 Subject: [PATCH 52/85] try-1 correct fix? wip minimized list.txt minimized list.txt minimized list.txt wip wip more wip more+ wip more+oops wip more+oops+printf --- src/micro.c | 182 ++++++++++++++++++++++++++++++------------- src/micro_endpoint.c | 94 +++++++++++++++------- src/micro_error.c | 2 +- src/microp.h | 22 ++++-- test/test.c | 102 +++++++++++++++--------- 5 files changed, 277 insertions(+), 125 deletions(-) diff --git a/src/micro.c b/src/micro.c index 004f25dbd..d965524fb 100644 --- a/src/micro.c +++ b/src/micro.c @@ -41,11 +41,6 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c MICRO_CALL(err, micro_clone_service_config(&m->cfg, cfg)); - if (err == NULL) - { - m->is_running = true; - } - // Wrap the connection callbacks before we subscribe to anything. MICRO_CALL(err, wrap_connection_event_callbacks(m)) MICRO_CALL(err, micro_init_monitoring(m)); @@ -81,6 +76,12 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, micro_lock_service(m); + if (m->stopping || m->stopped) + { + micro_unlock_service(m); + return micro_Errorf("can't add an endpoint %s to service %s: the service is stopped", cfg->Name, m->cfg->Name); + } + if (m->first_ep != NULL) { if (strcmp(m->first_ep->name, ep->name) == 0) @@ -158,83 +159,122 @@ microGroup_AddEndpoint(microGroup *g, microEndpointConfig *cfg) return micro_add_endpoint(NULL, g->m, g->prefix, cfg, false); } -microError * -microService_Stop(microService *m) +static void micro_finalize_stopping_service(microService *m) { - microError *err = NULL; - microEndpoint *ep = NULL; - - bool is_running = false; - microEndpoint *first_ep = NULL; + microDoneHandler invoke_done = NULL; + bool on_connection_closed_called = false; if (m == NULL) - return micro_ErrorInvalidArg; + return; micro_lock_service(m); - is_running = m->is_running; - first_ep = m->first_ep; - micro_unlock_service(m); + if (m->stopped || (m->num_eps_to_stop > 0)) + { + micro_unlock_service(m); + return; + } - if (!is_running) - return NULL; + on_connection_closed_called = m->connection_closed_called; + m->stopped = true; + m->stopping = false; + if (m->cfg->DoneHandler != NULL) + invoke_done = m->cfg->DoneHandler; + micro_unlock_service(m); unwrap_connection_event_callbacks(m); - for (ep = first_ep; (err == NULL) && (ep != NULL); ep = first_ep) + // If the connection closed callback has not been called yet, it will not be + // since we just removed it, so compensate for it by releaseing the service. + if (!on_connection_closed_called) { - micro_lock_service(m); - first_ep = ep->next; - m->first_ep = first_ep; - m->num_eps--; - micro_unlock_service(m); - - // micro_destroy_endpoint may release the service, locking it in the - // process, so we need to avoid a race. - if (err = micro_destroy_endpoint(ep), err != NULL) - { - err = microError_Wrapf(err, "failed to stop endpoint %s", ep->name); - } + micro_release_service(m); } - if (err == NULL) + if (invoke_done != NULL) + invoke_done(m); +} + +microError * +microService_Stop(microService *m) +{ + microError *err = NULL; + microEndpoint *ep = NULL; + bool drain_endpoints = false; + + if (m == NULL) + return micro_ErrorInvalidArg; + + micro_lock_service(m); + if (m->stopped) { - micro_lock_service(m); - m->is_running = false; - m->started = 0; - m->num_eps = 0; - m->first_ep = NULL; micro_unlock_service(m); + return NULL; + } + drain_endpoints = !m->stopping; + m->stopping = true; + ep = m->first_ep; + micro_unlock_service(m); - if (m->cfg->DoneHandler != NULL) - m->cfg->DoneHandler(m); + // Endpoints are never removed from the list (except when the service is + // destroyed, but that is after Stop's already been called), so we can iterate + // safely outside the lock. + if (drain_endpoints) + { + for (; ep != NULL; ep = ep->next) + { + if (err = micro_stop_endpoint(ep), err != NULL) + return microError_Wrapf(err, "failed to stop service %s", ep->config->Name); + } } - return err; + micro_finalize_stopping_service(m); + return NULL; } bool microService_IsStopped(microService *m) { - bool is_stopped; + bool stopped; if ((m == NULL) || (m->service_mu == NULL)) return true; micro_lock_service(m); - is_stopped = !m->is_running; + stopped = m->stopped; micro_unlock_service(m); - return is_stopped; + return stopped; } microError * microService_Destroy(microService *m) { microError *err = NULL; + microEndpoint *ep = NULL; err = microService_Stop(m); if (err != NULL) return err; + // Unlink all endpoints from the service, they will self-destruct by their + // onComplete handler. + while (true) + { + micro_lock_service(m); + + ep = m->first_ep; + if (ep == NULL) + { + micro_unlock_service(m); + break; + } + m->first_ep = ep->next; + micro_unlock_service(m); + + err = micro_destroy_endpoint(ep); + if (err != NULL) + return err; + } + micro_release_service(m); return NULL; } @@ -276,19 +316,16 @@ new_service(microService **ptr, natsConnection *nc) return NULL; } -microService * -micro_retain_service(microService *m) +void micro_retain_service(microService *m) { if (m == NULL) - return NULL; + return; micro_lock_service(m); m->refs++; micro_unlock_service(m); - - return m; } void micro_release_service(microService *m) @@ -308,6 +345,39 @@ void micro_release_service(microService *m) free_service(m); } +void micro_increment_endpoints_to_stop(microService *m) +{ + if (m == NULL) + return; + + micro_lock_service(m); + + m->num_eps_to_stop++; + + micro_unlock_service(m); +} + +void micro_decrement_endpoints_to_stop(microService *m) +{ + if (m == NULL) + return; + + micro_lock_service(m); + + if (m->num_eps_to_stop == 0) + { + micro_unlock_service(m); + fprintf(stderr, "FATAL ERROR: should be unreachable: unbalanced stopping refs on a microservice %s\n", m->cfg->Name); + return; + } + + m->num_eps_to_stop--; + + micro_unlock_service(m); + + micro_finalize_stopping_service(m); +} + static void free_service(microService *m) { microGroup *next = NULL; @@ -390,6 +460,12 @@ on_connection_closed(natsConnection *nc, void *closure) if (m == NULL) return; + micro_lock_service(m); + m->connection_closed_called = true; + micro_unlock_service(m); + + micro_release_service(m); + microService_Stop(m); } @@ -412,12 +488,12 @@ on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *closure) { if (micro_match_endpoint_subject(ep->subject, subject)) { - break; + our_subject = true; } } micro_unlock_service(m); - if (m->cfg->ErrHandler != NULL) + if (m->cfg->ErrHandler != NULL && our_subject) { (*m->cfg->ErrHandler)(m, ep, s); } @@ -441,7 +517,9 @@ wrap_connection_event_callbacks(microService *m) if ((m == NULL) || (m->nc == NULL) || (m->nc->opts == NULL)) return micro_ErrorInvalidArg; - IFOK(s, natsOptions_addConnectionClosedCallback(m->nc->opts,on_connection_closed, m)); + // add an extra reference to the service for the callbacks. + micro_retain_service(m); + IFOK(s, natsOptions_addConnectionClosedCallback(m->nc->opts, on_connection_closed, m)); IFOK(s, natsOptions_addErrorCallback(m->nc->opts, on_error, m)); return microError_Wrapf(micro_ErrorFromStatus(s), "failed to wrap connection event callbacks"); @@ -572,7 +650,7 @@ void microServiceInfo_Destroy(microServiceInfo *info) // casts to quiet the compiler. for (i = 0; i < info->SubjectsLen; i++) NATS_FREE((char *)info->Subjects[i]); - NATS_FREE((char *)info->Subjects); + NATS_FREE((char *)info->Subjects); NATS_FREE((char *)info->Name); NATS_FREE((char *)info->Version); NATS_FREE((char *)info->Description); diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index e5c89f9c2..c790d5b2a 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -20,7 +20,7 @@ static void free_endpoint(microEndpoint *ep); static void handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); static microError *new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); static void release_endpoint(microEndpoint *ep); -static microEndpoint *retain_endpoint(microEndpoint *ep); +static void retain_endpoint(microEndpoint *ep); microError * micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal) @@ -46,7 +46,8 @@ micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, return micro_ErrorOutOfMemory; ep->refs = 1; ep->is_monitoring_endpoint = is_internal; - ep->m = micro_retain_service(m); + micro_retain_service(m); + ep->m = m; MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->endpoint_mu))); MICRO_CALL(err, micro_clone_endpoint_config(&ep->config, cfg)); @@ -62,9 +63,25 @@ micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, return NULL; } -static void _release_on_complete(void *closure) +static void _release_on_endpoint_complete(void *closure) { - release_endpoint((microEndpoint *)closure); + microEndpoint *ep = (microEndpoint *)closure; + microService *m = NULL; + int n; + + if (ep == NULL) + return; + m = ep->m; + + micro_lock_endpoint(ep); + ep->is_draining = false; + n = --(ep->refs); + micro_unlock_endpoint(ep); + + micro_decrement_endpoints_to_stop(m); + + if (n == 0) + free_endpoint(ep); } microError * @@ -80,10 +97,6 @@ micro_start_endpoint(microEndpoint *ep) // reset the stats. memset(&ep->stats, 0, sizeof(ep->stats)); - // extra retain before subscribing since we'll need to hold it until - // on_complete on the subscription. - retain_endpoint(ep); - if (ep->is_monitoring_endpoint) s = natsConnection_Subscribe(&sub, ep->m->nc, ep->subject, handle_request, ep); else @@ -91,73 +104,96 @@ micro_start_endpoint(microEndpoint *ep) if (s == NATS_OK) { - natsSubscription_SetOnCompleteCB(sub, _release_on_complete, ep); + // extra retain before subscribing since we'll need to hold it until + // on_complete on the subscription. + retain_endpoint(ep); + + // Make sure the service is not stopped until this subscription has been closed. + micro_increment_endpoints_to_stop(ep->m); + + natsSubscription_SetOnCompleteCB(sub, _release_on_endpoint_complete, ep); micro_lock_endpoint(ep); ep->sub = sub; - ep->is_running = true; + ep->is_draining = false; micro_unlock_endpoint(ep); } else { natsSubscription_Destroy(sub); // likely always a no-op. - release_endpoint(ep); // to compensate for the extra retain above. } return micro_ErrorFromStatus(s); } microError * -micro_destroy_endpoint(microEndpoint *ep) +micro_stop_endpoint(microEndpoint *ep) { natsStatus s = NATS_OK; natsSubscription *sub = NULL; - bool is_running = false; + bool conn_closed = natsConnection_IsClosed(ep->m->nc); if (ep == NULL) return NULL; micro_lock_endpoint(ep); sub = ep->sub; - is_running = ep->is_running; - micro_unlock_endpoint(ep); - if (!is_running) + if (ep->is_draining || conn_closed || !natsSubscription_IsValid(sub)) + { + // If stopping, _release_on_endpoint_complete will take care of + // finalizing, nothing else to do. In other cases + // _release_on_endpoint_complete has already been called. + micro_unlock_endpoint(ep); return NULL; + } + + ep->is_draining = true; + micro_unlock_endpoint(ep); - if (!natsConnection_IsClosed(ep->m->nc) && natsSubscription_IsValid(sub)) + // When the drain is complete, will release the final ref on ep. + s = natsSubscription_Drain(sub); + if (s != NATS_OK) { - // When the drain is complete, will release the final ref on ep. - s = natsSubscription_Drain(sub); - if (s != NATS_OK) - { - return microError_Wrapf(micro_ErrorFromStatus(s), - "failed to stop endpoint %s: failed to drain subscription", ep->name); - } + return microError_Wrapf(micro_ErrorFromStatus(s), + "failed to stop endpoint %s: failed to drain subscription", ep->name); } + return NULL; +} + +microError * +micro_destroy_endpoint(microEndpoint *ep) +{ + microError *err = NULL; + + if (ep == NULL) + return NULL; + + if (err = micro_stop_endpoint(ep), err != NULL) + return err; + // Release ep since it's no longer running (to compensate for the retain in // micro_start_endpoint). release_endpoint(ep); return NULL; } -microEndpoint * +static void retain_endpoint(microEndpoint *ep) { if (ep == NULL) - return ep; + return; micro_lock_endpoint(ep); ep->refs++; micro_unlock_endpoint(ep); - - return ep; } -void release_endpoint(microEndpoint *ep) +static void +release_endpoint(microEndpoint *ep) { int refs = 0; diff --git a/src/micro_error.c b/src/micro_error.c index 8f2338e01..ad0385e64 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -107,7 +107,7 @@ micro_ErrorFromStatus(natsStatus s) err = NATS_CALLOC(1, sizeof(microError) + message_len + 1); if (err == NULL) return &_errorOutOfMemory; - + err->message = (char *)(err + 1); err->status = s; memcpy(err->message, message, message_len + 1); return err; diff --git a/src/microp.h b/src/microp.h index 79418c23a..ed4426ae3 100644 --- a/src/microp.h +++ b/src/microp.h @@ -71,7 +71,7 @@ struct micro_endpoint_s // Mutex for starting/stopping the endpoint, and for updating the stats. natsMutex *endpoint_mu; int refs; - bool is_running; + bool is_draining; // The subscription for the endpoint. If NULL, the endpoint is stopped. natsSubscription *sub; @@ -105,12 +105,17 @@ struct micro_service_s // need to be protected by mutex. natsMutex *service_mu; int refs; + int num_eps_to_stop; struct micro_endpoint_s *first_ep; int num_eps; int64_t started; // UTC time expressed as number of nanoseconds since epoch. - bool is_running; + // true once the stopping of the service, i.e. draining of endpoints' + // subsriptions has started. + bool stopped; + bool connection_closed_called; + bool stopping; }; /** @@ -142,10 +147,12 @@ extern microError *micro_ErrorInvalidArg; microError *micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); microError *micro_clone_endpoint_config(microEndpointConfig **new_cfg, microEndpointConfig *cfg); microError *micro_clone_service_config(microServiceConfig **new_cfg, microServiceConfig *cfg); +void micro_decrement_endpoints_to_stop(microService *m); microError *micro_destroy_endpoint(microEndpoint *ep); void micro_free_cloned_endpoint_config(microEndpointConfig *cfg); void micro_free_cloned_service_config(microServiceConfig *cfg); void micro_free_request(microRequest *req); +void micro_increment_endpoints_to_stop(microService *m); microError *micro_init_monitoring(microService *m); microError *micro_is_error_message(natsStatus s, natsMsg *msg); bool micro_is_valid_name(const char *name); @@ -155,14 +162,15 @@ microError *micro_new_control_subject(char **newSubject, const char *verb, const microError *micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); microError *micro_new_request(microRequest **new_request, microService *m, microEndpoint *ep, natsMsg *msg); void micro_release_service(microService *m); -microService *micro_retain_service(microService *m); +void micro_retain_service(microService *m); microError *micro_start_endpoint(microEndpoint *ep); +microError *micro_stop_endpoint(microEndpoint *ep); void micro_update_last_error(microEndpoint *ep, microError *err); -static inline void micro_lock_service(microService *m) { natsMutex_Lock(m->service_mu); } -static inline void micro_unlock_service(microService *m) { natsMutex_Unlock(m->service_mu); } -static inline void micro_lock_endpoint(microEndpoint *ep) { natsMutex_Lock(ep->endpoint_mu); } -static inline void micro_unlock_endpoint(microEndpoint *ep) { natsMutex_Unlock(ep->endpoint_mu); } +static inline void micro_lock_service(microService *m) { natsMutex_Lock(m->service_mu); } +static inline void micro_unlock_service(microService *m) { natsMutex_Unlock(m->service_mu); } +static inline void micro_lock_endpoint(microEndpoint *ep) { natsMutex_Lock(ep->endpoint_mu); } +static inline void micro_unlock_endpoint(microEndpoint *ep) { natsMutex_Unlock(ep->endpoint_mu); } static inline microError *micro_strdup(char **ptr, const char *str) { diff --git a/test/test.c b/test/test.c index 6cf081bf3..9b31a7abe 100644 --- a/test/test.c +++ b/test/test.c @@ -32417,7 +32417,9 @@ test_MicroAddService(void) s = _createDefaultThreadArgsForCbTests(&arg); if (s == NATS_OK) opts = _createReconnectOptions(); - if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) + if ((opts == NULL) + || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) + || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) { FAIL("Unable to setup test for MicroConnectionEvents!"); } @@ -32527,7 +32529,10 @@ test_MicroAddService(void) microServiceInfo_Destroy(info); microService_Destroy(m); } + natsConnection_Destroy(nc); + _waitForConnClosed(&arg); + natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); _stopServer(serverPid); @@ -32573,7 +32578,9 @@ test_MicroGroups(void) s = _createDefaultThreadArgsForCbTests(&arg); if (s == NATS_OK) opts = _createReconnectOptions(); - if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) + if ((opts == NULL) + || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) + || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) { FAIL("Unable to setup test for MicroConnectionEvents!"); } @@ -32629,6 +32636,8 @@ test_MicroGroups(void) microServiceInfo_Destroy(info); microService_Destroy(m); natsConnection_Destroy(nc); + _waitForConnClosed(&arg); + natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); _stopServer(serverPid); @@ -32677,7 +32686,9 @@ test_MicroBasics(void) s = _createDefaultThreadArgsForCbTests(&arg); if (s == NATS_OK) opts = _createReconnectOptions(); - if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) + if ((opts == NULL) + || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) + || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) { FAIL("Unable to setup test for MicroConnectionEvents!"); } @@ -32849,8 +32860,9 @@ test_MicroBasics(void) microService_Destroy(svcs[i]); } NATS_FREE(svcs); - natsConnection_Destroy(nc); + _waitForConnClosed(&arg); + natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); _stopServer(serverPid); @@ -32885,9 +32897,10 @@ test_MicroStartStop(void) s = _createDefaultThreadArgsForCbTests(&arg); if (s == NATS_OK) opts = _createReconnectOptions(); - if ((opts == NULL) || - (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) || - (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) + if ((opts == NULL) + || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) + || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) + || (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) { FAIL("Unable to setup test for MicroConnectionEvents!"); } @@ -32919,11 +32932,17 @@ test_MicroStartStop(void) for (i = 0; i < NUM_BASIC_MICRO_SERVICES; i++) { - microService_Destroy(svcs[i]); + snprintf(buf, sizeof(buf), "Destroy microservice #%d: ", i); + test(buf); + testCond(NULL == microService_Destroy(svcs[i])); } NATS_FREE(svcs); + test("Destroy the connection: "); natsConnection_Destroy(nc); + _waitForConnClosed(&arg); + testCond(1); + natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); _stopServer(serverPid); @@ -33030,9 +33049,10 @@ test_MicroServiceStopsWhenServerStops(void) if (s == NATS_OK) opts = _createReconnectOptions(); - if ((opts == NULL) || - (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) || - (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) + if ((opts == NULL) + || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) + || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) + || (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) { FAIL("Unable to setup test for MicroConnectionEvents!"); } @@ -33063,8 +33083,10 @@ test_MicroServiceStopsWhenServerStops(void) testCond(microService_IsStopped(m)) microService_Destroy(m); - natsOptions_Destroy(opts); natsConnection_Destroy(nc); + _waitForConnClosed(&arg); + + natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); } @@ -33073,30 +33095,53 @@ void micro_async_error_handler(microService *m, microEndpoint *ep, natsStatus s) struct threadArg *arg = (struct threadArg*) microService_GetState(m); natsMutex_Lock(arg->m); - - // unblock the blocked sub + // release the pending test request that caused the error arg->closed = true; // set the data to verify arg->status = s; + arg->string = nats_GetLastError(NULL); natsCondition_Broadcast(arg->c); natsMutex_Unlock(arg->m); } +microError * +micro_async_error_request_handler(microRequest *req) +{ + struct threadArg *arg = microRequest_GetServiceState(req); + + natsMutex_Lock(arg->m); + + arg->msgReceived = true; + natsCondition_Signal(arg->c); + + while (!arg->closed) + natsCondition_Wait(arg->c, arg->m); + + natsMutex_Unlock(arg->m); + + return NULL; +} + static void test_MicroAsyncErrorHandler(void) { natsStatus s; + struct threadArg arg; natsConnection *nc = NULL; natsOptions *opts = NULL; natsSubscription *sub = NULL; natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - microService *m = NULL; + microService *m = NULL; + microEndpoint *ep = NULL; + microEndpointConfig ep_cfg = { + .Name = "do", + .Subject = "async_test", + .Handler = micro_async_error_request_handler, + }; microServiceConfig cfg = { .Name = "test", .Version = "1.0.0", - .DoneHandler = micro_service_done_handler, .ErrHandler = micro_async_error_handler, .State = &arg, }; @@ -33108,7 +33153,6 @@ test_MicroAsyncErrorHandler(void) s = natsOptions_Create(&opts); IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 10)); - if (s != NATS_OK) FAIL("Unable to create options for test AsyncErrHandler"); @@ -33124,12 +33168,11 @@ test_MicroAsyncErrorHandler(void) test("Test microservice is running: "); testCond(!microService_IsStopped(m)) - test("Subscribe to async_test: "); - testCond(NATS_OK == natsConnection_Subscribe(&sub, nc, "async_test", _recvTestString, (void*) &arg)); + test("Add test endpoint: "); + testCond(NULL == micro_add_endpoint(&ep, m, NULL, &ep_cfg, true)); natsMutex_Lock(arg.m); arg.status = NATS_OK; - arg.control= 7; natsMutex_Unlock(arg.m); test("Cause an error by sending too many messages: "); @@ -33143,29 +33186,16 @@ test_MicroAsyncErrorHandler(void) test("Wait for async err callback: "); natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && (arg.status != NATS_SLOW_CONSUMER)) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) - && (arg.status == NATS_SLOW_CONSUMER)); - - test("Wait for the service to stop: "); - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) + while ((s != NATS_TIMEOUT) && !arg.closed) s = natsCondition_TimedWait(arg.c, arg.m, 1000); natsMutex_Unlock(arg.m); - testCond((s == NATS_OK) && arg.done); - - test("Test microservice is not running: \n\n"); - testCond(microService_IsStopped(m)) + testCond((s == NATS_OK) && arg.closed && (arg.status == NATS_SLOW_CONSUMER)); microService_Destroy(m); natsOptions_Destroy(opts); natsSubscription_Destroy(sub); natsConnection_Destroy(nc); - _destroyDefaultThreadArgs(&arg); - _stopServer(serverPid); } From b3ee831160f5f52732265e3993e95c85667175d0 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 29 May 2023 11:51:42 -0700 Subject: [PATCH 53/85] wip --- src/conn.h | 2 +- src/micro.c | 217 ++++++++++++++++++++++++++++++++----------- src/nats.c | 140 +++++++--------------------- src/nats.h | 2 + src/natsp.h | 28 ++---- src/opts.c | 260 +++++----------------------------------------------- src/opts.h | 21 +---- test/test.c | 209 ++--------------------------------------- 8 files changed, 243 insertions(+), 636 deletions(-) diff --git a/src/conn.h b/src/conn.h index 6321f9193..41e93f402 100644 --- a/src/conn.h +++ b/src/conn.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 The NATS Authors +// Copyright 2015-2021 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/src/micro.c b/src/micro.c index d965524fb..34a9f45e7 100644 --- a/src/micro.c +++ b/src/micro.c @@ -18,7 +18,53 @@ static microError *new_service(microService **ptr, natsConnection *nc); static void free_service(microService *m); static microError *wrap_connection_event_callbacks(microService *m); -static void unwrap_connection_event_callbacks(microService *m); + +static natsMutex *service_callback_mu; +static natsHash *all_services_to_callback; // uses `microService*` as the key and the value. + +static microError * +_start_service_callbacks(microService *m) +{ + natsStatus s = NATS_OK; + + if (m == NULL) + return micro_ErrorInvalidArg; + + if (service_callback_mu == NULL) + { + IFOK(s, natsMutex_Create(&service_callback_mu)); + if (s != NATS_OK) + return micro_ErrorFromStatus(s); + } + + if (all_services_to_callback == NULL) + { + IFOK(s, natsHash_Create(&all_services_to_callback, 8)); + if (s != NATS_OK) + return micro_ErrorFromStatus(s); + } + + micro_retain_service(m); + + natsMutex_Lock(service_callback_mu); + IFOK(s, natsHash_Set(all_services_to_callback, (int64_t)m, (void *)m, NULL)); + natsMutex_Unlock(service_callback_mu); + + return micro_ErrorFromStatus(s); +} + +static void +_stop_service_callbacks(microService *m) +{ + if ((m == NULL) || (all_services_to_callback == NULL) || (service_callback_mu == NULL)) + return; + + natsMutex_Lock(service_callback_mu); + natsHash_Remove(all_services_to_callback, (int64_t)m); + natsMutex_Unlock(service_callback_mu); + + micro_release_service(m); +} microError * micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *cfg) @@ -42,13 +88,14 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c MICRO_CALL(err, micro_clone_service_config(&m->cfg, cfg)); // Wrap the connection callbacks before we subscribe to anything. - MICRO_CALL(err, wrap_connection_event_callbacks(m)) + MICRO_CALL(err, wrap_connection_event_callbacks(m)); + MICRO_CALL(err, micro_init_monitoring(m)); MICRO_CALL(err, microService_AddEndpoint(m, cfg->Endpoint)); if (err != NULL) { - micro_release_service(m); + microError_Ignore(microService_Destroy(m)); return microError_Wrapf(err, "failed to add microservice %s", cfg->Name); } @@ -162,7 +209,6 @@ microGroup_AddEndpoint(microGroup *g, microEndpointConfig *cfg) static void micro_finalize_stopping_service(microService *m) { microDoneHandler invoke_done = NULL; - bool on_connection_closed_called = false; if (m == NULL) return; @@ -174,21 +220,14 @@ static void micro_finalize_stopping_service(microService *m) return; } - on_connection_closed_called = m->connection_closed_called; m->stopped = true; m->stopping = false; if (m->cfg->DoneHandler != NULL) invoke_done = m->cfg->DoneHandler; micro_unlock_service(m); - unwrap_connection_event_callbacks(m); - - // If the connection closed callback has not been called yet, it will not be - // since we just removed it, so compensate for it by releaseing the service. - if (!on_connection_closed_called) - { - micro_release_service(m); - } + // Disable any subsequent async callbacks. + _stop_service_callbacks(m); if (invoke_done != NULL) invoke_done(m); @@ -323,7 +362,7 @@ void micro_retain_service(microService *m) micro_lock_service(m); - m->refs++; + ++(m->refs); micro_unlock_service(m); } @@ -352,7 +391,7 @@ void micro_increment_endpoints_to_stop(microService *m) micro_lock_service(m); - m->num_eps_to_stop++; + ++(m->num_eps_to_stop); micro_unlock_service(m); } @@ -453,47 +492,96 @@ void micro_free_cloned_service_config(microServiceConfig *cfg) NATS_FREE(cfg); } +static microError * +_services_for_connection(microService ***to_call, int *num_microservices, natsConnection *nc) +{ + microService *m = NULL; + microService **p = NULL; + natsHashIter iter; + int n = 0; + int i; + + natsMutex_Lock(service_callback_mu); + + natsHashIter_Init(&iter, all_services_to_callback); + while (natsHashIter_Next(&iter, NULL, (void **)&m)) + if (m->nc == nc) + n++; + natsHashIter_Done(&iter); + if (n > 0) + { + p = NATS_CALLOC(n, sizeof(microService *)); + if (p == NULL) + { + natsMutex_Unlock(service_callback_mu); + return micro_ErrorOutOfMemory; + } + + natsHashIter_Init(&iter, all_services_to_callback); + i = 0; + while (natsHashIter_Next(&iter, NULL, (void **)&m)) + { + if (m->nc == nc) + { + micro_retain_service(m); // for the callback + p[i++] = m; + } + } + natsHashIter_Done(&iter); + } + + natsMutex_Unlock(service_callback_mu); + + *to_call = p; + *num_microservices = n; + return NULL; +} + static void -on_connection_closed(natsConnection *nc, void *closure) +on_connection_closed(natsConnection *nc, void *ignored) { - microService *m = (microService *)closure; - if (m == NULL) + microService *m = NULL; + microService **to_call = NULL; + microError *err = NULL; + int n = 0; + int i; + + err = _services_for_connection(&to_call, &n, nc); + if (err != NULL) + { + microError_Ignore(err); return; + } - micro_lock_service(m); - m->connection_closed_called = true; - micro_unlock_service(m); + for (i = 0; i < n; i++) + { + m = to_call[i]; + microError_Ignore( + microService_Stop(m)); - micro_release_service(m); + micro_release_service(m); + } - microService_Stop(m); + NATS_FREE(to_call); } static void -on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *closure) +on_service_error(microService *m, const char *subject, natsStatus s) { - microService *m = (microService *)closure; microEndpoint *ep = NULL; microError *err = NULL; - bool our_subject = false; - const char *subject = NULL; - if ((m == NULL) || (sub == NULL)) + if (m == NULL) return; - subject = natsSubscription_GetSubject(sub); - micro_lock_service(m); - for (ep = m->first_ep; (!our_subject) && (ep != NULL); ep = ep->next) - { - if (micro_match_endpoint_subject(ep->subject, subject)) - { - our_subject = true; - } - } + for (ep = m->first_ep; + (ep != NULL) && !micro_match_endpoint_subject(ep->subject, subject); + ep = ep->next) + ; micro_unlock_service(m); - if (m->cfg->ErrHandler != NULL && our_subject) + if (m->cfg->ErrHandler != NULL) { (*m->cfg->ErrHandler)(m, ep, s); } @@ -506,33 +594,54 @@ on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *closure) } // TODO: Should we stop the service? The Go client does. - microService_Stop(m); + microError_Ignore(microService_Stop(m)); } -static microError * -wrap_connection_event_callbacks(microService *m) +static void +on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_used) { - natsStatus s = NATS_OK; + microService *m = NULL; + microService **to_call = NULL; + microError *err = NULL; + const char *subject = NULL; + int n = 0; + int i; - if ((m == NULL) || (m->nc == NULL) || (m->nc->opts == NULL)) - return micro_ErrorInvalidArg; + if (sub == NULL) + return; + subject = natsSubscription_GetSubject(sub); - // add an extra reference to the service for the callbacks. - micro_retain_service(m); - IFOK(s, natsOptions_addConnectionClosedCallback(m->nc->opts, on_connection_closed, m)); - IFOK(s, natsOptions_addErrorCallback(m->nc->opts, on_error, m)); + err = _services_for_connection(&to_call, &n, nc); + if (err != NULL) + { + microError_Ignore(err); + return; + } - return microError_Wrapf(micro_ErrorFromStatus(s), "failed to wrap connection event callbacks"); + for (i = 0; i < n; i++) + { + m = to_call[i]; + on_service_error(m, subject, s); + micro_release_service(m); + } + + NATS_FREE(to_call); } -static void -unwrap_connection_event_callbacks(microService *m) +static microError * +wrap_connection_event_callbacks(microService *m) { + microError *err = NULL; + if ((m == NULL) || (m->nc == NULL) || (m->nc->opts == NULL)) - return; + return micro_ErrorInvalidArg; + + // The new service must be in the list for this to work. + MICRO_CALL(err, _start_service_callbacks(m)); + MICRO_CALL(err, micro_ErrorFromStatus( + natsOptions_setMicroCallbacks(m->nc->opts, on_connection_closed, on_error))); - natsOptions_removeConnectionClosedCallback(m->nc->opts, on_connection_closed, m); - natsOptions_removeErrorCallback(m->nc->opts, on_error, m); + return microError_Wrapf(err, "failed to wrap connection event callbacks"); } microError * diff --git a/src/nats.c b/src/nats.c index a943fafb1..b26e11cfd 100644 --- a/src/nats.c +++ b/src/nats.c @@ -1,4 +1,4 @@ -// Copyright 2015-2023 The NATS Authors +// Copyright 2015-2021 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -31,7 +31,6 @@ #include "sub.h" #include "nkeys.h" #include "crypto.h" -#include "opts.h" #define WAIT_LIB_INITIALIZED \ natsMutex_Lock(gLib.lock); \ @@ -724,44 +723,14 @@ _timerThread(void *arg) natsLib_Release(); } -// _dup_callback_list clones cb. However, for a single callback it avoids an -// allocation by copying it into a pre-allocated `single`. In this case `clone` -// is set to NULL and single is returned. If cb is a list, then the entire list -// is cloned, placed into `clone`, and returned. -static nats_CallbackList* -_dup_callback_list(nats_CallbackList *single, nats_CallbackList **clone, nats_CallbackList *cb) -{ - static nats_CallbackList empty = {.next = NULL}; - - *single = empty; - *clone = NULL; - - if (cb == NULL) - return NULL; - - if (cb->next == NULL) - { - *single = *cb; - return single; - } - - // Ignore the status, return NULL on errors. - natsOptions_cloneCallbackList(clone, cb); - return *clone; -} - - static void _asyncCbsThread(void *arg) { - natsLibAsyncCbs *asyncCbs = &(gLib.asyncCbs); - natsAsyncCbInfo *cb = NULL; - natsConnection *nc = NULL; - nats_CallbackList *call = NULL; - nats_CallbackList *clone = NULL; - nats_CallbackList single = {.next=NULL}; + natsLibAsyncCbs *asyncCbs = &(gLib.asyncCbs); + natsAsyncCbInfo *cb = NULL; + natsConnection *nc = NULL; #if defined(NATS_HAS_STREAMING) - stanConnection *sc = NULL; + stanConnection *sc = NULL; #endif WAIT_LIB_INITIALIZED; @@ -787,89 +756,50 @@ _asyncCbsThread(void *arg) natsMutex_Unlock(asyncCbs->lock); nc = cb->nc; - #if defined(NATS_HAS_STREAMING) sc = cb->sc; - - // handle streaming connection callbacks - if (cb->type == ASYNC_STAN_CONN_LOST) - { - (*(sc->opts->connectionLostCB))(sc, sc->connLostErrTxt, sc->opts->connectionLostCBClosure); - } - else - { - // handle all other callbacks #endif - // Default to a single natsConnectionHandler callback. Duplicated - // callbacks (closed, error) will update the type. - call = &single; - single.type = CALLBACK_TYPE_CONN; - - // Callback handlers can be updated on a live connection, so we need to - // lock. - natsOptions_lock(nc->opts); switch (cb->type) { - case ASYNC_CLOSED: - call = _dup_callback_list(&single, &clone, nc->opts->closedCb); - break; - case ASYNC_DISCONNECTED: - call->f.conn = nc->opts->disconnectedCb; - call->closure = nc->opts->disconnectedCbClosure; - break; - case ASYNC_RECONNECTED: - call->f.conn = nc->opts->reconnectedCb; - call->closure = nc->opts->reconnectedCbClosure; - break; - case ASYNC_CONNECTED: - call->f.conn = nc->opts->connectedCb; - call->closure = nc->opts->connectedCbClosure; - break; - case ASYNC_DISCOVERED_SERVERS: - call->f.conn = nc->opts->discoveredServersCb; - call->closure = nc->opts->discoveredServersClosure; - break; - case ASYNC_LAME_DUCK_MODE: - call->f.conn = nc->opts->lameDuckCb; - call->closure = nc->opts->lameDuckClosure; - break; - case ASYNC_ERROR: - call = _dup_callback_list(&single, &clone, nc->opts->asyncErrCb); - break; - default: - call = NULL; - break; - } - natsOptions_unlock(nc->opts); - - // Invoke the callbacks - for (; call != NULL; call = call->next) - { - switch (call->type) + case ASYNC_CLOSED: + (*(nc->opts->closedCb))(nc, nc->opts->closedCbClosure); + if (nc->opts->microClosedCb != NULL) + (*(nc->opts->microClosedCb))(nc, nc->opts->closedCbClosure); + break; + case ASYNC_DISCONNECTED: + (*(nc->opts->disconnectedCb))(nc, nc->opts->disconnectedCbClosure); + break; + case ASYNC_RECONNECTED: + (*(nc->opts->reconnectedCb))(nc, nc->opts->reconnectedCbClosure); + break; + case ASYNC_CONNECTED: + (*(nc->opts->connectedCb))(nc, nc->opts->connectedCbClosure); + break; + case ASYNC_DISCOVERED_SERVERS: + (*(nc->opts->discoveredServersCb))(nc, nc->opts->discoveredServersClosure); + break; + case ASYNC_LAME_DUCK_MODE: + (*(nc->opts->lameDuckCb))(nc, nc->opts->lameDuckClosure); + break; + case ASYNC_ERROR: { - case CALLBACK_TYPE_ERROR: if (cb->errTxt != NULL) nats_setErrStatusAndTxt(cb->err, cb->errTxt); - (*call->f.err)(nc, cb->sub, cb->err, call->closure); - break; + (*(nc->opts->asyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); - case CALLBACK_TYPE_CONN: - (*call->f.conn)(nc, call->closure); - break; - - default: + if (nc->opts->microAsyncErrCb != NULL) + (*(nc->opts->microAsyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); break; } - } - - // Free anything that might have been cloned. - natsOptions_freeCallbackList(clone); - clone = NULL; - #if defined(NATS_HAS_STREAMING) - } + case ASYNC_STAN_CONN_LOST: + (*(sc->opts->connectionLostCB))(sc, sc->connLostErrTxt, sc->opts->connectionLostCBClosure); + break; #endif + default: + break; + } natsAsyncCb_Destroy(cb); nats_clearLastError(); diff --git a/src/nats.h b/src/nats.h index f6e612548..a07d0a0cd 100644 --- a/src/nats.h +++ b/src/nats.h @@ -8183,6 +8183,8 @@ microError_Code(microError *err); NATS_EXTERN void microError_Destroy(microError *err); +#define microError_Ignore(__err) microError_Destroy(__err) + /** * @brief Returns the NATS status associated with the error. * diff --git a/src/natsp.h b/src/natsp.h index f2836eb66..74b96d41f 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -196,24 +196,6 @@ typedef struct __userCreds } userCreds; -typedef enum -{ - CALLBACK_TYPE_CONN = 0, - CALLBACK_TYPE_ERROR, -} nats_CallbackType; - -typedef struct __nats_CallbackList -{ - nats_CallbackType type; - union - { - natsConnectionHandler conn; - natsErrHandler err; - } f; - void *closure; - struct __nats_CallbackList *next; -} nats_CallbackList; - struct __natsOptions { // This field must be the first (see natsOptions_clone, same if you add @@ -243,8 +225,8 @@ struct __natsOptions natsTokenHandler tokenCb; void *tokenCbClosure; - nats_CallbackList *closedCb; - nats_CallbackList *asyncErrCb; + natsConnectionHandler closedCb; + void *closedCbClosure; natsConnectionHandler disconnectedCb; void *disconnectedCbClosure; @@ -262,6 +244,12 @@ struct __natsOptions natsConnectionHandler lameDuckCb; void *lameDuckClosure; + natsErrHandler asyncErrCb; + void *asyncErrCbClosure; + + natsConnectionHandler microClosedCb; + natsErrHandler microAsyncErrCb; + int64_t pingInterval; int maxPingsOut; int maxPendingMsgs; diff --git a/src/opts.c b/src/opts.c index e7e5b499d..8955d8d7d 100644 --- a/src/opts.c +++ b/src/opts.c @@ -883,191 +883,31 @@ natsOptions_SetMaxPendingMsgs(natsOptions *opts, int maxPending) return NATS_OK; } -void -natsOptions_unlinkCallback(nats_CallbackList **prior_cb_removed, nats_CallbackList **headptr, void(*f)(void), void *closure) -{ - nats_CallbackList *this, *prev; - - if (f == NULL) - return; - - for (this = *headptr, prev = NULL; this != NULL; prev = this, this = this->next) - { - if (((void(*)(void))(this->f.conn) == f) && (this->closure == closure)) - { - *prior_cb_removed = this; - if (prev == NULL) - { - *headptr = this->next; - } - else - { - prev->next = this->next; - } - - return; - } - } -} - -void -natsOptions_freeCallbackList(nats_CallbackList *cb) -{ - nats_CallbackList *next = NULL; - - while (cb != NULL) - { - next = cb->next; - NATS_FREE(cb); - cb = next; - } -} - natsStatus -natsOptions_cloneCallbackList(nats_CallbackList **clone, nats_CallbackList *this) -{ - nats_CallbackList *prev = NULL; - nats_CallbackList *head = NULL; - nats_CallbackList *tail = NULL; - - for (; this != NULL; this = this->next) - { - tail = NATS_CALLOC(1, sizeof(nats_CallbackList)); - if (tail == NULL) - { - natsOptions_freeCallbackList(head); - return nats_setDefaultError(NATS_NO_MEMORY); - } - *tail = *this; - tail->next = NULL; - - if (prev == NULL) - { - head = tail; - } - else - { - prev->next = tail; - } - prev = tail; - } - - *clone = head; - return NATS_OK; -} - -natsStatus -natsOptions_addConnectionClosedCallback(natsOptions *opts, natsConnectionHandler f, void *closure) -{ - natsStatus s = NATS_OK; - nats_CallbackList *cb = NULL; - nats_CallbackList *replaced = NULL; - - cb = NATS_CALLOC(1, sizeof(nats_CallbackList)); - if (cb == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - cb->type = CALLBACK_TYPE_CONN; - cb->f.conn = f; - cb->closure = closure; - - LOCK_AND_CHECK_OPTIONS(opts, 0); - - // unlink if already in the list. - natsOptions_unlinkCallback(&replaced, &opts->closedCb, (void(*)(void))f, closure); - - // add at the head. - cb->next = opts->closedCb; - opts->closedCb = cb; - - UNLOCK_OPTS(opts); - - NATS_FREE(replaced); - - return s; -} - -natsStatus -natsOptions_addErrorCallback(natsOptions *opts, natsErrHandler f, void *closure) +natsOptions_SetErrorHandler(natsOptions *opts, natsErrHandler errHandler, + void *closure) { - natsStatus s = NATS_OK; - nats_CallbackList *cb = NULL; - nats_CallbackList *replaced = NULL; - - cb = NATS_CALLOC(1, sizeof(nats_CallbackList)); - if (cb == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - cb->type = CALLBACK_TYPE_ERROR; - cb->f.err = f; - cb->closure = closure; - LOCK_AND_CHECK_OPTIONS(opts, 0); - // unlink if already in the list. - natsOptions_unlinkCallback(&replaced, &opts->asyncErrCb, (void(*)(void))f, closure); + opts->asyncErrCb = errHandler; + opts->asyncErrCbClosure = closure; - // add at the head. - cb->next = opts->asyncErrCb; - opts->asyncErrCb = cb; + if (opts->asyncErrCb == NULL) + opts->asyncErrCb = natsConn_defaultErrHandler; UNLOCK_OPTS(opts); - NATS_FREE(replaced); - return s; -} - -void natsOptions_removeConnectionClosedCallback(natsOptions *opts, natsConnectionHandler f, void *closure) -{ - nats_CallbackList *removed = NULL; - - natsOptions_lock(opts); - - natsOptions_unlinkCallback(&removed, &opts->closedCb, (void(*)(void))f, closure); - - natsOptions_unlock(opts); - - NATS_FREE(removed); -} - -void natsOptions_removeErrorCallback(natsOptions *opts, natsErrHandler f, void *closure) -{ - nats_CallbackList *removed = NULL; - - natsOptions_lock(opts); - - natsOptions_unlinkCallback(&removed, &opts->asyncErrCb, (void(*)(void))f, closure); - - natsOptions_unlock(opts); - - NATS_FREE(removed); + return NATS_OK; } natsStatus -natsOptions_SetErrorHandler(natsOptions *opts, natsErrHandler errHandler, - void *closure) +natsOptions_SetClosedCB(natsOptions *opts, natsConnectionHandler closedCb, + void *closure) { - if (errHandler == NULL) - { - errHandler = natsConn_defaultErrHandler; - closure = NULL; - } - LOCK_AND_CHECK_OPTIONS(opts, 0); - if (opts->asyncErrCb == NULL) - { - UNLOCK_OPTS(opts); - return natsOptions_addErrorCallback(opts, errHandler, closure); - } - if (opts->asyncErrCb->next != NULL) - { - // can't allow overriding a list of callbacks with a single one. - UNLOCK_OPTS(opts); - return nats_setDefaultError(NATS_ILLEGAL_STATE); - } - - opts->asyncErrCb->type = CALLBACK_TYPE_ERROR; - opts->asyncErrCb->f.err = errHandler; - opts->asyncErrCb->closure = closure; + opts->closedCb = closedCb; + opts->closedCbClosure = closure; UNLOCK_OPTS(opts); @@ -1075,40 +915,15 @@ natsOptions_SetErrorHandler(natsOptions *opts, natsErrHandler errHandler, } natsStatus -natsOptions_SetClosedCB(natsOptions *opts, natsConnectionHandler closedCb, - void *closure) +natsOptions_setMicroCallbacks(natsOptions *opts, natsConnectionHandler closed, natsErrHandler errHandler) { - nats_CallbackList *to_free = NULL; - LOCK_AND_CHECK_OPTIONS(opts, 0); - if ((opts->closedCb != NULL) && (opts->closedCb->next != NULL)) - { - // can't allow overriding a list of callbacks with a single one. - UNLOCK_OPTS(opts); - return nats_setDefaultError(NATS_ILLEGAL_STATE); - } - - if (closedCb == NULL) - { - to_free = opts->closedCb; - opts->closedCb = NULL; - } - else - { - if (opts->closedCb == NULL) - { - UNLOCK_OPTS(opts); - return natsOptions_addConnectionClosedCallback(opts, closedCb, closure); - } - opts->closedCb->type = CALLBACK_TYPE_CONN; - opts->closedCb->f.conn = closedCb; - opts->closedCb->closure = closure; - } + opts->microClosedCb = closed; + opts->microAsyncErrCb = errHandler; UNLOCK_OPTS(opts); - NATS_FREE(to_free); return NATS_OK; } @@ -1659,9 +1474,6 @@ _freeOptions(natsOptions *opts) if (opts == NULL) return; - natsOptions_freeCallbackList(opts->closedCb); - natsOptions_freeCallbackList(opts->asyncErrCb); - NATS_FREE(opts->url); NATS_FREE(opts->name); _freeServers(opts); @@ -1676,11 +1488,11 @@ _freeOptions(natsOptions *opts) NATS_FREE(opts); } -static natsStatus -_create_options(natsOptions **newOpts) +natsStatus +natsOptions_Create(natsOptions **newOpts) { - natsStatus s = NATS_OK; - natsOptions *opts = NULL; + natsStatus s; + natsOptions *opts = NULL; // Ensure the library is loaded s = nats_Open(-1); @@ -1697,27 +1509,6 @@ _create_options(natsOptions **newOpts) return NATS_UPDATE_ERR_STACK(NATS_NO_MEMORY); } - *newOpts = opts; - return NATS_OK; -} - -natsStatus -natsOptions_Create(natsOptions **newOpts) -{ - natsStatus s = NATS_OK; - natsOptions *opts = NULL; - nats_CallbackList *defaultErrorCb = NULL; - - defaultErrorCb = (nats_CallbackList*) NATS_CALLOC(1, sizeof(nats_CallbackList)); - if (defaultErrorCb == NULL) - return nats_setDefaultError(NATS_NO_MEMORY); - defaultErrorCb->type = CALLBACK_TYPE_ERROR; - defaultErrorCb->f.err = natsConn_defaultErrHandler; - - s = _create_options(&opts); - if (s != NATS_OK) - return s; - opts->allowReconnect = true; opts->secure = false; opts->maxReconnect = NATS_OPTS_DEFAULT_MAX_RECONNECT; @@ -1732,7 +1523,7 @@ natsOptions_Create(natsOptions **newOpts) opts->reconnectBufSize = NATS_OPTS_DEFAULT_RECONNECT_BUF_SIZE; opts->reconnectJitter = NATS_OPTS_DEFAULT_RECONNECT_JITTER; opts->reconnectJitterTLS = NATS_OPTS_DEFAULT_RECONNECT_JITTER_TLS; - opts->asyncErrCb = defaultErrorCb; + opts->asyncErrCb = natsConn_defaultErrHandler; *newOpts = opts; @@ -1746,13 +1537,13 @@ natsOptions_clone(natsOptions *opts) natsOptions *cloned = NULL; int muSize; - if ((s = _create_options(&cloned)) != NATS_OK) + if ((s = natsOptions_Create(&cloned)) != NATS_OK) { NATS_UPDATE_ERR_STACK(s); return NULL; } - natsOptions_lock(opts); + natsMutex_Lock(opts->mu); muSize = sizeof(cloned->mu); @@ -1773,17 +1564,12 @@ natsOptions_clone(natsOptions *opts) cloned->nkey = NULL; cloned->userCreds = NULL; cloned->inboxPfx = NULL; - cloned->closedCb = NULL; - cloned->asyncErrCb = NULL; - - IFOK(s, natsOptions_cloneCallbackList(&(cloned->closedCb), opts->closedCb)); - IFOK(s, natsOptions_cloneCallbackList(&(cloned->asyncErrCb), opts->asyncErrCb)); // Also, set the number of servers count to 0, until we update // it (if necessary) when calling SetServers. cloned->serversCount = 0; - if ((s == NATS_OK) && (opts->name != NULL)) + if (opts->name != NULL) s = natsOptions_SetName(cloned, opts->name); if ((s == NATS_OK) && (opts->url != NULL)) @@ -1834,7 +1620,7 @@ natsOptions_clone(natsOptions *opts) NATS_UPDATE_ERR_STACK(s); } - natsOptions_unlock(opts); + natsMutex_Unlock(opts->mu); return cloned; } diff --git a/src/opts.h b/src/opts.h index f9d672cfe..3955634e1 100644 --- a/src/opts.h +++ b/src/opts.h @@ -49,25 +49,6 @@ natsOptions* natsOptions_clone(natsOptions *opts); natsStatus -natsOptions_addConnectionClosedCallback(natsOptions *opts, natsConnectionHandler f, void *closure); - -void -natsOptions_removeConnectionClosedCallback(natsOptions *opts, natsConnectionHandler f, void *closure); - -natsStatus -natsOptions_addErrorCallback(natsOptions *opts, natsErrHandler f, void *closure); - -void -natsOptions_removeErrorCallback(natsOptions *opts, natsErrHandler f, void *closure); - -void -natsOptions_freeCallbackList(nats_CallbackList *cb); - -natsStatus -natsOptions_cloneCallbackList(nats_CallbackList **clone, nats_CallbackList *this); - -// for testing -void -natsOptions_unlinkCallback(nats_CallbackList **prior_cb_removed, nats_CallbackList **headptr, void (*f)(void), void *closure); +natsOptions_setMicroCallbacks(natsOptions *opts, natsConnectionHandler closedCb, natsErrHandler errCb); #endif /* OPTS_H_ */ diff --git a/test/test.c b/test/test.c index 9b31a7abe..8b175a3c0 100644 --- a/test/test.c +++ b/test/test.c @@ -2554,55 +2554,6 @@ _dummySigCb(char **customErrTxt, unsigned char **psig, int *sigLen, const char* return NATS_OK; } -static void -test_natsOptionsUnlinkCallback(void) -{ - nats_CallbackList cb1 = { - .f.conn = _dummyConnHandler, - .closure = (void*)1, - }; - nats_CallbackList cb2 = { - .f.err = _dummyErrHandler, - .closure = (void*)2, - }; - nats_CallbackList cb3 = { - .f.conn = _dummyConnHandler, - .closure = (void*)3, - }; - - test("the first only one: "); - nats_CallbackList test1[3] = {cb1}; - nats_CallbackList *head = &test1[0]; - nats_CallbackList *to_remove = NULL; - natsOptions_unlinkCallback(&to_remove, &head, (void(*)(void))cb1.f.conn, cb1.closure); - testCond((to_remove == &test1[0]) - && (head == NULL)); - - test("Unlink the first out of 3: "); - nats_CallbackList test2[3] = {cb1, cb2, cb3}; - head = &test2[0]; - to_remove = NULL; - test2[0].next = &test2[1]; - test2[1].next = &test2[2]; - natsOptions_unlinkCallback(&to_remove, &head, (void(*)(void))cb1.f.err, cb1.closure); - testCond((to_remove == &test2[0]) - && (head == &test2[1]) - && (test2[1].next == &test2[2]) - && (test2[2].next == NULL)); - - test("Unlink the middle out of 3: "); - nats_CallbackList testMiddle[3] = {cb1, cb2, cb3}; - head = &testMiddle[0]; - to_remove = NULL; - testMiddle[0].next = &testMiddle[1]; - testMiddle[1].next = &testMiddle[2]; - natsOptions_unlinkCallback(&to_remove, &head, (void(*)(void))cb2.f.err, cb2.closure); - testCond((to_remove == &testMiddle[1]) - && (head == &testMiddle[0]) - && (testMiddle[1].next == &testMiddle[2]) - && (testMiddle[2].next == NULL)); -} - static void test_natsOptions(void) { @@ -2766,7 +2717,8 @@ test_natsOptions(void) s = natsOptions_SetRetryOnFailedConnect(opts, false, _dummyConnHandler, (void*)1); testCond((s == NATS_OK) && (opts->retryOnFailedConnect == false) - && (opts->connectedCb == NULL)); + && (opts->connectedCb == NULL) + && (opts->connectedCbClosure == NULL)); test("Set Secure: "); s = natsOptions_SetSecure(opts, true); @@ -2859,23 +2811,15 @@ test_natsOptions(void) test("Set Error Handler: "); s = natsOptions_SetErrorHandler(opts, _dummyErrHandler, NULL); - testCond((s == NATS_OK) && (opts->asyncErrCb != NULL)&& (opts->asyncErrCb->f.err == _dummyErrHandler)); - - test("Set Error Handler 2nd time: "); - s = natsOptions_SetErrorHandler(opts, _dummyErrHandler, NULL); - testCond((s == NATS_OK) && (opts->asyncErrCb != NULL)&& (opts->asyncErrCb->f.err == _dummyErrHandler)); + testCond((s == NATS_OK) && (opts->asyncErrCb == _dummyErrHandler)); test("Remove Error Handler: "); s = natsOptions_SetErrorHandler(opts, NULL, NULL); - testCond((s == NATS_OK) && (opts->asyncErrCb != NULL)&& (opts->asyncErrCb->f.err == natsConn_defaultErrHandler)); + testCond((s == NATS_OK) && (opts->asyncErrCb == natsConn_defaultErrHandler)); test("Set ClosedCB: "); s = natsOptions_SetClosedCB(opts, _dummyConnHandler, NULL); - testCond((s == NATS_OK) && (opts->closedCb != NULL)&& (opts->closedCb->f.conn == _dummyConnHandler)); - - test("Set ClosedCB 2nd time: "); - s = natsOptions_SetClosedCB(opts, _dummyConnHandler, NULL); - testCond((s == NATS_OK) && (opts->closedCb != NULL)&& (opts->closedCb->f.conn == _dummyConnHandler)); + testCond((s == NATS_OK) && (opts->closedCb == _dummyConnHandler)); test("Remove ClosedCB: "); s = natsOptions_SetClosedCB(opts, NULL, NULL); @@ -3111,8 +3055,7 @@ test_natsOptions(void) if (cloned == NULL) s = NATS_NO_MEMORY; else if ((cloned->pingInterval != 3000) - || (cloned->asyncErrCb == NULL) - || (cloned->asyncErrCb->f.err != _dummyErrHandler) + || (cloned->asyncErrCb != _dummyErrHandler) || (cloned->name == NULL) || (strcmp(cloned->name, "name") != 0) || (cloned->url == NULL) @@ -3156,11 +3099,9 @@ test_natsOptions(void) s = natsOptions_Create(&opts); if (s == NATS_OK) cloned = natsOptions_clone(opts); - testCond((s == NATS_OK) - && (cloned != NULL) - && (cloned->asyncErrCb != NULL) - && (cloned->asyncErrCb->f.err == natsConn_defaultErrHandler) - && (cloned->asyncErrCb->closure == NULL)); + testCond((s == NATS_OK) && (cloned != NULL) + && (cloned->asyncErrCb == natsConn_defaultErrHandler) + && (cloned->asyncErrCbClosure == NULL)); natsOptions_Destroy(cloned); natsOptions_Destroy(opts); } @@ -8592,54 +8533,6 @@ test_ConnClosedCB(void) _stopServer(serverPid); } -static void -test_AddConnClosedCB(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s == NATS_OK) - opts = _createReconnectOptions(); - - if ((opts == NULL) - || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) - { - FAIL("Unable to setup test for AddConnClosedCB!"); - } - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("connect to server: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - test("set the connection closed handler in-flight"); - s = natsOptions_addConnectionClosedCallback(nc->opts, _closedCb, (void*)&arg); - testCond(s == NATS_OK); - - test("Test connection closed CB invoked: "); - natsConnection_Close(nc); - - natsMutex_Lock(arg.m); - s = NATS_OK; - while ((s != NATS_TIMEOUT) && !arg.closed) - s = natsCondition_TimedWait(arg.c, arg.m, 1000); - natsMutex_Unlock(arg.m); - - testCond((s == NATS_OK) && arg.closed); - - natsOptions_Destroy(opts); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - _stopServer(serverPid); -} - static void test_CloseDisconnectedCB(void) { @@ -14481,85 +14374,6 @@ test_AsyncErrHandler(void) _stopServer(serverPid); } -static void -test_AddAsyncErrHandler(void) -{ - natsStatus s; - natsConnection *nc = NULL; - natsOptions *opts = NULL; - natsSubscription *sub = NULL; - natsPid serverPid = NATS_INVALID_PID; - struct threadArg arg; - - s = _createDefaultThreadArgsForCbTests(&arg); - if (s != NATS_OK) - FAIL("Unable to setup test!"); - - arg.status = NATS_OK; - arg.control= 7; - - s = natsOptions_Create(&opts); - IFOK(s, natsOptions_SetURL(opts, NATS_DEFAULT_URL)); - IFOK(s, natsOptions_SetMaxPendingMsgs(opts, 10)); - - if (s != NATS_OK) - FAIL("Unable to create options for test AsyncErrHandler"); - - serverPid = _startServer("nats://127.0.0.1:4222", NULL, true); - CHECK_SERVER_STARTED(serverPid); - - test("connect: "); - s = natsConnection_Connect(&nc, opts); - testCond(s == NATS_OK); - - test("subscribe: "); - s = natsConnection_Subscribe(&sub, nc, "async_test", _recvTestString, (void*) &arg); - testCond(s == NATS_OK); - - natsMutex_Lock(arg.m); - arg.sub = sub; - natsMutex_Unlock(arg.m); - - test("send messages: "); - for (int i=0; - (s == NATS_OK) && (i < (opts->maxPendingMsgs)); i++) - { - s = natsConnection_PublishString(nc, "async_test", "hello"); - } - testCond(s == NATS_OK); - - test("add the error handler in-flight: ") - s = natsOptions_addErrorCallback(nc->opts, _asyncErrCb, (void*)&arg); - - for (int i=0; - (s == NATS_OK) && (i < 100); i++) - { - s = natsConnection_PublishString(nc, "async_test", "hello"); - } - IFOK(s, natsConnection_Flush(nc)); - testCond(s == NATS_OK); - - // Wait for async err callback - natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) - s = natsCondition_TimedWait(arg.c, arg.m, 2000); - natsMutex_Unlock(arg.m); - - test("Aync fired properly, and all checks are good: "); - testCond((s == NATS_OK) - && arg.done - && arg.closed - && (arg.status == NATS_OK)); - - natsOptions_Destroy(opts); - natsSubscription_Destroy(sub); - natsConnection_Destroy(nc); - - _destroyDefaultThreadArgs(&arg); - - _stopServer(serverPid); -} - static void _responseCb(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) @@ -32643,7 +32457,7 @@ test_MicroGroups(void) _stopServer(serverPid); } -#define NUM_BASIC_MICRO_SERVICES 5 +#define NUM_BASIC_MICRO_SERVICES 1 static void test_MicroBasics(void) @@ -35445,7 +35259,6 @@ static testInfo allTests[] = {"natsHashing", test_natsHashing}, {"natsStrHash", test_natsStrHash}, {"natsInbox", test_natsInbox}, - {"natsOptionsUnlinkCallback", test_natsOptionsUnlinkCallback}, {"natsOptions", test_natsOptions}, {"natsSock_ConnectTcp", test_natsSock_ConnectTcp}, {"natsSock_ShuffleIPs", test_natsSock_ShuffleIPs}, @@ -35500,7 +35313,6 @@ static testInfo allTests[] = {"ConnectionToWithNullURLs", test_ConnectionToWithNullURLs}, {"ConnectionStatus", test_ConnectionStatus}, {"ConnClosedCB", test_ConnClosedCB}, - {"AddConnClosedCB", test_AddConnClosedCB}, {"CloseDisconnectedCB", test_CloseDisconnectedCB}, {"ServerStopDisconnectedCB", test_ServerStopDisconnectedCB}, {"ClosedConnections", test_ClosedConnections}, @@ -35583,7 +35395,6 @@ static testInfo allTests[] = {"SyncSubscriptionPending", test_SyncSubscriptionPending}, {"SyncSubscriptionPendingDrain", test_SyncSubscriptionPendingDrain}, {"AsyncErrHandler", test_AsyncErrHandler}, - {"AddAsyncErrHandler", test_AddAsyncErrHandler}, {"AsyncSubscriberStarvation", test_AsyncSubscriberStarvation}, {"AsyncSubscriberOnClose", test_AsyncSubscriberOnClose}, {"NextMsgCallOnAsyncSub", test_NextMsgCallOnAsyncSub}, From 7d732fb18be9bbdcb05e91cc4109a90bc94f92ed Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 29 May 2023 12:18:53 -0700 Subject: [PATCH 54/85] wip --- src/micro.c | 8 +- src/micro_endpoint.c | 6 + src/micro_monitoring.c | 6 +- test/list.txt | 289 -------------------- test/test.c | 598 ++++++++++++++++++++--------------------- 5 files changed, 315 insertions(+), 592 deletions(-) diff --git a/src/micro.c b/src/micro.c index 34a9f45e7..fef797792 100644 --- a/src/micro.c +++ b/src/micro.c @@ -377,6 +377,7 @@ void micro_release_service(microService *m) micro_lock_service(m); refs = --(m->refs); + printf("<>/<> release_service--: %d\n", refs); micro_unlock_service(m); @@ -553,6 +554,8 @@ on_connection_closed(natsConnection *nc, void *ignored) return; } + printf("<>/<> on_connection_closed: %d services to call\n", n); + for (i = 0; i < n; i++) { m = to_call[i]; @@ -581,8 +584,9 @@ on_service_error(microService *m, const char *subject, natsStatus s) ; micro_unlock_service(m); - if (m->cfg->ErrHandler != NULL) + if ((ep != NULL) && (m->cfg->ErrHandler != NULL)) { + printf("<>/<> on_service_error: calling for %s\n", ep->name); (*m->cfg->ErrHandler)(m, ep, s); } @@ -618,6 +622,8 @@ on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_used return; } + printf("<>/<> on_error: %d services to call\n", n); + for (i = 0; i < n; i++) { m = to_call[i]; diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index c790d5b2a..16245e8f0 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -170,6 +170,8 @@ micro_destroy_endpoint(microEndpoint *ep) if (ep == NULL) return NULL; + printf("<>/<> micro_destroy_endpoint: %s\n", ep->name); + if (err = micro_stop_endpoint(ep), err != NULL) return err; @@ -204,6 +206,8 @@ release_endpoint(microEndpoint *ep) refs = --(ep->refs); + printf("<>/<> release_endpoint--: %s: %d\n", ep->name, refs); + micro_unlock_endpoint(ep); if (refs == 0) @@ -215,6 +219,8 @@ void free_endpoint(microEndpoint *ep) if (ep == NULL) return; + printf("<>/<> free_endpoint: %s\n", ep->name); + micro_release_service(ep->m); NATS_FREE(ep->name); NATS_FREE(ep->subject); diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index b79b53377..7f8f637ff 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -34,9 +34,9 @@ microError * micro_init_monitoring(microService *m) { microError *err = NULL; - MICRO_CALL(err, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); - MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); - MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); + // MICRO_CALL(err, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); + // MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); + // MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); return err; } diff --git a/test/list.txt b/test/list.txt index dde363ef3..901655645 100644 --- a/test/list.txt +++ b/test/list.txt @@ -1,265 +1,3 @@ -Version -VersionMatchesTag -OpenCloseAndWait -natsNowAndSleep -natsAllocSprintf -natsStrCaseStr -natsSnprintf -natsBuffer -natsParseInt64 -natsParseControl -natsNormalizeErr -natsMutex -natsThread -natsCondition -natsTimer -natsUrl -natsCreateStringFromBuffer -natsHash -natsHashing -natsStrHash -natsInbox -natsOptionsUnlinkCallback -natsOptions -natsSock_ConnectTcp -natsSock_ShuffleIPs -natsSock_IPOrder -natsSock_ReadLine -natsJSON -natsEncodeTimeUTC -natsErrWithLongText -natsErrStackMoreThanMaxFrames -natsMsg -natsBase32 -natsBase64 -natsCRC16 -natsKeys -natsReadFile -natsGetJWTOrSeed -natsHostIsIP -natsWaitReady -natsSign -HeadersLift -HeadersAPIs -MsgIsJSControl -SrvVersionAtLeast -ReconnectServerStats -ParseStateReconnectFunctionality -ServersRandomize -SelectNextServer -ParserPing -ParserErr -ParserOK -ParseINFO -ParserShouldFail -ParserSplitMsg -ProcessMsgArgs -LibMsgDelivery -AsyncINFO -RequestPool -NoFlusherIfSendAsapOption -HeadersAndSubPendingBytes -DefaultConnection -SimplifiedURLs -IPResolutionOrder -UseDefaultURLIfNoServerSpecified -ConnectToWithMultipleURLs -ConnectionWithNULLOptions -ConnectionToWithNullURLs -ConnectionStatus -ConnClosedCB -AddConnClosedCB -CloseDisconnectedCB -ServerStopDisconnectedCB -ClosedConnections -ConnectVerboseOption -ReconnectThreadLeak -ReconnectTotalTime -ReconnectDisallowedFlags -ReconnectAllowedFlags -ConnCloseBreaksReconnectLoop -BasicReconnectFunctionality -ExtendedReconnectFunctionality -QueueSubsOnReconnect -IsClosed -IsReconnectingAndStatus -ReconnectBufSize -RetryOnFailedConnect -NoPartialOnReconnect -ReconnectFailsPendingRequests -ErrOnConnectAndDeadlock -ErrOnMaxPayloadLimit -Auth -AuthFailNoDisconnectCB -AuthToken -AuthTokenHandler -PermViolation -AuthViolation -AuthenticationExpired -AuthenticationExpiredReconnect -ConnectedServer -MultipleClose -SimplePublish -SimplePublishNoData -PublishMsg -InvalidSubsArgs -AsyncSubscribe -AsyncSubscribeTimeout -SyncSubscribe -PubSubWithReply -NoResponders -Flush -ConnCloseDoesFlush -QueueSubscriber -ReplyArg -SyncReplyArg -Unsubscribe -DoubleUnsubscribe -SubRemovedWhileProcessingMsg -RequestTimeout -Request -RequestNoBody -RequestMuxWithMappedSubject -OldRequest -SimultaneousRequests -RequestClose -CustomInbox -MessagePadding -FlushInCb -ReleaseFlush -FlushErrOnDisconnect -Inbox -Stats -BadSubject -SubBadSubjectAndQueueNames -ClientAsyncAutoUnsub -ClientSyncAutoUnsub -ClientAutoUnsubAndReconnect -AutoUnsubNoUnsubOnDestroy -NextMsgOnClosedSub -CloseSubRelease -IsValidSubscriber -SlowSubscriber -SlowAsyncSubscriber -SlowConsumerCb -PendingLimitsDeliveredAndDropped -PendingLimitsWithSyncSub -AsyncSubscriptionPending -AsyncSubscriptionPendingDrain -SyncSubscriptionPending -SyncSubscriptionPendingDrain -AsyncErrHandler -AddAsyncErrHandler -AsyncSubscriberStarvation -AsyncSubscriberOnClose -NextMsgCallOnAsyncSub -SubOnComplete -GetLastError -StaleConnection -ServerErrorClosesConnection -NoEcho -NoEchoOldServer -DrainSub -DrainSubStops -DrainSubRaceOnAutoUnsub -DrainSubNotResentOnReconnect -DrainConn -NoDoubleCloseCbOnDrain -GetClientID -GetClientIP -GetRTT -GetLocalIPAndPort -UserCredsCallbacks -UserCredsFromFiles -UserCredsFromMemory -NKey -NKeyFromSeed -ConnSign -WriteDeadline -HeadersNotSupported -HeadersBasic -MsgsFilter -EventLoop -EventLoopRetryOnFailedConnect -EventLoopTLS -SSLBasic -SSLVerify -SSLCAFromMemory -SSLCertAndKeyFromMemory -SSLVerifyHostname -SSLSkipServerVerification -SSLCiphers -SSLMultithreads -SSLConnectVerboseOption -SSLSocketLeakEventLoop -SSLReconnectWithAuthError -ServersOption -AuthServers -AuthFailToReconnect -ReconnectWithTokenHandler -BasicClusterReconnect -HotSpotReconnect -ProperReconnectDelay -ProperFalloutAfterMaxAttempts -StopReconnectAfterTwoAuthErr -TimeoutOnNoServer -PingReconnect -GetServers -GetDiscoveredServers -DiscoveredServersCb -IgnoreDiscoveredServers -INFOAfterFirstPONGisProcessedOK -ServerPoolUpdatedOnClusterUpdate -ReconnectJitter -CustomReconnectDelay -LameDuckMode -ReconnectImplicitUserInfo -JetStreamUnmarshalAccInfo -JetStreamUnmarshalStreamState -JetStreamUnmarshalStreamCfg -JetStreamUnmarshalStreamInfo -JetStreamMarshalStreamCfg -JetStreamUnmarshalConsumerInfo -JetStreamContext -JetStreamDomain -JetStreamMgtStreams -JetStreamMgtConsumers -JetStreamPublish -JetStreamPublishAsync -JetStreamPublishAckHandler -JetStreamSubscribe -JetStreamSubscribeSync -JetStreamSubscribeConfigCheck -JetStreamSubscribeIdleHeartbeat -JetStreamSubscribeFlowControl -JetStreamSubscribePull -JetStreamSubscribeHeadersOnly -JetStreamOrderedCons -JetStreamOrderedConsWithErrors -JetStreamOrderedConsAutoUnsub -JetStreamOrderedConsSrvRestart -JetStreamSubscribeWithFWC -JetStreamStreamsSealAndRollup -JetStreamGetMsgAndLastMsg -JetStreamConvertDirectMsg -JetStreamDirectGetMsg -JetStreamNakWithDelay -JetStreamBackOffRedeliveries -JetStreamInfoWithSubjects -JetStreamInfoAlternates -KeyValueManager -KeyValueBasics -KeyValueWatch -KeyValueHistory -KeyValueKeys -KeyValueDeleteVsPurge -KeyValueDeleteTombstones -KeyValueDeleteMarkerThreshold -KeyValueCrossAccount -KeyValueDiscardOldToNew -KeyValueRePublish -KeyValueMirrorDirectGet -KeyValueMirrorCrossDomains MicroMatchEndpointSubject MicroAddService MicroGroups @@ -268,30 +6,3 @@ MicroStartStop MicroServiceStopsOnClosedConn MicroServiceStopsWhenServerStops MicroAsyncErrorHandler -StanPBufAllocator -StanConnOptions -StanSubOptions -StanMsg -StanServerNotReachable -StanBasicConnect -StanConnectError -StanBasicPublish -StanBasicPublishAsync -StanPublishTimeout -StanPublishMaxAcksInflight -StanBasicSubscription -StanSubscriptionCloseAndUnsub -StanDurableSubscription -StanBasicQueueSubscription -StanDurableQueueSubscription -StanCheckReceivedMsg -StanSubscriptionAckMsg -StanPings -StanPingsNoResponder -StanConnectionLostHandlerNotSet -StanPingsUnblockPublishCalls -StanGetNATSConnection -StanNoRetryOnFailedConnect -StanInternalSubsNotPooled -StanSubOnComplete -StanSubTimeout diff --git a/test/test.c b/test/test.c index 8b175a3c0..f0241768d 100644 --- a/test/test.c +++ b/test/test.c @@ -35238,278 +35238,278 @@ typedef struct __testInfo static testInfo allTests[] = { // Building blocks - {"Version", test_Version}, - {"VersionMatchesTag", test_VersionMatchesTag}, - {"OpenCloseAndWait", test_OpenCloseAndWait}, - {"natsNowAndSleep", test_natsNowAndSleep}, - {"natsAllocSprintf", test_natsAllocSprintf}, - {"natsStrCaseStr", test_natsStrCaseStr}, - {"natsSnprintf", test_natsSnprintf}, - {"natsBuffer", test_natsBuffer}, - {"natsParseInt64", test_natsParseInt64}, - {"natsParseControl", test_natsParseControl}, - {"natsNormalizeErr", test_natsNormalizeErr}, - {"natsMutex", test_natsMutex}, - {"natsThread", test_natsThread}, - {"natsCondition", test_natsCondition}, - {"natsTimer", test_natsTimer}, - {"natsUrl", test_natsUrl}, - {"natsCreateStringFromBuffer", test_natsCreateStringFromBuffer}, - {"natsHash", test_natsHash}, - {"natsHashing", test_natsHashing}, - {"natsStrHash", test_natsStrHash}, - {"natsInbox", test_natsInbox}, - {"natsOptions", test_natsOptions}, - {"natsSock_ConnectTcp", test_natsSock_ConnectTcp}, - {"natsSock_ShuffleIPs", test_natsSock_ShuffleIPs}, - {"natsSock_IPOrder", test_natsSock_IPOrder}, - {"natsSock_ReadLine", test_natsSock_ReadLine}, - {"natsJSON", test_natsJSON}, - {"natsEncodeTimeUTC", test_natsEncodeTimeUTC}, - {"natsErrWithLongText", test_natsErrWithLongText}, - {"natsErrStackMoreThanMaxFrames", test_natsErrStackMoreThanMaxFrames}, - {"natsMsg", test_natsMsg}, - {"natsBase32", test_natsBase32Decode}, - {"natsBase64", test_natsBase64Encode}, - {"natsCRC16", test_natsCRC16}, - {"natsKeys", test_natsKeys}, - {"natsReadFile", test_natsReadFile}, - {"natsGetJWTOrSeed", test_natsGetJWTOrSeed}, - {"natsHostIsIP", test_natsHostIsIP}, - {"natsWaitReady", test_natsWaitReady}, - {"natsSign", test_natsSign}, - {"HeadersLift", test_natsMsgHeadersLift}, - {"HeadersAPIs", test_natsMsgHeaderAPIs}, - {"MsgIsJSControl", test_natsMsgIsJSCtrl}, - {"SrvVersionAtLeast", test_natsSrvVersionAtLeast}, - - // Package Level Tests - - {"ReconnectServerStats", test_ReconnectServerStats}, - {"ParseStateReconnectFunctionality",test_ParseStateReconnectFunctionality}, - {"ServersRandomize", test_ServersRandomize}, - {"SelectNextServer", test_SelectNextServer}, - {"ParserPing", test_ParserPing}, - {"ParserErr", test_ParserErr}, - {"ParserOK", test_ParserOK}, - {"ParseINFO", test_ParseINFO}, - {"ParserShouldFail", test_ParserShouldFail}, - {"ParserSplitMsg", test_ParserSplitMsg}, - {"ProcessMsgArgs", test_ProcessMsgArgs}, - {"LibMsgDelivery", test_LibMsgDelivery}, - {"AsyncINFO", test_AsyncINFO}, - {"RequestPool", test_RequestPool}, - {"NoFlusherIfSendAsapOption", test_NoFlusherIfSendAsap}, - {"HeadersAndSubPendingBytes", test_HeadersAndSubPendingBytes}, - - // Public API Tests - - {"DefaultConnection", test_DefaultConnection}, - {"SimplifiedURLs", test_SimplifiedURLs}, - {"IPResolutionOrder", test_IPResolutionOrder}, - {"UseDefaultURLIfNoServerSpecified",test_UseDefaultURLIfNoServerSpecified}, - {"ConnectToWithMultipleURLs", test_ConnectToWithMultipleURLs}, - {"ConnectionWithNULLOptions", test_ConnectionWithNullOptions}, - {"ConnectionToWithNullURLs", test_ConnectionToWithNullURLs}, - {"ConnectionStatus", test_ConnectionStatus}, - {"ConnClosedCB", test_ConnClosedCB}, - {"CloseDisconnectedCB", test_CloseDisconnectedCB}, - {"ServerStopDisconnectedCB", test_ServerStopDisconnectedCB}, - {"ClosedConnections", test_ClosedConnections}, - {"ConnectVerboseOption", test_ConnectVerboseOption}, - {"ReconnectThreadLeak", test_ReconnectThreadLeak}, - {"ReconnectTotalTime", test_ReconnectTotalTime}, - {"ReconnectDisallowedFlags", test_ReconnectDisallowedFlags}, - {"ReconnectAllowedFlags", test_ReconnectAllowedFlags}, - {"ConnCloseBreaksReconnectLoop", test_ConnCloseBreaksReconnectLoop}, - {"BasicReconnectFunctionality", test_BasicReconnectFunctionality}, - {"ExtendedReconnectFunctionality", test_ExtendedReconnectFunctionality}, - {"QueueSubsOnReconnect", test_QueueSubsOnReconnect}, - {"IsClosed", test_IsClosed}, - {"IsReconnectingAndStatus", test_IsReconnectingAndStatus}, - {"ReconnectBufSize", test_ReconnectBufSize}, - {"RetryOnFailedConnect", test_RetryOnFailedConnect}, - {"NoPartialOnReconnect", test_NoPartialOnReconnect}, - {"ReconnectFailsPendingRequests", test_ReconnectFailsPendingRequest}, - - {"ErrOnConnectAndDeadlock", test_ErrOnConnectAndDeadlock}, - {"ErrOnMaxPayloadLimit", test_ErrOnMaxPayloadLimit}, - - {"Auth", test_Auth}, - {"AuthFailNoDisconnectCB", test_AuthFailNoDisconnectCB}, - {"AuthToken", test_AuthToken}, - {"AuthTokenHandler", test_AuthTokenHandler}, - {"PermViolation", test_PermViolation}, - {"AuthViolation", test_AuthViolation}, - {"AuthenticationExpired", test_AuthenticationExpired}, - {"AuthenticationExpiredReconnect", test_AuthenticationExpiredReconnect}, - {"ConnectedServer", test_ConnectedServer}, - {"MultipleClose", test_MultipleClose}, - {"SimplePublish", test_SimplePublish}, - {"SimplePublishNoData", test_SimplePublishNoData}, - {"PublishMsg", test_PublishMsg}, - {"InvalidSubsArgs", test_InvalidSubsArgs}, - {"AsyncSubscribe", test_AsyncSubscribe}, - {"AsyncSubscribeTimeout", test_AsyncSubscribeTimeout}, - {"SyncSubscribe", test_SyncSubscribe}, - {"PubSubWithReply", test_PubSubWithReply}, - {"NoResponders", test_NoResponders}, - {"Flush", test_Flush}, - {"ConnCloseDoesFlush", test_ConnCloseDoesFlush}, - {"QueueSubscriber", test_QueueSubscriber}, - {"ReplyArg", test_ReplyArg}, - {"SyncReplyArg", test_SyncReplyArg}, - {"Unsubscribe", test_Unsubscribe}, - {"DoubleUnsubscribe", test_DoubleUnsubscribe}, - {"SubRemovedWhileProcessingMsg", test_SubRemovedWhileProcessingMsg}, - {"RequestTimeout", test_RequestTimeout}, - {"Request", test_Request}, - {"RequestNoBody", test_RequestNoBody}, - {"RequestMuxWithMappedSubject", test_RequestMuxWithMappedSubject}, - {"OldRequest", test_OldRequest}, - {"SimultaneousRequests", test_SimultaneousRequest}, - {"RequestClose", test_RequestClose}, - {"CustomInbox", test_CustomInbox}, - {"MessagePadding", test_MessageBufferPadding}, - {"FlushInCb", test_FlushInCb}, - {"ReleaseFlush", test_ReleaseFlush}, - {"FlushErrOnDisconnect", test_FlushErrOnDisconnect}, - {"Inbox", test_Inbox}, - {"Stats", test_Stats}, - {"BadSubject", test_BadSubject}, - {"SubBadSubjectAndQueueNames", test_SubBadSubjectAndQueueName}, - {"ClientAsyncAutoUnsub", test_ClientAsyncAutoUnsub}, - {"ClientSyncAutoUnsub", test_ClientSyncAutoUnsub}, - {"ClientAutoUnsubAndReconnect", test_ClientAutoUnsubAndReconnect}, - {"AutoUnsubNoUnsubOnDestroy", test_AutoUnsubNoUnsubOnDestroy}, - {"NextMsgOnClosedSub", test_NextMsgOnClosedSub}, - {"CloseSubRelease", test_CloseSubRelease}, - {"IsValidSubscriber", test_IsValidSubscriber}, - {"SlowSubscriber", test_SlowSubscriber}, - {"SlowAsyncSubscriber", test_SlowAsyncSubscriber}, - {"SlowConsumerCb", test_SlowConsumerCB}, - {"PendingLimitsDeliveredAndDropped",test_PendingLimitsDeliveredAndDropped}, - {"PendingLimitsWithSyncSub", test_PendingLimitsWithSyncSub}, - {"AsyncSubscriptionPending", test_AsyncSubscriptionPending}, - {"AsyncSubscriptionPendingDrain", test_AsyncSubscriptionPendingDrain}, - {"SyncSubscriptionPending", test_SyncSubscriptionPending}, - {"SyncSubscriptionPendingDrain", test_SyncSubscriptionPendingDrain}, - {"AsyncErrHandler", test_AsyncErrHandler}, - {"AsyncSubscriberStarvation", test_AsyncSubscriberStarvation}, - {"AsyncSubscriberOnClose", test_AsyncSubscriberOnClose}, - {"NextMsgCallOnAsyncSub", test_NextMsgCallOnAsyncSub}, - {"SubOnComplete", test_SubOnComplete}, - {"GetLastError", test_GetLastError}, - {"StaleConnection", test_StaleConnection}, - {"ServerErrorClosesConnection", test_ServerErrorClosesConnection}, - {"NoEcho", test_NoEcho}, - {"NoEchoOldServer", test_NoEchoOldServer}, - {"DrainSub", test_DrainSub}, - {"DrainSubStops", test_DrainSubStops}, - {"DrainSubRaceOnAutoUnsub", test_DrainSubRaceOnAutoUnsub}, - {"DrainSubNotResentOnReconnect", test_DrainSubNotResentOnReconnect}, - {"DrainConn", test_DrainConn}, - {"NoDoubleCloseCbOnDrain", test_NoDoubleConnClosedOnDrain}, - {"GetClientID", test_GetClientID}, - {"GetClientIP", test_GetClientIP}, - {"GetRTT", test_GetRTT}, - {"GetLocalIPAndPort", test_GetLocalIPAndPort}, - {"UserCredsCallbacks", test_UserCredsCallbacks}, - {"UserCredsFromFiles", test_UserCredsFromFiles}, - {"UserCredsFromMemory", test_UserCredsFromMemory}, - {"NKey", test_NKey}, - {"NKeyFromSeed", test_NKeyFromSeed}, - {"ConnSign", test_ConnSign}, - {"WriteDeadline", test_WriteDeadline}, - {"HeadersNotSupported", test_HeadersNotSupported}, - {"HeadersBasic", test_HeadersBasic}, - {"MsgsFilter", test_natsMsgsFilter}, - {"EventLoop", test_EventLoop}, - {"EventLoopRetryOnFailedConnect", test_EventLoopRetryOnFailedConnect}, - {"EventLoopTLS", test_EventLoopTLS}, - {"SSLBasic", test_SSLBasic}, - {"SSLVerify", test_SSLVerify}, - {"SSLCAFromMemory", test_SSLLoadCAFromMemory}, - {"SSLCertAndKeyFromMemory", test_SSLCertAndKeyFromMemory}, - {"SSLVerifyHostname", test_SSLVerifyHostname}, - {"SSLSkipServerVerification", test_SSLSkipServerVerification}, - {"SSLCiphers", test_SSLCiphers}, - {"SSLMultithreads", test_SSLMultithreads}, - {"SSLConnectVerboseOption", test_SSLConnectVerboseOption}, - {"SSLSocketLeakEventLoop", test_SSLSocketLeakWithEventLoop}, - {"SSLReconnectWithAuthError", test_SSLReconnectWithAuthError}, - - // Clusters Tests - - {"ServersOption", test_ServersOption}, - {"AuthServers", test_AuthServers}, - {"AuthFailToReconnect", test_AuthFailToReconnect}, - {"ReconnectWithTokenHandler", test_ReconnectWithTokenHandler}, - {"BasicClusterReconnect", test_BasicClusterReconnect}, - {"HotSpotReconnect", test_HotSpotReconnect}, - {"ProperReconnectDelay", test_ProperReconnectDelay}, - {"ProperFalloutAfterMaxAttempts", test_ProperFalloutAfterMaxAttempts}, - {"StopReconnectAfterTwoAuthErr", test_StopReconnectAfterTwoAuthErr}, - {"TimeoutOnNoServer", test_TimeoutOnNoServer}, - {"PingReconnect", test_PingReconnect}, - {"GetServers", test_GetServers}, - {"GetDiscoveredServers", test_GetDiscoveredServers}, - {"DiscoveredServersCb", test_DiscoveredServersCb}, - {"IgnoreDiscoveredServers", test_IgnoreDiscoveredServers}, - {"INFOAfterFirstPONGisProcessedOK", test_ReceiveINFORightAfterFirstPONG}, - {"ServerPoolUpdatedOnClusterUpdate",test_ServerPoolUpdatedOnClusterUpdate}, - {"ReconnectJitter", test_ReconnectJitter}, - {"CustomReconnectDelay", test_CustomReconnectDelay}, - {"LameDuckMode", test_LameDuckMode}, - {"ReconnectImplicitUserInfo", test_ReconnectImplicitUserInfo}, - - {"JetStreamUnmarshalAccInfo", test_JetStreamUnmarshalAccountInfo}, - {"JetStreamUnmarshalStreamState", test_JetStreamUnmarshalStreamState}, - {"JetStreamUnmarshalStreamCfg", test_JetStreamUnmarshalStreamConfig}, - {"JetStreamUnmarshalStreamInfo", test_JetStreamUnmarshalStreamInfo}, - {"JetStreamMarshalStreamCfg", test_JetStreamMarshalStreamConfig}, - {"JetStreamUnmarshalConsumerInfo", test_JetStreamUnmarshalConsumerInfo}, - {"JetStreamContext", test_JetStreamContext}, - {"JetStreamDomain", test_JetStreamContextDomain}, - {"JetStreamMgtStreams", test_JetStreamMgtStreams}, - {"JetStreamMgtConsumers", test_JetStreamMgtConsumers}, - {"JetStreamPublish", test_JetStreamPublish}, - {"JetStreamPublishAsync", test_JetStreamPublishAsync}, - {"JetStreamPublishAckHandler", test_JetStreamPublishAckHandler}, - {"JetStreamSubscribe", test_JetStreamSubscribe}, - {"JetStreamSubscribeSync", test_JetStreamSubscribeSync}, - {"JetStreamSubscribeConfigCheck", test_JetStreamSubscribeConfigCheck}, - {"JetStreamSubscribeIdleHeartbeat", test_JetStreamSubscribeIdleHearbeat}, - {"JetStreamSubscribeFlowControl", test_JetStreamSubscribeFlowControl}, - {"JetStreamSubscribePull", test_JetStreamSubscribePull}, - {"JetStreamSubscribeHeadersOnly", test_JetStreamSubscribeHeadersOnly}, - {"JetStreamOrderedCons", test_JetStreamOrderedConsumer}, - {"JetStreamOrderedConsWithErrors", test_JetStreamOrderedConsumerWithErrors}, - {"JetStreamOrderedConsAutoUnsub", test_JetStreamOrderedConsumerWithAutoUnsub}, - {"JetStreamOrderedConsSrvRestart", test_JetStreamOrderedConsSrvRestart}, - {"JetStreamSubscribeWithFWC", test_JetStreamSubscribeWithFWC}, - {"JetStreamStreamsSealAndRollup", test_JetStreamStreamsSealAndRollup}, - {"JetStreamGetMsgAndLastMsg", test_JetStreamGetMsgAndLastMsg}, - {"JetStreamConvertDirectMsg", test_JetStreamConvertDirectMsg}, - {"JetStreamDirectGetMsg", test_JetStreamDirectGetMsg}, - {"JetStreamNakWithDelay", test_JetStreamNakWithDelay}, - {"JetStreamBackOffRedeliveries", test_JetStreamBackOffRedeliveries}, - {"JetStreamInfoWithSubjects", test_JetStreamInfoWithSubjects}, - {"JetStreamInfoAlternates", test_JetStreamInfoAlternates}, - - {"KeyValueManager", test_KeyValueManager}, - {"KeyValueBasics", test_KeyValueBasics}, - {"KeyValueWatch", test_KeyValueWatch}, - {"KeyValueHistory", test_KeyValueHistory}, - {"KeyValueKeys", test_KeyValueKeys}, - {"KeyValueDeleteVsPurge", test_KeyValueDeleteVsPurge}, - {"KeyValueDeleteTombstones", test_KeyValueDeleteTombstones}, - {"KeyValueDeleteMarkerThreshold", test_KeyValuePurgeDeletesMarkerThreshold}, - {"KeyValueCrossAccount", test_KeyValueCrossAccount}, - {"KeyValueDiscardOldToNew", test_KeyValueDiscardOldToNew}, - {"KeyValueRePublish", test_KeyValueRePublish}, - {"KeyValueMirrorDirectGet", test_KeyValueMirrorDirectGet}, - {"KeyValueMirrorCrossDomains", test_KeyValueMirrorCrossDomains}, + // {"Version", test_Version}, + // {"VersionMatchesTag", test_VersionMatchesTag}, + // {"OpenCloseAndWait", test_OpenCloseAndWait}, + // {"natsNowAndSleep", test_natsNowAndSleep}, + // {"natsAllocSprintf", test_natsAllocSprintf}, + // {"natsStrCaseStr", test_natsStrCaseStr}, + // {"natsSnprintf", test_natsSnprintf}, + // {"natsBuffer", test_natsBuffer}, + // {"natsParseInt64", test_natsParseInt64}, + // {"natsParseControl", test_natsParseControl}, + // {"natsNormalizeErr", test_natsNormalizeErr}, + // {"natsMutex", test_natsMutex}, + // {"natsThread", test_natsThread}, + // {"natsCondition", test_natsCondition}, + // {"natsTimer", test_natsTimer}, + // {"natsUrl", test_natsUrl}, + // {"natsCreateStringFromBuffer", test_natsCreateStringFromBuffer}, + // {"natsHash", test_natsHash}, + // {"natsHashing", test_natsHashing}, + // {"natsStrHash", test_natsStrHash}, + // {"natsInbox", test_natsInbox}, + // {"natsOptions", test_natsOptions}, + // {"natsSock_ConnectTcp", test_natsSock_ConnectTcp}, + // {"natsSock_ShuffleIPs", test_natsSock_ShuffleIPs}, + // {"natsSock_IPOrder", test_natsSock_IPOrder}, + // {"natsSock_ReadLine", test_natsSock_ReadLine}, + // {"natsJSON", test_natsJSON}, + // {"natsEncodeTimeUTC", test_natsEncodeTimeUTC}, + // {"natsErrWithLongText", test_natsErrWithLongText}, + // {"natsErrStackMoreThanMaxFrames", test_natsErrStackMoreThanMaxFrames}, + // {"natsMsg", test_natsMsg}, + // {"natsBase32", test_natsBase32Decode}, + // {"natsBase64", test_natsBase64Encode}, + // {"natsCRC16", test_natsCRC16}, + // {"natsKeys", test_natsKeys}, + // {"natsReadFile", test_natsReadFile}, + // {"natsGetJWTOrSeed", test_natsGetJWTOrSeed}, + // {"natsHostIsIP", test_natsHostIsIP}, + // {"natsWaitReady", test_natsWaitReady}, + // {"natsSign", test_natsSign}, + // {"HeadersLift", test_natsMsgHeadersLift}, + // {"HeadersAPIs", test_natsMsgHeaderAPIs}, + // {"MsgIsJSControl", test_natsMsgIsJSCtrl}, + // {"SrvVersionAtLeast", test_natsSrvVersionAtLeast}, + + // // Package Level Tests + + // {"ReconnectServerStats", test_ReconnectServerStats}, + // {"ParseStateReconnectFunctionality",test_ParseStateReconnectFunctionality}, + // {"ServersRandomize", test_ServersRandomize}, + // {"SelectNextServer", test_SelectNextServer}, + // {"ParserPing", test_ParserPing}, + // {"ParserErr", test_ParserErr}, + // {"ParserOK", test_ParserOK}, + // {"ParseINFO", test_ParseINFO}, + // {"ParserShouldFail", test_ParserShouldFail}, + // {"ParserSplitMsg", test_ParserSplitMsg}, + // {"ProcessMsgArgs", test_ProcessMsgArgs}, + // {"LibMsgDelivery", test_LibMsgDelivery}, + // {"AsyncINFO", test_AsyncINFO}, + // {"RequestPool", test_RequestPool}, + // {"NoFlusherIfSendAsapOption", test_NoFlusherIfSendAsap}, + // {"HeadersAndSubPendingBytes", test_HeadersAndSubPendingBytes}, + + // // Public API Tests + + // {"DefaultConnection", test_DefaultConnection}, + // {"SimplifiedURLs", test_SimplifiedURLs}, + // {"IPResolutionOrder", test_IPResolutionOrder}, + // {"UseDefaultURLIfNoServerSpecified",test_UseDefaultURLIfNoServerSpecified}, + // {"ConnectToWithMultipleURLs", test_ConnectToWithMultipleURLs}, + // {"ConnectionWithNULLOptions", test_ConnectionWithNullOptions}, + // {"ConnectionToWithNullURLs", test_ConnectionToWithNullURLs}, + // {"ConnectionStatus", test_ConnectionStatus}, + // {"ConnClosedCB", test_ConnClosedCB}, + // {"CloseDisconnectedCB", test_CloseDisconnectedCB}, + // {"ServerStopDisconnectedCB", test_ServerStopDisconnectedCB}, + // {"ClosedConnections", test_ClosedConnections}, + // {"ConnectVerboseOption", test_ConnectVerboseOption}, + // {"ReconnectThreadLeak", test_ReconnectThreadLeak}, + // {"ReconnectTotalTime", test_ReconnectTotalTime}, + // {"ReconnectDisallowedFlags", test_ReconnectDisallowedFlags}, + // {"ReconnectAllowedFlags", test_ReconnectAllowedFlags}, + // {"ConnCloseBreaksReconnectLoop", test_ConnCloseBreaksReconnectLoop}, + // {"BasicReconnectFunctionality", test_BasicReconnectFunctionality}, + // {"ExtendedReconnectFunctionality", test_ExtendedReconnectFunctionality}, + // {"QueueSubsOnReconnect", test_QueueSubsOnReconnect}, + // {"IsClosed", test_IsClosed}, + // {"IsReconnectingAndStatus", test_IsReconnectingAndStatus}, + // {"ReconnectBufSize", test_ReconnectBufSize}, + // {"RetryOnFailedConnect", test_RetryOnFailedConnect}, + // {"NoPartialOnReconnect", test_NoPartialOnReconnect}, + // {"ReconnectFailsPendingRequests", test_ReconnectFailsPendingRequest}, + + // {"ErrOnConnectAndDeadlock", test_ErrOnConnectAndDeadlock}, + // {"ErrOnMaxPayloadLimit", test_ErrOnMaxPayloadLimit}, + + // {"Auth", test_Auth}, + // {"AuthFailNoDisconnectCB", test_AuthFailNoDisconnectCB}, + // {"AuthToken", test_AuthToken}, + // {"AuthTokenHandler", test_AuthTokenHandler}, + // {"PermViolation", test_PermViolation}, + // {"AuthViolation", test_AuthViolation}, + // {"AuthenticationExpired", test_AuthenticationExpired}, + // {"AuthenticationExpiredReconnect", test_AuthenticationExpiredReconnect}, + // {"ConnectedServer", test_ConnectedServer}, + // {"MultipleClose", test_MultipleClose}, + // {"SimplePublish", test_SimplePublish}, + // {"SimplePublishNoData", test_SimplePublishNoData}, + // {"PublishMsg", test_PublishMsg}, + // {"InvalidSubsArgs", test_InvalidSubsArgs}, + // {"AsyncSubscribe", test_AsyncSubscribe}, + // {"AsyncSubscribeTimeout", test_AsyncSubscribeTimeout}, + // {"SyncSubscribe", test_SyncSubscribe}, + // {"PubSubWithReply", test_PubSubWithReply}, + // {"NoResponders", test_NoResponders}, + // {"Flush", test_Flush}, + // {"ConnCloseDoesFlush", test_ConnCloseDoesFlush}, + // {"QueueSubscriber", test_QueueSubscriber}, + // {"ReplyArg", test_ReplyArg}, + // {"SyncReplyArg", test_SyncReplyArg}, + // {"Unsubscribe", test_Unsubscribe}, + // {"DoubleUnsubscribe", test_DoubleUnsubscribe}, + // {"SubRemovedWhileProcessingMsg", test_SubRemovedWhileProcessingMsg}, + // {"RequestTimeout", test_RequestTimeout}, + // {"Request", test_Request}, + // {"RequestNoBody", test_RequestNoBody}, + // {"RequestMuxWithMappedSubject", test_RequestMuxWithMappedSubject}, + // {"OldRequest", test_OldRequest}, + // {"SimultaneousRequests", test_SimultaneousRequest}, + // {"RequestClose", test_RequestClose}, + // {"CustomInbox", test_CustomInbox}, + // {"MessagePadding", test_MessageBufferPadding}, + // {"FlushInCb", test_FlushInCb}, + // {"ReleaseFlush", test_ReleaseFlush}, + // {"FlushErrOnDisconnect", test_FlushErrOnDisconnect}, + // {"Inbox", test_Inbox}, + // {"Stats", test_Stats}, + // {"BadSubject", test_BadSubject}, + // {"SubBadSubjectAndQueueNames", test_SubBadSubjectAndQueueName}, + // {"ClientAsyncAutoUnsub", test_ClientAsyncAutoUnsub}, + // {"ClientSyncAutoUnsub", test_ClientSyncAutoUnsub}, + // {"ClientAutoUnsubAndReconnect", test_ClientAutoUnsubAndReconnect}, + // {"AutoUnsubNoUnsubOnDestroy", test_AutoUnsubNoUnsubOnDestroy}, + // {"NextMsgOnClosedSub", test_NextMsgOnClosedSub}, + // {"CloseSubRelease", test_CloseSubRelease}, + // {"IsValidSubscriber", test_IsValidSubscriber}, + // {"SlowSubscriber", test_SlowSubscriber}, + // {"SlowAsyncSubscriber", test_SlowAsyncSubscriber}, + // {"SlowConsumerCb", test_SlowConsumerCB}, + // {"PendingLimitsDeliveredAndDropped",test_PendingLimitsDeliveredAndDropped}, + // {"PendingLimitsWithSyncSub", test_PendingLimitsWithSyncSub}, + // {"AsyncSubscriptionPending", test_AsyncSubscriptionPending}, + // {"AsyncSubscriptionPendingDrain", test_AsyncSubscriptionPendingDrain}, + // {"SyncSubscriptionPending", test_SyncSubscriptionPending}, + // {"SyncSubscriptionPendingDrain", test_SyncSubscriptionPendingDrain}, + // {"AsyncErrHandler", test_AsyncErrHandler}, + // {"AsyncSubscriberStarvation", test_AsyncSubscriberStarvation}, + // {"AsyncSubscriberOnClose", test_AsyncSubscriberOnClose}, + // {"NextMsgCallOnAsyncSub", test_NextMsgCallOnAsyncSub}, + // {"SubOnComplete", test_SubOnComplete}, + // {"GetLastError", test_GetLastError}, + // {"StaleConnection", test_StaleConnection}, + // {"ServerErrorClosesConnection", test_ServerErrorClosesConnection}, + // {"NoEcho", test_NoEcho}, + // {"NoEchoOldServer", test_NoEchoOldServer}, + // {"DrainSub", test_DrainSub}, + // {"DrainSubStops", test_DrainSubStops}, + // {"DrainSubRaceOnAutoUnsub", test_DrainSubRaceOnAutoUnsub}, + // {"DrainSubNotResentOnReconnect", test_DrainSubNotResentOnReconnect}, + // {"DrainConn", test_DrainConn}, + // {"NoDoubleCloseCbOnDrain", test_NoDoubleConnClosedOnDrain}, + // {"GetClientID", test_GetClientID}, + // {"GetClientIP", test_GetClientIP}, + // {"GetRTT", test_GetRTT}, + // {"GetLocalIPAndPort", test_GetLocalIPAndPort}, + // {"UserCredsCallbacks", test_UserCredsCallbacks}, + // {"UserCredsFromFiles", test_UserCredsFromFiles}, + // {"UserCredsFromMemory", test_UserCredsFromMemory}, + // {"NKey", test_NKey}, + // {"NKeyFromSeed", test_NKeyFromSeed}, + // {"ConnSign", test_ConnSign}, + // {"WriteDeadline", test_WriteDeadline}, + // {"HeadersNotSupported", test_HeadersNotSupported}, + // {"HeadersBasic", test_HeadersBasic}, + // {"MsgsFilter", test_natsMsgsFilter}, + // {"EventLoop", test_EventLoop}, + // {"EventLoopRetryOnFailedConnect", test_EventLoopRetryOnFailedConnect}, + // {"EventLoopTLS", test_EventLoopTLS}, + // {"SSLBasic", test_SSLBasic}, + // {"SSLVerify", test_SSLVerify}, + // {"SSLCAFromMemory", test_SSLLoadCAFromMemory}, + // {"SSLCertAndKeyFromMemory", test_SSLCertAndKeyFromMemory}, + // {"SSLVerifyHostname", test_SSLVerifyHostname}, + // {"SSLSkipServerVerification", test_SSLSkipServerVerification}, + // {"SSLCiphers", test_SSLCiphers}, + // {"SSLMultithreads", test_SSLMultithreads}, + // {"SSLConnectVerboseOption", test_SSLConnectVerboseOption}, + // {"SSLSocketLeakEventLoop", test_SSLSocketLeakWithEventLoop}, + // {"SSLReconnectWithAuthError", test_SSLReconnectWithAuthError}, + + // // Clusters Tests + + // {"ServersOption", test_ServersOption}, + // {"AuthServers", test_AuthServers}, + // {"AuthFailToReconnect", test_AuthFailToReconnect}, + // {"ReconnectWithTokenHandler", test_ReconnectWithTokenHandler}, + // {"BasicClusterReconnect", test_BasicClusterReconnect}, + // {"HotSpotReconnect", test_HotSpotReconnect}, + // {"ProperReconnectDelay", test_ProperReconnectDelay}, + // {"ProperFalloutAfterMaxAttempts", test_ProperFalloutAfterMaxAttempts}, + // {"StopReconnectAfterTwoAuthErr", test_StopReconnectAfterTwoAuthErr}, + // {"TimeoutOnNoServer", test_TimeoutOnNoServer}, + // {"PingReconnect", test_PingReconnect}, + // {"GetServers", test_GetServers}, + // {"GetDiscoveredServers", test_GetDiscoveredServers}, + // {"DiscoveredServersCb", test_DiscoveredServersCb}, + // {"IgnoreDiscoveredServers", test_IgnoreDiscoveredServers}, + // {"INFOAfterFirstPONGisProcessedOK", test_ReceiveINFORightAfterFirstPONG}, + // {"ServerPoolUpdatedOnClusterUpdate",test_ServerPoolUpdatedOnClusterUpdate}, + // {"ReconnectJitter", test_ReconnectJitter}, + // {"CustomReconnectDelay", test_CustomReconnectDelay}, + // {"LameDuckMode", test_LameDuckMode}, + // {"ReconnectImplicitUserInfo", test_ReconnectImplicitUserInfo}, + + // {"JetStreamUnmarshalAccInfo", test_JetStreamUnmarshalAccountInfo}, + // {"JetStreamUnmarshalStreamState", test_JetStreamUnmarshalStreamState}, + // {"JetStreamUnmarshalStreamCfg", test_JetStreamUnmarshalStreamConfig}, + // {"JetStreamUnmarshalStreamInfo", test_JetStreamUnmarshalStreamInfo}, + // {"JetStreamMarshalStreamCfg", test_JetStreamMarshalStreamConfig}, + // {"JetStreamUnmarshalConsumerInfo", test_JetStreamUnmarshalConsumerInfo}, + // {"JetStreamContext", test_JetStreamContext}, + // {"JetStreamDomain", test_JetStreamContextDomain}, + // {"JetStreamMgtStreams", test_JetStreamMgtStreams}, + // {"JetStreamMgtConsumers", test_JetStreamMgtConsumers}, + // {"JetStreamPublish", test_JetStreamPublish}, + // {"JetStreamPublishAsync", test_JetStreamPublishAsync}, + // {"JetStreamPublishAckHandler", test_JetStreamPublishAckHandler}, + // {"JetStreamSubscribe", test_JetStreamSubscribe}, + // {"JetStreamSubscribeSync", test_JetStreamSubscribeSync}, + // {"JetStreamSubscribeConfigCheck", test_JetStreamSubscribeConfigCheck}, + // {"JetStreamSubscribeIdleHeartbeat", test_JetStreamSubscribeIdleHearbeat}, + // {"JetStreamSubscribeFlowControl", test_JetStreamSubscribeFlowControl}, + // {"JetStreamSubscribePull", test_JetStreamSubscribePull}, + // {"JetStreamSubscribeHeadersOnly", test_JetStreamSubscribeHeadersOnly}, + // {"JetStreamOrderedCons", test_JetStreamOrderedConsumer}, + // {"JetStreamOrderedConsWithErrors", test_JetStreamOrderedConsumerWithErrors}, + // {"JetStreamOrderedConsAutoUnsub", test_JetStreamOrderedConsumerWithAutoUnsub}, + // {"JetStreamOrderedConsSrvRestart", test_JetStreamOrderedConsSrvRestart}, + // {"JetStreamSubscribeWithFWC", test_JetStreamSubscribeWithFWC}, + // {"JetStreamStreamsSealAndRollup", test_JetStreamStreamsSealAndRollup}, + // {"JetStreamGetMsgAndLastMsg", test_JetStreamGetMsgAndLastMsg}, + // {"JetStreamConvertDirectMsg", test_JetStreamConvertDirectMsg}, + // {"JetStreamDirectGetMsg", test_JetStreamDirectGetMsg}, + // {"JetStreamNakWithDelay", test_JetStreamNakWithDelay}, + // {"JetStreamBackOffRedeliveries", test_JetStreamBackOffRedeliveries}, + // {"JetStreamInfoWithSubjects", test_JetStreamInfoWithSubjects}, + // {"JetStreamInfoAlternates", test_JetStreamInfoAlternates}, + + // {"KeyValueManager", test_KeyValueManager}, + // {"KeyValueBasics", test_KeyValueBasics}, + // {"KeyValueWatch", test_KeyValueWatch}, + // {"KeyValueHistory", test_KeyValueHistory}, + // {"KeyValueKeys", test_KeyValueKeys}, + // {"KeyValueDeleteVsPurge", test_KeyValueDeleteVsPurge}, + // {"KeyValueDeleteTombstones", test_KeyValueDeleteTombstones}, + // {"KeyValueDeleteMarkerThreshold", test_KeyValuePurgeDeletesMarkerThreshold}, + // {"KeyValueCrossAccount", test_KeyValueCrossAccount}, + // {"KeyValueDiscardOldToNew", test_KeyValueDiscardOldToNew}, + // {"KeyValueRePublish", test_KeyValueRePublish}, + // {"KeyValueMirrorDirectGet", test_KeyValueMirrorDirectGet}, + // {"KeyValueMirrorCrossDomains", test_KeyValueMirrorCrossDomains}, {"MicroMatchEndpointSubject", test_MicroMatchEndpointSubject}, {"MicroAddService", test_MicroAddService}, @@ -35521,33 +35521,33 @@ static testInfo allTests[] = {"MicroAsyncErrorHandler", test_MicroAsyncErrorHandler}, #if defined(NATS_HAS_STREAMING) - {"StanPBufAllocator", test_StanPBufAllocator}, - {"StanConnOptions", test_StanConnOptions}, - {"StanSubOptions", test_StanSubOptions}, - {"StanMsg", test_StanMsg}, - {"StanServerNotReachable", test_StanServerNotReachable}, - {"StanBasicConnect", test_StanBasicConnect}, - {"StanConnectError", test_StanConnectError}, - {"StanBasicPublish", test_StanBasicPublish}, - {"StanBasicPublishAsync", test_StanBasicPublishAsync}, - {"StanPublishTimeout", test_StanPublishTimeout}, - {"StanPublishMaxAcksInflight", test_StanPublishMaxAcksInflight}, - {"StanBasicSubscription", test_StanBasicSubscription}, - {"StanSubscriptionCloseAndUnsub", test_StanSubscriptionCloseAndUnsubscribe}, - {"StanDurableSubscription", test_StanDurableSubscription}, - {"StanBasicQueueSubscription", test_StanBasicQueueSubscription}, - {"StanDurableQueueSubscription", test_StanDurableQueueSubscription}, - {"StanCheckReceivedMsg", test_StanCheckReceivedvMsg}, - {"StanSubscriptionAckMsg", test_StanSubscriptionAckMsg}, - {"StanPings", test_StanPings}, - {"StanPingsNoResponder", test_StanPingsNoResponder}, - {"StanConnectionLostHandlerNotSet", test_StanConnectionLostHandlerNotSet}, - {"StanPingsUnblockPublishCalls", test_StanPingsUnblockPubCalls}, - {"StanGetNATSConnection", test_StanGetNATSConnection}, - {"StanNoRetryOnFailedConnect", test_StanNoRetryOnFailedConnect}, - {"StanInternalSubsNotPooled", test_StanInternalSubsNotPooled}, - {"StanSubOnComplete", test_StanSubOnComplete}, - {"StanSubTimeout", test_StanSubTimeout}, + // {"StanPBufAllocator", test_StanPBufAllocator}, + // {"StanConnOptions", test_StanConnOptions}, + // {"StanSubOptions", test_StanSubOptions}, + // {"StanMsg", test_StanMsg}, + // {"StanServerNotReachable", test_StanServerNotReachable}, + // {"StanBasicConnect", test_StanBasicConnect}, + // {"StanConnectError", test_StanConnectError}, + // {"StanBasicPublish", test_StanBasicPublish}, + // {"StanBasicPublishAsync", test_StanBasicPublishAsync}, + // {"StanPublishTimeout", test_StanPublishTimeout}, + // {"StanPublishMaxAcksInflight", test_StanPublishMaxAcksInflight}, + // {"StanBasicSubscription", test_StanBasicSubscription}, + // {"StanSubscriptionCloseAndUnsub", test_StanSubscriptionCloseAndUnsubscribe}, + // {"StanDurableSubscription", test_StanDurableSubscription}, + // {"StanBasicQueueSubscription", test_StanBasicQueueSubscription}, + // {"StanDurableQueueSubscription", test_StanDurableQueueSubscription}, + // {"StanCheckReceivedMsg", test_StanCheckReceivedvMsg}, + // {"StanSubscriptionAckMsg", test_StanSubscriptionAckMsg}, + // {"StanPings", test_StanPings}, + // {"StanPingsNoResponder", test_StanPingsNoResponder}, + // {"StanConnectionLostHandlerNotSet", test_StanConnectionLostHandlerNotSet}, + // {"StanPingsUnblockPublishCalls", test_StanPingsUnblockPubCalls}, + // {"StanGetNATSConnection", test_StanGetNATSConnection}, + // {"StanNoRetryOnFailedConnect", test_StanNoRetryOnFailedConnect}, + // {"StanInternalSubsNotPooled", test_StanInternalSubsNotPooled}, + // {"StanSubOnComplete", test_StanSubOnComplete}, + // {"StanSubTimeout", test_StanSubTimeout}, #endif From de0f8a8e9032bd3009ee33a4811f03fe0cb4a3df Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 29 May 2023 12:55:31 -0700 Subject: [PATCH 55/85] wip2 --- src/micro_monitoring.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 7f8f637ff..b79b53377 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -34,9 +34,9 @@ microError * micro_init_monitoring(microService *m) { microError *err = NULL; - // MICRO_CALL(err, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); - // MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); - // MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); + MICRO_CALL(err, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); + MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); + MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); return err; } From e531f28b81f769f7b6c78305ddcd9b50b8904c3c Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 29 May 2023 13:16:54 -0700 Subject: [PATCH 56/85] wip3 --- src/micro_endpoint.c | 4 ++-- test/test.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index 16245e8f0..47c1cc689 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -206,7 +206,7 @@ release_endpoint(microEndpoint *ep) refs = --(ep->refs); - printf("<>/<> release_endpoint--: %s: %d\n", ep->name, refs); + printf("<>/<> release_endpoint--: %s: %d\n", ep->name, refs);fflush(stdout); micro_unlock_endpoint(ep); @@ -219,7 +219,7 @@ void free_endpoint(microEndpoint *ep) if (ep == NULL) return; - printf("<>/<> free_endpoint: %s\n", ep->name); + printf("<>/<> free_endpoint: %s\n", ep->name);fflush(stdout); micro_release_service(ep->m); NATS_FREE(ep->name); diff --git a/test/test.c b/test/test.c index f0241768d..0e6094f9d 100644 --- a/test/test.c +++ b/test/test.c @@ -32760,6 +32760,9 @@ test_MicroStartStop(void) natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); _stopServer(serverPid); + + nats_Sleep(100); + FAIL("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); } void micro_service_done_handler(microService *m) From 0e8517549fbef89989e27f1e0d0c77771069f7e8 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 29 May 2023 13:42:42 -0700 Subject: [PATCH 57/85] wip4 --- test/test.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/test.c b/test/test.c index 0e6094f9d..f0241768d 100644 --- a/test/test.c +++ b/test/test.c @@ -32760,9 +32760,6 @@ test_MicroStartStop(void) natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); _stopServer(serverPid); - - nats_Sleep(100); - FAIL("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); } void micro_service_done_handler(microService *m) From e4b80fda9779f5f9654259380b23b44de88abe9e Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Mon, 29 May 2023 21:55:56 -0700 Subject: [PATCH 58/85] wip5 --- src/micro.c | 3 ++- src/micro_endpoint.c | 14 ++++++-------- src/microp.h | 4 ++++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/micro.c b/src/micro.c index fef797792..8c94fbac2 100644 --- a/src/micro.c +++ b/src/micro.c @@ -582,13 +582,14 @@ on_service_error(microService *m, const char *subject, natsStatus s) (ep != NULL) && !micro_match_endpoint_subject(ep->subject, subject); ep = ep->next) ; + micro_retain_endpoint(ep); micro_unlock_service(m); if ((ep != NULL) && (m->cfg->ErrHandler != NULL)) { - printf("<>/<> on_service_error: calling for %s\n", ep->name); (*m->cfg->ErrHandler)(m, ep, s); } + micro_release_endpoint(ep); if (ep != NULL) { diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index 47c1cc689..49dae429e 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -19,8 +19,6 @@ static microError *dup_with_prefix(char **dst, const char *prefix, const char *s static void free_endpoint(microEndpoint *ep); static void handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); static microError *new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); -static void release_endpoint(microEndpoint *ep); -static void retain_endpoint(microEndpoint *ep); microError * micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal) @@ -106,7 +104,7 @@ micro_start_endpoint(microEndpoint *ep) { // extra retain before subscribing since we'll need to hold it until // on_complete on the subscription. - retain_endpoint(ep); + micro_retain_endpoint(ep); // Make sure the service is not stopped until this subscription has been closed. micro_increment_endpoints_to_stop(ep->m); @@ -177,12 +175,12 @@ micro_destroy_endpoint(microEndpoint *ep) // Release ep since it's no longer running (to compensate for the retain in // micro_start_endpoint). - release_endpoint(ep); + micro_release_endpoint(ep); return NULL; } -static void -retain_endpoint(microEndpoint *ep) +void +micro_retain_endpoint(microEndpoint *ep) { if (ep == NULL) return; @@ -194,8 +192,8 @@ retain_endpoint(microEndpoint *ep) micro_unlock_endpoint(ep); } -static void -release_endpoint(microEndpoint *ep) +void +micro_release_endpoint(microEndpoint *ep) { int refs = 0; diff --git a/src/microp.h b/src/microp.h index ed4426ae3..00c368127 100644 --- a/src/microp.h +++ b/src/microp.h @@ -161,12 +161,16 @@ bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_sub microError *micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); microError *micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); microError *micro_new_request(microRequest **new_request, microService *m, microEndpoint *ep, natsMsg *msg); +void micro_release_endpoint(microEndpoint *ep); void micro_release_service(microService *m); +void micro_retain_endpoint(microEndpoint *ep); void micro_retain_service(microService *m); microError *micro_start_endpoint(microEndpoint *ep); microError *micro_stop_endpoint(microEndpoint *ep); void micro_update_last_error(microEndpoint *ep, microError *err); + + static inline void micro_lock_service(microService *m) { natsMutex_Lock(m->service_mu); } static inline void micro_unlock_service(microService *m) { natsMutex_Unlock(m->service_mu); } static inline void micro_lock_endpoint(microEndpoint *ep) { natsMutex_Lock(ep->endpoint_mu); } From 71bb34232abcb8f241b13a68739666b9365d0ce1 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Wed, 31 May 2023 16:59:11 -0700 Subject: [PATCH 59/85] reworked wip --- src/micro.c | 1114 +++++++++++++++++++++++++++++++----------- src/micro_endpoint.c | 480 ------------------ src/microp.h | 53 +- src/nats.c | 14 +- test/list.txt | 7 - test/test.c | 32 +- 6 files changed, 861 insertions(+), 839 deletions(-) delete mode 100644 src/micro_endpoint.c diff --git a/src/micro.c b/src/micro.c index 8c94fbac2..783e89df2 100644 --- a/src/micro.c +++ b/src/micro.c @@ -11,60 +11,34 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include "microp.h" #include "conn.h" #include "opts.h" -static microError *new_service(microService **ptr, natsConnection *nc); -static void free_service(microService *m); -static microError *wrap_connection_event_callbacks(microService *m); - static natsMutex *service_callback_mu; static natsHash *all_services_to_callback; // uses `microService*` as the key and the value. -static microError * -_start_service_callbacks(microService *m) -{ - natsStatus s = NATS_OK; +static inline void _lock_service(microService *m) { natsMutex_Lock(m->service_mu); } +static inline void _unlock_service(microService *m) { natsMutex_Unlock(m->service_mu); } +static inline void _lock_endpoint(microEndpoint *ep) { natsMutex_Lock(ep->endpoint_mu); } +static inline void _unlock_endpoint(microEndpoint *ep) { natsMutex_Unlock(ep->endpoint_mu); } - if (m == NULL) - return micro_ErrorInvalidArg; +static microError *_clone_service_config(microServiceConfig **out, microServiceConfig *cfg); +static microError *_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); +static microError *_new_service(microService **ptr, natsConnection *nc); +static microError *_start_endpoint_l(microService *m, microEndpoint *ep); +static microError *_stop_endpoint_l(microService *m, microEndpoint *ep); +static microError *_wrap_connection_event_callbacks(microService *m); - if (service_callback_mu == NULL) - { - IFOK(s, natsMutex_Create(&service_callback_mu)); - if (s != NATS_OK) - return micro_ErrorFromStatus(s); - } +static bool _is_valid_name(const char *name); - if (all_services_to_callback == NULL) - { - IFOK(s, natsHash_Create(&all_services_to_callback, 8)); - if (s != NATS_OK) - return micro_ErrorFromStatus(s); - } - - micro_retain_service(m); - - natsMutex_Lock(service_callback_mu); - IFOK(s, natsHash_Set(all_services_to_callback, (int64_t)m, (void *)m, NULL)); - natsMutex_Unlock(service_callback_mu); - - return micro_ErrorFromStatus(s); -} - -static void -_stop_service_callbacks(microService *m) -{ - if ((m == NULL) || (all_services_to_callback == NULL) || (service_callback_mu == NULL)) - return; - - natsMutex_Lock(service_callback_mu); - natsHash_Remove(all_services_to_callback, (int64_t)m); - natsMutex_Unlock(service_callback_mu); - - micro_release_service(m); -} +static void _finalize_stopping_service_l(microService *m); +static void _release_endpoint(microEndpoint *ep); +static void _release_endpoint_l(microEndpoint *ep); +static void _release_service(microService *m); +static void _retain_service_l(microService *m); microError * micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *cfg) @@ -73,11 +47,11 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c microError *err = NULL; microService *m = NULL; - if ((new_m == NULL) || (nc == NULL) || (cfg == NULL) || !micro_is_valid_name(cfg->Name) || nats_IsStringEmpty(cfg->Version)) + if ((new_m == NULL) || (nc == NULL) || (cfg == NULL) || !_is_valid_name(cfg->Name) || nats_IsStringEmpty(cfg->Version)) return micro_ErrorInvalidArg; // Make a microservice object, with a reference to a natsConnection. - err = new_service(&m, nc); + err = _new_service(&m, nc); if (err != NULL) return err; @@ -85,10 +59,10 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c IFOK(s, natsNUID_Next(m->id, sizeof(m->id))); err = micro_ErrorFromStatus(s); - MICRO_CALL(err, micro_clone_service_config(&m->cfg, cfg)); + MICRO_CALL(err, _clone_service_config(&m->cfg, cfg)); // Wrap the connection callbacks before we subscribe to anything. - MICRO_CALL(err, wrap_connection_event_callbacks(m)); + MICRO_CALL(err, _wrap_connection_event_callbacks(m)); MICRO_CALL(err, micro_init_monitoring(m)); MICRO_CALL(err, microService_AddEndpoint(m, cfg->Endpoint)); @@ -103,6 +77,21 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c return NULL; } +microError * +microService_AddEndpoint(microService *m, microEndpointConfig *cfg) +{ + return micro_add_endpoint(NULL, m, NULL, cfg, false); +} + +microError * +microGroup_AddEndpoint(microGroup *g, microEndpointConfig *cfg) +{ + if (g == NULL) + return micro_ErrorInvalidArg; + + return micro_add_endpoint(NULL, g->m, g->prefix, cfg, false); +} + microError * micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal) { @@ -117,15 +106,15 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, if (cfg == NULL) return NULL; - err = micro_new_endpoint(&ep, m, prefix, cfg, is_internal); + err = _new_endpoint(&ep, m, prefix, cfg, is_internal); if (err != NULL) return microError_Wrapf(err, "failed to create endpoint %s", cfg->Name); - micro_lock_service(m); + _lock_service(m); if (m->stopping || m->stopped) { - micro_unlock_service(m); + _unlock_service(m); return micro_Errorf("can't add an endpoint %s to service %s: the service is stopped", cfg->Name, m->cfg->Name); } @@ -161,25 +150,20 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, m->first_ep = ep; } - // Increment the number of endpoints, unless we are replacing an existing - // one. - if (prev_ep == NULL) - { - m->num_eps++; - } - - micro_unlock_service(m); + _unlock_service(m); if (prev_ep != NULL) { // Rid of the previous endpoint with the same name, if any. If this // fails we can return the error, leave the newly added endpoint in the // list, not started. A retry with the same name will clean it up. - if (err = micro_destroy_endpoint(prev_ep), err != NULL) + if (err = _stop_endpoint_l(m, prev_ep), err != NULL) return err; + + _release_endpoint_l(prev_ep); } - if (err = micro_start_endpoint(ep), err != NULL) + if (err = _start_endpoint_l(m, ep), err != NULL) { // Best effort, leave the new endpoint in the list, as is. A retry with // the same name will clean it up. @@ -192,239 +176,287 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, } microError * -microService_AddEndpoint(microService *m, microEndpointConfig *cfg) +microService_Stop(microService *m) { - return micro_add_endpoint(NULL, m, NULL, cfg, false); + microError *err = NULL; + microEndpoint *ep = NULL; + + if (m == NULL) + return micro_ErrorInvalidArg; + + _lock_service(m); + if (m->stopped) + { + _unlock_service(m); + printf("<>/<> microService_Stop: already stopped\n"); + return NULL; + } + ep = m->first_ep; + + for (; ep != NULL; ep = ep->next) + { + printf("<>/<> microService_Stop: stopping endpoint %s\n", ep->config->Name); + + if (err = _stop_endpoint_l(m, ep), err != NULL) + return microError_Wrapf(err, "failed to stop service %s", ep->config->Name); + } + _unlock_service(m); + + printf("<>/<> microService_Stop: finalize\n"); + _finalize_stopping_service_l(m); + return NULL; } -microError * -microGroup_AddEndpoint(microGroup *g, microEndpointConfig *cfg) +static microError * +_new_service(microService **ptr, natsConnection *nc) { - if (g == NULL) - return micro_ErrorInvalidArg; + *ptr = NATS_CALLOC(1, sizeof(microService)); + if (*ptr == NULL) + return micro_ErrorOutOfMemory; - return micro_add_endpoint(NULL, g->m, g->prefix, cfg, false); + natsConn_retain(nc); + printf("<>/<> _new_service: refs: 1\n"); + (*ptr)->refs = 1; + (*ptr)->nc = nc; + (*ptr)->started = nats_Now() * 1000000; + return NULL; } -static void micro_finalize_stopping_service(microService *m) +static inline microError * +new_service_config(microServiceConfig **ptr) { - microDoneHandler invoke_done = NULL; + *ptr = NATS_CALLOC(1, sizeof(microServiceConfig)); + return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; +} - if (m == NULL) - return; +static inline microError * +_new_endpoint_config(microEndpointConfig **ptr) +{ + *ptr = NATS_CALLOC(1, sizeof(microEndpointConfig)); + return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; +} - micro_lock_service(m); - if (m->stopped || (m->num_eps_to_stop > 0)) - { - micro_unlock_service(m); +static void +_free_cloned_endpoint_config(microEndpointConfig *cfg) +{ + if (cfg == NULL) return; - } - m->stopped = true; - m->stopping = false; - if (m->cfg->DoneHandler != NULL) - invoke_done = m->cfg->DoneHandler; - micro_unlock_service(m); + // the strings are declared const for the public, but in a clone these need + // to be freed. + NATS_FREE((char *)cfg->Name); + NATS_FREE((char *)cfg->Subject); - // Disable any subsequent async callbacks. - _stop_service_callbacks(m); + NATS_FREE(cfg); +} - if (invoke_done != NULL) - invoke_done(m); +static inline microError * +micro_strdup(char **ptr, const char *str) +{ + // Make a strdup(NULL) be a no-op, so we don't have to check for NULL + // everywhere. + if (str == NULL) + { + *ptr = NULL; + return NULL; + } + *ptr = NATS_STRDUP(str); + if (*ptr == NULL) + return micro_ErrorOutOfMemory; + return NULL; } -microError * -microService_Stop(microService *m) +static microError * +_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) { microError *err = NULL; - microEndpoint *ep = NULL; - bool drain_endpoints = false; + microEndpointConfig *new_cfg = NULL; - if (m == NULL) + if (out == NULL) return micro_ErrorInvalidArg; - micro_lock_service(m); - if (m->stopped) + if (cfg == NULL) { - micro_unlock_service(m); + *out = NULL; return NULL; } - drain_endpoints = !m->stopping; - m->stopping = true; - ep = m->first_ep; - micro_unlock_service(m); - // Endpoints are never removed from the list (except when the service is - // destroyed, but that is after Stop's already been called), so we can iterate - // safely outside the lock. - if (drain_endpoints) + err = _new_endpoint_config(&new_cfg); + if (err == NULL) { - for (; ep != NULL; ep = ep->next) - { - if (err = micro_stop_endpoint(ep), err != NULL) - return microError_Wrapf(err, "failed to stop service %s", ep->config->Name); - } + memcpy(new_cfg, cfg, sizeof(microEndpointConfig)); } - micro_finalize_stopping_service(m); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Subject, cfg->Subject)); + + if (err != NULL) + { + _free_cloned_endpoint_config(new_cfg); + return err; + } + + *out = new_cfg; return NULL; } -bool microService_IsStopped(microService *m) +static void +_free_endpoint(microEndpoint *ep) { - bool stopped; - - if ((m == NULL) || (m->service_mu == NULL)) - return true; + if (ep == NULL) + return; - micro_lock_service(m); - stopped = m->stopped; - micro_unlock_service(m); + printf("<>/<> _free_endpoint: %s\n", ep->subject); - return stopped; + NATS_FREE(ep->name); + NATS_FREE(ep->subject); + natsSubscription_Destroy(ep->sub); + natsMutex_Destroy(ep->endpoint_mu); + _free_cloned_endpoint_config(ep->config); + NATS_FREE(ep); } -microError * -microService_Destroy(microService *m) +static bool +_find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find) { - microError *err = NULL; microEndpoint *ep = NULL; + microEndpoint *prev_ep = NULL; - err = microService_Stop(m); - if (err != NULL) - return err; + if ((m == NULL) || (to_find == NULL)) + return false; - // Unlink all endpoints from the service, they will self-destruct by their - // onComplete handler. - while (true) + for (ep = m->first_ep; ep != NULL; ep = ep->next) { - micro_lock_service(m); - - ep = m->first_ep; - if (ep == NULL) + if (ep == to_find) { - micro_unlock_service(m); - break; + *prevp = prev_ep; + return true; } - m->first_ep = ep->next; - micro_unlock_service(m); - - err = micro_destroy_endpoint(ep); - if (err != NULL) - return err; } - micro_release_service(m); - return NULL; + return false; } -microError * -microService_Run(microService *m) +static void +_retain_endpoint(microEndpoint *ep) { - if ((m == NULL) || (m->service_mu == NULL)) - return micro_ErrorInvalidArg; - - while (!microService_IsStopped(m)) - { - nats_Sleep(50); - } - - return NULL; + ep->refs++; + printf("<>/<> _retain_endpoint++: %s, refs: %d\n", ep->subject, ep->refs); } -void * -microService_GetState(microService *m) +static void +_retain_endpoint_l(microEndpoint *ep) { - if (m == NULL) - return NULL; + if (ep == NULL) + return; - return m->cfg->State; + _lock_endpoint(ep); + ++(ep->refs); + printf("<>/<> _retain_endpoint_l++: %s, refs: %d\n", ep->subject, ep->refs); + _unlock_endpoint(ep); } -static microError * -new_service(microService **ptr, natsConnection *nc) +static void +_release_endpoint(microEndpoint *ep) { - *ptr = NATS_CALLOC(1, sizeof(microService)); - if (*ptr == NULL) - return micro_ErrorOutOfMemory; + int refs; - natsConn_retain(nc); - (*ptr)->refs = 1; - (*ptr)->nc = nc; - (*ptr)->started = nats_Now() * 1000000; - return NULL; -} - -void micro_retain_service(microService *m) -{ - if (m == NULL) + if (ep == NULL) return; - micro_lock_service(m); - - ++(m->refs); + refs = --(ep->refs); + printf("<>/<> _release_endpoint--: %s, refs: %d\n", ep->subject, ep->refs); - micro_unlock_service(m); + if (refs == 0) + _free_endpoint(ep); } -void micro_release_service(microService *m) +static void +_release_endpoint_l(microEndpoint *ep) { - int refs = 0; + int refs; - if (m == NULL) + if (ep == NULL) return; - micro_lock_service(m); - - refs = --(m->refs); - printf("<>/<> release_service--: %d\n", refs); - - micro_unlock_service(m); + _lock_endpoint(ep); + refs = --(ep->refs); + printf("<>/<> _release_endpoint_l--: %s, refs: %d\n", ep->subject, ep->refs); + _unlock_endpoint(ep); if (refs == 0) - free_service(m); + _free_endpoint(ep); } -void micro_increment_endpoints_to_stop(microService *m) +static void +_free_cloned_service_config(microServiceConfig *cfg) { - if (m == NULL) + if (cfg == NULL) return; - micro_lock_service(m); - - ++(m->num_eps_to_stop); - - micro_unlock_service(m); + // the strings are declared const for the public, but in a clone these need + // to be freed. + NATS_FREE((char *)cfg->Name); + NATS_FREE((char *)cfg->Version); + NATS_FREE((char *)cfg->Description); + _free_cloned_endpoint_config(cfg->Endpoint); + NATS_FREE(cfg); } -void micro_decrement_endpoints_to_stop(microService *m) +static microError * +_clone_service_config(microServiceConfig **out, microServiceConfig *cfg) { - if (m == NULL) - return; + microError *err = NULL; + microServiceConfig *new_cfg = NULL; - micro_lock_service(m); + if (out == NULL || cfg == NULL) + return micro_ErrorInvalidArg; - if (m->num_eps_to_stop == 0) + err = new_service_config(&new_cfg); + if (err == NULL) { - micro_unlock_service(m); - fprintf(stderr, "FATAL ERROR: should be unreachable: unbalanced stopping refs on a microservice %s\n", m->cfg->Name); - return; + memcpy(new_cfg, cfg, sizeof(microServiceConfig)); + } + // the strings are declared const for the public, but in a clone these need + // to be duplicated. + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Version, cfg->Version)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Description, cfg->Description)); + MICRO_CALL(err, _clone_endpoint_config(&new_cfg->Endpoint, cfg->Endpoint)); + if (err != NULL) + { + _free_cloned_service_config(new_cfg); + return err; } - m->num_eps_to_stop--; + *out = new_cfg; + return NULL; +} - micro_unlock_service(m); +static void +_retain_service_l(microService *m) +{ + if (m == NULL) + return; - micro_finalize_stopping_service(m); + _lock_service(m); + + int refs = ++(m->refs); + printf("<>/<> _retain_service_l++: %s, refs: %d\n", m->cfg->Name, refs); + + _unlock_service(m); } -static void free_service(microService *m) +static void +_free_service(microService *m) { microGroup *next = NULL; if (m == NULL) return; + printf("<>/<> _free_service: %s\n", m->cfg->Name); + // destroy all groups. if (m->groups != NULL) { @@ -437,60 +469,185 @@ static void free_service(microService *m) } } - micro_free_cloned_service_config(m->cfg); + _free_cloned_service_config(m->cfg); natsConn_release(m->nc); natsMutex_Destroy(m->service_mu); NATS_FREE(m); } -static inline microError *new_service_config(microServiceConfig **ptr) +static void +_release_service(microService *m) { - *ptr = NATS_CALLOC(1, sizeof(microServiceConfig)); - return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; + int refs = 0; + + if (m == NULL) + return; + + refs = --(m->refs); + printf("<>/<> _release_service--: %s, refs: %d\n", m->cfg->Name, refs); + + if (refs == 0) + _free_service(m); } -microError * -micro_clone_service_config(microServiceConfig **out, microServiceConfig *cfg) +static void +_release_service_l(microService *m) { + int refs = 0; + + if (m == NULL) + return; + + _lock_service(m); + + refs = --(m->refs); + printf("<>/<> _release_service_l--: %s, refs: %d\n", m->cfg->Name, refs); + + _unlock_service(m); + + if (refs == 0) + _free_service(m); +} + +bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject) +{ + const char *e = ep_subject; + const char *a = actual_subject; + const char *etok, *enext; + int etok_len; + bool last_etok = false; + const char *atok, *anext; + int atok_len; + bool last_atok = false; + + if (e == NULL || a == NULL) + return false; + + while (true) + { + enext = strchr(e, '.'); + if (enext == NULL) + { + enext = e + strlen(e); + last_etok = true; + } + etok = e; + etok_len = (int)(enext - e); + e = enext + 1; + + anext = strchr(a, '.'); + if (anext == NULL) + { + anext = a + strlen(a); + last_atok = true; + } + atok = a; + atok_len = (int)(anext - a); + a = anext + 1; + + if (last_etok) + { + if (etok_len == 1 && etok[0] == '>') + return true; + + if (!last_atok) + return false; + } + if (!(etok_len == 1 && etok[0] == '*') && + !(etok_len == atok_len && strncmp(etok, atok, etok_len) == 0)) + { + return false; + } + if (last_atok) + { + return last_etok; + } + } +} + +static void +_on_service_error_l(microService *m, const char *subject, natsStatus s) +{ + microEndpoint *ep = NULL; microError *err = NULL; - microServiceConfig *new_cfg = NULL; - if (out == NULL || cfg == NULL) + if (m == NULL) + return; + + _lock_service(m); + for (ep = m->first_ep; + (ep != NULL) && !micro_match_endpoint_subject(ep->subject, subject); + ep = ep->next) + ; + _retain_endpoint_l(ep); + _unlock_service(m); + + if ((ep != NULL) && (m->cfg->ErrHandler != NULL)) + { + (*m->cfg->ErrHandler)(m, ep, s); + } + _release_endpoint_l(ep); + + if (ep != NULL) + { + err = microError_Wrapf(micro_ErrorFromStatus(s), "NATS error on endpoint %s", ep->subject); + micro_update_last_error(ep, err); + microError_Destroy(err); + } + + // TODO: Should we stop the service? The Go client does. + microError_Ignore(microService_Stop(m)); +} + +static microError * +_start_service_callbacks(microService *m) +{ + natsStatus s = NATS_OK; + + if (m == NULL) return micro_ErrorInvalidArg; - err = new_service_config(&new_cfg); - if (err == NULL) + if (service_callback_mu == NULL) { - memcpy(new_cfg, cfg, sizeof(microServiceConfig)); + IFOK(s, natsMutex_Create(&service_callback_mu)); + if (s != NATS_OK) + return micro_ErrorFromStatus(s); } - // the strings are declared const for the public, but in a clone these need - // to be duplicated. - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Version, cfg->Version)); - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Description, cfg->Description)); - MICRO_CALL(err, micro_clone_endpoint_config(&new_cfg->Endpoint, cfg->Endpoint)); - if (err != NULL) + + if (all_services_to_callback == NULL) { - micro_free_cloned_service_config(new_cfg); - return err; + IFOK(s, natsHash_Create(&all_services_to_callback, 8)); + if (s != NATS_OK) + return micro_ErrorFromStatus(s); } - *out = new_cfg; - return NULL; + // Extra reference to the service as long as its callbacks are registered. + printf("<>/<> _start_service_callbacks: %s: retaining service\n", m->cfg->Name); + _retain_service_l(m); + + natsMutex_Lock(service_callback_mu); + IFOK(s, natsHash_Set(all_services_to_callback, (int64_t)m, (void *)m, NULL)); + natsMutex_Unlock(service_callback_mu); + if (s != NATS_OK) + { + _release_service_l(m); + } + + return micro_ErrorFromStatus(s); } -void micro_free_cloned_service_config(microServiceConfig *cfg) +static void +_stop_service_callbacks(microService *m) { - if (cfg == NULL) + if ((m == NULL) || (all_services_to_callback == NULL) || (service_callback_mu == NULL)) return; - // the strings are declared const for the public, but in a clone these need - // to be freed. - NATS_FREE((char *)cfg->Name); - NATS_FREE((char *)cfg->Version); - NATS_FREE((char *)cfg->Description); - micro_free_cloned_endpoint_config(cfg->Endpoint); - NATS_FREE(cfg); + natsMutex_Lock(service_callback_mu); + natsHash_Remove(all_services_to_callback, (int64_t)m); + natsMutex_Unlock(service_callback_mu); + + printf("<>/<> _stop_service_callbacks: %s: releasing service\n", m->cfg->Name); + _release_service_l(m); } static microError * @@ -524,7 +681,7 @@ _services_for_connection(microService ***to_call, int *num_microservices, natsCo { if (m->nc == nc) { - micro_retain_service(m); // for the callback + _retain_service_l(m); // for the callback p[i++] = m; } } @@ -539,7 +696,7 @@ _services_for_connection(microService ***to_call, int *num_microservices, natsCo } static void -on_connection_closed(natsConnection *nc, void *ignored) +_on_connection_closed(natsConnection *nc, void *ignored) { microService *m = NULL; microService **to_call = NULL; @@ -562,48 +719,14 @@ on_connection_closed(natsConnection *nc, void *ignored) microError_Ignore( microService_Stop(m)); - micro_release_service(m); + _release_service_l(m); } NATS_FREE(to_call); } static void -on_service_error(microService *m, const char *subject, natsStatus s) -{ - microEndpoint *ep = NULL; - microError *err = NULL; - - if (m == NULL) - return; - - micro_lock_service(m); - for (ep = m->first_ep; - (ep != NULL) && !micro_match_endpoint_subject(ep->subject, subject); - ep = ep->next) - ; - micro_retain_endpoint(ep); - micro_unlock_service(m); - - if ((ep != NULL) && (m->cfg->ErrHandler != NULL)) - { - (*m->cfg->ErrHandler)(m, ep, s); - } - micro_release_endpoint(ep); - - if (ep != NULL) - { - err = microError_Wrapf(micro_ErrorFromStatus(s), "NATS error on endpoint %s", ep->name); - micro_update_last_error(ep, err); - microError_Destroy(err); - } - - // TODO: Should we stop the service? The Go client does. - microError_Ignore(microService_Stop(m)); -} - -static void -on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_used) +_on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_used) { microService *m = NULL; microService **to_call = NULL; @@ -612,10 +735,16 @@ on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_used int n = 0; int i; + printf("<>/<> on_error: %d\n", s); + if (sub == NULL) + { + printf("<>/<> on_error: no sub, nothing to do\n"); return; + } subject = natsSubscription_GetSubject(sub); + // `to_call` will have a list of retained service pointers. err = _services_for_connection(&to_call, &n, nc); if (err != NULL) { @@ -628,15 +757,15 @@ on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_used for (i = 0; i < n; i++) { m = to_call[i]; - on_service_error(m, subject, s); - micro_release_service(m); + _on_service_error_l(m, subject, s); + _release_service_l(m); // release the extra ref in `to_call`. } NATS_FREE(to_call); } static microError * -wrap_connection_event_callbacks(microService *m) +_wrap_connection_event_callbacks(microService *m) { microError *err = NULL; @@ -646,11 +775,64 @@ wrap_connection_event_callbacks(microService *m) // The new service must be in the list for this to work. MICRO_CALL(err, _start_service_callbacks(m)); MICRO_CALL(err, micro_ErrorFromStatus( - natsOptions_setMicroCallbacks(m->nc->opts, on_connection_closed, on_error))); + natsOptions_setMicroCallbacks(m->nc->opts, _on_connection_closed, _on_error))); return microError_Wrapf(err, "failed to wrap connection event callbacks"); } +bool microService_IsStopped(microService *m) +{ + bool stopped; + + if ((m == NULL) || (m->service_mu == NULL)) + return true; + + _lock_service(m); + stopped = m->stopped; + _unlock_service(m); + + return stopped; +} + +microError * +microService_Destroy(microService *m) +{ + microError *err = NULL; + + printf("<>/<> microService_Destroy: %s\n", m->cfg->Name); + + err = microService_Stop(m); + if (err != NULL) + return err; + + printf("<>/<> microService_Destroy: %s: called stop, releasing\n", m->cfg->Name); + _release_service_l(m); + return NULL; +} + +microError * +microService_Run(microService *m) +{ + if ((m == NULL) || (m->service_mu == NULL)) + return micro_ErrorInvalidArg; + + while (!microService_IsStopped(m)) + { + nats_Sleep(50); + } + + return NULL; +} + +void * +microService_GetState(microService *m) +{ + if (m == NULL) + return NULL; + + return m->cfg->State; +} + microError * microService_AddGroup(microGroup **new_group, microService *m, const char *prefix) { @@ -710,12 +892,326 @@ microService_GetConnection(microService *m) return m->nc; } +static void +_update_last_error(microEndpoint *ep, microError *err) +{ + ep->stats.NumErrors++; + microError_String(err, ep->stats.LastErrorString, sizeof(ep->stats.LastErrorString)); +} + +void micro_update_last_error(microEndpoint *ep, microError *err) +{ + if (err == NULL || ep == NULL) + return; + + _lock_endpoint(ep); + _update_last_error(ep, err); + _unlock_endpoint(ep); +} + +static void +_handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +{ + microError *err = NULL; + microError *service_err = NULL; + microEndpoint *ep = (microEndpoint *)closure; + microService *m; + microEndpointStats *stats = NULL; + microRequestHandler handler; + microRequest *req = NULL; + int64_t start, elapsed_ns = 0, full_s; + + if ((ep == NULL) || (ep->endpoint_mu == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) + { + // This would be a bug, we should not have received a message on this + // subscription. + return; + } + + stats = &ep->stats; + m = ep->service_ptr_for_on_complete; + handler = ep->config->Handler; + + err = micro_new_request(&req, m, ep, msg); + if (err == NULL) + { + // handle the request. + start = nats_NowInNanoSeconds(); + + service_err = handler(req); + if (service_err != NULL) + { + // if the handler returned an error, we attempt to respond with it. + // Note that if the handler chose to do its own RespondError which + // fails, and then the handler returns its error - we'll try to + // RespondError again, double-counting the error. + err = microRequest_RespondError(req, service_err); + } + + elapsed_ns = nats_NowInNanoSeconds() - start; + } + + // Update stats. + _lock_endpoint(ep); + stats->NumRequests++; + stats->ProcessingTimeNanoseconds += elapsed_ns; + full_s = stats->ProcessingTimeNanoseconds / 1000000000; + stats->ProcessingTimeSeconds += full_s; + stats->ProcessingTimeNanoseconds -= full_s * 1000000000; + _update_last_error(ep, err); + _unlock_endpoint(ep); + + microError_Destroy(err); + micro_free_request(req); + natsMsg_Destroy(msg); +} + +static void +_finalize_stopping_service_l(microService *m) +{ + if (m == NULL) + return; + + _lock_service(m); + + int n = 0; + for (microEndpoint *ep = m->first_ep; ep != NULL; ep = ep->next) + { + n++; + } + + // If the service is already stopped there is nothing to do. Also, if this + // function is called while the endpoints are still linked to the service, + // it means they are draining and we should wait for them to complete. + if (m->stopped || m->first_ep != NULL) + { + _unlock_service(m); + printf("<>/<> finalize_stopping_service_l: already stopped or draining %d\n", n); + return; + } + m->stopped = true; + _unlock_service(m); + + printf("<>/<> _finalize_stopping_service_l: stop service callbacks: %d draining\n", n); + // Disable any subsequent async callbacks. + _stop_service_callbacks(m); + + if (m->cfg->DoneHandler != NULL) + { + _retain_service_l(m); + m->cfg->DoneHandler(m); + _release_service_l(m); + } +} + +static void +_release_on_endpoint_complete(void *closure) +{ + microEndpoint *ep = (microEndpoint *)closure; + microEndpoint *prev_ep = NULL; + microService *m = NULL; + + if (ep == NULL) + return; + + printf("<>/<> _release_on_endpoint_complete: remove EP %s and finalize\n", ep->subject); + m = ep->service_ptr_for_on_complete; + if ((m == NULL) || (m->service_mu == NULL)) + return; + + _lock_endpoint(ep); + ep->is_draining = false; + _release_endpoint(ep); + _unlock_endpoint(ep); + + _lock_service(m); + if (_find_endpoint(&prev_ep, m, ep)) + { + if (prev_ep != NULL) + prev_ep->next = ep->next; + else + m->first_ep = ep->next; + } + _release_service(m); + _unlock_service(m); + + // If this is the last shutting down endpoint, finalize the service + // shutdown. + _finalize_stopping_service_l(m); +} + +static microError * +_start_endpoint_l(microService *m, microEndpoint *ep) +{ + natsStatus s = NATS_OK; + natsSubscription *sub = NULL; + + if ((ep->subject == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) + // nothing to do + return NULL; + + // reset the stats. + memset(&ep->stats, 0, sizeof(ep->stats)); + + if (ep->is_monitoring_endpoint) + s = natsConnection_Subscribe(&sub, m->nc, ep->subject, _handle_request, ep); + else + s = natsConnection_QueueSubscribe(&sub, m->nc, ep->subject, MICRO_QUEUE_GROUP, _handle_request, ep); + + if (s == NATS_OK) + { + // extra retain before subscribing since we'll need to hold it until + // on_complete on the subscription. + _lock_endpoint(ep); + _retain_endpoint(ep); + ep->sub = sub; + ep->is_draining = false; + _unlock_endpoint(ep); + + natsSubscription_SetOnCompleteCB(sub, _release_on_endpoint_complete, ep); + } + else + { + natsSubscription_Destroy(sub); // likely always a no-op. + } + + return micro_ErrorFromStatus(s); +} + +static microError * +_stop_endpoint_l(microService *m, microEndpoint *ep) +{ + natsStatus s = NATS_OK; + natsSubscription *sub = NULL; + bool conn_closed = natsConnection_IsClosed(m->nc); + + if (ep == NULL) + return NULL; + + _lock_endpoint(ep); + sub = ep->sub; + + if (ep->is_draining || conn_closed || !natsSubscription_IsValid(sub)) + { + // If stopping, _release_on_endpoint_complete will take care of + // finalizing, nothing else to do. In other cases + // _release_on_endpoint_complete has already been called. + _unlock_endpoint(ep); + return NULL; + } + + ep->is_draining = true; + _unlock_endpoint(ep); + + // When the drain is complete, will release the final ref on ep. + s = natsSubscription_Drain(sub); + if (s != NATS_OK) + { + return microError_Wrapf(micro_ErrorFromStatus(s), + "failed to stop endpoint %s: failed to drain subscription", ep->name); + } + + return NULL; +} + +static bool +_is_valid_subject(const char *subject) +{ + int i; + int len; + + if (subject == NULL) + return false; + + len = (int)strlen(subject); + if (len == 0) + return false; + + for (i = 0; i < len - 1; i++) + { + if ((subject[i] == ' ') || (subject[i] == '>')) + return false; + } + + if ((subject[i] == ' ')) + return false; + + return true; +} + +static microError * +_dup_with_prefix(char **dst, const char *prefix, const char *src) +{ + size_t len = strlen(src) + 1; + char *p; + + if (!nats_IsStringEmpty(prefix)) + len += strlen(prefix) + 1; + + *dst = NATS_CALLOC(1, len); + if (*dst == NULL) + return micro_ErrorOutOfMemory; + + p = *dst; + if (!nats_IsStringEmpty(prefix)) + { + len = strlen(prefix); + memcpy(p, prefix, len); + p[len] = '.'; + p += len + 1; + } + memcpy(p, src, strlen(src) + 1); + return NULL; +} + +static microError * +_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal) +{ + microError *err = NULL; + microEndpoint *ep = NULL; + const char *subj; + + if (cfg == NULL) + return microError_Wrapf(micro_ErrorInvalidArg, "NULL endpoint config"); + if (!_is_valid_name(cfg->Name)) + return microError_Wrapf(micro_ErrorInvalidArg, "invalid endpoint name %s", cfg->Name); + if (cfg->Handler == NULL) + return microError_Wrapf(micro_ErrorInvalidArg, "NULL endpoint request handler for %s", cfg->Name); + + if ((cfg->Subject != NULL) && !_is_valid_subject(cfg->Subject)) + return micro_ErrorInvalidArg; + + subj = nats_IsStringEmpty(cfg->Subject) ? cfg->Name : cfg->Subject; + + ep = NATS_CALLOC(1, sizeof(microEndpoint)); + if (ep == NULL) + return micro_ErrorOutOfMemory; + ep->is_monitoring_endpoint = is_internal; + + // retain `m` before storing it for callbacks. + _retain_service_l(m); + ep->service_ptr_for_on_complete = m; + + MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->endpoint_mu))); + MICRO_CALL(err, _clone_endpoint_config(&ep->config, cfg)); + MICRO_CALL(err, _dup_with_prefix(&ep->name, prefix, cfg->Name)); + MICRO_CALL(err, _dup_with_prefix(&ep->subject, prefix, subj)); + if (err != NULL) + { + _free_endpoint(ep); + return err; + } + + *new_ep = ep; + return NULL; +} + microError * microService_GetInfo(microServiceInfo **new_info, microService *m) { microServiceInfo *info = NULL; microEndpoint *ep = NULL; - int len = 0; + int len; if ((new_info == NULL) || (m == NULL) || (m->service_mu == NULL)) return micro_ErrorInvalidArg; @@ -730,17 +1226,25 @@ microService_GetInfo(microServiceInfo **new_info, microService *m) micro_strdup((char **)&info->Id, m->id); info->Type = MICRO_INFO_RESPONSE_TYPE; - micro_lock_service(m); + _lock_service(m); + + len = 0; + for (ep = m->first_ep; ep != NULL; ep = ep->next) + { + if ((!ep->is_monitoring_endpoint) && (ep->subject != NULL)) + len++; + } // Overallocate subjects, will filter out internal ones. - info->Subjects = NATS_CALLOC(m->num_eps, sizeof(char *)); + info->Subjects = NATS_CALLOC(len, sizeof(char *)); if (info->Subjects == NULL) { - micro_unlock_service(m); + _unlock_service(m); NATS_FREE(info); return micro_ErrorOutOfMemory; } + len = 0; for (ep = m->first_ep; ep != NULL; ep = ep->next) { if ((!ep->is_monitoring_endpoint) && (ep->subject != NULL)) @@ -750,7 +1254,7 @@ microService_GetInfo(microServiceInfo **new_info, microService *m) } } info->SubjectsLen = len; - micro_unlock_service(m); + _unlock_service(m); *new_info = info; return NULL; @@ -779,7 +1283,7 @@ microService_GetStats(microServiceStats **new_stats, microService *m) { microServiceStats *stats = NULL; microEndpoint *ep = NULL; - int len = 0; + int len; long double avg = 0.0; if ((new_stats == NULL) || (m == NULL) || (m->service_mu == NULL)) @@ -795,23 +1299,30 @@ microService_GetStats(microServiceStats **new_stats, microService *m) stats->Started = m->started; stats->Type = MICRO_STATS_RESPONSE_TYPE; - micro_lock_service(m); + _lock_service(m); + + len = 0; + for (ep = m->first_ep; ep != NULL; ep = ep->next) + { + if ((ep != NULL) && (!ep->is_monitoring_endpoint)) + len++; + } - // Allocate the actual structs, not pointers. Overallocate for the internal - // endpoints even though they are filtered out. - stats->Endpoints = NATS_CALLOC(m->num_eps, sizeof(microEndpointStats)); + // Allocate the actual structs, not pointers. + stats->Endpoints = NATS_CALLOC(len, sizeof(microEndpointStats)); if (stats->Endpoints == NULL) { - micro_unlock_service(m); + _unlock_service(m); NATS_FREE(stats); return micro_ErrorOutOfMemory; } + len = 0; for (ep = m->first_ep; ep != NULL; ep = ep->next) { if ((ep != NULL) && (!ep->is_monitoring_endpoint) && (ep->endpoint_mu != NULL)) { - micro_lock_endpoint(ep); + _lock_endpoint(ep); // copy the entire struct, including the last error buffer. stats->Endpoints[len] = ep->stats; @@ -821,11 +1332,11 @@ microService_GetStats(microServiceStats **new_stats, microService *m) avg = avg / (long double)ep->stats.NumRequests; stats->Endpoints[len].AverageProcessingTimeNanoseconds = (int64_t)avg; len++; - micro_unlock_endpoint(ep); + _unlock_endpoint(ep); } } - micro_unlock_service(m); + _unlock_service(m); stats->EndpointsLen = len; *new_stats = stats; @@ -850,3 +1361,24 @@ void microServiceStats_Destroy(microServiceStats *stats) NATS_FREE((char *)stats->Id); NATS_FREE(stats); } + +static bool +_is_valid_name(const char *name) +{ + int i; + int len; + + if (name == NULL) + return false; + + len = (int)strlen(name); + if (len == 0) + return false; + + for (i = 0; i < len; i++) + { + if (!isalnum(name[i]) && (name[i] != '_') && (name[i] != '-')) + return false; + } + return true; +} diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c deleted file mode 100644 index 49dae429e..000000000 --- a/src/micro_endpoint.c +++ /dev/null @@ -1,480 +0,0 @@ -// Copyright 2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include - -#include "microp.h" - -static microError *dup_with_prefix(char **dst, const char *prefix, const char *src); -static void free_endpoint(microEndpoint *ep); -static void handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); -static microError *new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); - -microError * -micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal) -{ - microError *err = NULL; - microEndpoint *ep = NULL; - const char *subj; - - if (cfg == NULL) - return microError_Wrapf(micro_ErrorInvalidArg, "NULL endpoint config"); - if (!micro_is_valid_name(cfg->Name)) - return microError_Wrapf(micro_ErrorInvalidArg, "invalid endpoint name %s", cfg->Name); - if (cfg->Handler == NULL) - return microError_Wrapf(micro_ErrorInvalidArg, "NULL endpoint request handler for %s", cfg->Name); - - if ((cfg->Subject != NULL) && !micro_is_valid_subject(cfg->Subject)) - return micro_ErrorInvalidArg; - - subj = nats_IsStringEmpty(cfg->Subject) ? cfg->Name : cfg->Subject; - - ep = NATS_CALLOC(1, sizeof(microEndpoint)); - if (ep == NULL) - return micro_ErrorOutOfMemory; - ep->refs = 1; - ep->is_monitoring_endpoint = is_internal; - micro_retain_service(m); - ep->m = m; - - MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->endpoint_mu))); - MICRO_CALL(err, micro_clone_endpoint_config(&ep->config, cfg)); - MICRO_CALL(err, dup_with_prefix(&ep->name, prefix, cfg->Name)); - MICRO_CALL(err, dup_with_prefix(&ep->subject, prefix, subj)); - if (err != NULL) - { - free_endpoint(ep); - return err; - } - - *new_ep = ep; - return NULL; -} - -static void _release_on_endpoint_complete(void *closure) -{ - microEndpoint *ep = (microEndpoint *)closure; - microService *m = NULL; - int n; - - if (ep == NULL) - return; - m = ep->m; - - micro_lock_endpoint(ep); - ep->is_draining = false; - n = --(ep->refs); - micro_unlock_endpoint(ep); - - micro_decrement_endpoints_to_stop(m); - - if (n == 0) - free_endpoint(ep); -} - -microError * -micro_start_endpoint(microEndpoint *ep) -{ - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; - - if ((ep->subject == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) - // nothing to do - return NULL; - - // reset the stats. - memset(&ep->stats, 0, sizeof(ep->stats)); - - if (ep->is_monitoring_endpoint) - s = natsConnection_Subscribe(&sub, ep->m->nc, ep->subject, handle_request, ep); - else - s = natsConnection_QueueSubscribe(&sub, ep->m->nc, ep->subject, MICRO_QUEUE_GROUP, handle_request, ep); - - if (s == NATS_OK) - { - // extra retain before subscribing since we'll need to hold it until - // on_complete on the subscription. - micro_retain_endpoint(ep); - - // Make sure the service is not stopped until this subscription has been closed. - micro_increment_endpoints_to_stop(ep->m); - - natsSubscription_SetOnCompleteCB(sub, _release_on_endpoint_complete, ep); - - micro_lock_endpoint(ep); - ep->sub = sub; - ep->is_draining = false; - micro_unlock_endpoint(ep); - } - else - { - natsSubscription_Destroy(sub); // likely always a no-op. - } - - return micro_ErrorFromStatus(s); -} - -microError * -micro_stop_endpoint(microEndpoint *ep) -{ - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; - bool conn_closed = natsConnection_IsClosed(ep->m->nc); - - if (ep == NULL) - return NULL; - - micro_lock_endpoint(ep); - sub = ep->sub; - - if (ep->is_draining || conn_closed || !natsSubscription_IsValid(sub)) - { - // If stopping, _release_on_endpoint_complete will take care of - // finalizing, nothing else to do. In other cases - // _release_on_endpoint_complete has already been called. - micro_unlock_endpoint(ep); - return NULL; - } - - ep->is_draining = true; - micro_unlock_endpoint(ep); - - // When the drain is complete, will release the final ref on ep. - s = natsSubscription_Drain(sub); - if (s != NATS_OK) - { - return microError_Wrapf(micro_ErrorFromStatus(s), - "failed to stop endpoint %s: failed to drain subscription", ep->name); - } - - return NULL; -} - -microError * -micro_destroy_endpoint(microEndpoint *ep) -{ - microError *err = NULL; - - if (ep == NULL) - return NULL; - - printf("<>/<> micro_destroy_endpoint: %s\n", ep->name); - - if (err = micro_stop_endpoint(ep), err != NULL) - return err; - - // Release ep since it's no longer running (to compensate for the retain in - // micro_start_endpoint). - micro_release_endpoint(ep); - return NULL; -} - -void -micro_retain_endpoint(microEndpoint *ep) -{ - if (ep == NULL) - return; - - micro_lock_endpoint(ep); - - ep->refs++; - - micro_unlock_endpoint(ep); -} - -void -micro_release_endpoint(microEndpoint *ep) -{ - int refs = 0; - - if (ep == NULL) - return; - - micro_lock_endpoint(ep); - - refs = --(ep->refs); - - printf("<>/<> release_endpoint--: %s: %d\n", ep->name, refs);fflush(stdout); - - micro_unlock_endpoint(ep); - - if (refs == 0) - free_endpoint(ep); -} - -void free_endpoint(microEndpoint *ep) -{ - if (ep == NULL) - return; - - printf("<>/<> free_endpoint: %s\n", ep->name);fflush(stdout); - - micro_release_service(ep->m); - NATS_FREE(ep->name); - NATS_FREE(ep->subject); - natsSubscription_Destroy(ep->sub); - natsMutex_Destroy(ep->endpoint_mu); - micro_free_cloned_endpoint_config(ep->config); - NATS_FREE(ep); -} - -static void update_last_error(microEndpoint *ep, microError *err) -{ - ep->stats.NumErrors++; - microError_String(err, ep->stats.LastErrorString, sizeof(ep->stats.LastErrorString)); -} - -static void -handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - microError *err = NULL; - microError *service_err = NULL; - microEndpoint *ep = (microEndpoint *)closure; - microEndpointStats *stats = NULL; - microRequestHandler handler; - microRequest *req = NULL; - int64_t start, elapsed_ns = 0, full_s; - - if ((ep == NULL) || (ep->endpoint_mu == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) - { - // This would be a bug, we should not have received a message on this - // subscription. - return; - } - - stats = &ep->stats; - handler = ep->config->Handler; - - err = micro_new_request(&req, ep->m, ep, msg); - if (err == NULL) - { - // handle the request. - start = nats_NowInNanoSeconds(); - - service_err = handler(req); - if (service_err != NULL) - { - // if the handler returned an error, we attempt to respond with it. - // Note that if the handler chose to do its own RespondError which - // fails, and then the handler returns its error - we'll try to - // RespondError again, double-counting the error. - err = microRequest_RespondError(req, service_err); - } - - elapsed_ns = nats_NowInNanoSeconds() - start; - } - - // Update stats. - micro_lock_endpoint(ep); - - stats->NumRequests++; - stats->ProcessingTimeNanoseconds += elapsed_ns; - full_s = stats->ProcessingTimeNanoseconds / 1000000000; - stats->ProcessingTimeSeconds += full_s; - stats->ProcessingTimeNanoseconds -= full_s * 1000000000; - update_last_error(ep, err); - - micro_unlock_endpoint(ep); - - microError_Destroy(err); - micro_free_request(req); - natsMsg_Destroy(msg); -} - -void micro_update_last_error(microEndpoint *ep, microError *err) -{ - if (err == NULL || ep == NULL) - return; - - micro_lock_endpoint(ep); - update_last_error(ep, err); - micro_unlock_endpoint(ep); -} - -bool micro_is_valid_name(const char *name) -{ - int i; - int len; - - if (name == NULL) - return false; - - len = (int)strlen(name); - if (len == 0) - return false; - - for (i = 0; i < len; i++) - { - if (!isalnum(name[i]) && (name[i] != '_') && (name[i] != '-')) - return false; - } - return true; -} - -bool micro_is_valid_subject(const char *subject) -{ - int i; - int len; - - if (subject == NULL) - return false; - - len = (int)strlen(subject); - if (len == 0) - return false; - - for (i = 0; i < len - 1; i++) - { - if ((subject[i] == ' ') || (subject[i] == '>')) - return false; - } - - if ((subject[i] == ' ')) - return false; - - return true; -} - -static inline microError * -new_endpoint_config(microEndpointConfig **ptr) -{ - *ptr = NATS_CALLOC(1, sizeof(microEndpointConfig)); - return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; -} - -microError * -micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) -{ - microError *err = NULL; - microEndpointConfig *new_cfg = NULL; - - if (out == NULL) - return micro_ErrorInvalidArg; - - if (cfg == NULL) - { - *out = NULL; - return NULL; - } - - err = new_endpoint_config(&new_cfg); - if (err == NULL) - { - memcpy(new_cfg, cfg, sizeof(microEndpointConfig)); - } - - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Subject, cfg->Subject)); - - if (err != NULL) - { - micro_free_cloned_endpoint_config(new_cfg); - return err; - } - - *out = new_cfg; - return NULL; -} - -void micro_free_cloned_endpoint_config(microEndpointConfig *cfg) -{ - if (cfg == NULL) - return; - - // the strings are declared const for the public, but in a clone these need - // to be freed. - NATS_FREE((char *)cfg->Name); - NATS_FREE((char *)cfg->Subject); - - NATS_FREE(cfg); -} - -bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject) -{ - const char *e = ep_subject; - const char *a = actual_subject; - const char *etok, *enext; - int etok_len; - bool last_etok = false; - const char *atok, *anext; - int atok_len; - bool last_atok = false; - - if (e == NULL || a == NULL) - return false; - - while (true) - { - enext = strchr(e, '.'); - if (enext == NULL) - { - enext = e + strlen(e); - last_etok = true; - } - etok = e; - etok_len = (int)(enext - e); - e = enext + 1; - - anext = strchr(a, '.'); - if (anext == NULL) - { - anext = a + strlen(a); - last_atok = true; - } - atok = a; - atok_len = (int)(anext - a); - a = anext + 1; - - if (last_etok) - { - if (etok_len == 1 && etok[0] == '>') - return true; - - if (!last_atok) - return false; - } - if (!(etok_len == 1 && etok[0] == '*') && - !(etok_len == atok_len && strncmp(etok, atok, etok_len) == 0)) - { - return false; - } - if (last_atok) - { - return last_etok; - } - } -} - -static microError *dup_with_prefix(char **dst, const char *prefix, const char *src) -{ - size_t len = strlen(src) + 1; - char *p; - - if (!nats_IsStringEmpty(prefix)) - len += strlen(prefix) + 1; - - *dst = NATS_CALLOC(1, len); - if (*dst == NULL) - return micro_ErrorOutOfMemory; - - p = *dst; - if (!nats_IsStringEmpty(prefix)) - { - len = strlen(prefix); - memcpy(p, prefix, len); - p[len] = '.'; - p += len + 1; - } - memcpy(p, src, strlen(src) + 1); - return NULL; -} diff --git a/src/microp.h b/src/microp.h index 00c368127..243bb5e2c 100644 --- a/src/microp.h +++ b/src/microp.h @@ -53,7 +53,6 @@ struct micro_endpoint_s char *subject; // References to other entities. - microService *m; microEndpointConfig *config; // Monitoring endpoints are different in a few ways. For now, express it as @@ -73,6 +72,10 @@ struct micro_endpoint_s int refs; bool is_draining; + // Not retained by the endpoint, retained before passing to the + // service-level callbacks. + microService *service_ptr_for_on_complete; + // The subscription for the endpoint. If NULL, the endpoint is stopped. natsSubscription *sub; @@ -105,10 +108,8 @@ struct micro_service_s // need to be protected by mutex. natsMutex *service_mu; int refs; - int num_eps_to_stop; struct micro_endpoint_s *first_ep; - int num_eps; int64_t started; // UTC time expressed as number of nanoseconds since epoch. // true once the stopping of the service, i.e. draining of endpoints' @@ -145,50 +146,16 @@ extern microError *micro_ErrorOutOfMemory; extern microError *micro_ErrorInvalidArg; microError *micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); -microError *micro_clone_endpoint_config(microEndpointConfig **new_cfg, microEndpointConfig *cfg); -microError *micro_clone_service_config(microServiceConfig **new_cfg, microServiceConfig *cfg); -void micro_decrement_endpoints_to_stop(microService *m); -microError *micro_destroy_endpoint(microEndpoint *ep); -void micro_free_cloned_endpoint_config(microEndpointConfig *cfg); -void micro_free_cloned_service_config(microServiceConfig *cfg); -void micro_free_request(microRequest *req); -void micro_increment_endpoints_to_stop(microService *m); microError *micro_init_monitoring(microService *m); microError *micro_is_error_message(natsStatus s, natsMsg *msg); -bool micro_is_valid_name(const char *name); -bool micro_is_valid_subject(const char *subject); -bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject); -microError *micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); -microError *micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); microError *micro_new_request(microRequest **new_request, microService *m, microEndpoint *ep, natsMsg *msg); -void micro_release_endpoint(microEndpoint *ep); -void micro_release_service(microService *m); -void micro_retain_endpoint(microEndpoint *ep); -void micro_retain_service(microService *m); -microError *micro_start_endpoint(microEndpoint *ep); -microError *micro_stop_endpoint(microEndpoint *ep); -void micro_update_last_error(microEndpoint *ep, microError *err); - - -static inline void micro_lock_service(microService *m) { natsMutex_Lock(m->service_mu); } -static inline void micro_unlock_service(microService *m) { natsMutex_Unlock(m->service_mu); } -static inline void micro_lock_endpoint(microEndpoint *ep) { natsMutex_Lock(ep->endpoint_mu); } -static inline void micro_unlock_endpoint(microEndpoint *ep) { natsMutex_Unlock(ep->endpoint_mu); } +void micro_free_request(microRequest *req); +void micro_update_last_error(microEndpoint *ep, microError *err); -static inline microError *micro_strdup(char **ptr, const char *str) -{ - // Make a strdup(NULL) be a no-op, so we don't have to check for NULL - // everywhere. - if (str == NULL) - { - *ptr = NULL; - return NULL; - } - *ptr = NATS_STRDUP(str); - if (*ptr == NULL) - return micro_ErrorOutOfMemory; - return NULL; -} +// +// Exposed only for testing. +bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject); +microError *micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); #endif /* MICROP_H_ */ diff --git a/src/nats.c b/src/nats.c index b26e11cfd..f011f9a5d 100644 --- a/src/nats.c +++ b/src/nats.c @@ -763,10 +763,14 @@ _asyncCbsThread(void *arg) switch (cb->type) { case ASYNC_CLOSED: - (*(nc->opts->closedCb))(nc, nc->opts->closedCbClosure); - if (nc->opts->microClosedCb != NULL) - (*(nc->opts->microClosedCb))(nc, nc->opts->closedCbClosure); + { + (*(nc->opts->closedCb))(nc, nc->opts->closedCbClosure); + + if (nc->opts->microClosedCb != NULL) + (*(nc->opts->microClosedCb))(nc, nc->opts->closedCbClosure); break; + } + case ASYNC_DISCONNECTED: (*(nc->opts->disconnectedCb))(nc, nc->opts->disconnectedCbClosure); break; @@ -786,10 +790,12 @@ _asyncCbsThread(void *arg) { if (cb->errTxt != NULL) nats_setErrStatusAndTxt(cb->err, cb->errTxt); - (*(nc->opts->asyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); if (nc->opts->microAsyncErrCb != NULL) (*(nc->opts->microAsyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); + + (*(nc->opts->asyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); + break; } #if defined(NATS_HAS_STREAMING) diff --git a/test/list.txt b/test/list.txt index 901655645..f416dc4f1 100644 --- a/test/list.txt +++ b/test/list.txt @@ -1,8 +1 @@ -MicroMatchEndpointSubject -MicroAddService -MicroGroups -MicroBasics -MicroStartStop -MicroServiceStopsOnClosedConn MicroServiceStopsWhenServerStops -MicroAsyncErrorHandler diff --git a/test/test.c b/test/test.c index f0241768d..d7f4f2d0f 100644 --- a/test/test.c +++ b/test/test.c @@ -119,6 +119,7 @@ struct threadArg int64_t reconnectedAt[4]; int reconnects; bool msgReceived; + bool microDone; bool done; int results[10]; const char *tokens[3]; @@ -32767,10 +32768,9 @@ void micro_service_done_handler(microService *m) struct threadArg *arg = (struct threadArg*) microService_GetState(m); natsMutex_Lock(arg->m); - arg->done = true; + arg->microDone = true; natsCondition_Broadcast(arg->c); natsMutex_Unlock(arg->m); - } static void @@ -32821,15 +32821,20 @@ test_MicroServiceStopsOnClosedConn(void) test("Close the connection: "); testCond(NATS_OK == natsConnection_Drain(nc)); natsConnection_Close(nc); + + test("Wait for it: "); + _waitForConnClosed(&arg); + testCond(1); + test("Ensure the connection has closed: "); testCond(natsConnection_IsClosed(nc)); test("Wait for the service to stop: "); natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) + while ((s != NATS_TIMEOUT) && !arg.microDone) s = natsCondition_TimedWait(arg.c, arg.m, 1000); natsMutex_Unlock(arg.m); - testCond(arg.done); + testCond(arg.microDone); test("Test microservice is stopped: "); testCond(microService_IsStopped(m)); @@ -32888,10 +32893,10 @@ test_MicroServiceStopsWhenServerStops(void) test("Wait for the service to stop: "); natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.done) + while ((s != NATS_TIMEOUT) && !arg.microDone) s = natsCondition_TimedWait(arg.c, arg.m, 1000); natsMutex_Unlock(arg.m); - testCond(arg.done); + testCond(arg.microDone); test("Test microservice is not running: "); testCond(microService_IsStopped(m)) @@ -32914,7 +32919,6 @@ void micro_async_error_handler(microService *m, microEndpoint *ep, natsStatus s) // set the data to verify arg->status = s; - arg->string = nats_GetLastError(NULL); natsCondition_Broadcast(arg->c); natsMutex_Unlock(arg->m); } @@ -35511,14 +35515,14 @@ static testInfo allTests[] = // {"KeyValueMirrorDirectGet", test_KeyValueMirrorDirectGet}, // {"KeyValueMirrorCrossDomains", test_KeyValueMirrorCrossDomains}, - {"MicroMatchEndpointSubject", test_MicroMatchEndpointSubject}, - {"MicroAddService", test_MicroAddService}, - {"MicroGroups", test_MicroGroups}, - {"MicroBasics", test_MicroBasics}, - {"MicroStartStop", test_MicroStartStop}, - {"MicroServiceStopsOnClosedConn", test_MicroServiceStopsOnClosedConn}, + // {"MicroMatchEndpointSubject", test_MicroMatchEndpointSubject}, + // {"MicroAddService", test_MicroAddService}, + // {"MicroGroups", test_MicroGroups}, + // {"MicroBasics", test_MicroBasics}, + // {"MicroStartStop", test_MicroStartStop}, + // {"MicroServiceStopsOnClosedConn", test_MicroServiceStopsOnClosedConn}, {"MicroServiceStopsWhenServerStops", test_MicroServiceStopsWhenServerStops}, - {"MicroAsyncErrorHandler", test_MicroAsyncErrorHandler}, + // {"MicroAsyncErrorHandler", test_MicroAsyncErrorHandler}, #if defined(NATS_HAS_STREAMING) // {"StanPBufAllocator", test_StanPBufAllocator}, From c38ed2741a1613b08f032cdc705ac9e888221fdf Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Wed, 31 May 2023 17:07:17 -0700 Subject: [PATCH 60/85] reworked wip2 --- src/micro.c | 5 ++++- src/micro_monitoring.c | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/micro.c b/src/micro.c index 783e89df2..c43daab61 100644 --- a/src/micro.c +++ b/src/micro.c @@ -313,7 +313,10 @@ _free_endpoint(microEndpoint *ep) natsSubscription_Destroy(ep->sub); natsMutex_Destroy(ep->endpoint_mu); _free_cloned_endpoint_config(ep->config); - NATS_FREE(ep); + + + + // NATS_FREE(ep); } static bool diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index b79b53377..0c5e33e6b 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -35,8 +35,8 @@ micro_init_monitoring(microService *m) { microError *err = NULL; MICRO_CALL(err, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); - MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); - MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); + // MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); + // MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); return err; } @@ -205,15 +205,15 @@ add_verb_handlers(microService *m, const char *verb, microRequestHandler handler snprintf(name, sizeof(name), "%s-all", verb); err = add_internal_handler(m, verb, "", "", name, handler); - if (err == NULL) - { - snprintf(name, sizeof(name), "%s-kind", verb); - err = add_internal_handler(m, verb, m->cfg->Name, "", name, handler); - } - if (err == NULL) - { - err = add_internal_handler(m, verb, m->cfg->Name, m->id, verb, handler); - } + // if (err == NULL) + // { + // snprintf(name, sizeof(name), "%s-kind", verb); + // err = add_internal_handler(m, verb, m->cfg->Name, "", name, handler); + // } + // if (err == NULL) + // { + // err = add_internal_handler(m, verb, m->cfg->Name, m->id, verb, handler); + // } return err; } From 3d0d3abe00f153dbb26ae3d2b75ca0068797cc8f Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Wed, 31 May 2023 23:29:45 -0700 Subject: [PATCH 61/85] reworked wip3 --- src/micro.c | 254 +++++++++++++++++++++-------------------- src/micro_monitoring.c | 22 ++-- test/list.txt | 7 ++ test/test.c | 14 +-- 4 files changed, 157 insertions(+), 140 deletions(-) diff --git a/src/micro.c b/src/micro.c index c43daab61..ede0cd204 100644 --- a/src/micro.c +++ b/src/micro.c @@ -35,10 +35,43 @@ static microError *_wrap_connection_event_callbacks(microService *m); static bool _is_valid_name(const char *name); static void _finalize_stopping_service_l(microService *m); -static void _release_endpoint(microEndpoint *ep); -static void _release_endpoint_l(microEndpoint *ep); -static void _release_service(microService *m); -static void _retain_service_l(microService *m); +static void _retain_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *comment); +static void _release_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *comment); +static void _release_service(microService *m, bool lock, const char *caller, const char *comment); +static void _retain_service(microService *m, bool lock, const char *caller, const char *comment); + +#define RETAIN_EP(__ep, __comment) _retain_endpoint(__ep, true, __comment, __func__) +#define RETAIN_EP_INLINE(__ep, __comment) _retain_endpoint(__ep, false, __comment, __func__) +#define RELEASE_EP(__ep, __comment) _release_endpoint(__ep, true, __comment, __func__) +#define RELEASE_EP_INLINE(__ep, __comment) _release_endpoint(__ep, false, __comment, __func__) +#define RETAIN_SERVICE(__m, __comment) _retain_service(__m, true, __comment, __func__) +#define RETAIN_SERVICE_INLINE(__m, __comment) _retain_service(__m, false, __comment, __func__) +#define RELEASE_SERVICE(__m, __comment) _release_service(__m, true, __comment, __func__) +#define RELEASE_SERVICE_INLINE(__m, __comment) _release_service(__m, false, __comment, __func__) + +static inline int _endpoint_count(microService *m) +{ + int n = 0; + for (microEndpoint *ep = m->first_ep; ep != NULL; ep = ep->next) + { + n++; + } + return n; +} + +static inline void _dump_endpoints(microService *m) +{ + const char *sep = NULL; + for (microEndpoint *ep = m->first_ep; ep != NULL; ep = ep->next) + { + if (sep == NULL) + sep = ","; + else + printf("%s", sep); + printf("%s", ep->subject); + } + printf("\n"); +} microError * micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *cfg) @@ -159,8 +192,7 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, // list, not started. A retry with the same name will clean it up. if (err = _stop_endpoint_l(m, prev_ep), err != NULL) return err; - - _release_endpoint_l(prev_ep); + RELEASE_EP(prev_ep, "replaced ep"); } if (err = _start_endpoint_l(m, ep), err != NULL) @@ -313,10 +345,7 @@ _free_endpoint(microEndpoint *ep) natsSubscription_Destroy(ep->sub); natsMutex_Destroy(ep->endpoint_mu); _free_cloned_endpoint_config(ep->config); - - - - // NATS_FREE(ep); + NATS_FREE(ep); } static bool @@ -328,10 +357,13 @@ _find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find) if ((m == NULL) || (to_find == NULL)) return false; + printf("<>/<> _find_endpoint: looking for %s\n", to_find->subject); for (ep = m->first_ep; ep != NULL; ep = ep->next) { + printf("<>/<> _find_endpoint: ...scanning %s\n", ep->subject); if (ep == to_find) { + printf("<>/<> _find_endpoint: found! prev: %s\n", prev_ep? prev_ep->subject : "NULL"); *prevp = prev_ep; return true; } @@ -341,51 +373,37 @@ _find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find) } static void -_retain_endpoint(microEndpoint *ep) -{ - ep->refs++; - printf("<>/<> _retain_endpoint++: %s, refs: %d\n", ep->subject, ep->refs); -} - -static void -_retain_endpoint_l(microEndpoint *ep) +_retain_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *comment) { if (ep == NULL) return; - _lock_endpoint(ep); - ++(ep->refs); - printf("<>/<> _retain_endpoint_l++: %s, refs: %d\n", ep->subject, ep->refs); - _unlock_endpoint(ep); -} - -static void -_release_endpoint(microEndpoint *ep) -{ - int refs; - - if (ep == NULL) - return; + if (lock) + _lock_endpoint(ep); - refs = --(ep->refs); - printf("<>/<> _release_endpoint--: %s, refs: %d\n", ep->subject, ep->refs); + ep->refs++; + printf("<>/<> _retain_endpoint++: %s by %s (%s), refs: %d\n", ep->subject, caller ? caller : "NULL", comment ? comment : "NULL", ep->refs); - if (refs == 0) - _free_endpoint(ep); + if (lock) + _unlock_endpoint(ep); } static void -_release_endpoint_l(microEndpoint *ep) +_release_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *comment) { int refs; if (ep == NULL) return; - _lock_endpoint(ep); + if (lock) + _lock_endpoint(ep); + refs = --(ep->refs); - printf("<>/<> _release_endpoint_l--: %s, refs: %d\n", ep->subject, ep->refs); - _unlock_endpoint(ep); + printf("<>/<> _release_endpoint--: %s by %s (%s), refs: %d\n", ep->subject, caller ? caller : "NULL", comment ? comment : "NULL", ep->refs); + + if (lock) + _unlock_endpoint(ep); if (refs == 0) _free_endpoint(ep); @@ -437,17 +455,19 @@ _clone_service_config(microServiceConfig **out, microServiceConfig *cfg) } static void -_retain_service_l(microService *m) +_retain_service(microService *m, bool lock, const char *caller, const char *comment) { if (m == NULL) return; - _lock_service(m); + if (lock) + _lock_service(m); int refs = ++(m->refs); - printf("<>/<> _retain_service_l++: %s, refs: %d\n", m->cfg->Name, refs); + printf("<>/<> _retain_service++: %s by %s (%s), refs: %d\n", m->cfg->Name, caller ? caller : "NULL", comment ? comment : "NULL", refs); - _unlock_service(m); + if (lock) + _unlock_service(m); } static void @@ -479,34 +499,21 @@ _free_service(microService *m) } static void -_release_service(microService *m) -{ - int refs = 0; - - if (m == NULL) - return; - - refs = --(m->refs); - printf("<>/<> _release_service--: %s, refs: %d\n", m->cfg->Name, refs); - - if (refs == 0) - _free_service(m); -} - -static void -_release_service_l(microService *m) +_release_service(microService *m, bool lock, const char *caller, const char *comment) { int refs = 0; if (m == NULL) return; - _lock_service(m); + if (lock) + _lock_service(m); refs = --(m->refs); - printf("<>/<> _release_service_l--: %s, refs: %d\n", m->cfg->Name, refs); + printf("<>/<> _release_service--: %s by %s (%s), refs: %d\n", m->cfg->Name, caller ? caller : "NULL", comment ? comment : "NULL", refs); - _unlock_service(m); + if (lock) + _unlock_service(m); if (refs == 0) _free_service(m); @@ -568,40 +575,6 @@ bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_sub } } -static void -_on_service_error_l(microService *m, const char *subject, natsStatus s) -{ - microEndpoint *ep = NULL; - microError *err = NULL; - - if (m == NULL) - return; - - _lock_service(m); - for (ep = m->first_ep; - (ep != NULL) && !micro_match_endpoint_subject(ep->subject, subject); - ep = ep->next) - ; - _retain_endpoint_l(ep); - _unlock_service(m); - - if ((ep != NULL) && (m->cfg->ErrHandler != NULL)) - { - (*m->cfg->ErrHandler)(m, ep, s); - } - _release_endpoint_l(ep); - - if (ep != NULL) - { - err = microError_Wrapf(micro_ErrorFromStatus(s), "NATS error on endpoint %s", ep->subject); - micro_update_last_error(ep, err); - microError_Destroy(err); - } - - // TODO: Should we stop the service? The Go client does. - microError_Ignore(microService_Stop(m)); -} - static microError * _start_service_callbacks(microService *m) { @@ -625,15 +598,14 @@ _start_service_callbacks(microService *m) } // Extra reference to the service as long as its callbacks are registered. - printf("<>/<> _start_service_callbacks: %s: retaining service\n", m->cfg->Name); - _retain_service_l(m); + RETAIN_SERVICE(m, "Started service callbacks"); natsMutex_Lock(service_callback_mu); IFOK(s, natsHash_Set(all_services_to_callback, (int64_t)m, (void *)m, NULL)); natsMutex_Unlock(service_callback_mu); if (s != NATS_OK) { - _release_service_l(m); + RELEASE_SERVICE(m, "in error"); } return micro_ErrorFromStatus(s); @@ -649,8 +621,7 @@ _stop_service_callbacks(microService *m) natsHash_Remove(all_services_to_callback, (int64_t)m); natsMutex_Unlock(service_callback_mu); - printf("<>/<> _stop_service_callbacks: %s: releasing service\n", m->cfg->Name); - _release_service_l(m); + RELEASE_SERVICE(m, ""); } static microError * @@ -684,7 +655,7 @@ _services_for_connection(microService ***to_call, int *num_microservices, natsCo { if (m->nc == nc) { - _retain_service_l(m); // for the callback + RETAIN_SERVICE(m, "For the specific connection closed/error callback"); // for the callback p[i++] = m; } } @@ -707,6 +678,8 @@ _on_connection_closed(natsConnection *nc, void *ignored) int n = 0; int i; + printf("<>/<> on_connection_closed: CALLED\n"); + err = _services_for_connection(&to_call, &n, nc); if (err != NULL) { @@ -722,12 +695,46 @@ _on_connection_closed(natsConnection *nc, void *ignored) microError_Ignore( microService_Stop(m)); - _release_service_l(m); + RELEASE_SERVICE(m, "After connection closed callback"); } NATS_FREE(to_call); } +static void +_on_service_error_l(microService *m, const char *subject, natsStatus s) +{ + microEndpoint *ep = NULL; + microError *err = NULL; + + if (m == NULL) + return; + + _lock_service(m); + for (ep = m->first_ep; + (ep != NULL) && !micro_match_endpoint_subject(ep->subject, subject); + ep = ep->next) + ; + RETAIN_EP(ep, "For error callback"); // for the callback + _unlock_service(m); + + if ((ep != NULL) && (m->cfg->ErrHandler != NULL)) + { + (*m->cfg->ErrHandler)(m, ep, s); + } + RELEASE_EP(ep, "After error callback"); // after the callback + + if (ep != NULL) + { + err = microError_Wrapf(micro_ErrorFromStatus(s), "NATS error on endpoint %s", ep->subject); + micro_update_last_error(ep, err); + microError_Destroy(err); + } + + // TODO: Should we stop the service? The Go client does. + microError_Ignore(microService_Stop(m)); +} + static void _on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_used) { @@ -738,11 +745,11 @@ _on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_use int n = 0; int i; - printf("<>/<> on_error: %d\n", s); + printf("<>/<> on_error: CALLED: %d\n", s); if (sub == NULL) { - printf("<>/<> on_error: no sub, nothing to do\n"); + printf("<>/<> on_error: no sub, nothing to do\n"); return; } subject = natsSubscription_GetSubject(sub); @@ -761,7 +768,7 @@ _on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_use { m = to_call[i]; _on_service_error_l(m, subject, s); - _release_service_l(m); // release the extra ref in `to_call`. + RELEASE_SERVICE(m, "After on_error callback"); // release the extra ref in `to_call`. } NATS_FREE(to_call); @@ -808,8 +815,7 @@ microService_Destroy(microService *m) if (err != NULL) return err; - printf("<>/<> microService_Destroy: %s: called stop, releasing\n", m->cfg->Name); - _release_service_l(m); + RELEASE_SERVICE(m, "Destroy"); return NULL; } @@ -977,33 +983,27 @@ _finalize_stopping_service_l(microService *m) _lock_service(m); - int n = 0; - for (microEndpoint *ep = m->first_ep; ep != NULL; ep = ep->next) - { - n++; - } - // If the service is already stopped there is nothing to do. Also, if this // function is called while the endpoints are still linked to the service, // it means they are draining and we should wait for them to complete. if (m->stopped || m->first_ep != NULL) { + printf("<>/<> finalize_stopping_service_l: already stopped or draining %d\n", _endpoint_count(m)); _unlock_service(m); - printf("<>/<> finalize_stopping_service_l: already stopped or draining %d\n", n); return; } m->stopped = true; _unlock_service(m); - printf("<>/<> _finalize_stopping_service_l: stop service callbacks: %d draining\n", n); + printf("<>/<> _finalize_stopping_service_l: stop service callbacks: not yet stopped, and nothing draining\n"); // Disable any subsequent async callbacks. _stop_service_callbacks(m); if (m->cfg->DoneHandler != NULL) { - _retain_service_l(m); + RETAIN_SERVICE(m, "For DONE handler"); m->cfg->DoneHandler(m); - _release_service_l(m); + RELEASE_SERVICE(m, "After DONE handler"); } } @@ -1024,10 +1024,13 @@ _release_on_endpoint_complete(void *closure) _lock_endpoint(ep); ep->is_draining = false; - _release_endpoint(ep); _unlock_endpoint(ep); _lock_service(m); + + printf("<>/<> _release_on_endpoint_complete: %s: endpoints before removal:", ep->subject); + _dump_endpoints(m); + if (_find_endpoint(&prev_ep, m, ep)) { if (prev_ep != NULL) @@ -1035,9 +1038,15 @@ _release_on_endpoint_complete(void *closure) else m->first_ep = ep->next; } - _release_service(m); + RELEASE_SERVICE(m, "after removing EP"); + + printf("<>/<> _release_on_endpoint_complete: %s: endpoints after removal:", ep->subject); + _dump_endpoints(m); + RELEASE_EP(ep, "drained or closed endpoint"); + _unlock_service(m); + // If this is the last shutting down endpoint, finalize the service // shutdown. _finalize_stopping_service_l(m); @@ -1066,7 +1075,7 @@ _start_endpoint_l(microService *m, microEndpoint *ep) // extra retain before subscribing since we'll need to hold it until // on_complete on the subscription. _lock_endpoint(ep); - _retain_endpoint(ep); + RETAIN_EP_INLINE(ep, "Started endpoint"); ep->sub = sub; ep->is_draining = false; _unlock_endpoint(ep); @@ -1104,6 +1113,7 @@ _stop_endpoint_l(microService *m, microEndpoint *ep) } ep->is_draining = true; + // RELEASE_EP_INLINE(ep, "stopped (counter to initial ref=1), draining endpoint"); _unlock_endpoint(ep); // When the drain is complete, will release the final ref on ep. @@ -1192,7 +1202,7 @@ _new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, micro ep->is_monitoring_endpoint = is_internal; // retain `m` before storing it for callbacks. - _retain_service_l(m); + RETAIN_SERVICE(m, "in endpoint for callbacks"); ep->service_ptr_for_on_complete = m; MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->endpoint_mu))); diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 0c5e33e6b..b79b53377 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -35,8 +35,8 @@ micro_init_monitoring(microService *m) { microError *err = NULL; MICRO_CALL(err, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); - // MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); - // MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); + MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); + MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); return err; } @@ -205,15 +205,15 @@ add_verb_handlers(microService *m, const char *verb, microRequestHandler handler snprintf(name, sizeof(name), "%s-all", verb); err = add_internal_handler(m, verb, "", "", name, handler); - // if (err == NULL) - // { - // snprintf(name, sizeof(name), "%s-kind", verb); - // err = add_internal_handler(m, verb, m->cfg->Name, "", name, handler); - // } - // if (err == NULL) - // { - // err = add_internal_handler(m, verb, m->cfg->Name, m->id, verb, handler); - // } + if (err == NULL) + { + snprintf(name, sizeof(name), "%s-kind", verb); + err = add_internal_handler(m, verb, m->cfg->Name, "", name, handler); + } + if (err == NULL) + { + err = add_internal_handler(m, verb, m->cfg->Name, m->id, verb, handler); + } return err; } diff --git a/test/list.txt b/test/list.txt index f416dc4f1..901655645 100644 --- a/test/list.txt +++ b/test/list.txt @@ -1 +1,8 @@ +MicroMatchEndpointSubject +MicroAddService +MicroGroups +MicroBasics +MicroStartStop +MicroServiceStopsOnClosedConn MicroServiceStopsWhenServerStops +MicroAsyncErrorHandler diff --git a/test/test.c b/test/test.c index d7f4f2d0f..3cce7cb44 100644 --- a/test/test.c +++ b/test/test.c @@ -35515,14 +35515,14 @@ static testInfo allTests[] = // {"KeyValueMirrorDirectGet", test_KeyValueMirrorDirectGet}, // {"KeyValueMirrorCrossDomains", test_KeyValueMirrorCrossDomains}, - // {"MicroMatchEndpointSubject", test_MicroMatchEndpointSubject}, - // {"MicroAddService", test_MicroAddService}, - // {"MicroGroups", test_MicroGroups}, - // {"MicroBasics", test_MicroBasics}, - // {"MicroStartStop", test_MicroStartStop}, - // {"MicroServiceStopsOnClosedConn", test_MicroServiceStopsOnClosedConn}, + {"MicroMatchEndpointSubject", test_MicroMatchEndpointSubject}, + {"MicroAddService", test_MicroAddService}, + {"MicroGroups", test_MicroGroups}, + {"MicroBasics", test_MicroBasics}, + {"MicroStartStop", test_MicroStartStop}, + {"MicroServiceStopsOnClosedConn", test_MicroServiceStopsOnClosedConn}, {"MicroServiceStopsWhenServerStops", test_MicroServiceStopsWhenServerStops}, - // {"MicroAsyncErrorHandler", test_MicroAsyncErrorHandler}, + {"MicroAsyncErrorHandler", test_MicroAsyncErrorHandler}, #if defined(NATS_HAS_STREAMING) // {"StanPBufAllocator", test_StanPBufAllocator}, From cb68bad01e5d248de7147999c983fbde8833ff32 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Thu, 1 Jun 2023 11:14:10 -0700 Subject: [PATCH 62/85] reworked wip4 --- src/micro.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/micro.c b/src/micro.c index ede0cd204..6dbbe0d07 100644 --- a/src/micro.c +++ b/src/micro.c @@ -367,6 +367,7 @@ _find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find) *prevp = prev_ep; return true; } + prev_ep = ep; } return false; @@ -381,6 +382,8 @@ _retain_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *c if (lock) _lock_endpoint(ep); + // nats_Sleep(500); + ep->refs++; printf("<>/<> _retain_endpoint++: %s by %s (%s), refs: %d\n", ep->subject, caller ? caller : "NULL", comment ? comment : "NULL", ep->refs); @@ -399,6 +402,8 @@ _release_endpoint(microEndpoint *ep, bool lock, const char *caller, const char * if (lock) _lock_endpoint(ep); + // nats_Sleep(500); + refs = --(ep->refs); printf("<>/<> _release_endpoint--: %s by %s (%s), refs: %d\n", ep->subject, caller ? caller : "NULL", comment ? comment : "NULL", ep->refs); @@ -463,6 +468,8 @@ _retain_service(microService *m, bool lock, const char *caller, const char *comm if (lock) _lock_service(m); + // nats_Sleep(500); + int refs = ++(m->refs); printf("<>/<> _retain_service++: %s by %s (%s), refs: %d\n", m->cfg->Name, caller ? caller : "NULL", comment ? comment : "NULL", refs); @@ -509,6 +516,8 @@ _release_service(microService *m, bool lock, const char *caller, const char *com if (lock) _lock_service(m); + // nats_Sleep(500); + refs = --(m->refs); printf("<>/<> _release_service--: %s by %s (%s), refs: %d\n", m->cfg->Name, caller ? caller : "NULL", comment ? comment : "NULL", refs); @@ -978,6 +987,8 @@ _handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *c static void _finalize_stopping_service_l(microService *m) { + microDoneHandler doneHandler = NULL; + if (m == NULL) return; @@ -993,16 +1004,17 @@ _finalize_stopping_service_l(microService *m) return; } m->stopped = true; + doneHandler = m->cfg->DoneHandler; _unlock_service(m); printf("<>/<> _finalize_stopping_service_l: stop service callbacks: not yet stopped, and nothing draining\n"); // Disable any subsequent async callbacks. _stop_service_callbacks(m); - if (m->cfg->DoneHandler != NULL) + if (doneHandler != NULL) { RETAIN_SERVICE(m, "For DONE handler"); - m->cfg->DoneHandler(m); + doneHandler(m); RELEASE_SERVICE(m, "After DONE handler"); } } From a4d89bcabd1a6db9a0c92f2bbe57d26e1acdabf6 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Thu, 1 Jun 2023 11:35:58 -0700 Subject: [PATCH 63/85] reworked wip5 --- src/nats.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/nats.c b/src/nats.c index f011f9a5d..12ad614a9 100644 --- a/src/nats.c +++ b/src/nats.c @@ -765,9 +765,8 @@ _asyncCbsThread(void *arg) case ASYNC_CLOSED: { (*(nc->opts->closedCb))(nc, nc->opts->closedCbClosure); - if (nc->opts->microClosedCb != NULL) - (*(nc->opts->microClosedCb))(nc, nc->opts->closedCbClosure); + (*(nc->opts->microClosedCb))(nc, NULL); break; } @@ -791,10 +790,14 @@ _asyncCbsThread(void *arg) if (cb->errTxt != NULL) nats_setErrStatusAndTxt(cb->err, cb->errTxt); - if (nc->opts->microAsyncErrCb != NULL) - (*(nc->opts->microAsyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); - + // Call the connection's own handler first. The microservice + // callback may result in destroying the subscription, which + // would then crash the default (logger). If there was a custom + // error handler already set on the connection, pray that it + // does not destroy the subscription. (*(nc->opts->asyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); + if (nc->opts->microAsyncErrCb != NULL) + (*(nc->opts->microAsyncErrCb))(nc, cb->sub, cb->err, NULL); break; } From 8f87ea21f459e8f8dd437d4a5f3440103020668f Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Thu, 1 Jun 2023 13:00:07 -0700 Subject: [PATCH 64/85] reworked wip5 --- src/micro.c | 65 +++++++++++++++++++++++++---------------------------- src/nats.c | 7 ++++-- src/sub.c | 3 +++ test/test.c | 2 ++ 4 files changed, 41 insertions(+), 36 deletions(-) diff --git a/src/micro.c b/src/micro.c index 6dbbe0d07..a74ec1c8b 100644 --- a/src/micro.c +++ b/src/micro.c @@ -34,7 +34,7 @@ static microError *_wrap_connection_event_callbacks(microService *m); static bool _is_valid_name(const char *name); -static void _finalize_stopping_service_l(microService *m); +static void _finalize_stopping_service_l(microService *m, const char *comment); static void _retain_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *comment); static void _release_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *comment); static void _release_service(microService *m, bool lock, const char *caller, const char *comment); @@ -59,19 +59,19 @@ static inline int _endpoint_count(microService *m) return n; } -static inline void _dump_endpoints(microService *m) -{ - const char *sep = NULL; - for (microEndpoint *ep = m->first_ep; ep != NULL; ep = ep->next) - { - if (sep == NULL) - sep = ","; - else - printf("%s", sep); - printf("%s", ep->subject); - } - printf("\n"); -} +// static inline void _dump_endpoints(microService *m) +// { +// const char *sep = NULL; +// for (microEndpoint *ep = m->first_ep; ep != NULL; ep = ep->next) +// { +// if (sep == NULL) +// sep = ","; +// else +// printf("%s", sep); +// printf("%s", ep->subject); +// } +// printf("\n"); +// } microError * micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *cfg) @@ -235,7 +235,7 @@ microService_Stop(microService *m) _unlock_service(m); printf("<>/<> microService_Stop: finalize\n"); - _finalize_stopping_service_l(m); + _finalize_stopping_service_l(m, "STOP"); return NULL; } @@ -357,13 +357,10 @@ _find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find) if ((m == NULL) || (to_find == NULL)) return false; - printf("<>/<> _find_endpoint: looking for %s\n", to_find->subject); for (ep = m->first_ep; ep != NULL; ep = ep->next) { - printf("<>/<> _find_endpoint: ...scanning %s\n", ep->subject); if (ep == to_find) { - printf("<>/<> _find_endpoint: found! prev: %s\n", prev_ep? prev_ep->subject : "NULL"); *prevp = prev_ep; return true; } @@ -741,7 +738,7 @@ _on_service_error_l(microService *m, const char *subject, natsStatus s) } // TODO: Should we stop the service? The Go client does. - microError_Ignore(microService_Stop(m)); + // microError_Ignore(microService_Stop(m)); } static void @@ -985,7 +982,7 @@ _handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *c } static void -_finalize_stopping_service_l(microService *m) +_finalize_stopping_service_l(microService *m, const char *comment) { microDoneHandler doneHandler = NULL; @@ -999,7 +996,7 @@ _finalize_stopping_service_l(microService *m) // it means they are draining and we should wait for them to complete. if (m->stopped || m->first_ep != NULL) { - printf("<>/<> finalize_stopping_service_l: already stopped or draining %d\n", _endpoint_count(m)); + printf("<>/<> finalize_stopping_service_l: %s: already stopped or draining %d\n", comment, _endpoint_count(m)); _unlock_service(m); return; } @@ -1007,7 +1004,7 @@ _finalize_stopping_service_l(microService *m) doneHandler = m->cfg->DoneHandler; _unlock_service(m); - printf("<>/<> _finalize_stopping_service_l: stop service callbacks: not yet stopped, and nothing draining\n"); + printf("<>/<> _finalize_stopping_service_l: %s: stop now!!!\n", comment); // Disable any subsequent async callbacks. _stop_service_callbacks(m); @@ -1025,24 +1022,26 @@ _release_on_endpoint_complete(void *closure) microEndpoint *ep = (microEndpoint *)closure; microEndpoint *prev_ep = NULL; microService *m = NULL; + natsSubscription *sub = NULL; if (ep == NULL) return; - printf("<>/<> _release_on_endpoint_complete: remove EP %s and finalize\n", ep->subject); + nats_Sleep(1000); + m = ep->service_ptr_for_on_complete; if ((m == NULL) || (m->service_mu == NULL)) return; _lock_endpoint(ep); + printf("<>/<> EP DONE: remove EP %s and finalize\n", ep->subject); ep->is_draining = false; + sub = ep->sub; + ep->sub = NULL; _unlock_endpoint(ep); + // Unlink the endpoint from the service. _lock_service(m); - - printf("<>/<> _release_on_endpoint_complete: %s: endpoints before removal:", ep->subject); - _dump_endpoints(m); - if (_find_endpoint(&prev_ep, m, ep)) { if (prev_ep != NULL) @@ -1050,18 +1049,16 @@ _release_on_endpoint_complete(void *closure) else m->first_ep = ep->next; } - RELEASE_SERVICE(m, "after removing EP"); - - printf("<>/<> _release_on_endpoint_complete: %s: endpoints after removal:", ep->subject); - _dump_endpoints(m); - RELEASE_EP(ep, "drained or closed endpoint"); - + RELEASE_SERVICE(m, "EP DONE"); _unlock_service(m); + // Force the subscription to be destroyed now. + natsSubscription_Destroy(sub); + RELEASE_EP(ep, "EP DONE"); // If this is the last shutting down endpoint, finalize the service // shutdown. - _finalize_stopping_service_l(m); + _finalize_stopping_service_l(m, ep->subject); } static microError * diff --git a/src/nats.c b/src/nats.c index 12ad614a9..9dacce013 100644 --- a/src/nats.c +++ b/src/nats.c @@ -795,10 +795,13 @@ _asyncCbsThread(void *arg) // would then crash the default (logger). If there was a custom // error handler already set on the connection, pray that it // does not destroy the subscription. - (*(nc->opts->asyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); + + // natsSub_retain(cb->sub); if (nc->opts->microAsyncErrCb != NULL) (*(nc->opts->microAsyncErrCb))(nc, cb->sub, cb->err, NULL); - + else + (*(nc->opts->asyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); + // natsSub_release(cb->sub); break; } #if defined(NATS_HAS_STREAMING) diff --git a/src/sub.c b/src/sub.c index 3c68c0ed6..a53a16d0f 100644 --- a/src/sub.c +++ b/src/sub.c @@ -81,6 +81,9 @@ _freeSubscription(natsSubscription *sub) void natsSub_retain(natsSubscription *sub) { + if (sub == NULL) + return; + natsSub_Lock(sub); sub->refs++; diff --git a/test/test.c b/test/test.c index 3cce7cb44..190be4b65 100644 --- a/test/test.c +++ b/test/test.c @@ -32913,6 +32913,8 @@ void micro_async_error_handler(microService *m, microEndpoint *ep, natsStatus s) { struct threadArg *arg = (struct threadArg*) microService_GetState(m); + printf("<>/<> micro_async_error_handler: %d\n", s); + natsMutex_Lock(arg->m); // release the pending test request that caused the error arg->closed = true; From 3ca86b6dfcc154405354b5962334eb2223d8a31a Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Fri, 2 Jun 2023 06:22:50 -0700 Subject: [PATCH 65/85] reworked wip6 --- src/micro.c | 55 ++++++++++++++++++++++++++---------------- src/micro_monitoring.c | 4 +-- src/nats.h | 2 +- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/micro.c b/src/micro.c index a74ec1c8b..be38ed54c 100644 --- a/src/micro.c +++ b/src/micro.c @@ -59,19 +59,19 @@ static inline int _endpoint_count(microService *m) return n; } -// static inline void _dump_endpoints(microService *m) -// { -// const char *sep = NULL; -// for (microEndpoint *ep = m->first_ep; ep != NULL; ep = ep->next) -// { -// if (sep == NULL) -// sep = ","; -// else -// printf("%s", sep); -// printf("%s", ep->subject); -// } -// printf("\n"); -// } +static inline void _dump_endpoints(microService *m) +{ + // const char *sep = NULL; + for (microEndpoint *ep = m->first_ep; ep != NULL; ep = ep->next) + { + // if (sep == NULL) + // sep = "\n"; + // else + // printf("%s", sep); + printf("\t%s\n", ep->subject); + } + printf("\n"); +} microError * micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *cfg) @@ -208,7 +208,7 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, } microError * -microService_Stop(microService *m) +microService_Stop(microService *m, const char *comment) { microError *err = NULL; microEndpoint *ep = NULL; @@ -220,22 +220,22 @@ microService_Stop(microService *m) if (m->stopped) { _unlock_service(m); - printf("<>/<> microService_Stop: already stopped\n"); + printf("<>/<> microService_Stop: %s: already stopped\n", comment); return NULL; } ep = m->first_ep; for (; ep != NULL; ep = ep->next) { - printf("<>/<> microService_Stop: stopping endpoint %s\n", ep->config->Name); + printf("<>/<> microService_Stop: %s: stopping endpoint %s\n", comment, ep->config->Name); if (err = _stop_endpoint_l(m, ep), err != NULL) return microError_Wrapf(err, "failed to stop service %s", ep->config->Name); } _unlock_service(m); - printf("<>/<> microService_Stop: finalize\n"); - _finalize_stopping_service_l(m, "STOP"); + printf("<>/<> microService_Stop: %s: finalize\n", comment); + _finalize_stopping_service_l(m, comment); return NULL; } @@ -699,7 +699,7 @@ _on_connection_closed(natsConnection *nc, void *ignored) { m = to_call[i]; microError_Ignore( - microService_Stop(m)); + microService_Stop(m, "STOP from connection closed callback")); RELEASE_SERVICE(m, "After connection closed callback"); } @@ -738,7 +738,7 @@ _on_service_error_l(microService *m, const char *subject, natsStatus s) } // TODO: Should we stop the service? The Go client does. - // microError_Ignore(microService_Stop(m)); + microError_Ignore(microService_Stop(m, "STOP from error callback")); } static void @@ -817,7 +817,7 @@ microService_Destroy(microService *m) printf("<>/<> microService_Destroy: %s\n", m->cfg->Name); - err = microService_Stop(m); + err = microService_Stop(m, "STOP from Destroy"); if (err != NULL) return err; @@ -1044,10 +1044,23 @@ _release_on_endpoint_complete(void *closure) _lock_service(m); if (_find_endpoint(&prev_ep, m, ep)) { + printf("<>/<> ------- still draining endpoints:\n"); + _dump_endpoints(m); + if (prev_ep != NULL) + { + // printf("<>/<> ======= removed %s, prev_ep %s now points to %s\n", ep->subject, prev_ep->subject, ep->next ? ep->next->subject : "NULL"); prev_ep->next = ep->next; + + } else + { + // printf("<>/<> ======= removed %s, HEAD now points to %s, was %s\n", ep->subject, ep->next ? ep->next->subject : "NULL"); m->first_ep = ep->next; + } + + printf("<>/<> ------- after removing %s:\n", ep->subject); + _dump_endpoints(m); } RELEASE_SERVICE(m, "EP DONE"); _unlock_service(m); diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index b79b53377..5c07f774b 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -35,8 +35,8 @@ micro_init_monitoring(microService *m) { microError *err = NULL; MICRO_CALL(err, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); - MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); - MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); + // MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); + // MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); return err; } diff --git a/src/nats.h b/src/nats.h index a07d0a0cd..0ed18a1c8 100644 --- a/src/nats.h +++ b/src/nats.h @@ -7801,7 +7801,7 @@ microService_Run(microService *m); * * @see #micro_AddService, #microService_Run */ -NATS_EXTERN microError *microService_Stop(microService *m); +NATS_EXTERN microError *microService_Stop(microService *m, const char *comment); /** @} */ // end of microServiceFunctions From 52c4881ad9322b4fba313bc5add3c8042c8a3da3 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sat, 3 Jun 2023 04:51:46 -0700 Subject: [PATCH 66/85] reworked wip7 --- src/micro.c | 171 ++++++++++------------- src/micro_monitoring.c | 4 +- src/nats.c | 2 + test/test.c | 304 ++++++++++++++++++++++++++--------------- 4 files changed, 276 insertions(+), 205 deletions(-) diff --git a/src/micro.c b/src/micro.c index be38ed54c..43b84a05e 100644 --- a/src/micro.c +++ b/src/micro.c @@ -34,20 +34,18 @@ static microError *_wrap_connection_event_callbacks(microService *m); static bool _is_valid_name(const char *name); -static void _finalize_stopping_service_l(microService *m, const char *comment); static void _retain_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *comment); -static void _release_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *comment); -static void _release_service(microService *m, bool lock, const char *caller, const char *comment); +static void _release_endpoint(microEndpoint *ep, const char *caller, const char *comment); +static void _release_service(microService *m, const char *caller, const char *comment); static void _retain_service(microService *m, bool lock, const char *caller, const char *comment); +static void _stop_service_callbacks(microService *m); #define RETAIN_EP(__ep, __comment) _retain_endpoint(__ep, true, __comment, __func__) #define RETAIN_EP_INLINE(__ep, __comment) _retain_endpoint(__ep, false, __comment, __func__) -#define RELEASE_EP(__ep, __comment) _release_endpoint(__ep, true, __comment, __func__) -#define RELEASE_EP_INLINE(__ep, __comment) _release_endpoint(__ep, false, __comment, __func__) +#define RELEASE_EP(__ep, __comment) _release_endpoint(__ep, __comment, __func__) #define RETAIN_SERVICE(__m, __comment) _retain_service(__m, true, __comment, __func__) #define RETAIN_SERVICE_INLINE(__m, __comment) _retain_service(__m, false, __comment, __func__) -#define RELEASE_SERVICE(__m, __comment) _release_service(__m, true, __comment, __func__) -#define RELEASE_SERVICE_INLINE(__m, __comment) _release_service(__m, false, __comment, __func__) +#define RELEASE_SERVICE(__m, __comment) _release_service(__m, __comment, __func__) static inline int _endpoint_count(microService *m) { @@ -212,6 +210,8 @@ microService_Stop(microService *m, const char *comment) { microError *err = NULL; microEndpoint *ep = NULL; + bool finalize = false; + microDoneHandler doneHandler = NULL; if (m == NULL) return micro_ErrorInvalidArg; @@ -227,15 +227,31 @@ microService_Stop(microService *m, const char *comment) for (; ep != NULL; ep = ep->next) { - printf("<>/<> microService_Stop: %s: stopping endpoint %s\n", comment, ep->config->Name); + printf("<>/<> microService_Stop: %s: stopping endpoint %s\n", comment, ep->subject); if (err = _stop_endpoint_l(m, ep), err != NULL) - return microError_Wrapf(err, "failed to stop service %s", ep->config->Name); + return microError_Wrapf(err, "failed to stop service '%s', stopping endpoint '%s'", m->cfg->Name, ep->name); } + + finalize = (m->first_ep == NULL); + if (finalize) + { + _stop_service_callbacks(m); + m->stopped = true; + doneHandler = m->cfg->DoneHandler; + } + _unlock_service(m); - printf("<>/<> microService_Stop: %s: finalize\n", comment); - _finalize_stopping_service_l(m, comment); + if (finalize) + { + printf("<>/<> microService_Stop: %s: finalize\n", comment); + if (doneHandler != NULL) + doneHandler(m); + + RELEASE_SERVICE(m, "After callbacks removed, final"); + } + return NULL; } @@ -379,8 +395,6 @@ _retain_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *c if (lock) _lock_endpoint(ep); - // nats_Sleep(500); - ep->refs++; printf("<>/<> _retain_endpoint++: %s by %s (%s), refs: %d\n", ep->subject, caller ? caller : "NULL", comment ? comment : "NULL", ep->refs); @@ -389,23 +403,19 @@ _retain_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *c } static void -_release_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *comment) +_release_endpoint(microEndpoint *ep, const char *caller, const char *comment) { int refs; if (ep == NULL) return; - if (lock) - _lock_endpoint(ep); - - // nats_Sleep(500); + _lock_endpoint(ep); refs = --(ep->refs); printf("<>/<> _release_endpoint--: %s by %s (%s), refs: %d\n", ep->subject, caller ? caller : "NULL", comment ? comment : "NULL", ep->refs); - if (lock) - _unlock_endpoint(ep); + _unlock_endpoint(ep); if (refs == 0) _free_endpoint(ep); @@ -465,10 +475,8 @@ _retain_service(microService *m, bool lock, const char *caller, const char *comm if (lock) _lock_service(m); - // nats_Sleep(500); - - int refs = ++(m->refs); - printf("<>/<> _retain_service++: %s by %s (%s), refs: %d\n", m->cfg->Name, caller ? caller : "NULL", comment ? comment : "NULL", refs); + ++(m->refs); + printf("<>/<> _retain_service++: %s by %s (%s), refs: %d\n", m->cfg->Name, caller ? caller : "NULL", comment ? comment : "NULL", m->refs); if (lock) _unlock_service(m); @@ -482,7 +490,7 @@ _free_service(microService *m) if (m == NULL) return; - printf("<>/<> _free_service: %s\n", m->cfg->Name); + printf("<>/<> %s: %s\n", __func__, m->cfg->Name); // destroy all groups. if (m->groups != NULL) @@ -503,23 +511,19 @@ _free_service(microService *m) } static void -_release_service(microService *m, bool lock, const char *caller, const char *comment) +_release_service(microService *m, const char *caller, const char *comment) { int refs = 0; if (m == NULL) return; - if (lock) - _lock_service(m); - - // nats_Sleep(500); + _lock_service(m); refs = --(m->refs); printf("<>/<> _release_service--: %s by %s (%s), refs: %d\n", m->cfg->Name, caller ? caller : "NULL", comment ? comment : "NULL", refs); - if (lock) - _unlock_service(m); + _unlock_service(m); if (refs == 0) _free_service(m); @@ -626,8 +630,6 @@ _stop_service_callbacks(microService *m) natsMutex_Lock(service_callback_mu); natsHash_Remove(all_services_to_callback, (int64_t)m); natsMutex_Unlock(service_callback_mu); - - RELEASE_SERVICE(m, ""); } static microError * @@ -724,18 +726,16 @@ _on_service_error_l(microService *m, const char *subject, natsStatus s) RETAIN_EP(ep, "For error callback"); // for the callback _unlock_service(m); - if ((ep != NULL) && (m->cfg->ErrHandler != NULL)) - { - (*m->cfg->ErrHandler)(m, ep, s); - } - RELEASE_EP(ep, "After error callback"); // after the callback - if (ep != NULL) { + if (m->cfg->ErrHandler != NULL) + (*m->cfg->ErrHandler)(m, ep, s); + err = microError_Wrapf(micro_ErrorFromStatus(s), "NATS error on endpoint %s", ep->subject); micro_update_last_error(ep, err); microError_Destroy(err); } + RELEASE_EP(ep, "After error callback"); // after the callback // TODO: Should we stop the service? The Go client does. microError_Ignore(microService_Stop(m, "STOP from error callback")); @@ -774,7 +774,7 @@ _on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_use { m = to_call[i]; _on_service_error_l(m, subject, s); - RELEASE_SERVICE(m, "After on_error callback"); // release the extra ref in `to_call`. + RELEASE_SERVICE(m, "After on_error invocation"); // release the extra ref in `to_call`. } NATS_FREE(to_call); @@ -952,7 +952,6 @@ _handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *c { // handle the request. start = nats_NowInNanoSeconds(); - service_err = handler(req); if (service_err != NULL) { @@ -981,41 +980,6 @@ _handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *c natsMsg_Destroy(msg); } -static void -_finalize_stopping_service_l(microService *m, const char *comment) -{ - microDoneHandler doneHandler = NULL; - - if (m == NULL) - return; - - _lock_service(m); - - // If the service is already stopped there is nothing to do. Also, if this - // function is called while the endpoints are still linked to the service, - // it means they are draining and we should wait for them to complete. - if (m->stopped || m->first_ep != NULL) - { - printf("<>/<> finalize_stopping_service_l: %s: already stopped or draining %d\n", comment, _endpoint_count(m)); - _unlock_service(m); - return; - } - m->stopped = true; - doneHandler = m->cfg->DoneHandler; - _unlock_service(m); - - printf("<>/<> _finalize_stopping_service_l: %s: stop now!!!\n", comment); - // Disable any subsequent async callbacks. - _stop_service_callbacks(m); - - if (doneHandler != NULL) - { - RETAIN_SERVICE(m, "For DONE handler"); - doneHandler(m); - RELEASE_SERVICE(m, "After DONE handler"); - } -} - static void _release_on_endpoint_complete(void *closure) { @@ -1023,12 +987,13 @@ _release_on_endpoint_complete(void *closure) microEndpoint *prev_ep = NULL; microService *m = NULL; natsSubscription *sub = NULL; + microDoneHandler doneHandler = NULL; + bool free_ep = false; + bool finalize = false; if (ep == NULL) return; - nats_Sleep(1000); - m = ep->service_ptr_for_on_complete; if ((m == NULL) || (m->service_mu == NULL)) return; @@ -1038,40 +1003,53 @@ _release_on_endpoint_complete(void *closure) ep->is_draining = false; sub = ep->sub; ep->sub = NULL; + ep->refs--; + free_ep = (ep->refs == 0); _unlock_endpoint(ep); - // Unlink the endpoint from the service. + // Force the subscription to be destroyed now. + natsSubscription_Destroy(sub); + _lock_service(m); + + // Release the service reference for the completed endpoint. It can not be + // the last reference, so no need to free m. + m->refs--; + + // Unlink the endpoint from the service. if (_find_endpoint(&prev_ep, m, ep)) { - printf("<>/<> ------- still draining endpoints:\n"); - _dump_endpoints(m); - if (prev_ep != NULL) { - // printf("<>/<> ======= removed %s, prev_ep %s now points to %s\n", ep->subject, prev_ep->subject, ep->next ? ep->next->subject : "NULL"); prev_ep->next = ep->next; } else { - // printf("<>/<> ======= removed %s, HEAD now points to %s, was %s\n", ep->subject, ep->next ? ep->next->subject : "NULL"); m->first_ep = ep->next; } + } - printf("<>/<> ------- after removing %s:\n", ep->subject); - _dump_endpoints(m); + finalize = (!m->stopped) && (m->first_ep == NULL); + if (finalize) + { + _stop_service_callbacks(m); + m->stopped = true; + doneHandler = m->cfg->DoneHandler; } - RELEASE_SERVICE(m, "EP DONE"); - _unlock_service(m); - // Force the subscription to be destroyed now. - natsSubscription_Destroy(sub); - RELEASE_EP(ep, "EP DONE"); + _unlock_service(m); + + if (free_ep) + _free_endpoint(ep); + + if (finalize) + { + if (doneHandler != NULL) + doneHandler(m); - // If this is the last shutting down endpoint, finalize the service - // shutdown. - _finalize_stopping_service_l(m, ep->subject); + RELEASE_SERVICE(m, "After callbacks removed, final"); + } } static microError * @@ -1102,6 +1080,8 @@ _start_endpoint_l(microService *m, microEndpoint *ep) ep->is_draining = false; _unlock_endpoint(ep); + // The service needs to be retained + natsSubscription_SetOnCompleteCB(sub, _release_on_endpoint_complete, ep); } else @@ -1135,7 +1115,6 @@ _stop_endpoint_l(microService *m, microEndpoint *ep) } ep->is_draining = true; - // RELEASE_EP_INLINE(ep, "stopped (counter to initial ref=1), draining endpoint"); _unlock_endpoint(ep); // When the drain is complete, will release the final ref on ep. diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index 5c07f774b..b79b53377 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -35,8 +35,8 @@ micro_init_monitoring(microService *m) { microError *err = NULL; MICRO_CALL(err, add_verb_handlers(m, MICRO_PING_VERB, handle_ping)); - // MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); - // MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); + MICRO_CALL(err, add_verb_handlers(m, MICRO_STATS_VERB, handle_stats)); + MICRO_CALL(err, add_verb_handlers(m, MICRO_INFO_VERB, handle_info)); return err; } diff --git a/src/nats.c b/src/nats.c index 9dacce013..306a16479 100644 --- a/src/nats.c +++ b/src/nats.c @@ -760,6 +760,8 @@ _asyncCbsThread(void *arg) sc = cb->sc; #endif + printf("<>/<> ====> async cb type=%d status:%d\n", cb->type, cb->err); + switch (cb->type) { case ASYNC_CLOSED: diff --git a/test/test.c b/test/test.c index 6ed7611ca..5b8536060 100644 --- a/test/test.c +++ b/test/test.c @@ -119,7 +119,8 @@ struct threadArg int64_t reconnectedAt[4]; int reconnects; bool msgReceived; - bool microDone; + int microRunningServiceCount; + bool microAllDone; bool done; int results[10]; const char *tokens[3]; @@ -32220,7 +32221,13 @@ test_MicroMatchEndpointSubject(void) } static microError * -micro_basics_handle_request(microRequest *req) +_microHandleRequest42(microRequest *req) +{ + return microRequest_Respond(req, "42", 2); +} + +static microError * +_microHandleRequestNoisy42(microRequest *req) { if ((rand() % 10) == 0) return micro_Errorf("Unexpected error!"); @@ -32229,9 +32236,95 @@ micro_basics_handle_request(microRequest *req) // Random delay between 5-10ms nats_Sleep(5 + (rand() % 5)); + printf("<>/<> Responding with 42!!!!\n"); + return microRequest_Respond(req, "42", 2); } +static void +_microServiceDoneHandler(microService *m) +{ + struct threadArg *arg = (struct threadArg*) microService_GetState(m); + + printf("<>/<> ========= SERVICE DONE\n"); + + natsMutex_Lock(arg->m); + arg->microRunningServiceCount--; + if (arg->microRunningServiceCount == 0) + { + arg->microAllDone = true; + natsCondition_Broadcast(arg->c); + } + natsMutex_Unlock(arg->m); +} + +static void +_startMicroservice(microService** new_m, natsConnection *nc, microServiceConfig *cfg, struct threadArg *arg) +{ + char buf[64]; + + cfg->DoneHandler = _microServiceDoneHandler; + cfg->State = arg; + + snprintf(buf, sizeof(buf), "Start microservice %s: ", cfg->Name); + test(buf); + + natsMutex_Lock(arg->m); + arg->microRunningServiceCount++; + arg->microAllDone = false; + natsMutex_Unlock(arg->m); + + testCond (NULL == micro_AddService(new_m, nc, cfg)); +} + + +static void +_startManyMicroservices(microService** svcs, int n, natsConnection *nc, microServiceConfig *cfg, struct threadArg *arg) +{ + int i; + + for (i = 0; i < n; i++) + { + _startMicroservice(&(svcs[i]), nc, cfg, arg); + } + + testCond(true); +} + +static void +_waitForMicroservicesAllDone(struct threadArg *arg) +{ + natsStatus s = NATS_OK; + + test("Wait for all microservices to stop: "); + natsMutex_Lock(arg->m); + while ((s != NATS_TIMEOUT) && !arg->microAllDone) + s = natsCondition_TimedWait(arg->c, arg->m, 1000); + natsMutex_Unlock(arg->m); + testCond((NATS_OK == s) && arg->microAllDone); + + // `Done` may be immediately followed by freeing the service, so wait a bit + // to make sure it happens before the test exits. + nats_Sleep(100); +} + +static void +_destroyMicroservicesAndWaitForAllDone(microService** svcs, int n, struct threadArg *arg) +{ + char buf[64]; + + snprintf(buf, sizeof(buf), "Wait for all %d microservices to stop: ", n); + test(buf); + + for (int i = 0; i < n; i++) + { + if (NULL != microService_Destroy(svcs[i])) + FAIL("Unable to destroy microservice!"); + } + + _waitForMicroservicesAllDone(arg); +} + typedef struct { const char *name; @@ -32266,32 +32359,40 @@ test_MicroAddService(void) microEndpointConfig default_ep_cfg = { .Name = "default", - .Handler = micro_basics_handle_request, + .Handler = _microHandleRequestNoisy42, }; microEndpointConfig ep1_cfg = { .Name = "ep1", - .Handler = micro_basics_handle_request, + .Handler = _microHandleRequestNoisy42, }; microEndpointConfig ep2_cfg = { .Name = "ep2", .Subject = "different-from-name", - .Handler = micro_basics_handle_request, + .Handler = _microHandleRequestNoisy42, }; microEndpointConfig ep3_cfg = { .Name = "ep3", - .Handler = micro_basics_handle_request, + .Handler = _microHandleRequestNoisy42, }; microEndpointConfig *all_ep_cfgs[] = {&ep1_cfg, &ep2_cfg, &ep3_cfg}; microServiceConfig minimal_cfg = { .Version = "1.0.0", .Name = "minimal", + + // for the test to work + .DoneHandler = _microServiceDoneHandler, + .State = &arg, }; microServiceConfig full_cfg = { .Version = "1.0.0", .Name = "full", .Endpoint = &default_ep_cfg, .Description = "fully declared microservice", + + // for the test to work + .DoneHandler = _microServiceDoneHandler, + .State = &arg, }; microServiceConfig err_no_name_cfg = { .Version = "1.0.0", @@ -32321,33 +32422,33 @@ test_MicroAddService(void) .num_endpoints = sizeof(all_ep_cfgs) / sizeof(all_ep_cfgs[0]), .expected_num_subjects = 4, }, - { - .name = "Err-null-connection", - .cfg = &minimal_cfg, - .null_nc = true, - .expected_err = "status 16: invalid function argument", - }, - { - .name = "Err-null-receiver", - .cfg = &minimal_cfg, - .null_receiver = true, - .expected_err = "status 16: invalid function argument", - }, - { - .name = "Err-no-name", - .cfg = &err_no_name_cfg, - .expected_err = "status 16: invalid function argument", - }, - { - .name = "Err-no-version", - .cfg = &err_no_version_cfg, - .expected_err = "status 16: invalid function argument", - }, - { - .name = "Err-invalid-version", - .cfg = &err_invalid_version_cfg, - // TODO: validate the version format. - }, + // { + // .name = "Err-null-connection", + // .cfg = &minimal_cfg, + // .null_nc = true, + // .expected_err = "status 16: invalid function argument", + // }, + // { + // .name = "Err-null-receiver", + // .cfg = &minimal_cfg, + // .null_receiver = true, + // .expected_err = "status 16: invalid function argument", + // }, + // { + // .name = "Err-no-name", + // .cfg = &err_no_name_cfg, + // .expected_err = "status 16: invalid function argument", + // }, + // { + // .name = "Err-no-version", + // .cfg = &err_no_version_cfg, + // .expected_err = "status 16: invalid function argument", + // }, + // { + // .name = "Err-invalid-version", + // .cfg = &err_invalid_version_cfg, + // // TODO: validate the version format. + // }, }; add_service_test_case_t tc; @@ -32373,8 +32474,14 @@ test_MicroAddService(void) { tc = tcs[n]; + natsMutex_Lock(arg.m); + arg.microRunningServiceCount = 1; + arg.microAllDone = false; + natsMutex_Unlock(arg.m); + snprintf(buf, sizeof(buf), "%s: AddService: ", tc.name); test(buf); + m = NULL; err = micro_AddService( tc.null_receiver ? NULL : &m, tc.null_nc ? NULL : nc, @@ -32466,7 +32573,14 @@ test_MicroAddService(void) } microServiceInfo_Destroy(info); - microService_Destroy(m); + + if (m != NULL) + { + snprintf(buf, sizeof(buf), "%s: Destroy service: ", m->cfg->Name); + test(buf); + testCond(NULL == microService_Destroy(m)); + _waitForMicroservicesAllDone(&arg); + } } natsConnection_Destroy(nc); @@ -32494,11 +32608,11 @@ test_MicroGroups(void) microEndpointConfig ep1_cfg = { .Name = "ep1", - .Handler = micro_basics_handle_request, + .Handler = _microHandleRequest42, }; microEndpointConfig ep2_cfg = { .Name = "ep2", - .Handler = micro_basics_handle_request, + .Handler = _microHandleRequest42, }; microServiceConfig cfg = { .Version = "1.0.0", @@ -32530,8 +32644,7 @@ test_MicroGroups(void) test("Connect to server: "); testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - test("AddService: "); - testCond(NULL == micro_AddService(&m, nc, &cfg)); + _startMicroservice(&m, nc, &cfg, &arg); test("AddEndpoint 1 to service: "); testCond(NULL == microService_AddEndpoint(m, &ep1_cfg)); @@ -32573,7 +32686,10 @@ test_MicroGroups(void) testCond(true); microServiceInfo_Destroy(info); + microService_Destroy(m); + _waitForMicroservicesAllDone(&arg); + natsConnection_Destroy(nc); _waitForConnClosed(&arg); @@ -32582,7 +32698,7 @@ test_MicroGroups(void) _stopServer(serverPid); } -#define NUM_BASIC_MICRO_SERVICES 1 +#define NUM_MICRO_SERVICES 1 static void test_MicroBasics(void) @@ -32593,11 +32709,11 @@ test_MicroBasics(void) natsOptions *opts = NULL; natsConnection *nc = NULL; natsPid serverPid = NATS_INVALID_PID; - microService **svcs = NATS_CALLOC(NUM_BASIC_MICRO_SERVICES, sizeof(microService *)); + microService *svcs[NUM_MICRO_SERVICES]; microEndpointConfig ep_cfg = { .Name = "do", .Subject = "svc.do", - .Handler = micro_basics_handle_request, + .Handler = _microHandleRequestNoisy42, }; microServiceConfig cfg = { .Version = "1.0.0", @@ -32638,16 +32754,10 @@ test_MicroBasics(void) test("Connect to server: "); testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - // start 5 instances of the basic service. - for (i = 0; i < NUM_BASIC_MICRO_SERVICES; i++) - { - snprintf(buf, sizeof(buf), "Start microservice #%d: ", i); - test(buf); - testCond(NULL == micro_AddService(&svcs[i], nc, &cfg)); - } + _startManyMicroservices(svcs, NUM_MICRO_SERVICES, nc, &cfg, &arg); // Now send 50 requests. - test("Send 50 requests: "); + test("Send 50 requests (no matter response): "); for (i = 0; i < 50; i++) { s = natsConnection_Request(&reply, nc, "svc.do", NULL, 0, 1000); @@ -32658,7 +32768,7 @@ test_MicroBasics(void) testCond(NATS_OK == s); // Make sure we can request valid info with local API. - for (i = 0; i < NUM_BASIC_MICRO_SERVICES; i++) + for (i = 0; i < NUM_MICRO_SERVICES; i++) { snprintf(buf, sizeof(buf), "Check local info #%d: ", i); test(buf); @@ -32686,7 +32796,7 @@ test_MicroBasics(void) s = natsSubscription_NextMsg(&reply, sub, 250); if (s == NATS_TIMEOUT) { - testCond(i == NUM_BASIC_MICRO_SERVICES); + testCond(i == NUM_MICRO_SERVICES); break; } testCond(NATS_OK == s); @@ -32719,7 +32829,7 @@ test_MicroBasics(void) s = natsSubscription_NextMsg(&reply, sub, 250); if (s == NATS_TIMEOUT) { - testCond(i == NUM_BASIC_MICRO_SERVICES); + testCond(i == NUM_MICRO_SERVICES); break; } testCond(NATS_OK == s); @@ -32753,7 +32863,7 @@ test_MicroBasics(void) s = natsSubscription_NextMsg(&reply, sub, 250); if (s == NATS_TIMEOUT) { - testCond(i == NUM_BASIC_MICRO_SERVICES); + testCond(i == NUM_MICRO_SERVICES); break; } testCond(NATS_OK == s); @@ -32794,11 +32904,8 @@ test_MicroBasics(void) natsInbox_Destroy(inbox); NATS_FREE(subject); - for (i = 0; i < NUM_BASIC_MICRO_SERVICES; i++) - { - microService_Destroy(svcs[i]); - } - NATS_FREE(svcs); + _destroyMicroservicesAndWaitForAllDone(svcs, NUM_MICRO_SERVICES, &arg); + natsConnection_Destroy(nc); _waitForConnClosed(&arg); @@ -32815,11 +32922,11 @@ test_MicroStartStop(void) natsOptions *opts = NULL; natsConnection *nc = NULL; natsPid serverPid = NATS_INVALID_PID; - microService **svcs = NATS_CALLOC(NUM_BASIC_MICRO_SERVICES, sizeof(microService *)); + microService *svcs[NUM_MICRO_SERVICES]; microEndpointConfig ep_cfg = { .Name = "do", .Subject = "svc.do", - .Handler = micro_basics_handle_request, + .Handler = _microHandleRequest42, }; microServiceConfig cfg = { .Version = "1.0.0", @@ -32829,7 +32936,6 @@ test_MicroStartStop(void) }; natsMsg *reply = NULL; int i; - char buf[256]; srand((unsigned int)nats_NowInNanoSeconds()); @@ -32850,32 +32956,25 @@ test_MicroStartStop(void) test("Connect to server: "); testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - // start 5 instances of the basic service. - for (i = 0; i < NUM_BASIC_MICRO_SERVICES; i++) - { - snprintf(buf, sizeof(buf), "Start microservice #%d: ", i); - test(buf); - testCond(NULL == micro_AddService(&svcs[i], nc, &cfg)); - } + _startManyMicroservices(svcs, NUM_MICRO_SERVICES, nc, &cfg, &arg); - // Now send 50 requests. - test("Send 50 requests: "); - for (i = 0; i < 50; i++) + // Now send some requests. + test("Send requests: "); + for (i = 0; i < 20; i++) { - s = natsConnection_Request(&reply, nc, "svc.do", NULL, 0, 1000); + // printf("<>/<> sending %d\n", i); + reply = NULL; + s = natsConnection_Request(&reply, nc, "svc.do", NULL, 0, 2000); if (NATS_OK != s) FAIL("Unable to send request"); + // printf("<>/<> received %d: '%.*s'\n", i, reply->dataLen, reply->data); + if (reply == NULL || reply->dataLen != 2 || memcmp(reply->data, "42", 2) != 0) + FAIL("Unexpected reply"); natsMsg_Destroy(reply); } testCond(NATS_OK == s); - for (i = 0; i < NUM_BASIC_MICRO_SERVICES; i++) - { - snprintf(buf, sizeof(buf), "Destroy microservice #%d: ", i); - test(buf); - testCond(NULL == microService_Destroy(svcs[i])); - } - NATS_FREE(svcs); + _destroyMicroservicesAndWaitForAllDone(svcs, NUM_MICRO_SERVICES, &arg); test("Destroy the connection: "); natsConnection_Destroy(nc); @@ -32887,16 +32986,6 @@ test_MicroStartStop(void) _stopServer(serverPid); } -void micro_service_done_handler(microService *m) -{ - struct threadArg *arg = (struct threadArg*) microService_GetState(m); - - natsMutex_Lock(arg->m); - arg->microDone = true; - natsCondition_Broadcast(arg->c); - natsMutex_Unlock(arg->m); -} - static void test_MicroServiceStopsOnClosedConn(void) { @@ -32909,8 +32998,6 @@ test_MicroServiceStopsOnClosedConn(void) microServiceConfig cfg = { .Name = "test", .Version = "1.0.0", - .DoneHandler = micro_service_done_handler, - .State = &arg, }; natsMsg *reply = NULL; @@ -32931,8 +33018,7 @@ test_MicroServiceStopsOnClosedConn(void) test("Connect for microservice: "); testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - test("Start microservice: "); - testCond(NULL == micro_AddService(&m, nc, &cfg)); + _startMicroservice(&m, nc, &cfg, &arg); test("Test microservice is running: "); testCond(!microService_IsStopped(m)) @@ -32955,16 +33041,17 @@ test_MicroServiceStopsOnClosedConn(void) test("Wait for the service to stop: "); natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.microDone) + while ((s != NATS_TIMEOUT) && !arg.microAllDone) s = natsCondition_TimedWait(arg.c, arg.m, 1000); natsMutex_Unlock(arg.m); - testCond(arg.microDone); + testCond(arg.microAllDone); test("Test microservice is stopped: "); testCond(microService_IsStopped(m)); test("Destroy microservice (final): "); testCond(NULL == microService_Destroy(m)) + _waitForMicroservicesAllDone(&arg); natsOptions_Destroy(opts); natsConnection_Destroy(nc); @@ -32984,8 +33071,6 @@ test_MicroServiceStopsWhenServerStops(void) microServiceConfig cfg = { .Name = "test", .Version = "1.0.0", - .DoneHandler = micro_service_done_handler, - .State = &arg, }; s = _createDefaultThreadArgsForCbTests(&arg); @@ -33006,26 +33091,30 @@ test_MicroServiceStopsWhenServerStops(void) test("Connect for microservice: "); testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - test("Start microservice: "); - testCond(NULL == micro_AddService(&m, nc, &cfg)); - + _startMicroservice(&m, nc, &cfg, &arg); + test("Test microservice is running: "); testCond(!microService_IsStopped(m)) test("Stop the server: "); testCond((_stopServer(serverPid), true)); + nats_Sleep(1000); + test("Wait for the service to stop: "); natsMutex_Lock(arg.m); - while ((s != NATS_TIMEOUT) && !arg.microDone) + while ((s != NATS_TIMEOUT) && !arg.microAllDone) s = natsCondition_TimedWait(arg.c, arg.m, 1000); natsMutex_Unlock(arg.m); - testCond(arg.microDone); + testCond(arg.microAllDone); test("Test microservice is not running: "); testCond(microService_IsStopped(m)) microService_Destroy(m); + _waitForMicroservicesAllDone(&arg); + + natsConnection_Destroy(nc); _waitForConnClosed(&arg); @@ -33033,11 +33122,11 @@ test_MicroServiceStopsWhenServerStops(void) _destroyDefaultThreadArgs(&arg); } -void micro_async_error_handler(microService *m, microEndpoint *ep, natsStatus s) +void _microAsyncErrorHandler(microService *m, microEndpoint *ep, natsStatus s) { struct threadArg *arg = (struct threadArg*) microService_GetState(m); - printf("<>/<> micro_async_error_handler: %d\n", s); + printf("<>/<> _microAsyncErrorHandler: %d\n", s); natsMutex_Lock(arg->m); // release the pending test request that caused the error @@ -33050,7 +33139,7 @@ void micro_async_error_handler(microService *m, microEndpoint *ep, natsStatus s) } microError * -micro_async_error_request_handler(microRequest *req) +_microAsyncErrorRequestHandler(microRequest *req) { struct threadArg *arg = microRequest_GetServiceState(req); @@ -33081,13 +33170,14 @@ test_MicroAsyncErrorHandler(void) microEndpointConfig ep_cfg = { .Name = "do", .Subject = "async_test", - .Handler = micro_async_error_request_handler, + .Handler = _microAsyncErrorRequestHandler, }; microServiceConfig cfg = { .Name = "test", .Version = "1.0.0", - .ErrHandler = micro_async_error_handler, + .ErrHandler = _microAsyncErrorHandler, .State = &arg, + .DoneHandler = _microServiceDoneHandler, }; s = _createDefaultThreadArgsForCbTests(&arg); From 2019f51a20af65263dc5013d44e1ed3babe673e1 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sat, 3 Jun 2023 06:00:52 -0700 Subject: [PATCH 67/85] reworked wip8 --- test/test.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/test.c b/test/test.c index 5b8536060..f0bd34de0 100644 --- a/test/test.c +++ b/test/test.c @@ -33196,8 +33196,7 @@ test_MicroAsyncErrorHandler(void) test("Connect to NATS: "); testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - test("Start microservice: "); - testCond(NULL == micro_AddService(&m, nc, &cfg)); + _startMicroservice(&m, nc, &cfg, &arg); test("Test microservice is running: "); testCond(!microService_IsStopped(m)) @@ -33225,10 +33224,15 @@ test_MicroAsyncErrorHandler(void) natsMutex_Unlock(arg.m); testCond((s == NATS_OK) && arg.closed && (arg.status == NATS_SLOW_CONSUMER)); - microService_Destroy(m); - natsOptions_Destroy(opts); natsSubscription_Destroy(sub); + + microService_Destroy(m); + _waitForMicroservicesAllDone(&arg); + natsConnection_Destroy(nc); + _waitForConnClosed(&arg); + + natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); _stopServer(serverPid); } From f76a3c586b47e641fbc808a5d7ce4191833e33db Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sat, 3 Jun 2023 06:37:30 -0700 Subject: [PATCH 68/85] reworked wip9 --- src/micro.c | 7 +++- test/test.c | 113 +++++++++++++++++++++++++++------------------------- 2 files changed, 65 insertions(+), 55 deletions(-) diff --git a/src/micro.c b/src/micro.c index 43b84a05e..52287e787 100644 --- a/src/micro.c +++ b/src/micro.c @@ -999,7 +999,6 @@ _release_on_endpoint_complete(void *closure) return; _lock_endpoint(ep); - printf("<>/<> EP DONE: remove EP %s and finalize\n", ep->subject); ep->is_draining = false; sub = ep->sub; ep->sub = NULL; @@ -1015,6 +1014,7 @@ _release_on_endpoint_complete(void *closure) // Release the service reference for the completed endpoint. It can not be // the last reference, so no need to free m. m->refs--; + printf("<>/<> EP DONE: remove EP %s and finalize: m->refs:%d, ep->refs:%d\n", ep->subject, m->refs, ep->refs); // Unlink the endpoint from the service. if (_find_endpoint(&prev_ep, m, ep)) @@ -1045,11 +1045,16 @@ _release_on_endpoint_complete(void *closure) if (finalize) { + printf("<>/<> EP DONE: final!!!\n"); if (doneHandler != NULL) doneHandler(m); RELEASE_SERVICE(m, "After callbacks removed, final"); } + else + { + printf("<>/<> EP DONE: already stopped or not final yet.\n"); + } } static microError * diff --git a/test/test.c b/test/test.c index f0bd34de0..9e1563f27 100644 --- a/test/test.c +++ b/test/test.c @@ -32246,10 +32246,9 @@ _microServiceDoneHandler(microService *m) { struct threadArg *arg = (struct threadArg*) microService_GetState(m); - printf("<>/<> ========= SERVICE DONE\n"); - natsMutex_Lock(arg->m); arg->microRunningServiceCount--; + printf("<>/<> ========= SERVICE DONE, %d remain\n", arg->microRunningServiceCount); if (arg->microRunningServiceCount == 0) { arg->microAllDone = true; @@ -32258,25 +32257,43 @@ _microServiceDoneHandler(microService *m) natsMutex_Unlock(arg->m); } -static void +static microError * _startMicroservice(microService** new_m, natsConnection *nc, microServiceConfig *cfg, struct threadArg *arg) { - char buf[64]; + microError *err = NULL; + bool prev_done; cfg->DoneHandler = _microServiceDoneHandler; cfg->State = arg; - snprintf(buf, sizeof(buf), "Start microservice %s: ", cfg->Name); - test(buf); - natsMutex_Lock(arg->m); + arg->microRunningServiceCount++; + prev_done = arg->microAllDone; arg->microAllDone = false; - natsMutex_Unlock(arg->m); - testCond (NULL == micro_AddService(new_m, nc, cfg)); + err = micro_AddService(new_m, nc, cfg); + if (err != NULL) + { + arg->microRunningServiceCount--; + arg->microAllDone = prev_done; + } + + natsMutex_Unlock(arg->m); + + return err; } +static void +_startMicroserviceOK(microService** new_m, natsConnection *nc, microServiceConfig *cfg, struct threadArg *arg) +{ + char buf[64]; + + snprintf(buf, sizeof(buf), "Start microservice %s: ", cfg->Name); + test(buf); + + testCond (NULL == _startMicroservice(new_m, nc, cfg, arg)); +} static void _startManyMicroservices(microService** svcs, int n, natsConnection *nc, microServiceConfig *cfg, struct threadArg *arg) @@ -32285,7 +32302,7 @@ _startManyMicroservices(microService** svcs, int n, natsConnection *nc, microSer for (i = 0; i < n; i++) { - _startMicroservice(&(svcs[i]), nc, cfg, arg); + _startMicroserviceOK(&(svcs[i]), nc, cfg, arg); } testCond(true); @@ -32380,29 +32397,22 @@ test_MicroAddService(void) .Version = "1.0.0", .Name = "minimal", - // for the test to work - .DoneHandler = _microServiceDoneHandler, - .State = &arg, }; microServiceConfig full_cfg = { .Version = "1.0.0", .Name = "full", .Endpoint = &default_ep_cfg, .Description = "fully declared microservice", - - // for the test to work - .DoneHandler = _microServiceDoneHandler, - .State = &arg, }; microServiceConfig err_no_name_cfg = { .Version = "1.0.0", }; microServiceConfig err_no_version_cfg = { - .Name = "minimal", + .Name = "no-version", }; microServiceConfig err_invalid_version_cfg = { .Version = "BLAH42", - .Name = "minimal", + .Name = "invalid-version", }; add_service_test_case_t tcs[] = { @@ -32422,33 +32432,33 @@ test_MicroAddService(void) .num_endpoints = sizeof(all_ep_cfgs) / sizeof(all_ep_cfgs[0]), .expected_num_subjects = 4, }, - // { - // .name = "Err-null-connection", - // .cfg = &minimal_cfg, - // .null_nc = true, - // .expected_err = "status 16: invalid function argument", - // }, - // { - // .name = "Err-null-receiver", - // .cfg = &minimal_cfg, - // .null_receiver = true, - // .expected_err = "status 16: invalid function argument", - // }, - // { - // .name = "Err-no-name", - // .cfg = &err_no_name_cfg, - // .expected_err = "status 16: invalid function argument", - // }, - // { - // .name = "Err-no-version", - // .cfg = &err_no_version_cfg, - // .expected_err = "status 16: invalid function argument", - // }, - // { - // .name = "Err-invalid-version", - // .cfg = &err_invalid_version_cfg, - // // TODO: validate the version format. - // }, + { + .name = "Err-null-connection", + .cfg = &minimal_cfg, + .null_nc = true, + .expected_err = "status 16: invalid function argument", + }, + { + .name = "Err-null-receiver", + .cfg = &minimal_cfg, + .null_receiver = true, + .expected_err = "status 16: invalid function argument", + }, + { + .name = "Err-no-name", + .cfg = &err_no_name_cfg, + .expected_err = "status 16: invalid function argument", + }, + { + .name = "Err-no-version", + .cfg = &err_no_version_cfg, + .expected_err = "status 16: invalid function argument", + }, + { + .name = "Err-invalid-version", + .cfg = &err_invalid_version_cfg, + // TODO: validate the version format. + }, }; add_service_test_case_t tc; @@ -32474,18 +32484,13 @@ test_MicroAddService(void) { tc = tcs[n]; - natsMutex_Lock(arg.m); - arg.microRunningServiceCount = 1; - arg.microAllDone = false; - natsMutex_Unlock(arg.m); - snprintf(buf, sizeof(buf), "%s: AddService: ", tc.name); test(buf); m = NULL; - err = micro_AddService( + err = _startMicroservice( tc.null_receiver ? NULL : &m, tc.null_nc ? NULL : nc, - tc.cfg); + tc.cfg, &arg); if (nats_IsStringEmpty(tc.expected_err)) { testCond(err == NULL); @@ -32576,7 +32581,7 @@ test_MicroAddService(void) if (m != NULL) { - snprintf(buf, sizeof(buf), "%s: Destroy service: ", m->cfg->Name); + snprintf(buf, sizeof(buf), "%s: Destroy service: %d", m->cfg->Name, m->refs); test(buf); testCond(NULL == microService_Destroy(m)); _waitForMicroservicesAllDone(&arg); From 104a46c9a2ce1af762c56a6e553cabed8fb5479f Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sat, 3 Jun 2023 07:09:45 -0700 Subject: [PATCH 69/85] reworked wip10 --- src/micro.c | 2 +- src/microp.h | 4 ---- src/nats.c | 13 ++----------- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/micro.c b/src/micro.c index 52287e787..dc61a7260 100644 --- a/src/micro.c +++ b/src/micro.c @@ -143,7 +143,7 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, _lock_service(m); - if (m->stopping || m->stopped) + if (m->stopped) { _unlock_service(m); return micro_Errorf("can't add an endpoint %s to service %s: the service is stopped", cfg->Name, m->cfg->Name); diff --git a/src/microp.h b/src/microp.h index 243bb5e2c..081bdb4bb 100644 --- a/src/microp.h +++ b/src/microp.h @@ -112,11 +112,7 @@ struct micro_service_s struct micro_endpoint_s *first_ep; int64_t started; // UTC time expressed as number of nanoseconds since epoch. - // true once the stopping of the service, i.e. draining of endpoints' - // subsriptions has started. bool stopped; - bool connection_closed_called; - bool stopping; }; /** diff --git a/src/nats.c b/src/nats.c index 306a16479..5acc4a17d 100644 --- a/src/nats.c +++ b/src/nats.c @@ -1,4 +1,4 @@ -// Copyright 2015-2021 The NATS Authors +// Copyright 2015-2023 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -792,18 +792,9 @@ _asyncCbsThread(void *arg) if (cb->errTxt != NULL) nats_setErrStatusAndTxt(cb->err, cb->errTxt); - // Call the connection's own handler first. The microservice - // callback may result in destroying the subscription, which - // would then crash the default (logger). If there was a custom - // error handler already set on the connection, pray that it - // does not destroy the subscription. - - // natsSub_retain(cb->sub); + (*(nc->opts->asyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); if (nc->opts->microAsyncErrCb != NULL) (*(nc->opts->microAsyncErrCb))(nc, cb->sub, cb->err, NULL); - else - (*(nc->opts->asyncErrCb))(nc, cb->sub, cb->err, nc->opts->asyncErrCbClosure); - // natsSub_release(cb->sub); break; } #if defined(NATS_HAS_STREAMING) From b80dc9628f10a1e5118cb9e9c7df4891a37051fa Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sat, 3 Jun 2023 07:24:59 -0700 Subject: [PATCH 70/85] reworked wip11 --- src/micro.c | 93 ++++++++++++++++------------------------------------- src/nats.c | 2 -- src/nats.h | 2 +- test/test.c | 7 ---- 4 files changed, 29 insertions(+), 75 deletions(-) diff --git a/src/micro.c b/src/micro.c index dc61a7260..3c442d507 100644 --- a/src/micro.c +++ b/src/micro.c @@ -34,19 +34,12 @@ static microError *_wrap_connection_event_callbacks(microService *m); static bool _is_valid_name(const char *name); -static void _retain_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *comment); -static void _release_endpoint(microEndpoint *ep, const char *caller, const char *comment); -static void _release_service(microService *m, const char *caller, const char *comment); -static void _retain_service(microService *m, bool lock, const char *caller, const char *comment); +static void _retain_endpoint(microEndpoint *ep, bool lock); +static void _release_endpoint(microEndpoint *ep); +static void _release_service(microService *m); +static void _retain_service(microService *m, bool lock); static void _stop_service_callbacks(microService *m); -#define RETAIN_EP(__ep, __comment) _retain_endpoint(__ep, true, __comment, __func__) -#define RETAIN_EP_INLINE(__ep, __comment) _retain_endpoint(__ep, false, __comment, __func__) -#define RELEASE_EP(__ep, __comment) _release_endpoint(__ep, __comment, __func__) -#define RETAIN_SERVICE(__m, __comment) _retain_service(__m, true, __comment, __func__) -#define RETAIN_SERVICE_INLINE(__m, __comment) _retain_service(__m, false, __comment, __func__) -#define RELEASE_SERVICE(__m, __comment) _release_service(__m, __comment, __func__) - static inline int _endpoint_count(microService *m) { int n = 0; @@ -190,7 +183,7 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, // list, not started. A retry with the same name will clean it up. if (err = _stop_endpoint_l(m, prev_ep), err != NULL) return err; - RELEASE_EP(prev_ep, "replaced ep"); + _release_endpoint(prev_ep); } if (err = _start_endpoint_l(m, ep), err != NULL) @@ -206,7 +199,7 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, } microError * -microService_Stop(microService *m, const char *comment) +microService_Stop(microService *m) { microError *err = NULL; microEndpoint *ep = NULL; @@ -217,18 +210,16 @@ microService_Stop(microService *m, const char *comment) return micro_ErrorInvalidArg; _lock_service(m); + if (m->stopped) { _unlock_service(m); - printf("<>/<> microService_Stop: %s: already stopped\n", comment); return NULL; } ep = m->first_ep; for (; ep != NULL; ep = ep->next) { - printf("<>/<> microService_Stop: %s: stopping endpoint %s\n", comment, ep->subject); - if (err = _stop_endpoint_l(m, ep), err != NULL) return microError_Wrapf(err, "failed to stop service '%s', stopping endpoint '%s'", m->cfg->Name, ep->name); } @@ -245,11 +236,10 @@ microService_Stop(microService *m, const char *comment) if (finalize) { - printf("<>/<> microService_Stop: %s: finalize\n", comment); if (doneHandler != NULL) doneHandler(m); - RELEASE_SERVICE(m, "After callbacks removed, final"); + _release_service(m); } return NULL; @@ -263,7 +253,6 @@ _new_service(microService **ptr, natsConnection *nc) return micro_ErrorOutOfMemory; natsConn_retain(nc); - printf("<>/<> _new_service: refs: 1\n"); (*ptr)->refs = 1; (*ptr)->nc = nc; (*ptr)->started = nats_Now() * 1000000; @@ -354,8 +343,6 @@ _free_endpoint(microEndpoint *ep) if (ep == NULL) return; - printf("<>/<> _free_endpoint: %s\n", ep->subject); - NATS_FREE(ep->name); NATS_FREE(ep->subject); natsSubscription_Destroy(ep->sub); @@ -387,7 +374,7 @@ _find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find) } static void -_retain_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *comment) +_retain_endpoint(microEndpoint *ep, bool lock) { if (ep == NULL) return; @@ -396,14 +383,13 @@ _retain_endpoint(microEndpoint *ep, bool lock, const char *caller, const char *c _lock_endpoint(ep); ep->refs++; - printf("<>/<> _retain_endpoint++: %s by %s (%s), refs: %d\n", ep->subject, caller ? caller : "NULL", comment ? comment : "NULL", ep->refs); if (lock) _unlock_endpoint(ep); } static void -_release_endpoint(microEndpoint *ep, const char *caller, const char *comment) +_release_endpoint(microEndpoint *ep) { int refs; @@ -413,7 +399,6 @@ _release_endpoint(microEndpoint *ep, const char *caller, const char *comment) _lock_endpoint(ep); refs = --(ep->refs); - printf("<>/<> _release_endpoint--: %s by %s (%s), refs: %d\n", ep->subject, caller ? caller : "NULL", comment ? comment : "NULL", ep->refs); _unlock_endpoint(ep); @@ -467,7 +452,7 @@ _clone_service_config(microServiceConfig **out, microServiceConfig *cfg) } static void -_retain_service(microService *m, bool lock, const char *caller, const char *comment) +_retain_service(microService *m, bool lock) { if (m == NULL) return; @@ -476,7 +461,6 @@ _retain_service(microService *m, bool lock, const char *caller, const char *comm _lock_service(m); ++(m->refs); - printf("<>/<> _retain_service++: %s by %s (%s), refs: %d\n", m->cfg->Name, caller ? caller : "NULL", comment ? comment : "NULL", m->refs); if (lock) _unlock_service(m); @@ -490,8 +474,6 @@ _free_service(microService *m) if (m == NULL) return; - printf("<>/<> %s: %s\n", __func__, m->cfg->Name); - // destroy all groups. if (m->groups != NULL) { @@ -511,7 +493,7 @@ _free_service(microService *m) } static void -_release_service(microService *m, const char *caller, const char *comment) +_release_service(microService *m) { int refs = 0; @@ -521,7 +503,6 @@ _release_service(microService *m, const char *caller, const char *comment) _lock_service(m); refs = --(m->refs); - printf("<>/<> _release_service--: %s by %s (%s), refs: %d\n", m->cfg->Name, caller ? caller : "NULL", comment ? comment : "NULL", refs); _unlock_service(m); @@ -608,14 +589,14 @@ _start_service_callbacks(microService *m) } // Extra reference to the service as long as its callbacks are registered. - RETAIN_SERVICE(m, "Started service callbacks"); + _retain_service(m, true); natsMutex_Lock(service_callback_mu); IFOK(s, natsHash_Set(all_services_to_callback, (int64_t)m, (void *)m, NULL)); natsMutex_Unlock(service_callback_mu); if (s != NATS_OK) { - RELEASE_SERVICE(m, "in error"); + _release_service(m); } return micro_ErrorFromStatus(s); @@ -663,7 +644,7 @@ _services_for_connection(microService ***to_call, int *num_microservices, natsCo { if (m->nc == nc) { - RETAIN_SERVICE(m, "For the specific connection closed/error callback"); // for the callback + _retain_service(m, true); // for the callback p[i++] = m; } } @@ -686,8 +667,6 @@ _on_connection_closed(natsConnection *nc, void *ignored) int n = 0; int i; - printf("<>/<> on_connection_closed: CALLED\n"); - err = _services_for_connection(&to_call, &n, nc); if (err != NULL) { @@ -695,15 +674,12 @@ _on_connection_closed(natsConnection *nc, void *ignored) return; } - printf("<>/<> on_connection_closed: %d services to call\n", n); - for (i = 0; i < n; i++) { m = to_call[i]; - microError_Ignore( - microService_Stop(m, "STOP from connection closed callback")); + microError_Ignore(microService_Stop(m)); - RELEASE_SERVICE(m, "After connection closed callback"); + _release_service(m); } NATS_FREE(to_call); @@ -723,7 +699,7 @@ _on_service_error_l(microService *m, const char *subject, natsStatus s) (ep != NULL) && !micro_match_endpoint_subject(ep->subject, subject); ep = ep->next) ; - RETAIN_EP(ep, "For error callback"); // for the callback + _retain_endpoint(ep, true); // for the callback _unlock_service(m); if (ep != NULL) @@ -735,10 +711,10 @@ _on_service_error_l(microService *m, const char *subject, natsStatus s) micro_update_last_error(ep, err); microError_Destroy(err); } - RELEASE_EP(ep, "After error callback"); // after the callback + _release_endpoint(ep); // after the callback // TODO: Should we stop the service? The Go client does. - microError_Ignore(microService_Stop(m, "STOP from error callback")); + microError_Ignore(microService_Stop(m)); } static void @@ -751,11 +727,8 @@ _on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_use int n = 0; int i; - printf("<>/<> on_error: CALLED: %d\n", s); - if (sub == NULL) { - printf("<>/<> on_error: no sub, nothing to do\n"); return; } subject = natsSubscription_GetSubject(sub); @@ -768,13 +741,11 @@ _on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_use return; } - printf("<>/<> on_error: %d services to call\n", n); - for (i = 0; i < n; i++) { m = to_call[i]; _on_service_error_l(m, subject, s); - RELEASE_SERVICE(m, "After on_error invocation"); // release the extra ref in `to_call`. + _release_service(m); // release the extra ref in `to_call`. } NATS_FREE(to_call); @@ -815,13 +786,11 @@ microService_Destroy(microService *m) { microError *err = NULL; - printf("<>/<> microService_Destroy: %s\n", m->cfg->Name); - - err = microService_Stop(m, "STOP from Destroy"); + err = microService_Stop(m); if (err != NULL) return err; - RELEASE_SERVICE(m, "Destroy"); + _release_service(m); return NULL; } @@ -1014,7 +983,6 @@ _release_on_endpoint_complete(void *closure) // Release the service reference for the completed endpoint. It can not be // the last reference, so no need to free m. m->refs--; - printf("<>/<> EP DONE: remove EP %s and finalize: m->refs:%d, ep->refs:%d\n", ep->subject, m->refs, ep->refs); // Unlink the endpoint from the service. if (_find_endpoint(&prev_ep, m, ep)) @@ -1045,15 +1013,10 @@ _release_on_endpoint_complete(void *closure) if (finalize) { - printf("<>/<> EP DONE: final!!!\n"); if (doneHandler != NULL) doneHandler(m); - RELEASE_SERVICE(m, "After callbacks removed, final"); - } - else - { - printf("<>/<> EP DONE: already stopped or not final yet.\n"); + _release_service(m); } } @@ -1077,10 +1040,10 @@ _start_endpoint_l(microService *m, microEndpoint *ep) if (s == NATS_OK) { - // extra retain before subscribing since we'll need to hold it until - // on_complete on the subscription. + // extra retain for the subscription since we'll need to hold it until + // on_complete. _lock_endpoint(ep); - RETAIN_EP_INLINE(ep, "Started endpoint"); + ep->refs++; ep->sub = sub; ep->is_draining = false; _unlock_endpoint(ep); @@ -1208,7 +1171,7 @@ _new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, micro ep->is_monitoring_endpoint = is_internal; // retain `m` before storing it for callbacks. - RETAIN_SERVICE(m, "in endpoint for callbacks"); + _retain_service(m, true); ep->service_ptr_for_on_complete = m; MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->endpoint_mu))); diff --git a/src/nats.c b/src/nats.c index 5acc4a17d..8e35b917d 100644 --- a/src/nats.c +++ b/src/nats.c @@ -760,8 +760,6 @@ _asyncCbsThread(void *arg) sc = cb->sc; #endif - printf("<>/<> ====> async cb type=%d status:%d\n", cb->type, cb->err); - switch (cb->type) { case ASYNC_CLOSED: diff --git a/src/nats.h b/src/nats.h index 0ed18a1c8..a07d0a0cd 100644 --- a/src/nats.h +++ b/src/nats.h @@ -7801,7 +7801,7 @@ microService_Run(microService *m); * * @see #micro_AddService, #microService_Run */ -NATS_EXTERN microError *microService_Stop(microService *m, const char *comment); +NATS_EXTERN microError *microService_Stop(microService *m); /** @} */ // end of microServiceFunctions diff --git a/test/test.c b/test/test.c index 237fe27ce..b6bba9025 100644 --- a/test/test.c +++ b/test/test.c @@ -32260,8 +32260,6 @@ _microHandleRequestNoisy42(microRequest *req) // Random delay between 5-10ms nats_Sleep(5 + (rand() % 5)); - printf("<>/<> Responding with 42!!!!\n"); - return microRequest_Respond(req, "42", 2); } @@ -32272,7 +32270,6 @@ _microServiceDoneHandler(microService *m) natsMutex_Lock(arg->m); arg->microRunningServiceCount--; - printf("<>/<> ========= SERVICE DONE, %d remain\n", arg->microRunningServiceCount); if (arg->microRunningServiceCount == 0) { arg->microAllDone = true; @@ -32991,12 +32988,10 @@ test_MicroStartStop(void) test("Send requests: "); for (i = 0; i < 20; i++) { - // printf("<>/<> sending %d\n", i); reply = NULL; s = natsConnection_Request(&reply, nc, "svc.do", NULL, 0, 2000); if (NATS_OK != s) FAIL("Unable to send request"); - // printf("<>/<> received %d: '%.*s'\n", i, reply->dataLen, reply->data); if (reply == NULL || reply->dataLen != 2 || memcmp(reply->data, "42", 2) != 0) FAIL("Unexpected reply"); natsMsg_Destroy(reply); @@ -33155,8 +33150,6 @@ void _microAsyncErrorHandler(microService *m, microEndpoint *ep, natsStatus s) { struct threadArg *arg = (struct threadArg*) microService_GetState(m); - printf("<>/<> _microAsyncErrorHandler: %d\n", s); - natsMutex_Lock(arg->m); // release the pending test request that caused the error arg->closed = true; From 5ae5e6f3854c0148f38def4a1afbf5ac9b7b29c0 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sat, 3 Jun 2023 07:28:24 -0700 Subject: [PATCH 71/85] reworked wip12 --- src/sub.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sub.c b/src/sub.c index a53a16d0f..3c68c0ed6 100644 --- a/src/sub.c +++ b/src/sub.c @@ -81,9 +81,6 @@ _freeSubscription(natsSubscription *sub) void natsSub_retain(natsSubscription *sub) { - if (sub == NULL) - return; - natsSub_Lock(sub); sub->refs++; From e2d41f0df1162d48a67b5cc9fea8792cd2c06f54 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sat, 3 Jun 2023 07:34:25 -0700 Subject: [PATCH 72/85] reworked wip13 --- src/micro.c | 49 +++++++++++---------------------------------- src/micro_error.c | 2 +- src/micro_request.c | 2 +- 3 files changed, 14 insertions(+), 39 deletions(-) diff --git a/src/micro.c b/src/micro.c index 3c442d507..75e87c28a 100644 --- a/src/micro.c +++ b/src/micro.c @@ -28,8 +28,8 @@ static inline void _unlock_endpoint(microEndpoint *ep) { natsMutex_Unlock(ep->en static microError *_clone_service_config(microServiceConfig **out, microServiceConfig *cfg); static microError *_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); static microError *_new_service(microService **ptr, natsConnection *nc); -static microError *_start_endpoint_l(microService *m, microEndpoint *ep); -static microError *_stop_endpoint_l(microService *m, microEndpoint *ep); +static microError *_start_endpoint(microService *m, microEndpoint *ep); +static microError *_stop_endpoint(microService *m, microEndpoint *ep); static microError *_wrap_connection_event_callbacks(microService *m); static bool _is_valid_name(const char *name); @@ -40,30 +40,6 @@ static void _release_service(microService *m); static void _retain_service(microService *m, bool lock); static void _stop_service_callbacks(microService *m); -static inline int _endpoint_count(microService *m) -{ - int n = 0; - for (microEndpoint *ep = m->first_ep; ep != NULL; ep = ep->next) - { - n++; - } - return n; -} - -static inline void _dump_endpoints(microService *m) -{ - // const char *sep = NULL; - for (microEndpoint *ep = m->first_ep; ep != NULL; ep = ep->next) - { - // if (sep == NULL) - // sep = "\n"; - // else - // printf("%s", sep); - printf("\t%s\n", ep->subject); - } - printf("\n"); -} - microError * micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *cfg) { @@ -181,12 +157,12 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, // Rid of the previous endpoint with the same name, if any. If this // fails we can return the error, leave the newly added endpoint in the // list, not started. A retry with the same name will clean it up. - if (err = _stop_endpoint_l(m, prev_ep), err != NULL) + if (err = _stop_endpoint(m, prev_ep), err != NULL) return err; _release_endpoint(prev_ep); } - if (err = _start_endpoint_l(m, ep), err != NULL) + if (err = _start_endpoint(m, ep), err != NULL) { // Best effort, leave the new endpoint in the list, as is. A retry with // the same name will clean it up. @@ -220,7 +196,7 @@ microService_Stop(microService *m) for (; ep != NULL; ep = ep->next) { - if (err = _stop_endpoint_l(m, ep), err != NULL) + if (err = _stop_endpoint(m, ep), err != NULL) return microError_Wrapf(err, "failed to stop service '%s', stopping endpoint '%s'", m->cfg->Name, ep->name); } @@ -686,7 +662,7 @@ _on_connection_closed(natsConnection *nc, void *ignored) } static void -_on_service_error_l(microService *m, const char *subject, natsStatus s) +_on_service_error(microService *m, const char *subject, natsStatus s) { microEndpoint *ep = NULL; microError *err = NULL; @@ -744,7 +720,7 @@ _on_error(natsConnection *nc, natsSubscription *sub, natsStatus s, void *not_use for (i = 0; i < n; i++) { m = to_call[i]; - _on_service_error_l(m, subject, s); + _on_service_error(m, subject, s); _release_service(m); // release the extra ref in `to_call`. } @@ -990,7 +966,6 @@ _release_on_endpoint_complete(void *closure) if (prev_ep != NULL) { prev_ep->next = ep->next; - } else { @@ -1007,10 +982,10 @@ _release_on_endpoint_complete(void *closure) } _unlock_service(m); - + if (free_ep) _free_endpoint(ep); - + if (finalize) { if (doneHandler != NULL) @@ -1021,7 +996,7 @@ _release_on_endpoint_complete(void *closure) } static microError * -_start_endpoint_l(microService *m, microEndpoint *ep) +_start_endpoint(microService *m, microEndpoint *ep) { natsStatus s = NATS_OK; natsSubscription *sub = NULL; @@ -1048,7 +1023,7 @@ _start_endpoint_l(microService *m, microEndpoint *ep) ep->is_draining = false; _unlock_endpoint(ep); - // The service needs to be retained + // The service needs to be retained natsSubscription_SetOnCompleteCB(sub, _release_on_endpoint_complete, ep); } @@ -1061,7 +1036,7 @@ _start_endpoint_l(microService *m, microEndpoint *ep) } static microError * -_stop_endpoint_l(microService *m, microEndpoint *ep) +_stop_endpoint(microService *m, microEndpoint *ep) { natsStatus s = NATS_OK; natsSubscription *sub = NULL; diff --git a/src/micro_error.c b/src/micro_error.c index ad0385e64..b31fd4d2a 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -132,7 +132,7 @@ micro_is_error_message(natsStatus status, natsMsg *msg) code = atoi(c); } is_service_error = (code != 0) || !nats_IsStringEmpty(d); - + if (is_service_error && !is_nats_error) { return micro_ErrorfCode(code, d); diff --git a/src/micro_request.c b/src/micro_request.c index 8968aa3f1..72159b7e3 100644 --- a/src/micro_request.c +++ b/src/micro_request.c @@ -57,7 +57,7 @@ microRequest_RespondCustom(microRequest *req, microError *service_error, const c s = natsMsgHeader_Set(msg, MICRO_ERROR_CODE_HDR, buf); } } - if (s == NATS_OK) + if (s == NATS_OK) { s = natsConnection_PublishMsg(req->Message->sub->conn, msg); } From f1f1191c49a1a0df7f662ba4aa55b30b172a5d9e Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sun, 4 Jun 2023 05:09:40 -0700 Subject: [PATCH 73/85] reworked wip14 --- src/micro.c | 603 ++++++------------------------------------- src/micro_endpoint.c | 431 +++++++++++++++++++++++++++++++ src/microp.h | 44 +++- 3 files changed, 548 insertions(+), 530 deletions(-) create mode 100644 src/micro_endpoint.c diff --git a/src/micro.c b/src/micro.c index 75e87c28a..029e532f9 100644 --- a/src/micro.c +++ b/src/micro.c @@ -22,20 +22,13 @@ static natsHash *all_services_to_callback; // uses `microService*` as the key an static inline void _lock_service(microService *m) { natsMutex_Lock(m->service_mu); } static inline void _unlock_service(microService *m) { natsMutex_Unlock(m->service_mu); } -static inline void _lock_endpoint(microEndpoint *ep) { natsMutex_Lock(ep->endpoint_mu); } -static inline void _unlock_endpoint(microEndpoint *ep) { natsMutex_Unlock(ep->endpoint_mu); } static microError *_clone_service_config(microServiceConfig **out, microServiceConfig *cfg); -static microError *_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); static microError *_new_service(microService **ptr, natsConnection *nc); -static microError *_start_endpoint(microService *m, microEndpoint *ep); -static microError *_stop_endpoint(microService *m, microEndpoint *ep); static microError *_wrap_connection_event_callbacks(microService *m); -static bool _is_valid_name(const char *name); +static bool _find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find); -static void _retain_endpoint(microEndpoint *ep, bool lock); -static void _release_endpoint(microEndpoint *ep); static void _release_service(microService *m); static void _retain_service(microService *m, bool lock); static void _stop_service_callbacks(microService *m); @@ -47,7 +40,7 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c microError *err = NULL; microService *m = NULL; - if ((new_m == NULL) || (nc == NULL) || (cfg == NULL) || !_is_valid_name(cfg->Name) || nats_IsStringEmpty(cfg->Version)) + if ((new_m == NULL) || (nc == NULL) || (cfg == NULL) || !micro_is_valid_name(cfg->Name) || nats_IsStringEmpty(cfg->Version)) return micro_ErrorInvalidArg; // Make a microservice object, with a reference to a natsConnection. @@ -106,7 +99,10 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, if (cfg == NULL) return NULL; - err = _new_endpoint(&ep, m, prefix, cfg, is_internal); + // retain `m` before the endpoint uses it for its on_complete callback. + _retain_service(m, true); + + err = micro_new_endpoint(&ep, m, prefix, cfg, is_internal); if (err != NULL) return microError_Wrapf(err, "failed to create endpoint %s", cfg->Name); @@ -157,12 +153,12 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, // Rid of the previous endpoint with the same name, if any. If this // fails we can return the error, leave the newly added endpoint in the // list, not started. A retry with the same name will clean it up. - if (err = _stop_endpoint(m, prev_ep), err != NULL) + if (err = micro_stop_endpoint(prev_ep), err != NULL) return err; - _release_endpoint(prev_ep); + micro_release_endpoint(prev_ep); } - if (err = _start_endpoint(m, ep), err != NULL) + if (err = micro_start_endpoint(ep), err != NULL) { // Best effort, leave the new endpoint in the list, as is. A retry with // the same name will clean it up. @@ -196,7 +192,7 @@ microService_Stop(microService *m) for (; ep != NULL; ep = ep->next) { - if (err = _stop_endpoint(m, ep), err != NULL) + if (err = micro_stop_endpoint(ep), err != NULL) return microError_Wrapf(err, "failed to stop service '%s', stopping endpoint '%s'", m->cfg->Name, ep->name); } @@ -221,110 +217,94 @@ microService_Stop(microService *m) return NULL; } -static microError * -_new_service(microService **ptr, natsConnection *nc) -{ - *ptr = NATS_CALLOC(1, sizeof(microService)); - if (*ptr == NULL) - return micro_ErrorOutOfMemory; - - natsConn_retain(nc); - (*ptr)->refs = 1; - (*ptr)->nc = nc; - (*ptr)->started = nats_Now() * 1000000; - return NULL; -} - -static inline microError * -new_service_config(microServiceConfig **ptr) -{ - *ptr = NATS_CALLOC(1, sizeof(microServiceConfig)); - return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; -} - -static inline microError * -_new_endpoint_config(microEndpointConfig **ptr) +void micro_release_on_endpoint_complete(void *closure) { - *ptr = NATS_CALLOC(1, sizeof(microEndpointConfig)); - return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; -} + microEndpoint *ep = (microEndpoint *)closure; + microEndpoint *prev_ep = NULL; + microService *m = NULL; + natsSubscription *sub = NULL; + microDoneHandler doneHandler = NULL; + bool free_ep = false; + bool finalize = false; -static void -_free_cloned_endpoint_config(microEndpointConfig *cfg) -{ - if (cfg == NULL) + if (ep == NULL) return; - // the strings are declared const for the public, but in a clone these need - // to be freed. - NATS_FREE((char *)cfg->Name); - NATS_FREE((char *)cfg->Subject); + m = ep->m; + if ((m == NULL) || (m->service_mu == NULL)) + return; - NATS_FREE(cfg); -} + micro_lock_endpoint(ep); + ep->is_draining = false; + sub = ep->sub; + ep->sub = NULL; + ep->refs--; + free_ep = (ep->refs == 0); + micro_unlock_endpoint(ep); -static inline microError * -micro_strdup(char **ptr, const char *str) -{ - // Make a strdup(NULL) be a no-op, so we don't have to check for NULL - // everywhere. - if (str == NULL) - { - *ptr = NULL; - return NULL; - } - *ptr = NATS_STRDUP(str); - if (*ptr == NULL) - return micro_ErrorOutOfMemory; - return NULL; -} + // Force the subscription to be destroyed now. + natsSubscription_Destroy(sub); -static microError * -_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) -{ - microError *err = NULL; - microEndpointConfig *new_cfg = NULL; + _lock_service(m); - if (out == NULL) - return micro_ErrorInvalidArg; + // Release the service reference for the completed endpoint. It can not be + // the last reference, so no need to free m. + m->refs--; - if (cfg == NULL) + // Unlink the endpoint from the service. + if (_find_endpoint(&prev_ep, m, ep)) { - *out = NULL; - return NULL; + if (prev_ep != NULL) + { + prev_ep->next = ep->next; + } + else + { + m->first_ep = ep->next; + } } - err = _new_endpoint_config(&new_cfg); - if (err == NULL) + finalize = (!m->stopped) && (m->first_ep == NULL); + if (finalize) { - memcpy(new_cfg, cfg, sizeof(microEndpointConfig)); + _stop_service_callbacks(m); + m->stopped = true; + doneHandler = m->cfg->DoneHandler; } - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Subject, cfg->Subject)); + _unlock_service(m); - if (err != NULL) + if (free_ep) + micro_free_endpoint(ep); + + if (finalize) { - _free_cloned_endpoint_config(new_cfg); - return err; + if (doneHandler != NULL) + doneHandler(m); + + _release_service(m); } +} - *out = new_cfg; +static microError * +_new_service(microService **ptr, natsConnection *nc) +{ + *ptr = NATS_CALLOC(1, sizeof(microService)); + if (*ptr == NULL) + return micro_ErrorOutOfMemory; + + natsConn_retain(nc); + (*ptr)->refs = 1; + (*ptr)->nc = nc; + (*ptr)->started = nats_Now() * 1000000; return NULL; } -static void -_free_endpoint(microEndpoint *ep) +static inline microError * +new_service_config(microServiceConfig **ptr) { - if (ep == NULL) - return; - - NATS_FREE(ep->name); - NATS_FREE(ep->subject); - natsSubscription_Destroy(ep->sub); - natsMutex_Destroy(ep->endpoint_mu); - _free_cloned_endpoint_config(ep->config); - NATS_FREE(ep); + *ptr = NATS_CALLOC(1, sizeof(microServiceConfig)); + return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; } static bool @@ -349,39 +329,6 @@ _find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find) return false; } -static void -_retain_endpoint(microEndpoint *ep, bool lock) -{ - if (ep == NULL) - return; - - if (lock) - _lock_endpoint(ep); - - ep->refs++; - - if (lock) - _unlock_endpoint(ep); -} - -static void -_release_endpoint(microEndpoint *ep) -{ - int refs; - - if (ep == NULL) - return; - - _lock_endpoint(ep); - - refs = --(ep->refs); - - _unlock_endpoint(ep); - - if (refs == 0) - _free_endpoint(ep); -} - static void _free_cloned_service_config(microServiceConfig *cfg) { @@ -393,7 +340,7 @@ _free_cloned_service_config(microServiceConfig *cfg) NATS_FREE((char *)cfg->Name); NATS_FREE((char *)cfg->Version); NATS_FREE((char *)cfg->Description); - _free_cloned_endpoint_config(cfg->Endpoint); + micro_free_cloned_endpoint_config(cfg->Endpoint); NATS_FREE(cfg); } @@ -416,7 +363,7 @@ _clone_service_config(microServiceConfig **out, microServiceConfig *cfg) MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); MICRO_CALL(err, micro_strdup((char **)&new_cfg->Version, cfg->Version)); MICRO_CALL(err, micro_strdup((char **)&new_cfg->Description, cfg->Description)); - MICRO_CALL(err, _clone_endpoint_config(&new_cfg->Endpoint, cfg->Endpoint)); + MICRO_CALL(err, micro_clone_endpoint_config(&new_cfg->Endpoint, cfg->Endpoint)); if (err != NULL) { _free_cloned_service_config(new_cfg); @@ -486,62 +433,6 @@ _release_service(microService *m) _free_service(m); } -bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject) -{ - const char *e = ep_subject; - const char *a = actual_subject; - const char *etok, *enext; - int etok_len; - bool last_etok = false; - const char *atok, *anext; - int atok_len; - bool last_atok = false; - - if (e == NULL || a == NULL) - return false; - - while (true) - { - enext = strchr(e, '.'); - if (enext == NULL) - { - enext = e + strlen(e); - last_etok = true; - } - etok = e; - etok_len = (int)(enext - e); - e = enext + 1; - - anext = strchr(a, '.'); - if (anext == NULL) - { - anext = a + strlen(a); - last_atok = true; - } - atok = a; - atok_len = (int)(anext - a); - a = anext + 1; - - if (last_etok) - { - if (etok_len == 1 && etok[0] == '>') - return true; - - if (!last_atok) - return false; - } - if (!(etok_len == 1 && etok[0] == '*') && - !(etok_len == atok_len && strncmp(etok, atok, etok_len) == 0)) - { - return false; - } - if (last_atok) - { - return last_etok; - } - } -} - static microError * _start_service_callbacks(microService *m) { @@ -675,7 +566,7 @@ _on_service_error(microService *m, const char *subject, natsStatus s) (ep != NULL) && !micro_match_endpoint_subject(ep->subject, subject); ep = ep->next) ; - _retain_endpoint(ep, true); // for the callback + micro_retain_endpoint(ep); // for the callback _unlock_service(m); if (ep != NULL) @@ -687,7 +578,7 @@ _on_service_error(microService *m, const char *subject, natsStatus s) micro_update_last_error(ep, err); microError_Destroy(err); } - _release_endpoint(ep); // after the callback + micro_release_endpoint(ep); // after the callback // TODO: Should we stop the service? The Go client does. microError_Ignore(microService_Stop(m)); @@ -852,317 +743,6 @@ microService_GetConnection(microService *m) return m->nc; } -static void -_update_last_error(microEndpoint *ep, microError *err) -{ - ep->stats.NumErrors++; - microError_String(err, ep->stats.LastErrorString, sizeof(ep->stats.LastErrorString)); -} - -void micro_update_last_error(microEndpoint *ep, microError *err) -{ - if (err == NULL || ep == NULL) - return; - - _lock_endpoint(ep); - _update_last_error(ep, err); - _unlock_endpoint(ep); -} - -static void -_handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) -{ - microError *err = NULL; - microError *service_err = NULL; - microEndpoint *ep = (microEndpoint *)closure; - microService *m; - microEndpointStats *stats = NULL; - microRequestHandler handler; - microRequest *req = NULL; - int64_t start, elapsed_ns = 0, full_s; - - if ((ep == NULL) || (ep->endpoint_mu == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) - { - // This would be a bug, we should not have received a message on this - // subscription. - return; - } - - stats = &ep->stats; - m = ep->service_ptr_for_on_complete; - handler = ep->config->Handler; - - err = micro_new_request(&req, m, ep, msg); - if (err == NULL) - { - // handle the request. - start = nats_NowInNanoSeconds(); - service_err = handler(req); - if (service_err != NULL) - { - // if the handler returned an error, we attempt to respond with it. - // Note that if the handler chose to do its own RespondError which - // fails, and then the handler returns its error - we'll try to - // RespondError again, double-counting the error. - err = microRequest_RespondError(req, service_err); - } - - elapsed_ns = nats_NowInNanoSeconds() - start; - } - - // Update stats. - _lock_endpoint(ep); - stats->NumRequests++; - stats->ProcessingTimeNanoseconds += elapsed_ns; - full_s = stats->ProcessingTimeNanoseconds / 1000000000; - stats->ProcessingTimeSeconds += full_s; - stats->ProcessingTimeNanoseconds -= full_s * 1000000000; - _update_last_error(ep, err); - _unlock_endpoint(ep); - - microError_Destroy(err); - micro_free_request(req); - natsMsg_Destroy(msg); -} - -static void -_release_on_endpoint_complete(void *closure) -{ - microEndpoint *ep = (microEndpoint *)closure; - microEndpoint *prev_ep = NULL; - microService *m = NULL; - natsSubscription *sub = NULL; - microDoneHandler doneHandler = NULL; - bool free_ep = false; - bool finalize = false; - - if (ep == NULL) - return; - - m = ep->service_ptr_for_on_complete; - if ((m == NULL) || (m->service_mu == NULL)) - return; - - _lock_endpoint(ep); - ep->is_draining = false; - sub = ep->sub; - ep->sub = NULL; - ep->refs--; - free_ep = (ep->refs == 0); - _unlock_endpoint(ep); - - // Force the subscription to be destroyed now. - natsSubscription_Destroy(sub); - - _lock_service(m); - - // Release the service reference for the completed endpoint. It can not be - // the last reference, so no need to free m. - m->refs--; - - // Unlink the endpoint from the service. - if (_find_endpoint(&prev_ep, m, ep)) - { - if (prev_ep != NULL) - { - prev_ep->next = ep->next; - } - else - { - m->first_ep = ep->next; - } - } - - finalize = (!m->stopped) && (m->first_ep == NULL); - if (finalize) - { - _stop_service_callbacks(m); - m->stopped = true; - doneHandler = m->cfg->DoneHandler; - } - - _unlock_service(m); - - if (free_ep) - _free_endpoint(ep); - - if (finalize) - { - if (doneHandler != NULL) - doneHandler(m); - - _release_service(m); - } -} - -static microError * -_start_endpoint(microService *m, microEndpoint *ep) -{ - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; - - if ((ep->subject == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) - // nothing to do - return NULL; - - // reset the stats. - memset(&ep->stats, 0, sizeof(ep->stats)); - - if (ep->is_monitoring_endpoint) - s = natsConnection_Subscribe(&sub, m->nc, ep->subject, _handle_request, ep); - else - s = natsConnection_QueueSubscribe(&sub, m->nc, ep->subject, MICRO_QUEUE_GROUP, _handle_request, ep); - - if (s == NATS_OK) - { - // extra retain for the subscription since we'll need to hold it until - // on_complete. - _lock_endpoint(ep); - ep->refs++; - ep->sub = sub; - ep->is_draining = false; - _unlock_endpoint(ep); - - // The service needs to be retained - - natsSubscription_SetOnCompleteCB(sub, _release_on_endpoint_complete, ep); - } - else - { - natsSubscription_Destroy(sub); // likely always a no-op. - } - - return micro_ErrorFromStatus(s); -} - -static microError * -_stop_endpoint(microService *m, microEndpoint *ep) -{ - natsStatus s = NATS_OK; - natsSubscription *sub = NULL; - bool conn_closed = natsConnection_IsClosed(m->nc); - - if (ep == NULL) - return NULL; - - _lock_endpoint(ep); - sub = ep->sub; - - if (ep->is_draining || conn_closed || !natsSubscription_IsValid(sub)) - { - // If stopping, _release_on_endpoint_complete will take care of - // finalizing, nothing else to do. In other cases - // _release_on_endpoint_complete has already been called. - _unlock_endpoint(ep); - return NULL; - } - - ep->is_draining = true; - _unlock_endpoint(ep); - - // When the drain is complete, will release the final ref on ep. - s = natsSubscription_Drain(sub); - if (s != NATS_OK) - { - return microError_Wrapf(micro_ErrorFromStatus(s), - "failed to stop endpoint %s: failed to drain subscription", ep->name); - } - - return NULL; -} - -static bool -_is_valid_subject(const char *subject) -{ - int i; - int len; - - if (subject == NULL) - return false; - - len = (int)strlen(subject); - if (len == 0) - return false; - - for (i = 0; i < len - 1; i++) - { - if ((subject[i] == ' ') || (subject[i] == '>')) - return false; - } - - if ((subject[i] == ' ')) - return false; - - return true; -} - -static microError * -_dup_with_prefix(char **dst, const char *prefix, const char *src) -{ - size_t len = strlen(src) + 1; - char *p; - - if (!nats_IsStringEmpty(prefix)) - len += strlen(prefix) + 1; - - *dst = NATS_CALLOC(1, len); - if (*dst == NULL) - return micro_ErrorOutOfMemory; - - p = *dst; - if (!nats_IsStringEmpty(prefix)) - { - len = strlen(prefix); - memcpy(p, prefix, len); - p[len] = '.'; - p += len + 1; - } - memcpy(p, src, strlen(src) + 1); - return NULL; -} - -static microError * -_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal) -{ - microError *err = NULL; - microEndpoint *ep = NULL; - const char *subj; - - if (cfg == NULL) - return microError_Wrapf(micro_ErrorInvalidArg, "NULL endpoint config"); - if (!_is_valid_name(cfg->Name)) - return microError_Wrapf(micro_ErrorInvalidArg, "invalid endpoint name %s", cfg->Name); - if (cfg->Handler == NULL) - return microError_Wrapf(micro_ErrorInvalidArg, "NULL endpoint request handler for %s", cfg->Name); - - if ((cfg->Subject != NULL) && !_is_valid_subject(cfg->Subject)) - return micro_ErrorInvalidArg; - - subj = nats_IsStringEmpty(cfg->Subject) ? cfg->Name : cfg->Subject; - - ep = NATS_CALLOC(1, sizeof(microEndpoint)); - if (ep == NULL) - return micro_ErrorOutOfMemory; - ep->is_monitoring_endpoint = is_internal; - - // retain `m` before storing it for callbacks. - _retain_service(m, true); - ep->service_ptr_for_on_complete = m; - - MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->endpoint_mu))); - MICRO_CALL(err, _clone_endpoint_config(&ep->config, cfg)); - MICRO_CALL(err, _dup_with_prefix(&ep->name, prefix, cfg->Name)); - MICRO_CALL(err, _dup_with_prefix(&ep->subject, prefix, subj)); - if (err != NULL) - { - _free_endpoint(ep); - return err; - } - - *new_ep = ep; - return NULL; -} - microError * microService_GetInfo(microServiceInfo **new_info, microService *m) { @@ -1279,7 +859,7 @@ microService_GetStats(microServiceStats **new_stats, microService *m) { if ((ep != NULL) && (!ep->is_monitoring_endpoint) && (ep->endpoint_mu != NULL)) { - _lock_endpoint(ep); + micro_lock_endpoint(ep); // copy the entire struct, including the last error buffer. stats->Endpoints[len] = ep->stats; @@ -1289,7 +869,7 @@ microService_GetStats(microServiceStats **new_stats, microService *m) avg = avg / (long double)ep->stats.NumRequests; stats->Endpoints[len].AverageProcessingTimeNanoseconds = (int64_t)avg; len++; - _unlock_endpoint(ep); + micro_unlock_endpoint(ep); } } @@ -1318,24 +898,3 @@ void microServiceStats_Destroy(microServiceStats *stats) NATS_FREE((char *)stats->Id); NATS_FREE(stats); } - -static bool -_is_valid_name(const char *name) -{ - int i; - int len; - - if (name == NULL) - return false; - - len = (int)strlen(name); - if (len == 0) - return false; - - for (i = 0; i < len; i++) - { - if (!isalnum(name[i]) && (name[i] != '_') && (name[i] != '-')) - return false; - } - return true; -} diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c new file mode 100644 index 000000000..6ea4efc5c --- /dev/null +++ b/src/micro_endpoint.c @@ -0,0 +1,431 @@ +// Copyright 2023 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "microp.h" + +static microError *_dup_with_prefix(char **dst, const char *prefix, const char *src); + +static void _handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure); + +static void _retain_endpoint(microEndpoint *ep, bool lock); +static void _release_endpoint(microEndpoint *ep); + +microError * +micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal) +{ + microError *err = NULL; + microEndpoint *ep = NULL; + const char *subj; + + if (cfg == NULL) + return microError_Wrapf(micro_ErrorInvalidArg, "NULL endpoint config"); + if (!micro_is_valid_name(cfg->Name)) + return microError_Wrapf(micro_ErrorInvalidArg, "invalid endpoint name %s", cfg->Name); + if (cfg->Handler == NULL) + return microError_Wrapf(micro_ErrorInvalidArg, "NULL endpoint request handler for %s", cfg->Name); + + if ((cfg->Subject != NULL) && !micro_is_valid_subject(cfg->Subject)) + return micro_ErrorInvalidArg; + + subj = nats_IsStringEmpty(cfg->Subject) ? cfg->Name : cfg->Subject; + + ep = NATS_CALLOC(1, sizeof(microEndpoint)); + if (ep == NULL) + return micro_ErrorOutOfMemory; + ep->is_monitoring_endpoint = is_internal; + ep->m = m; + + MICRO_CALL(err, micro_ErrorFromStatus(natsMutex_Create(&ep->endpoint_mu))); + MICRO_CALL(err, micro_clone_endpoint_config(&ep->config, cfg)); + MICRO_CALL(err, _dup_with_prefix(&ep->name, prefix, cfg->Name)); + MICRO_CALL(err, _dup_with_prefix(&ep->subject, prefix, subj)); + if (err != NULL) + { + micro_free_endpoint(ep); + return err; + } + + *new_ep = ep; + return NULL; +} + +microError * +micro_start_endpoint(microEndpoint *ep) +{ + natsStatus s = NATS_OK; + natsSubscription *sub = NULL; + + if ((ep->subject == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL) || (ep->m == NULL)) + // nothing to do + return NULL; + + // reset the stats. + memset(&ep->stats, 0, sizeof(ep->stats)); + + if (ep->is_monitoring_endpoint) + s = natsConnection_Subscribe(&sub, ep->m->nc, ep->subject, _handle_request, ep); + else + s = natsConnection_QueueSubscribe(&sub, ep->m->nc, ep->subject, MICRO_QUEUE_GROUP, _handle_request, ep); + + if (s == NATS_OK) + { + // extra retain for the subscription since we'll need to hold it until + // on_complete. + micro_lock_endpoint(ep); + ep->refs++; + ep->sub = sub; + ep->is_draining = false; + micro_unlock_endpoint(ep); + + // The service needs to be retained + + natsSubscription_SetOnCompleteCB(sub, micro_release_on_endpoint_complete, ep); + } + else + { + natsSubscription_Destroy(sub); // likely always a no-op. + } + + return micro_ErrorFromStatus(s); +} + +microError * +micro_stop_endpoint(microEndpoint *ep) +{ + natsStatus s = NATS_OK; + natsSubscription *sub = NULL; + + if ((ep == NULL) || (ep->m == NULL)) + return NULL; + + micro_lock_endpoint(ep); + sub = ep->sub; + + if (ep->is_draining || natsConnection_IsClosed(ep->m->nc) || !natsSubscription_IsValid(sub)) + { + // If stopping, _release_on_endpoint_complete will take care of + // finalizing, nothing else to do. In other cases + // _release_on_endpoint_complete has already been called. + micro_unlock_endpoint(ep); + return NULL; + } + + ep->is_draining = true; + micro_unlock_endpoint(ep); + + // When the drain is complete, will release the final ref on ep. + s = natsSubscription_Drain(sub); + if (s != NATS_OK) + { + return microError_Wrapf(micro_ErrorFromStatus(s), + "failed to stop endpoint %s: failed to drain subscription", ep->name); + } + + return NULL; +} + +void micro_retain_endpoint(microEndpoint *ep) +{ + if (ep == NULL) + return; + + micro_lock_endpoint(ep); + + ep->refs++; + + micro_unlock_endpoint(ep); +} + +void micro_release_endpoint(microEndpoint *ep) +{ + int refs; + + if (ep == NULL) + return; + + micro_lock_endpoint(ep); + + refs = --(ep->refs); + + micro_unlock_endpoint(ep); + + if (refs == 0) + micro_free_endpoint(ep); +} + +void micro_free_endpoint(microEndpoint *ep) +{ + if (ep == NULL) + return; + + NATS_FREE(ep->name); + NATS_FREE(ep->subject); + natsSubscription_Destroy(ep->sub); + natsMutex_Destroy(ep->endpoint_mu); + micro_free_cloned_endpoint_config(ep->config); + NATS_FREE(ep); +} + +static void +_update_last_error(microEndpoint *ep, microError *err) +{ + ep->stats.NumErrors++; + microError_String(err, ep->stats.LastErrorString, sizeof(ep->stats.LastErrorString)); +} + +static void +_handle_request(natsConnection *nc, natsSubscription *sub, natsMsg *msg, void *closure) +{ + microError *err = NULL; + microError *service_err = NULL; + microEndpoint *ep = (microEndpoint *)closure; + microService *m; + microEndpointStats *stats = NULL; + microRequestHandler handler; + microRequest *req = NULL; + int64_t start, elapsed_ns = 0, full_s; + + if ((ep == NULL) || (ep->endpoint_mu == NULL) || (ep->config == NULL) || (ep->config->Handler == NULL)) + { + // This would be a bug, we should not have received a message on this + // subscription. + return; + } + + stats = &ep->stats; + m = ep->m; + handler = ep->config->Handler; + + err = micro_new_request(&req, m, ep, msg); + if (err == NULL) + { + // handle the request. + start = nats_NowInNanoSeconds(); + service_err = handler(req); + if (service_err != NULL) + { + // if the handler returned an error, we attempt to respond with it. + // Note that if the handler chose to do its own RespondError which + // fails, and then the handler returns its error - we'll try to + // RespondError again, double-counting the error. + err = microRequest_RespondError(req, service_err); + } + + elapsed_ns = nats_NowInNanoSeconds() - start; + } + + // Update stats. + micro_lock_endpoint(ep); + stats->NumRequests++; + stats->ProcessingTimeNanoseconds += elapsed_ns; + full_s = stats->ProcessingTimeNanoseconds / 1000000000; + stats->ProcessingTimeSeconds += full_s; + stats->ProcessingTimeNanoseconds -= full_s * 1000000000; + _update_last_error(ep, err); + micro_unlock_endpoint(ep); + + microError_Destroy(err); + micro_free_request(req); + natsMsg_Destroy(msg); +} + +void micro_update_last_error(microEndpoint *ep, microError *err) +{ + if (err == NULL || ep == NULL) + return; + + micro_lock_endpoint(ep); + _update_last_error(ep, err); + micro_unlock_endpoint(ep); +} + +bool micro_is_valid_name(const char *name) +{ + int i; + int len; + + if (name == NULL) + return false; + + len = (int)strlen(name); + if (len == 0) + return false; + + for (i = 0; i < len; i++) + { + if (!isalnum(name[i]) && (name[i] != '_') && (name[i] != '-')) + return false; + } + return true; +} + +bool micro_is_valid_subject(const char *subject) +{ + int i; + int len; + + if (subject == NULL) + return false; + + len = (int)strlen(subject); + if (len == 0) + return false; + + for (i = 0; i < len - 1; i++) + { + if ((subject[i] == ' ') || (subject[i] == '>')) + return false; + } + + if ((subject[i] == ' ')) + return false; + + return true; +} + +static inline microError * +_new_endpoint_config(microEndpointConfig **ptr) +{ + *ptr = NATS_CALLOC(1, sizeof(microEndpointConfig)); + return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; +} + +microError * +micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) +{ + microError *err = NULL; + microEndpointConfig *new_cfg = NULL; + + if (out == NULL) + return micro_ErrorInvalidArg; + + if (cfg == NULL) + { + *out = NULL; + return NULL; + } + + err = _new_endpoint_config(&new_cfg); + if (err == NULL) + { + memcpy(new_cfg, cfg, sizeof(microEndpointConfig)); + } + + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Subject, cfg->Subject)); + + if (err != NULL) + { + micro_free_cloned_endpoint_config(new_cfg); + return err; + } + + *out = new_cfg; + return NULL; +} + +void micro_free_cloned_endpoint_config(microEndpointConfig *cfg) +{ + if (cfg == NULL) + return; + + // the strings are declared const for the public, but in a clone these need + // to be freed. + NATS_FREE((char *)cfg->Name); + NATS_FREE((char *)cfg->Subject); + + NATS_FREE(cfg); +} + +bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject) +{ + const char *e = ep_subject; + const char *a = actual_subject; + const char *etok, *enext; + int etok_len; + bool last_etok = false; + const char *atok, *anext; + int atok_len; + bool last_atok = false; + + if (e == NULL || a == NULL) + return false; + + while (true) + { + enext = strchr(e, '.'); + if (enext == NULL) + { + enext = e + strlen(e); + last_etok = true; + } + etok = e; + etok_len = (int)(enext - e); + e = enext + 1; + + anext = strchr(a, '.'); + if (anext == NULL) + { + anext = a + strlen(a); + last_atok = true; + } + atok = a; + atok_len = (int)(anext - a); + a = anext + 1; + + if (last_etok) + { + if (etok_len == 1 && etok[0] == '>') + return true; + + if (!last_atok) + return false; + } + if (!(etok_len == 1 && etok[0] == '*') && + !(etok_len == atok_len && strncmp(etok, atok, etok_len) == 0)) + { + return false; + } + if (last_atok) + { + return last_etok; + } + } +} + +static microError * +_dup_with_prefix(char **dst, const char *prefix, const char *src) +{ + size_t len = strlen(src) + 1; + char *p; + + if (!nats_IsStringEmpty(prefix)) + len += strlen(prefix) + 1; + + *dst = NATS_CALLOC(1, len); + if (*dst == NULL) + return micro_ErrorOutOfMemory; + + p = *dst; + if (!nats_IsStringEmpty(prefix)) + { + len = strlen(prefix); + memcpy(p, prefix, len); + p[len] = '.'; + p += len + 1; + } + memcpy(p, src, strlen(src) + 1); + return NULL; +} \ No newline at end of file diff --git a/src/microp.h b/src/microp.h index 081bdb4bb..8bff43aee 100644 --- a/src/microp.h +++ b/src/microp.h @@ -52,8 +52,12 @@ struct micro_endpoint_s char *name; char *subject; - // References to other entities. + // A copy of the config provided to add_endpoint. microEndpointConfig *config; + + // Retained/released by the service that owns the endpoint to avoid race + // conditions. + microService *m; // Monitoring endpoints are different in a few ways. For now, express it as // a single flag but consider unbundling: @@ -72,10 +76,6 @@ struct micro_endpoint_s int refs; bool is_draining; - // Not retained by the endpoint, retained before passing to the - // service-level callbacks. - microService *service_ptr_for_on_complete; - // The subscription for the endpoint. If NULL, the endpoint is stopped. natsSubscription *sub; @@ -142,16 +142,44 @@ extern microError *micro_ErrorOutOfMemory; extern microError *micro_ErrorInvalidArg; microError *micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); +microError *micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg); microError *micro_init_monitoring(microService *m); microError *micro_is_error_message(natsStatus s, natsMsg *msg); +microError *micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); +microError *micro_new_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); microError *micro_new_request(microRequest **new_request, microService *m, microEndpoint *ep, natsMsg *msg); +microError *micro_start_endpoint(microEndpoint *ep); +microError *micro_stop_endpoint(microEndpoint *ep); +void micro_free_cloned_endpoint_config(microEndpointConfig *cfg); +void micro_free_endpoint(microEndpoint *ep); void micro_free_request(microRequest *req); +void micro_release_endpoint(microEndpoint *ep); +void micro_release_on_endpoint_complete(void *closure); +void micro_retain_endpoint(microEndpoint *ep); void micro_update_last_error(microEndpoint *ep, microError *err); -// -// Exposed only for testing. +bool micro_is_valid_name(const char *name); +bool micro_is_valid_subject(const char *subject); bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_subject); -microError *micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); + +static inline void micro_lock_endpoint(microEndpoint *ep) { natsMutex_Lock(ep->endpoint_mu); } +static inline void micro_unlock_endpoint(microEndpoint *ep) { natsMutex_Unlock(ep->endpoint_mu); } + +static inline microError * +micro_strdup(char **ptr, const char *str) +{ + // Make a strdup(NULL) be a no-op, so we don't have to check for NULL + // everywhere. + if (str == NULL) + { + *ptr = NULL; + return NULL; + } + *ptr = NATS_STRDUP(str); + if (*ptr == NULL) + return micro_ErrorOutOfMemory; + return NULL; +} #endif /* MICROP_H_ */ From 8d66c11823faef8f5ee4a25949b1bef43136a211 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sun, 4 Jun 2023 05:39:23 -0700 Subject: [PATCH 74/85] reworked wip15 --- src/micro_endpoint.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index 6ea4efc5c..c5368f363 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -89,8 +89,6 @@ micro_start_endpoint(microEndpoint *ep) ep->is_draining = false; micro_unlock_endpoint(ep); - // The service needs to be retained - natsSubscription_SetOnCompleteCB(sub, micro_release_on_endpoint_complete, ep); } else @@ -405,8 +403,7 @@ bool micro_match_endpoint_subject(const char *ep_subject, const char *actual_sub } } -static microError * -_dup_with_prefix(char **dst, const char *prefix, const char *src) +static microError *_dup_with_prefix(char **dst, const char *prefix, const char *src) { size_t len = strlen(src) + 1; char *p; @@ -428,4 +425,4 @@ _dup_with_prefix(char **dst, const char *prefix, const char *src) } memcpy(p, src, strlen(src) + 1); return NULL; -} \ No newline at end of file +} From 9da1b563985e5a6ee32976ffbd3a8838567857e1 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sun, 4 Jun 2023 06:06:11 -0700 Subject: [PATCH 75/85] wip nits --- src/micro.c | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/micro.c b/src/micro.c index 029e532f9..c1ddccab3 100644 --- a/src/micro.c +++ b/src/micro.c @@ -29,8 +29,9 @@ static microError *_wrap_connection_event_callbacks(microService *m); static bool _find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find); +static void _free_cloned_service_config(microServiceConfig *cfg); static void _release_service(microService *m); -static void _retain_service(microService *m, bool lock); +static void _retain_service(microService *m); static void _stop_service_callbacks(microService *m); microError * @@ -100,7 +101,7 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, return NULL; // retain `m` before the endpoint uses it for its on_complete callback. - _retain_service(m, true); + _retain_service(m); err = micro_new_endpoint(&ep, m, prefix, cfg, is_internal); if (err != NULL) @@ -329,21 +330,6 @@ _find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find) return false; } -static void -_free_cloned_service_config(microServiceConfig *cfg) -{ - if (cfg == NULL) - return; - - // the strings are declared const for the public, but in a clone these need - // to be freed. - NATS_FREE((char *)cfg->Name); - NATS_FREE((char *)cfg->Version); - NATS_FREE((char *)cfg->Description); - micro_free_cloned_endpoint_config(cfg->Endpoint); - NATS_FREE(cfg); -} - static microError * _clone_service_config(microServiceConfig **out, microServiceConfig *cfg) { @@ -375,18 +361,31 @@ _clone_service_config(microServiceConfig **out, microServiceConfig *cfg) } static void -_retain_service(microService *m, bool lock) +_free_cloned_service_config(microServiceConfig *cfg) +{ + if (cfg == NULL) + return; + + // the strings are declared const for the public, but in a clone these need + // to be freed. + NATS_FREE((char *)cfg->Name); + NATS_FREE((char *)cfg->Version); + NATS_FREE((char *)cfg->Description); + micro_free_cloned_endpoint_config(cfg->Endpoint); + NATS_FREE(cfg); +} + +static void +_retain_service(microService *m) { if (m == NULL) return; - if (lock) - _lock_service(m); + _lock_service(m); ++(m->refs); - if (lock) - _unlock_service(m); + _unlock_service(m); } static void @@ -456,7 +455,7 @@ _start_service_callbacks(microService *m) } // Extra reference to the service as long as its callbacks are registered. - _retain_service(m, true); + _retain_service(m); natsMutex_Lock(service_callback_mu); IFOK(s, natsHash_Set(all_services_to_callback, (int64_t)m, (void *)m, NULL)); @@ -511,7 +510,7 @@ _services_for_connection(microService ***to_call, int *num_microservices, natsCo { if (m->nc == nc) { - _retain_service(m, true); // for the callback + _retain_service(m); // for the callback p[i++] = m; } } From 10fe2c58166f0229a38649f5cb282918cb9e2a8e Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Sun, 4 Jun 2023 06:20:25 -0700 Subject: [PATCH 76/85] wip more nits --- src/micro.c | 279 ++++++++++++++++++++++++++-------------------------- 1 file changed, 139 insertions(+), 140 deletions(-) diff --git a/src/micro.c b/src/micro.c index c1ddccab3..e6857fedb 100644 --- a/src/micro.c +++ b/src/micro.c @@ -27,9 +27,8 @@ static microError *_clone_service_config(microServiceConfig **out, microServiceC static microError *_new_service(microService **ptr, natsConnection *nc); static microError *_wrap_connection_event_callbacks(microService *m); -static bool _find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find); - static void _free_cloned_service_config(microServiceConfig *cfg); +static void _free_service(microService *m); static void _release_service(microService *m); static void _retain_service(microService *m); static void _stop_service_callbacks(microService *m); @@ -71,21 +70,6 @@ micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *c return NULL; } -microError * -microService_AddEndpoint(microService *m, microEndpointConfig *cfg) -{ - return micro_add_endpoint(NULL, m, NULL, cfg, false); -} - -microError * -microGroup_AddEndpoint(microGroup *g, microEndpointConfig *cfg) -{ - if (g == NULL) - return micro_ErrorInvalidArg; - - return micro_add_endpoint(NULL, g->m, g->prefix, cfg, false); -} - microError * micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal) { @@ -171,6 +155,21 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, return NULL; } +microError * +microService_AddEndpoint(microService *m, microEndpointConfig *cfg) +{ + return micro_add_endpoint(NULL, m, NULL, cfg, false); +} + +microError * +microGroup_AddEndpoint(microGroup *g, microEndpointConfig *cfg) +{ + if (g == NULL) + return micro_ErrorInvalidArg; + + return micro_add_endpoint(NULL, g->m, g->prefix, cfg, false); +} + microError * microService_Stop(microService *m) { @@ -218,6 +217,28 @@ microService_Stop(microService *m) return NULL; } +static bool +_find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find) +{ + microEndpoint *ep = NULL; + microEndpoint *prev_ep = NULL; + + if ((m == NULL) || (to_find == NULL)) + return false; + + for (ep = m->first_ep; ep != NULL; ep = ep->next) + { + if (ep == to_find) + { + *prevp = prev_ep; + return true; + } + prev_ep = ep; + } + + return false; +} + void micro_release_on_endpoint_complete(void *closure) { microEndpoint *ep = (microEndpoint *)closure; @@ -287,105 +308,99 @@ void micro_release_on_endpoint_complete(void *closure) } } -static microError * -_new_service(microService **ptr, natsConnection *nc) +bool microService_IsStopped(microService *m) { - *ptr = NATS_CALLOC(1, sizeof(microService)); - if (*ptr == NULL) - return micro_ErrorOutOfMemory; + bool stopped; - natsConn_retain(nc); - (*ptr)->refs = 1; - (*ptr)->nc = nc; - (*ptr)->started = nats_Now() * 1000000; - return NULL; + if ((m == NULL) || (m->service_mu == NULL)) + return true; + + _lock_service(m); + stopped = m->stopped; + _unlock_service(m); + + return stopped; } -static inline microError * -new_service_config(microServiceConfig **ptr) +microError * +microService_Destroy(microService *m) { - *ptr = NATS_CALLOC(1, sizeof(microServiceConfig)); - return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; + microError *err = NULL; + + err = microService_Stop(m); + if (err != NULL) + return err; + + _release_service(m); + return NULL; } -static bool -_find_endpoint(microEndpoint **prevp, microService *m, microEndpoint *to_find) +microError * +microService_Run(microService *m) { - microEndpoint *ep = NULL; - microEndpoint *prev_ep = NULL; - - if ((m == NULL) || (to_find == NULL)) - return false; + if ((m == NULL) || (m->service_mu == NULL)) + return micro_ErrorInvalidArg; - for (ep = m->first_ep; ep != NULL; ep = ep->next) + while (!microService_IsStopped(m)) { - if (ep == to_find) - { - *prevp = prev_ep; - return true; - } - prev_ep = ep; + nats_Sleep(50); } - return false; + return NULL; } -static microError * -_clone_service_config(microServiceConfig **out, microServiceConfig *cfg) +void * +microService_GetState(microService *m) { - microError *err = NULL; - microServiceConfig *new_cfg = NULL; + if (m == NULL) + return NULL; - if (out == NULL || cfg == NULL) - return micro_ErrorInvalidArg; + return m->cfg->State; +} - err = new_service_config(&new_cfg); - if (err == NULL) - { - memcpy(new_cfg, cfg, sizeof(microServiceConfig)); - } - // the strings are declared const for the public, but in a clone these need - // to be duplicated. - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Version, cfg->Version)); - MICRO_CALL(err, micro_strdup((char **)&new_cfg->Description, cfg->Description)); - MICRO_CALL(err, micro_clone_endpoint_config(&new_cfg->Endpoint, cfg->Endpoint)); - if (err != NULL) - { - _free_cloned_service_config(new_cfg); - return err; - } +static microError * +_new_service(microService **ptr, natsConnection *nc) +{ + *ptr = NATS_CALLOC(1, sizeof(microService)); + if (*ptr == NULL) + return micro_ErrorOutOfMemory; - *out = new_cfg; + natsConn_retain(nc); + (*ptr)->refs = 1; + (*ptr)->nc = nc; + (*ptr)->started = nats_Now() * 1000000; return NULL; } static void -_free_cloned_service_config(microServiceConfig *cfg) +_retain_service(microService *m) { - if (cfg == NULL) + if (m == NULL) return; - // the strings are declared const for the public, but in a clone these need - // to be freed. - NATS_FREE((char *)cfg->Name); - NATS_FREE((char *)cfg->Version); - NATS_FREE((char *)cfg->Description); - micro_free_cloned_endpoint_config(cfg->Endpoint); - NATS_FREE(cfg); + _lock_service(m); + + ++(m->refs); + + _unlock_service(m); } static void -_retain_service(microService *m) +_release_service(microService *m) { + int refs = 0; + if (m == NULL) return; _lock_service(m); - ++(m->refs); + refs = --(m->refs); _unlock_service(m); + + if (refs == 0) + _free_service(m); } static void @@ -414,22 +429,56 @@ _free_service(microService *m) NATS_FREE(m); } -static void -_release_service(microService *m) +static inline microError * +_new_service_config(microServiceConfig **ptr) { - int refs = 0; + *ptr = NATS_CALLOC(1, sizeof(microServiceConfig)); + return (*ptr == NULL) ? micro_ErrorOutOfMemory : NULL; +} - if (m == NULL) - return; +static microError * +_clone_service_config(microServiceConfig **out, microServiceConfig *cfg) +{ + microError *err = NULL; + microServiceConfig *new_cfg = NULL; - _lock_service(m); + if (out == NULL || cfg == NULL) + return micro_ErrorInvalidArg; - refs = --(m->refs); + err = _new_service_config(&new_cfg); + if (err == NULL) + { + memcpy(new_cfg, cfg, sizeof(microServiceConfig)); + } + // the strings are declared const for the public, but in a clone these need + // to be duplicated. + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Version, cfg->Version)); + MICRO_CALL(err, micro_strdup((char **)&new_cfg->Description, cfg->Description)); + MICRO_CALL(err, micro_clone_endpoint_config(&new_cfg->Endpoint, cfg->Endpoint)); + if (err != NULL) + { + _free_cloned_service_config(new_cfg); + return err; + } - _unlock_service(m); + *out = new_cfg; + return NULL; +} - if (refs == 0) - _free_service(m); +static void +_free_cloned_service_config(microServiceConfig *cfg) +{ + if (cfg == NULL) + return; + + // the strings are declared const for the public, but in a clone these need + // to be freed. + NATS_FREE((char *)cfg->Name); + NATS_FREE((char *)cfg->Version); + NATS_FREE((char *)cfg->Description); + micro_free_cloned_endpoint_config(cfg->Endpoint); + NATS_FREE(cfg); } static microError * @@ -633,56 +682,6 @@ _wrap_connection_event_callbacks(microService *m) return microError_Wrapf(err, "failed to wrap connection event callbacks"); } -bool microService_IsStopped(microService *m) -{ - bool stopped; - - if ((m == NULL) || (m->service_mu == NULL)) - return true; - - _lock_service(m); - stopped = m->stopped; - _unlock_service(m); - - return stopped; -} - -microError * -microService_Destroy(microService *m) -{ - microError *err = NULL; - - err = microService_Stop(m); - if (err != NULL) - return err; - - _release_service(m); - return NULL; -} - -microError * -microService_Run(microService *m) -{ - if ((m == NULL) || (m->service_mu == NULL)) - return micro_ErrorInvalidArg; - - while (!microService_IsStopped(m)) - { - nats_Sleep(50); - } - - return NULL; -} - -void * -microService_GetState(microService *m) -{ - if (m == NULL) - return NULL; - - return m->cfg->State; -} - microError * microService_AddGroup(microGroup **new_group, microService *m, const char *prefix) { From f255707181610fea59c77cf0c71181a6a230f04c Mon Sep 17 00:00:00 2001 From: Ivan Kozlovic Date: Mon, 5 Jun 2023 15:28:31 -0600 Subject: [PATCH 77/85] Moving global variables inside natsLib for proper cleanup on close Signed-off-by: Ivan Kozlovic --- src/conn.c | 2 +- src/micro.c | 49 ++++++++++--------------------------------------- src/nats.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/natsp.h | 12 ++++++++++++ test/test.c | 32 +++++++++++++++----------------- 5 files changed, 84 insertions(+), 57 deletions(-) diff --git a/src/conn.c b/src/conn.c index 81e107172..fbd67f3a1 100644 --- a/src/conn.c +++ b/src/conn.c @@ -2015,7 +2015,7 @@ _connect(natsConnection *nc) bool retry = false; bool retryOnFailedConnect = false; bool hasConnectedCb = false; - + natsOptions_lock(nc->opts); hasConnectedCb = (nc->opts->connectedCb != NULL); retryOnFailedConnect = nc->opts->retryOnFailedConnect; diff --git a/src/micro.c b/src/micro.c index e6857fedb..00dad4978 100644 --- a/src/micro.c +++ b/src/micro.c @@ -17,9 +17,6 @@ #include "conn.h" #include "opts.h" -static natsMutex *service_callback_mu; -static natsHash *all_services_to_callback; // uses `microService*` as the key and the value. - static inline void _lock_service(microService *m) { natsMutex_Lock(m->service_mu); } static inline void _unlock_service(microService *m) { natsMutex_Unlock(m->service_mu); } @@ -31,7 +28,6 @@ static void _free_cloned_service_config(microServiceConfig *cfg); static void _free_service(microService *m); static void _release_service(microService *m); static void _retain_service(microService *m); -static void _stop_service_callbacks(microService *m); microError * micro_AddService(microService **new_m, natsConnection *nc, microServiceConfig *cfg) @@ -199,7 +195,7 @@ microService_Stop(microService *m) finalize = (m->first_ep == NULL); if (finalize) { - _stop_service_callbacks(m); + natsLib_stopServiceCallbacks(m); m->stopped = true; doneHandler = m->cfg->DoneHandler; } @@ -289,7 +285,7 @@ void micro_release_on_endpoint_complete(void *closure) finalize = (!m->stopped) && (m->first_ep == NULL); if (finalize) { - _stop_service_callbacks(m); + natsLib_stopServiceCallbacks(m); m->stopped = true; doneHandler = m->cfg->DoneHandler; } @@ -489,26 +485,10 @@ _start_service_callbacks(microService *m) if (m == NULL) return micro_ErrorInvalidArg; - if (service_callback_mu == NULL) - { - IFOK(s, natsMutex_Create(&service_callback_mu)); - if (s != NATS_OK) - return micro_ErrorFromStatus(s); - } - - if (all_services_to_callback == NULL) - { - IFOK(s, natsHash_Create(&all_services_to_callback, 8)); - if (s != NATS_OK) - return micro_ErrorFromStatus(s); - } - // Extra reference to the service as long as its callbacks are registered. _retain_service(m); - natsMutex_Lock(service_callback_mu); - IFOK(s, natsHash_Set(all_services_to_callback, (int64_t)m, (void *)m, NULL)); - natsMutex_Unlock(service_callback_mu); + s = natsLib_startServiceCallbacks(m); if (s != NATS_OK) { _release_service(m); @@ -517,29 +497,20 @@ _start_service_callbacks(microService *m) return micro_ErrorFromStatus(s); } -static void -_stop_service_callbacks(microService *m) -{ - if ((m == NULL) || (all_services_to_callback == NULL) || (service_callback_mu == NULL)) - return; - - natsMutex_Lock(service_callback_mu); - natsHash_Remove(all_services_to_callback, (int64_t)m); - natsMutex_Unlock(service_callback_mu); -} - static microError * _services_for_connection(microService ***to_call, int *num_microservices, natsConnection *nc) { + natsMutex *mu = natsLib_getServiceCallbackMutex(); + natsHash *h = natsLib_getAllServicesToCallback(); microService *m = NULL; microService **p = NULL; natsHashIter iter; int n = 0; int i; - natsMutex_Lock(service_callback_mu); + natsMutex_Lock(mu); - natsHashIter_Init(&iter, all_services_to_callback); + natsHashIter_Init(&iter, h); while (natsHashIter_Next(&iter, NULL, (void **)&m)) if (m->nc == nc) n++; @@ -549,11 +520,11 @@ _services_for_connection(microService ***to_call, int *num_microservices, natsCo p = NATS_CALLOC(n, sizeof(microService *)); if (p == NULL) { - natsMutex_Unlock(service_callback_mu); + natsMutex_Unlock(mu); return micro_ErrorOutOfMemory; } - natsHashIter_Init(&iter, all_services_to_callback); + natsHashIter_Init(&iter, h); i = 0; while (natsHashIter_Next(&iter, NULL, (void **)&m)) { @@ -566,7 +537,7 @@ _services_for_connection(microService ***to_call, int *num_microservices, natsCo natsHashIter_Done(&iter); } - natsMutex_Unlock(service_callback_mu); + natsMutex_Unlock(mu); *to_call = p; *num_microservices = n; diff --git a/src/nats.c b/src/nats.c index 8e35b917d..465491d77 100644 --- a/src/nats.c +++ b/src/nats.c @@ -123,6 +123,11 @@ typedef struct __natsLib natsGCList gc; + // For micro services code + natsMutex *service_callback_mu; + // uses `microService*` as the key and the value. + natsHash *all_services_to_callback; + } natsLib; int64_t gLockSpinCount = 2000; @@ -332,6 +337,8 @@ _freeLib(void) _freeGC(); _freeDlvWorkers(); natsNUID_free(); + natsMutex_Destroy(gLib.service_callback_mu); + natsHash_Destroy(gLib.all_services_to_callback); natsCondition_Destroy(gLib.cond); @@ -1056,6 +1063,10 @@ nats_Open(int64_t lockSpinCount) if (gLib.dlvWorkers.workers == NULL) s = NATS_NO_MEMORY; } + if (s == NATS_OK) + s = natsMutex_Create(&gLib.service_callback_mu); + if (s == NATS_OK) + s = natsHash_Create(&gLib.all_services_to_callback, 8); if (s == NATS_OK) gLib.initialized = true; @@ -2000,3 +2011,38 @@ natsLib_getMsgDeliveryPoolInfo(int *maxSize, int *size, int *idx, natsMsgDlvWork *workersArray = workers->workers; natsMutex_Unlock(workers->lock); } + +natsStatus +natsLib_startServiceCallbacks(microService *m) +{ + natsStatus s; + + natsMutex_Lock(gLib.service_callback_mu); + s = natsHash_Set(gLib.all_services_to_callback, (int64_t)m, (void *)m, NULL); + natsMutex_Unlock(gLib.service_callback_mu); + + return NATS_UPDATE_ERR_STACK(s); +} + +void +natsLib_stopServiceCallbacks(microService *m) +{ + if (m == NULL) + return; + + natsMutex_Lock(gLib.service_callback_mu); + natsHash_Remove(gLib.all_services_to_callback, (int64_t)m); + natsMutex_Unlock(gLib.service_callback_mu); +} + +natsMutex* +natsLib_getServiceCallbackMutex(void) +{ + return gLib.service_callback_mu; +} + +natsHash* +natsLib_getAllServicesToCallback(void) +{ + return gLib.all_services_to_callback; +} diff --git a/src/natsp.h b/src/natsp.h index 74b96d41f..a3dac236b 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -796,6 +796,18 @@ natsLib_getMsgDeliveryPoolInfo(int *maxSize, int *size, int *idx, natsMsgDlvWork void nats_setNATSThreadKey(void); +natsStatus +natsLib_startServiceCallbacks(microService *m); + +void +natsLib_stopServiceCallbacks(microService *m); + +natsMutex* +natsLib_getServiceCallbackMutex(void); + +natsHash* +natsLib_getAllServicesToCallback(void); + // // Threads // diff --git a/test/test.c b/test/test.c index b6bba9025..953a2c606 100644 --- a/test/test.c +++ b/test/test.c @@ -32294,14 +32294,14 @@ _startMicroservice(microService** new_m, natsConnection *nc, microServiceConfig arg->microAllDone = false; err = micro_AddService(new_m, nc, cfg); - if (err != NULL) + if (err != NULL) { arg->microRunningServiceCount--; arg->microAllDone = prev_done; } - + natsMutex_Unlock(arg->m); - + return err; } @@ -32325,7 +32325,7 @@ _startManyMicroservices(microService** svcs, int n, natsConnection *nc, microSer { _startMicroserviceOK(&(svcs[i]), nc, cfg, arg); } - + testCond(true); } @@ -32488,7 +32488,7 @@ test_MicroAddService(void) s = _createDefaultThreadArgsForCbTests(&arg); if (s == NATS_OK) opts = _createReconnectOptions(); - if ((opts == NULL) + if ((opts == NULL) || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK)) { @@ -32599,7 +32599,7 @@ test_MicroAddService(void) } microServiceInfo_Destroy(info); - + if (m != NULL) { snprintf(buf, sizeof(buf), "%s: Destroy service: %d", m->cfg->Name, m->refs); @@ -32712,10 +32712,10 @@ test_MicroGroups(void) testCond(true); microServiceInfo_Destroy(info); - + microService_Destroy(m); _waitForMicroservicesAllDone(&arg); - + natsConnection_Destroy(nc); _waitForConnClosed(&arg); @@ -32997,7 +32997,7 @@ test_MicroStartStop(void) natsMsg_Destroy(reply); } testCond(NATS_OK == s); - + _destroyMicroservicesAndWaitForAllDone(svcs, NUM_MICRO_SERVICES, &arg); test("Destroy the connection: "); @@ -33029,8 +33029,9 @@ test_MicroServiceStopsOnClosedConn(void) if (s == NATS_OK) opts = _createReconnectOptions(); - if ((opts == NULL) || + if ((opts == NULL) || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) || + (natsOptions_SetClosedCB(opts, _closedCb, (void*) &arg)) || (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) { FAIL("Unable to setup test for MicroConnectionEvents!"); @@ -33057,8 +33058,7 @@ test_MicroServiceStopsOnClosedConn(void) natsConnection_Close(nc); test("Wait for it: "); - _waitForConnClosed(&arg); - testCond(1); + testCond(_waitForConnClosed(&arg) == NATS_OK); test("Ensure the connection has closed: "); testCond(natsConnection_IsClosed(nc)); @@ -33102,7 +33102,7 @@ test_MicroServiceStopsWhenServerStops(void) opts = _createReconnectOptions(); if ((opts == NULL) - || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) + || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) || (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) { @@ -33116,15 +33116,13 @@ test_MicroServiceStopsWhenServerStops(void) testCond(NATS_OK == natsConnection_Connect(&nc, opts)); _startMicroservice(&m, nc, &cfg, &arg); - + test("Test microservice is running: "); testCond(!microService_IsStopped(m)) test("Stop the server: "); testCond((_stopServer(serverPid), true)); - nats_Sleep(1000); - test("Wait for the service to stop: "); natsMutex_Lock(arg.m); while ((s != NATS_TIMEOUT) && !arg.microAllDone) @@ -33236,7 +33234,7 @@ test_MicroAsyncErrorHandler(void) { s = natsConnection_PublishString(nc, "async_test", "hello"); } - testCond((NATS_OK == s) + testCond((NATS_OK == s) && (NATS_OK == natsConnection_Flush(nc))); test("Wait for async err callback: "); From 4e0709d5ab7338dd308598082e7acff4f0ce0d42 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 6 Jun 2023 05:49:15 -0700 Subject: [PATCH 78/85] PR feedback sans global elimination --- src/micro.c | 12 +++++++++--- test/test.c | 37 +++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/micro.c b/src/micro.c index e6857fedb..8ec623cc9 100644 --- a/src/micro.c +++ b/src/micro.c @@ -84,9 +84,6 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, if (cfg == NULL) return NULL; - // retain `m` before the endpoint uses it for its on_complete callback. - _retain_service(m); - err = micro_new_endpoint(&ep, m, prefix, cfg, is_internal); if (err != NULL) return microError_Wrapf(err, "failed to create endpoint %s", cfg->Name); @@ -143,10 +140,14 @@ micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, micro_release_endpoint(prev_ep); } + // retain `m` before the endpoint uses it for its on_complete callback. + _retain_service(m); + if (err = micro_start_endpoint(ep), err != NULL) { // Best effort, leave the new endpoint in the list, as is. A retry with // the same name will clean it up. + _release_service(m); return microError_Wrapf(err, "failed to start endpoint %s", ep->name); } @@ -193,7 +194,10 @@ microService_Stop(microService *m) for (; ep != NULL; ep = ep->next) { if (err = micro_stop_endpoint(ep), err != NULL) + { + _unlock_service(m); return microError_Wrapf(err, "failed to stop service '%s', stopping endpoint '%s'", m->cfg->Name, ep->name); + } } finalize = (m->first_ep == NULL); @@ -211,6 +215,7 @@ microService_Stop(microService *m) if (doneHandler != NULL) doneHandler(m); + // Relase the endpoint's server reference from `micro_add_endpoint`. _release_service(m); } @@ -304,6 +309,7 @@ void micro_release_on_endpoint_complete(void *closure) if (doneHandler != NULL) doneHandler(m); + // Relase the endpoint's server reference from `micro_add_endpoint`. _release_service(m); } } diff --git a/test/test.c b/test/test.c index b6bba9025..ff6f4a02c 100644 --- a/test/test.c +++ b/test/test.c @@ -20556,7 +20556,7 @@ test_EventLoop(void) test("Close and wait for close cb: "); natsConnection_Close(nc); - _waitForConnClosed(&arg); + s = _waitForConnClosed(&arg); testCond(s == NATS_OK); natsMutex_Lock(arg.m); @@ -32343,7 +32343,7 @@ _waitForMicroservicesAllDone(struct threadArg *arg) // `Done` may be immediately followed by freeing the service, so wait a bit // to make sure it happens before the test exits. - nats_Sleep(100); + nats_Sleep(20); } static void @@ -32609,8 +32609,9 @@ test_MicroAddService(void) } } + test("Destroy the test connection: "); natsConnection_Destroy(nc); - _waitForConnClosed(&arg); + testCond(NATS_OK == _waitForConnClosed(&arg)); natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); @@ -32716,8 +32717,9 @@ test_MicroGroups(void) microService_Destroy(m); _waitForMicroservicesAllDone(&arg); + test("Destroy the test connection: "); natsConnection_Destroy(nc); - _waitForConnClosed(&arg); + testCond(NATS_OK == _waitForConnClosed(&arg)); natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); @@ -32932,8 +32934,9 @@ test_MicroBasics(void) _destroyMicroservicesAndWaitForAllDone(svcs, NUM_MICRO_SERVICES, &arg); + test("Destroy the test connection: "); natsConnection_Destroy(nc); - _waitForConnClosed(&arg); + testCond(NATS_OK == _waitForConnClosed(&arg)); natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); @@ -33000,10 +33003,9 @@ test_MicroStartStop(void) _destroyMicroservicesAndWaitForAllDone(svcs, NUM_MICRO_SERVICES, &arg); - test("Destroy the connection: "); + test("Destroy the test connection: "); natsConnection_Destroy(nc); - _waitForConnClosed(&arg); - testCond(1); + testCond(NATS_OK == _waitForConnClosed(&arg)); natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); @@ -33029,9 +33031,10 @@ test_MicroServiceStopsOnClosedConn(void) if (s == NATS_OK) opts = _createReconnectOptions(); - if ((opts == NULL) || - (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) || - (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) + if ((opts == NULL) + || (natsOptions_SetURL(opts, NATS_DEFAULT_URL) != NATS_OK) + || (natsOptions_SetClosedCB(opts, _closedCb, &arg) != NATS_OK) + || (natsOptions_SetAllowReconnect(opts, false) != NATS_OK)) { FAIL("Unable to setup test for MicroConnectionEvents!"); } @@ -33057,8 +33060,7 @@ test_MicroServiceStopsOnClosedConn(void) natsConnection_Close(nc); test("Wait for it: "); - _waitForConnClosed(&arg); - testCond(1); + testCond(NATS_OK == _waitForConnClosed(&arg)); test("Ensure the connection has closed: "); testCond(natsConnection_IsClosed(nc)); @@ -33123,8 +33125,6 @@ test_MicroServiceStopsWhenServerStops(void) test("Stop the server: "); testCond((_stopServer(serverPid), true)); - nats_Sleep(1000); - test("Wait for the service to stop: "); natsMutex_Lock(arg.m); while ((s != NATS_TIMEOUT) && !arg.microAllDone) @@ -33138,9 +33138,9 @@ test_MicroServiceStopsWhenServerStops(void) microService_Destroy(m); _waitForMicroservicesAllDone(&arg); - + test("Destroy the test connection: "); natsConnection_Destroy(nc); - _waitForConnClosed(&arg); + testCond(NATS_OK == _waitForConnClosed(&arg)); natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); @@ -33251,8 +33251,9 @@ test_MicroAsyncErrorHandler(void) microService_Destroy(m); _waitForMicroservicesAllDone(&arg); + test("Destroy the test connection: "); natsConnection_Destroy(nc); - _waitForConnClosed(&arg); + testCond(NATS_OK == _waitForConnClosed(&arg)); natsOptions_Destroy(opts); _destroyDefaultThreadArgs(&arg); From 394e4b246c0d52415397f4e58f62a7cf308ce2d1 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 6 Jun 2023 06:02:16 -0700 Subject: [PATCH 79/85] Restored num_services=5 in tests --- test/test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test.c b/test/test.c index be9c1aa02..fccca5d10 100644 --- a/test/test.c +++ b/test/test.c @@ -32716,7 +32716,7 @@ test_MicroGroups(void) microService_Destroy(m); _waitForMicroservicesAllDone(&arg); - + test("Destroy the test connection: "); natsConnection_Destroy(nc); testCond(NATS_OK == _waitForConnClosed(&arg)); @@ -32726,7 +32726,7 @@ test_MicroGroups(void) _stopServer(serverPid); } -#define NUM_MICRO_SERVICES 1 +#define NUM_MICRO_SERVICES 5 static void test_MicroBasics(void) From fd95b7b49345517e7fd772fd85b980a821e84a92 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Wed, 7 Jun 2023 05:38:57 -0700 Subject: [PATCH 80/85] PR feedback: microError.message --- src/micro_error.c | 23 +++++++++++++++-------- src/microp.h | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/micro_error.c b/src/micro_error.c index b31fd4d2a..0a30db51a 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -18,19 +18,19 @@ static microError _errorOutOfMemory = { .is_internal = true, .status = NATS_NO_MEMORY, - .message = (char *)"out of memory", + .message = "out of memory", }; static microError _errorInvalidArg = { .is_internal = true, .status = NATS_INVALID_ARG, - .message = (char *)"invalid function argument", + .message = "invalid function argument", }; static microError _errorInvalidFormat = { .is_internal = true, .status = NATS_INVALID_ARG, - .message = (char *)"invalid format string", + .message = "invalid format string", }; microError *micro_ErrorOutOfMemory = &_errorOutOfMemory; @@ -40,6 +40,7 @@ static microError * verrorf(natsStatus s, int code, const char *format, va_list args) { microError *err = NULL; + char *ptr; int message_len = 0; va_list args2; @@ -62,11 +63,14 @@ verrorf(natsStatus s, int code, const char *format, va_list args) return &_errorOutOfMemory; } + ptr = (char *)(err) + sizeof(microError); + vsnprintf(ptr, message_len + 1, format, args2); + va_end(args2); + err->message = (const char *)ptr; + err->code = code; err->status = s; - err->message = (char *)(err + 1); - vsnprintf(err->message, message_len + 1, format, args2); - va_end(args2); + return err; } @@ -100,6 +104,7 @@ micro_ErrorFromStatus(natsStatus s) microError *err = NULL; const char *message = natsStatus_GetText(s); size_t message_len = strlen(message); + char *ptr; if (s == NATS_OK) return NULL; @@ -107,9 +112,11 @@ micro_ErrorFromStatus(natsStatus s) err = NATS_CALLOC(1, sizeof(microError) + message_len + 1); if (err == NULL) return &_errorOutOfMemory; - err->message = (char *)(err + 1); + + ptr = (char *)(err) + sizeof(microError); + memcpy(ptr, message, message_len + 1); + err->message = ptr; err->status = s; - memcpy(err->message, message, message_len + 1); return err; } diff --git a/src/microp.h b/src/microp.h index 8bff43aee..8cf780c00 100644 --- a/src/microp.h +++ b/src/microp.h @@ -37,7 +37,7 @@ struct micro_error_s struct micro_error_s *cause; natsStatus status; int code; - char *message; + const char *message; }; struct micro_client_s From e92657b92c899209da402a053f42398bb89d45b2 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Wed, 7 Jun 2023 14:13:03 -0700 Subject: [PATCH 81/85] PR feedback: nats_vsnprintf --- src/micro_error.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/micro_error.c b/src/micro_error.c index 0a30db51a..330d73a0b 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -49,7 +49,7 @@ verrorf(natsStatus s, int code, const char *format, va_list args) if (format == NULL) format = ""; - message_len = vsnprintf(NULL, 0, format, args); + message_len = nats_vsnprintf(NULL, 0, format, args); if (message_len < 0) { va_end(args2); @@ -64,7 +64,7 @@ verrorf(natsStatus s, int code, const char *format, va_list args) } ptr = (char *)(err) + sizeof(microError); - vsnprintf(ptr, message_len + 1, format, args2); + nats_vsnprintf(ptr, message_len + 1, format, args2); va_end(args2); err->message = (const char *)ptr; From 338951d28561f6cb45a1f8790c5560864969bcb6 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Wed, 7 Jun 2023 14:13:43 -0700 Subject: [PATCH 82/85] PR feedback: nats_vsnprintf, #2 --- src/win/strings.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/win/strings.c b/src/win/strings.c index e39629acb..07d41eeb7 100644 --- a/src/win/strings.c +++ b/src/win/strings.c @@ -29,7 +29,7 @@ nats_asprintf(char **newStr, const char *fmt, ...) do { va_start(ap, fmt); - n = vsnprintf(str, size, fmt, ap); + n = nats_vsnprintf(str, size, fmt, ap); va_end(ap); if ((n < 0) || (n >= size)) @@ -128,7 +128,7 @@ nats_snprintf(char *buffer, size_t countszt, char *format, ...) memset(buffer, 0, count); va_start(ap, format); - len = (int) vsnprintf(buffer, count, format, ap); + len = (int) nats_vsnprintf(buffer, count, format, ap); va_end(ap); if ((len == count) || (len < 0)) { From eaa49258841106e83e3642d03bb2c3f66e097c15 Mon Sep 17 00:00:00 2001 From: Lev <1187448+levb@users.noreply.github.com> Date: Tue, 13 Jun 2023 06:22:37 -0700 Subject: [PATCH 83/85] [CHANGED] More verbose endpoint INFO (#663) * More verbose endpoint INFO * better clone/free for metadata * (valgrind) free test JSON array --- src/micro.c | 216 ++++++++++++++++++++++++++++------------- src/micro_endpoint.c | 2 + src/micro_monitoring.c | 53 +++++++--- src/microp.h | 2 + src/nats.h | 69 ++++++++++++- src/util.c | 5 +- test/test.c | 172 +++++++++++++++++++++++--------- 7 files changed, 385 insertions(+), 134 deletions(-) diff --git a/src/micro.c b/src/micro.c index 61797794a..0ee378536 100644 --- a/src/micro.c +++ b/src/micro.c @@ -457,6 +457,7 @@ _clone_service_config(microServiceConfig **out, microServiceConfig *cfg) MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); MICRO_CALL(err, micro_strdup((char **)&new_cfg->Version, cfg->Version)); MICRO_CALL(err, micro_strdup((char **)&new_cfg->Description, cfg->Description)); + MICRO_CALL(err, micro_clone_metadata(&new_cfg->Metadata, &new_cfg->MetadataLen, cfg->Metadata, cfg->MetadataLen)); MICRO_CALL(err, micro_clone_endpoint_config(&new_cfg->Endpoint, cfg->Endpoint)); if (err != NULL) { @@ -479,6 +480,7 @@ _free_cloned_service_config(microServiceConfig *cfg) NATS_FREE((char *)cfg->Name); NATS_FREE((char *)cfg->Version); NATS_FREE((char *)cfg->Description); + micro_free_cloned_metadata(cfg->Metadata, cfg->MetadataLen); micro_free_cloned_endpoint_config(cfg->Endpoint); NATS_FREE(cfg); } @@ -507,7 +509,7 @@ static microError * _services_for_connection(microService ***to_call, int *num_microservices, natsConnection *nc) { natsMutex *mu = natsLib_getServiceCallbackMutex(); - natsHash *h = natsLib_getAllServicesToCallback(); + natsHash *h = natsLib_getAllServicesToCallback(); microService *m = NULL; microService **p = NULL; natsHashIter iter; @@ -721,6 +723,7 @@ microService_GetConnection(microService *m) microError * microService_GetInfo(microServiceInfo **new_info, microService *m) { + microError *err = NULL; microServiceInfo *info = NULL; microEndpoint *ep = NULL; int len; @@ -732,41 +735,55 @@ microService_GetInfo(microServiceInfo **new_info, microService *m) if (info == NULL) return micro_ErrorOutOfMemory; - micro_strdup((char **)&info->Name, m->cfg->Name); - micro_strdup((char **)&info->Version, m->cfg->Version); - micro_strdup((char **)&info->Description, m->cfg->Description); - micro_strdup((char **)&info->Id, m->id); - info->Type = MICRO_INFO_RESPONSE_TYPE; - - _lock_service(m); + MICRO_CALL(err, micro_strdup((char **)&info->Name, m->cfg->Name)); + MICRO_CALL(err, micro_strdup((char **)&info->Version, m->cfg->Version)); + MICRO_CALL(err, micro_strdup((char **)&info->Description, m->cfg->Description)); + MICRO_CALL(err, micro_strdup((char **)&info->Id, m->id)); + MICRO_CALL(err, micro_clone_metadata(&info->Metadata, &info->MetadataLen, m->cfg->Metadata, m->cfg->MetadataLen)); - len = 0; - for (ep = m->first_ep; ep != NULL; ep = ep->next) + if (err == NULL) { - if ((!ep->is_monitoring_endpoint) && (ep->subject != NULL)) - len++; - } + info->Type = MICRO_INFO_RESPONSE_TYPE; - // Overallocate subjects, will filter out internal ones. - info->Subjects = NATS_CALLOC(len, sizeof(char *)); - if (info->Subjects == NULL) - { + _lock_service(m); + + len = 0; + for (ep = m->first_ep; ep != NULL; ep = ep->next) + { + if ((!ep->is_monitoring_endpoint) && (ep->subject != NULL)) + len++; + } + + // Overallocate subjects, will filter out internal ones. + info->Endpoints = NATS_CALLOC(len, sizeof(microEndpointInfo)); + if (info->Endpoints == NULL) + { + err = micro_ErrorOutOfMemory; + } + + len = 0; + for (ep = m->first_ep; (err == NULL) && (ep != NULL); ep = ep->next) + { + if ((!ep->is_monitoring_endpoint) && (ep->subject != NULL)) + { + MICRO_CALL(err, micro_strdup((char **)&info->Endpoints[len].Name, ep->name)); + MICRO_CALL(err, micro_strdup((char **)&info->Endpoints[len].Subject, ep->subject)); + MICRO_CALL(err, micro_clone_metadata(&(info->Endpoints[len].Metadata), &info->Endpoints[len].MetadataLen, ep->config->Metadata, ep->config->MetadataLen)); + if (err == NULL) + { + len++; + info->EndpointsLen = len; + } + } + } _unlock_service(m); - NATS_FREE(info); - return micro_ErrorOutOfMemory; } - len = 0; - for (ep = m->first_ep; ep != NULL; ep = ep->next) + if (err != NULL) { - if ((!ep->is_monitoring_endpoint) && (ep->subject != NULL)) - { - micro_strdup((char **)&info->Subjects[len], ep->subject); - len++; - } + microServiceInfo_Destroy(info); + return err; } - info->SubjectsLen = len; - _unlock_service(m); *new_info = info; return NULL; @@ -780,19 +797,25 @@ void microServiceInfo_Destroy(microServiceInfo *info) return; // casts to quiet the compiler. - for (i = 0; i < info->SubjectsLen; i++) - NATS_FREE((char *)info->Subjects[i]); - NATS_FREE((char *)info->Subjects); + for (i = 0; i < info->EndpointsLen; i++) + { + NATS_FREE((char *)info->Endpoints[i].Name); + NATS_FREE((char *)info->Endpoints[i].Subject); + micro_free_cloned_metadata(info->Endpoints[i].Metadata, info->Endpoints[i].MetadataLen); + } + NATS_FREE((char *)info->Endpoints); NATS_FREE((char *)info->Name); NATS_FREE((char *)info->Version); NATS_FREE((char *)info->Description); NATS_FREE((char *)info->Id); + micro_free_cloned_metadata(info->Metadata, info->MetadataLen); NATS_FREE(info); } microError * microService_GetStats(microServiceStats **new_stats, microService *m) { + microError *err = NULL; microServiceStats *stats = NULL; microEndpoint *ep = NULL; int len; @@ -805,52 +828,62 @@ microService_GetStats(microServiceStats **new_stats, microService *m) if (stats == NULL) return micro_ErrorOutOfMemory; - micro_strdup((char **)&stats->Name, m->cfg->Name); - micro_strdup((char **)&stats->Version, m->cfg->Version); - micro_strdup((char **)&stats->Id, m->id); - stats->Started = m->started; - stats->Type = MICRO_STATS_RESPONSE_TYPE; + MICRO_CALL(err, micro_strdup((char **)&stats->Name, m->cfg->Name)); + MICRO_CALL(err, micro_strdup((char **)&stats->Version, m->cfg->Version)); + MICRO_CALL(err, micro_strdup((char **)&stats->Id, m->id)); - _lock_service(m); - - len = 0; - for (ep = m->first_ep; ep != NULL; ep = ep->next) + if (err == NULL) { - if ((ep != NULL) && (!ep->is_monitoring_endpoint)) - len++; - } + stats->Started = m->started; + stats->Type = MICRO_STATS_RESPONSE_TYPE; - // Allocate the actual structs, not pointers. - stats->Endpoints = NATS_CALLOC(len, sizeof(microEndpointStats)); - if (stats->Endpoints == NULL) - { - _unlock_service(m); - NATS_FREE(stats); - return micro_ErrorOutOfMemory; - } + _lock_service(m); - len = 0; - for (ep = m->first_ep; ep != NULL; ep = ep->next) - { - if ((ep != NULL) && (!ep->is_monitoring_endpoint) && (ep->endpoint_mu != NULL)) + len = 0; + for (ep = m->first_ep; ep != NULL; ep = ep->next) { - micro_lock_endpoint(ep); - // copy the entire struct, including the last error buffer. - stats->Endpoints[len] = ep->stats; - - micro_strdup((char **)&stats->Endpoints[len].Name, ep->name); - micro_strdup((char **)&stats->Endpoints[len].Subject, ep->subject); - avg = (long double)ep->stats.ProcessingTimeSeconds * 1000000000.0 + (long double)ep->stats.ProcessingTimeNanoseconds; - avg = avg / (long double)ep->stats.NumRequests; - stats->Endpoints[len].AverageProcessingTimeNanoseconds = (int64_t)avg; - len++; - micro_unlock_endpoint(ep); + if ((ep != NULL) && (!ep->is_monitoring_endpoint)) + len++; } - } - _unlock_service(m); - stats->EndpointsLen = len; + // Allocate the actual structs, not pointers. + stats->Endpoints = NATS_CALLOC(len, sizeof(microEndpointStats)); + if (stats->Endpoints == NULL) + { + err = micro_ErrorOutOfMemory; + } + + len = 0; + for (ep = m->first_ep; ((err == NULL) && (ep != NULL)); ep = ep->next) + { + if ((ep != NULL) && (!ep->is_monitoring_endpoint) && (ep->endpoint_mu != NULL)) + { + micro_lock_endpoint(ep); + // copy the entire struct, including the last error buffer. + stats->Endpoints[len] = ep->stats; + + MICRO_CALL(err, micro_strdup((char **)&stats->Endpoints[len].Name, ep->name)); + MICRO_CALL(err, micro_strdup((char **)&stats->Endpoints[len].Subject, ep->subject)); + if (err == NULL) + { + avg = (long double)ep->stats.ProcessingTimeSeconds * 1000000000.0 + (long double)ep->stats.ProcessingTimeNanoseconds; + avg = avg / (long double)ep->stats.NumRequests; + stats->Endpoints[len].AverageProcessingTimeNanoseconds = (int64_t)avg; + len++; + stats->EndpointsLen = len; + } + micro_unlock_endpoint(ep); + } + } + + _unlock_service(m); + } + if (err != NULL) + { + microServiceStats_Destroy(stats); + return err; + } *new_stats = stats; return NULL; } @@ -873,3 +906,48 @@ void microServiceStats_Destroy(microServiceStats *stats) NATS_FREE((char *)stats->Id); NATS_FREE(stats); } + +void micro_free_cloned_metadata(const char **metadata, int len) +{ + int i; + + if (metadata == NULL) + return; + + for (i = 0; i < len*2; i++) + { + NATS_FREE((char *)metadata[i]); + } + NATS_FREE((char **)metadata); +} + +microError *micro_clone_metadata(const char ***new_metadata, int *new_len, const char **metadata, int len) +{ + char **dup = NULL; + int i; + + if (new_metadata == NULL) + return micro_ErrorInvalidArg; + *new_metadata = NULL; + + if (len == 0) + return NULL; + + dup = NATS_CALLOC(len * 2, sizeof(char *)); + if (dup == NULL) + return micro_ErrorOutOfMemory; + + for (i = 0; i < len*2; i++) + { + micro_strdup(&dup[i], metadata[i]); + if (dup[i] == NULL) + { + micro_free_cloned_metadata((const char **)dup, i); + return micro_ErrorOutOfMemory; + } + } + + *new_metadata = (const char **)dup; + *new_len = len; + return NULL; +} diff --git a/src/micro_endpoint.c b/src/micro_endpoint.c index c5368f363..95240989e 100644 --- a/src/micro_endpoint.c +++ b/src/micro_endpoint.c @@ -323,6 +323,7 @@ micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg) MICRO_CALL(err, micro_strdup((char **)&new_cfg->Name, cfg->Name)); MICRO_CALL(err, micro_strdup((char **)&new_cfg->Subject, cfg->Subject)); + MICRO_CALL(err, micro_clone_metadata(&new_cfg->Metadata, &new_cfg->MetadataLen, cfg->Metadata, cfg->MetadataLen)); if (err != NULL) { @@ -343,6 +344,7 @@ void micro_free_cloned_endpoint_config(microEndpointConfig *cfg) // to be freed. NATS_FREE((char *)cfg->Name); NATS_FREE((char *)cfg->Subject); + micro_free_cloned_metadata(cfg->Metadata, cfg->MetadataLen); NATS_FREE(cfg); } diff --git a/src/micro_monitoring.c b/src/micro_monitoring.c index b79b53377..82608d13c 100644 --- a/src/micro_monitoring.c +++ b/src/micro_monitoring.c @@ -250,32 +250,63 @@ marshal_ping(natsBuffer **new_buf, microService *m) return NULL; } +natsStatus +_marshal_metadata(natsBuffer *buf, const char **metadata, int len) +{ + natsStatus s = NATS_OK; + int i; + + if (len > 0) + { + IFOK(s, natsBuf_Append(buf, "\"metadata\":{", -1)); + for (i = 0; ((s == NATS_OK) && (i < len)); i++) + { + IFOK(s, natsBuf_AppendByte(buf, '"')); + IFOK(s, natsBuf_Append(buf, metadata[i * 2], -1)); + IFOK(s, natsBuf_Append(buf, "\":\"", 3)); + IFOK(s, natsBuf_Append(buf, metadata[i * 2 + 1], -1)); + IFOK(s, natsBuf_AppendByte(buf, '"')); + if (i != len - 1) + IFOK(s, natsBuf_AppendByte(buf, ',')); + } + IFOK(s, natsBuf_Append(buf, "},", 2)); + } + return NATS_OK; +} + static microError * marshal_info(natsBuffer **new_buf, microServiceInfo *info) { natsBuffer *buf = NULL; natsStatus s; + int i; s = natsBuf_Create(&buf, 4096); IFOK(s, natsBuf_AppendByte(buf, '{')); + IFOK_attr("description", info->Description, ","); - IFOK_attr("id", info->Id, ","); - IFOK_attr("name", info->Name, ","); - IFOK_attr("type", info->Type, ","); - if ((s == NATS_OK) && (info->SubjectsLen > 0)) + + // "endpoints":{...} + if ((s == NATS_OK) && (info->EndpointsLen > 0)) { - int i; - IFOK(s, natsBuf_Append(buf, "\"subjects\":[", -1)); - for (i = 0; i < info->SubjectsLen; i++) + IFOK(s, natsBuf_Append(buf, "\"endpoints\":[", -1)); + for (i = 0; ((s == NATS_OK) && (i < info->EndpointsLen)); i++) { - IFOK(s, natsBuf_AppendByte(buf, '"')); - IFOK(s, natsBuf_Append(buf, info->Subjects[i], -1)); - IFOK(s, natsBuf_AppendByte(buf, '"')); - if (i < (info->SubjectsLen - 1)) + IFOK(s, natsBuf_AppendByte(buf, '{')); + IFOK_attr("name", info->Endpoints[i].Name, ","); + IFOK(s, _marshal_metadata(buf, info->Endpoints[i].Metadata, info->Endpoints[i].MetadataLen)); + IFOK_attr("subject", info->Endpoints[i].Subject, ""); + IFOK(s, natsBuf_AppendByte(buf, '}')); // end endpoint + if (i != info->EndpointsLen - 1) IFOK(s, natsBuf_AppendByte(buf, ',')); } IFOK(s, natsBuf_Append(buf, "],", 2)); } + + IFOK_attr("id", info->Id, ","); + IFOK(s, _marshal_metadata(buf, info->Metadata, info->MetadataLen)); + IFOK_attr("name", info->Name, ","); + IFOK_attr("type", info->Type, ","); IFOK_attr("version", info->Version, ""); IFOK(s, natsBuf_AppendByte(buf, '}')); diff --git a/src/microp.h b/src/microp.h index 8cf780c00..75b90646a 100644 --- a/src/microp.h +++ b/src/microp.h @@ -143,6 +143,7 @@ extern microError *micro_ErrorInvalidArg; microError *micro_add_endpoint(microEndpoint **new_ep, microService *m, const char *prefix, microEndpointConfig *cfg, bool is_internal); microError *micro_clone_endpoint_config(microEndpointConfig **out, microEndpointConfig *cfg); +microError *micro_clone_metadata(const char ***new_metadata, int *new_len, const char **metadata, int len); microError *micro_init_monitoring(microService *m); microError *micro_is_error_message(natsStatus s, natsMsg *msg); microError *micro_new_control_subject(char **newSubject, const char *verb, const char *name, const char *id); @@ -152,6 +153,7 @@ microError *micro_start_endpoint(microEndpoint *ep); microError *micro_stop_endpoint(microEndpoint *ep); void micro_free_cloned_endpoint_config(microEndpointConfig *cfg); +void micro_free_cloned_metadata(const char **metadata, int len); void micro_free_endpoint(microEndpoint *ep); void micro_free_request(microRequest *req); void micro_release_endpoint(microEndpoint *ep); diff --git a/src/nats.h b/src/nats.h index a07d0a0cd..d4b7c2265 100644 --- a/src/nats.h +++ b/src/nats.h @@ -7180,6 +7180,17 @@ typedef struct micro_endpoint_s microEndpoint; */ typedef struct micro_endpoint_config_s microEndpointConfig; +/** + * @brief static information about an endpoint. + * + * microEndpointInfo is returned by microService_GetInfo function, as part of microServiceInfo. It + * is also accessible by sending a `$SRV.INFO.[.]` request to + * the service. See micro_endpoint_info_s for descriptions of the fields. + * + * @see micro_endpoint_info_s, micro_service_info_s, microService_GetInfo + */ +typedef struct micro_endpoint_info_s microEndpointInfo; + /** * @brief The Microservice endpoint-level stats struct. * @@ -7245,7 +7256,7 @@ typedef struct micro_service_s microService; typedef struct micro_service_config_s microServiceConfig; /** - * @brief information about a running microservice. + * @brief Information about a running microservice. * * microServiceInfo is the struct returned by microService_GetInfo function. It * is also accessible by sending a `$SRV.INFO.[.]` request to @@ -7354,6 +7365,14 @@ struct micro_endpoint_config_s */ const char *Subject; + /** + * @brief Metadata for the endpoint in the form of a string array [n1, v1, + * n2, v2, ...] representing key/value pairs {n1:v1, n2:v2, ...}. + * MetadataLen contains the number of **pairs** in Metadata. + */ + const char **Metadata; + int MetadataLen; + /** * @brief The request handler for the endpoint. */ @@ -7366,6 +7385,30 @@ struct micro_endpoint_config_s void *State; }; +/** + * microEndpointInfo is the struct for the endpoint's static metadata. + */ +struct micro_endpoint_info_s +{ + /** + * @brief The name of the service. + */ + const char *Name; + + /** + * @brief The semantic version of the service. + */ + const char *Subject; + + /** + * @brief The metadata for the endpoint in the form of a string array [n1, + * v1, n2, v2, ...] representing key/value pairs {n1:v1, n2:v2, ...}. + * MetadataLen contains the number of **pairs** in Metadata. + */ + const char **Metadata; + int MetadataLen; +}; + /** * The Microservice endpoint stats struct. */ @@ -7431,6 +7474,14 @@ struct micro_service_config_s */ const char *Description; + /** + * @brief Metadata for the service in the form of a string array [n1, v1, + * n2, v2, ...] representing key/value pairs {n1:v1, n2:v2, ...}. + * MetadataLen contains the number of **pairs** in Metadata. + */ + const char **Metadata; + int MetadataLen; + /** * @brief The "main" (aka default) endpoint configuration. * @@ -7511,14 +7562,22 @@ struct micro_service_info_s const char *Id; /** - * @brief All endpoint subjects the service is listening on. + * @brief The service metadata in the form of a string array [n1, v1, n2, + * v2, ...] representing key/value pairs {n1:v1, n2:v2, ...}. MetadataLen + * contains the number of **pairs** in Metadata. */ - const char **Subjects; + const char **Metadata; + int MetadataLen; /** - * @brief The number of subjects in the `subjects` array. + * @brief Endpoints. */ - int SubjectsLen; + microEndpointInfo *Endpoints; + + /** + * @brief The number of endpoints in the `Endpoints` array. + */ + int EndpointsLen; }; /** diff --git a/src/util.c b/src/util.c index 22eeef364..7064f2d18 100644 --- a/src/util.c +++ b/src/util.c @@ -2303,7 +2303,10 @@ nats_marshalDuration(natsBuffer *out_buf, bool comma, const char *field_name, in w--; if (u == 0) { - return natsBuf_Append(out_buf, "0s", 2); + s = natsBuf_Append(out_buf, start, -1); + IFOK(s, natsBuf_Append(out_buf, field_name, -1)); + IFOK(s, natsBuf_Append(out_buf, "\":\"0s\"", -1)); + return NATS_UPDATE_ERR_STACK(s); } else if (u < 1000) { diff --git a/test/test.c b/test/test.c index fccca5d10..26650dc40 100644 --- a/test/test.c +++ b/test/test.c @@ -32279,10 +32279,11 @@ _microServiceDoneHandler(microService *m) } static microError * -_startMicroservice(microService** new_m, natsConnection *nc, microServiceConfig *cfg, struct threadArg *arg) +_startMicroservice(microService** new_m, natsConnection *nc, microServiceConfig *cfg, microEndpointConfig **eps, int num_eps, struct threadArg *arg) { microError *err = NULL; bool prev_done; + int i; cfg->DoneHandler = _microServiceDoneHandler; cfg->State = arg; @@ -32302,28 +32303,42 @@ _startMicroservice(microService** new_m, natsConnection *nc, microServiceConfig natsMutex_Unlock(arg->m); - return err; + if (err != NULL) + return err; + + for (i=0; i < num_eps; i++) + { + err = microService_AddEndpoint(*new_m, eps[i]); + if (err != NULL) + { + microService_Destroy(*new_m); + *new_m = NULL; + return err; + } + } + + return NULL; } static void -_startMicroserviceOK(microService** new_m, natsConnection *nc, microServiceConfig *cfg, struct threadArg *arg) +_startMicroserviceOK(microService** new_m, natsConnection *nc, microServiceConfig *cfg, microEndpointConfig **eps, int num_eps, struct threadArg *arg) { char buf[64]; snprintf(buf, sizeof(buf), "Start microservice %s: ", cfg->Name); test(buf); - testCond (NULL == _startMicroservice(new_m, nc, cfg, arg)); + testCond (NULL == _startMicroservice(new_m, nc, cfg, eps, num_eps, arg)); } static void -_startManyMicroservices(microService** svcs, int n, natsConnection *nc, microServiceConfig *cfg, struct threadArg *arg) +_startManyMicroservices(microService** svcs, int n, natsConnection *nc, microServiceConfig *cfg, microEndpointConfig **eps, int num_eps, struct threadArg *arg) { int i; for (i = 0; i < n; i++) { - _startMicroserviceOK(&(svcs[i]), nc, cfg, arg); + _startMicroserviceOK(&(svcs[i]), nc, cfg, eps, num_eps, arg); } testCond(true); @@ -32391,7 +32406,7 @@ test_MicroAddService(void) char buf[1024]; char *subject = NULL; nats_JSON *js = NULL; - char **array; + nats_JSON **array = NULL; int array_len; const char *str; @@ -32511,7 +32526,7 @@ test_MicroAddService(void) err = _startMicroservice( tc.null_receiver ? NULL : &m, tc.null_nc ? NULL : nc, - tc.cfg, &arg); + tc.cfg, NULL, 0, &arg); if (nats_IsStringEmpty(tc.expected_err)) { testCond(err == NULL); @@ -32575,23 +32590,16 @@ test_MicroAddService(void) snprintf(buf, sizeof(buf), "%s: Verify INFO subject %s: ", tc.name, subject); test(buf); s = natsConnection_Request(&reply, nc, subject, NULL, 0, 1000); - - IFOK(s, nats_JSONParse(&js, natsMsg_GetData(reply), natsMsg_GetDataLength(reply))); - IFOK(s, nats_JSONGetStrPtr(js, "id", &str)); - IFOK(s, (strcmp(str, info->Id) == 0) ? NATS_OK : NATS_ERR); - IFOK(s, nats_JSONGetStrPtr(js, "name", &str)); - IFOK(s, (strcmp(str, tc.cfg->Name) == 0) ? NATS_OK : NATS_ERR); - IFOK(s, nats_JSONGetStrPtr(js, "description", &str)); - IFOK(s, (!tc.cfg->Description || strcmp(str, tc.cfg->Description) == 0) ? NATS_OK : NATS_ERR); - IFOK(s, nats_JSONGetStrPtr(js, "type", &str)); - IFOK(s, (strcmp(str, MICRO_INFO_RESPONSE_TYPE) == 0) ? NATS_OK : NATS_ERR); array = NULL; array_len = 0; - IFOK(s, nats_JSONGetArrayStr(js, "subjects", &array, &array_len)); - IFOK(s, (array_len == tc.expected_num_subjects) ? NATS_OK : NATS_ERR); - testCond(s == NATS_OK); - for (int ia=0; iaId) == 0) + && (NATS_OK == nats_JSONGetStrPtr(js, "name", &str)) && (strcmp(str, tc.cfg->Name) == 0) + && (NATS_OK == nats_JSONGetStrPtr(js, "type", &str)) && (strcmp(str, MICRO_INFO_RESPONSE_TYPE) == 0) + && (NATS_OK == nats_JSONGetArrayObject(js, "endpoints", &array, &array_len)) && (array_len == tc.expected_num_subjects) + ); + NATS_FREE(array); nats_JSONDestroy(js); natsMsg_Destroy(reply); @@ -32653,7 +32661,7 @@ test_MicroGroups(void) "g1.g2.ep2", "g1.ep2", }; - int expected_num_subjects = sizeof(expected_subjects) / sizeof(expected_subjects[0]); + int expected_num_endpoints = sizeof(expected_subjects) / sizeof(expected_subjects[0]); s = _createDefaultThreadArgsForCbTests(&arg); if (s == NATS_OK) @@ -32671,7 +32679,7 @@ test_MicroGroups(void) test("Connect to server: "); testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - _startMicroservice(&m, nc, &cfg, &arg); + _startMicroservice(&m, nc, &cfg, NULL, 0, &arg); test("AddEndpoint 1 to service: "); testCond(NULL == microService_AddEndpoint(m, &ep1_cfg)); @@ -32698,15 +32706,15 @@ test_MicroGroups(void) if (err != NULL) FAIL("failed to get service info!") - test("Verify number of subjects: "); - testCond(info->SubjectsLen == expected_num_subjects); + test("Verify number of endpoints: "); + testCond(info->EndpointsLen == expected_num_endpoints); - test("Verify subjects: "); - for (i = 0; i < info->SubjectsLen; i++) + test("Verify endpoint subjects: "); + for (i = 0; i < info->EndpointsLen; i++) { - if (strcmp(info->Subjects[i], expected_subjects[i]) != 0) { + if (strcmp(info->Endpoints[i].Subject, expected_subjects[i]) != 0) { char buf[1024]; - snprintf(buf, sizeof(buf), "expected %s, got %s", expected_subjects[i], info->Subjects[i]); + snprintf(buf, sizeof(buf), "expected %s, got %s", expected_subjects[i], info->Endpoints[i].Subject); FAIL(buf); } } @@ -32738,16 +32746,37 @@ test_MicroBasics(void) natsConnection *nc = NULL; natsPid serverPid = NATS_INVALID_PID; microService *svcs[NUM_MICRO_SERVICES]; - microEndpointConfig ep_cfg = { + microEndpointConfig ep1_cfg = { .Name = "do", .Subject = "svc.do", .Handler = _microHandleRequestNoisy42, }; + const char *ep_md[] = { + "key1", "value1", + "key2", "value2", + "key3", "value3", + }; + const char *service_md[] = { + "skey1", "svalue1", + "skey2", "svalue2", + }; + microEndpointConfig ep2_cfg = { + .Name = "unused", + .Subject = "svc.unused", + .Handler = _microHandleRequestNoisy42, + .MetadataLen = 3, + .Metadata = ep_md, + }; + microEndpointConfig *eps[] = { + &ep1_cfg, + &ep2_cfg, + }; microServiceConfig cfg = { .Version = "1.0.0", .Name = "CoolService", .Description = "returns 42", - .Endpoint = &ep_cfg, + .MetadataLen = 2, + .Metadata = service_md, }; natsMsg *reply = NULL; microServiceInfo *info = NULL; @@ -32757,6 +32786,7 @@ test_MicroBasics(void) natsInbox *inbox = NULL; natsSubscription *sub = NULL; nats_JSON *js = NULL; + nats_JSON *md = NULL; int num_requests = 0; int num_errors = 0; int n; @@ -32782,7 +32812,7 @@ test_MicroBasics(void) test("Connect to server: "); testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - _startManyMicroservices(svcs, NUM_MICRO_SERVICES, nc, &cfg, &arg); + _startManyMicroservices(svcs, NUM_MICRO_SERVICES, nc, &cfg, eps, sizeof(eps)/sizeof(eps[0]), &arg); // Now send 50 requests. test("Send 50 requests (no matter response): "); @@ -32803,8 +32833,10 @@ test_MicroBasics(void) err = microService_GetInfo(&info, svcs[i]); testCond((err == NULL) && (strcmp(info->Name, "CoolService") == 0) && - (strlen(info->Description) > 0) && - (strlen(info->Version) > 0)); + (strlen(info->Id) > 0) && + (strcmp(info->Description, "returns 42") == 0) && + (strcmp(info->Version, "1.0.0") == 0) && + (info->MetadataLen == 2)); microServiceInfo_Destroy(info); } @@ -32828,14 +32860,58 @@ test_MicroBasics(void) break; } testCond(NATS_OK == s); - snprintf(buf, sizeof(buf), "Validate INFO response #%d: ", i); + + snprintf(buf, sizeof(buf), "Parse INFO response#%d: ", i); test(buf); js = NULL; - testCond((NATS_OK == nats_JSONParse(&js, reply->data, reply->dataLen)) && - (NATS_OK == nats_JSONGetStrPtr(js, "name", &str)) && - (strcmp(str, "CoolService") == 0)); + testCond(NATS_OK == nats_JSONParse(&js, reply->data, reply->dataLen)) ; + + snprintf(buf, sizeof(buf), "Validate INFO response strings#%d: ", i); + test(buf); + testCond( + (NATS_OK == nats_JSONGetStrPtr(js, "name", &str)) && (strcmp(str, "CoolService") == 0) + && (NATS_OK == nats_JSONGetStrPtr(js, "description", &str)) && (strcmp(str, "returns 42") == 0) + && (NATS_OK == nats_JSONGetStrPtr(js, "version", &str)) && (strcmp(str, "1.0.0") == 0) + && (NATS_OK == nats_JSONGetStrPtr(js, "id", &str)) && (strlen(str) > 0) + ); + + snprintf(buf, sizeof(buf), "Validate INFO service metadata#%d: ", i); + test(buf); + md = NULL; + testCond( + (NATS_OK == nats_JSONGetObject(js, "metadata", &md)) + && (NATS_OK == nats_JSONGetStrPtr(md, "skey1", &str)) && (strcmp(str, "svalue1") == 0) + && (NATS_OK == nats_JSONGetStrPtr(md, "skey2", &str)) && (strcmp(str, "svalue2") == 0) + ); + + test("Validate INFO has 2 endpoints: "); + array = NULL; + array_len = 0; + s = nats_JSONGetArrayObject(js, "endpoints", &array, &array_len); + testCond((NATS_OK == s) && (array != NULL) && (array_len == 2)); + + test("Validate INFO svc.do endpoint: "); + md = NULL; + testCond( + (NATS_OK == nats_JSONGetStrPtr(array[0], "name", &str)) && (strcmp(str, "do") == 0) + && (NATS_OK == nats_JSONGetStrPtr(array[0], "subject", &str)) && (strcmp(str, "svc.do") == 0) + && (NATS_OK == nats_JSONGetObject(array[0], "metadata", &md)) && (md == NULL) + ); + + test("Validate INFO unused endpoint with metadata: "); + md = NULL; + testCond( + (NATS_OK == nats_JSONGetStrPtr(array[1], "name", &str)) && (strcmp(str, "unused") == 0) + && (NATS_OK == nats_JSONGetStrPtr(array[1], "subject", &str)) && (strcmp(str, "svc.unused") == 0) + && (NATS_OK == nats_JSONGetObject(array[1], "metadata", &md)) + && (NATS_OK == nats_JSONGetStrPtr(md, "key1", &str)) && (strcmp(str, "value1") == 0) + && (NATS_OK == nats_JSONGetStrPtr(md, "key2", &str)) && (strcmp(str, "value2") == 0) + && (NATS_OK == nats_JSONGetStrPtr(md, "key3", &str)) && (strcmp(str, "value3") == 0) + ); + nats_JSONDestroy(js); natsMsg_Destroy(reply); + NATS_FREE(array); } natsSubscription_Destroy(sub); natsInbox_Destroy(inbox); @@ -32901,19 +32977,19 @@ test_MicroBasics(void) s = nats_JSONParse(&js, reply->data, reply->dataLen); testCond((NATS_OK == s) && (js != NULL)); - test("Ensure STATS has one endpoint: "); + test("Ensure STATS has 2 endpoints: "); array = NULL; array_len = 0; s = nats_JSONGetArrayObject(js, "endpoints", &array, &array_len); - testCond((NATS_OK == s) && (array != NULL) && (array_len == 1)) + testCond((NATS_OK == s) && (array != NULL) && (array_len == 2)) - test("Ensure endpoint has num_requests: "); + test("Ensure endpoint 0 has num_requests: "); n = 0; s = nats_JSONGetInt(array[0], "num_requests", &n); testCond(NATS_OK == s); num_requests += n; - test("Ensure endpoint has num_errors: "); + test("Ensure endpoint 0 has num_errors: "); n = 0; s = nats_JSONGetInt(array[0], "num_errors", &n); testCond(NATS_OK == s); @@ -32985,7 +33061,7 @@ test_MicroStartStop(void) test("Connect to server: "); testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - _startManyMicroservices(svcs, NUM_MICRO_SERVICES, nc, &cfg, &arg); + _startManyMicroservices(svcs, NUM_MICRO_SERVICES, nc, &cfg, NULL, 0, &arg); // Now send some requests. test("Send requests: "); @@ -33045,7 +33121,7 @@ test_MicroServiceStopsOnClosedConn(void) test("Connect for microservice: "); testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - _startMicroservice(&m, nc, &cfg, &arg); + _startMicroservice(&m, nc, &cfg, NULL, 0, &arg); test("Test microservice is running: "); testCond(!microService_IsStopped(m)) @@ -33117,7 +33193,7 @@ test_MicroServiceStopsWhenServerStops(void) test("Connect for microservice: "); testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - _startMicroservice(&m, nc, &cfg, &arg); + _startMicroservice(&m, nc, &cfg, NULL, 0, &arg); test("Test microservice is running: "); testCond(!microService_IsStopped(m)) @@ -33218,7 +33294,7 @@ test_MicroAsyncErrorHandler(void) test("Connect to NATS: "); testCond(NATS_OK == natsConnection_Connect(&nc, opts)); - _startMicroservice(&m, nc, &cfg, &arg); + _startMicroservice(&m, nc, &cfg, NULL, 0, &arg); test("Test microservice is running: "); testCond(!microService_IsStopped(m)) From 9016f153da4fd8ddd75ffc01f3e88fa68060499a Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 13 Jun 2023 06:26:12 -0700 Subject: [PATCH 84/85] PR feedback: unsed var --- test/test.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/test.c b/test/test.c index 26650dc40..9c4a4a9b9 100644 --- a/test/test.c +++ b/test/test.c @@ -33261,7 +33261,6 @@ test_MicroAsyncErrorHandler(void) struct threadArg arg; natsConnection *nc = NULL; natsOptions *opts = NULL; - natsSubscription *sub = NULL; natsPid serverPid = NATS_INVALID_PID; microService *m = NULL; microEndpoint *ep = NULL; @@ -33322,8 +33321,6 @@ test_MicroAsyncErrorHandler(void) natsMutex_Unlock(arg.m); testCond((s == NATS_OK) && arg.closed && (arg.status == NATS_SLOW_CONSUMER)); - natsSubscription_Destroy(sub); - microService_Destroy(m); _waitForMicroservicesAllDone(&arg); From accace4c204f27cd7dd640a99661e7190d9938d3 Mon Sep 17 00:00:00 2001 From: Ivan Kozlovic Date: Tue, 13 Jun 2023 12:29:52 -0600 Subject: [PATCH 85/85] Fix to run on Windows The change that I asked to make earlier regarding nats_vsnprint in some places was wrong, but more importantly, you can't pass a NULL buffer on Windows in order to calculate the length of the formatted string. Instead, I added a new macro that uses a different function for Windows. Signed-off-by: Ivan Kozlovic --- src/include/n-unix.h | 2 ++ src/include/n-win.h | 2 ++ src/micro_error.c | 5 ++++- src/win/strings.c | 4 ++-- test/test.c | 1 + 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/include/n-unix.h b/src/include/n-unix.h index 3b775cd42..ac4919a25 100644 --- a/src/include/n-unix.h +++ b/src/include/n-unix.h @@ -64,4 +64,6 @@ typedef size_t natsRecvLen; #define nats_vsnprintf vsnprintf #define nats_strtok strtok_r +#define nats_vscprintf(f, a) vsnprintf(NULL, 0, (f), (a)) + #endif /* N_UNIX_H_ */ diff --git a/src/include/n-win.h b/src/include/n-win.h index 328756e79..e84d65170 100644 --- a/src/include/n-win.h +++ b/src/include/n-win.h @@ -63,6 +63,8 @@ typedef int natsRecvLen; #define nats_vsnprintf(b, sb, f, a) vsnprintf_s((b), (sb), (_TRUNCATE), (f), (a)) +#define nats_vscprintf _vscprintf + int nats_asprintf(char **newStr, const char *fmt, ...); diff --git a/src/micro_error.c b/src/micro_error.c index 330d73a0b..4b64a4088 100644 --- a/src/micro_error.c +++ b/src/micro_error.c @@ -49,7 +49,10 @@ verrorf(natsStatus s, int code, const char *format, va_list args) if (format == NULL) format = ""; - message_len = nats_vsnprintf(NULL, 0, format, args); + // Do not use nats_vsnprintf here since we want to calculate the size of + // the resulting formatted string. On Windows, that would fail. Use + // that instead. + message_len = nats_vscprintf(format, args); if (message_len < 0) { va_end(args2); diff --git a/src/win/strings.c b/src/win/strings.c index 07d41eeb7..e39629acb 100644 --- a/src/win/strings.c +++ b/src/win/strings.c @@ -29,7 +29,7 @@ nats_asprintf(char **newStr, const char *fmt, ...) do { va_start(ap, fmt); - n = nats_vsnprintf(str, size, fmt, ap); + n = vsnprintf(str, size, fmt, ap); va_end(ap); if ((n < 0) || (n >= size)) @@ -128,7 +128,7 @@ nats_snprintf(char *buffer, size_t countszt, char *format, ...) memset(buffer, 0, count); va_start(ap, format); - len = (int) nats_vsnprintf(buffer, count, format, ap); + len = (int) vsnprintf(buffer, count, format, ap); va_end(ap); if ((len == count) || (len < 0)) { diff --git a/test/test.c b/test/test.c index 9c4a4a9b9..44d55a261 100644 --- a/test/test.c +++ b/test/test.c @@ -14501,6 +14501,7 @@ test_AsyncErrHandlerSubDestroyed(void) test("Destroy subscription: "); natsSubscription_Destroy(sub); + testCond(true); test("Wait for async error callback to return: "); natsMutex_Lock(arg.m);