From e6e738bfea36617ee44ddaf31ab1fa4e370dfe62 Mon Sep 17 00:00:00 2001 From: Attila Kovacs Date: Sat, 14 Sep 2024 11:34:47 +0200 Subject: [PATCH] Updates and fixes --- include/smax-private.h | 36 +- include/smax.h | 70 ++-- src/smax-buffers.c | 726 ---------------------------------------- src/smax-easy.c | 510 ++++++++++++++-------------- src/smax-lazy.c | 180 ++++++---- src/smax-messages.c | 126 ++++--- src/smax-meta.c | 161 +++++---- src/smax-queue.c | 108 +++--- src/smax-resilient.c | 37 +- src/smax-util.c | 355 +++++++++++++------- src/smax.c | 722 +++++++++++++++++++++++---------------- tests/src/benchmarkRM.c | 2 +- tools/src/smaxScrub.c | 18 - tools/src/smaxValue.c | 2 +- tools/src/smaxWrite.c | 2 +- 15 files changed, 1343 insertions(+), 1712 deletions(-) delete mode 100644 src/smax-buffers.c delete mode 100644 tools/src/smaxScrub.c diff --git a/include/smax-private.h b/include/smax-private.h index 96b38d8..f6812a8 100644 --- a/include/smax-private.h +++ b/include/smax-private.h @@ -13,14 +13,14 @@ #ifndef SMAX_PRIVATE_H_ #define SMAX_PRIVATE_H_ +#define __XCHANGE_INTERNAL_API__ ///< User internal definitions + #include "smax.h" -#include "redisx.h" #define RELEASEID "" ///< Redis PUB/SUB channel prefix for wait release notifications. /// \cond PROTECTED - typedef struct PullRequest { char *group; char *key; @@ -31,31 +31,13 @@ typedef struct PullRequest { struct PullRequest *next; } PullRequest; -/** - * An single entry (array of doubles) in a SMA-X buffer, - */ -typedef struct Entry { - double t; - double *values; -} Entry; -/** - * A buffered sequence of SMA-X numerical data. - * - */ -typedef struct Buffer { - pthread_mutex_t mutex; - int id; - char *channel; - char *table; - char *key; - int count; - int size; - int firstIndex; - int n; - Entry *entries; - struct Buffer *next; -} Buffer; +int smaxLockConfig(); +int smaxUnlockConfig(); +int smaxLockNotify(); +int smaxUnlockNotify(); + +long smaxGetHash(const char *buf, const int size, const XType type); int smaxRead(PullRequest *req, int channel); int smaxWrite(const char *group, const XField *f); @@ -65,7 +47,7 @@ void smaxProcessPipedWritesAsync(RESP *reply); unsigned char smaxGetHashLookupIndex(const char *group, int lGroup, const char *key, int lKey); char *smaxGetUpdateChannelPattern(const char *table, const char *key); int smaxStorePush(const char *table, const XField *field); -void smaxTransmitErrorHandler(Redis *r, int channel, const char *op); +void smaxTransmitErrorHandler(Redis *r, enum redisx_channel channel, const char *op); int smaxScriptError(const char *name, int status); int smaxScriptErrorAsync(const char *name, int status); boolean smaxIsDisabled(); diff --git a/include/smax.h b/include/smax.h index 0de841b..38412da 100644 --- a/include/smax.h +++ b/include/smax.h @@ -14,17 +14,29 @@ #include "redisx.h" #include "xchange.h" +#ifndef SMAX_DEFAULT_HOSTNAME +# define SMAX_DEFAULT_HOSTNAME "smax" ///< Host name of Redis server used for SMA-X. +#endif -#define SMAX_DEFAULT_HOSTNAME "smax" ///< Host name of Redis server used for SMA-X. +#ifndef SMAX_DEFAULT_PIPELINE_ENABLED +# define SMAX_DEFAULT_PIPELINE_ENABLED TRUE ///< Whether pipelining is enabled by default. +#endif -#define SMAX_DEFAULT_PIPELINE_ENABLED TRUE ///< Whether pipelining is enabled by default. +#ifndef SMAX_RESTORE_QUEUE_ON_RECONNECT +# define SMAX_RESTORE_QUEUE_ON_RECONNECT TRUE ///< Whether read queues are restored if SMA-X is disconnected/reconnected. +#endif -#define SMAX_RESTORE_QUEUE_ON_RECONNECT TRUE ///< Whether read queues are restored if SMA-X is disconnected/reconnected. -#define SMAX_DEFAULT_MAX_QUEUED 1024 ///< Maximum number of pull requests allowed to be queued at once. -#define SMAX_PIPE_READ_TIMEOUT_MILLIS 3000 ///< (ms) Timeout for pipelined (queued) pull requests +#ifndef SMAX_DEFAULT_MAX_QUEUED +# define SMAX_DEFAULT_MAX_QUEUED 1024 ///< Maximum number of pull requests allowed to be queued at once. +#endif -#define SMAX_RECONNECT_RETRY_SECONDS 3 ///< (s) Time between reconnection attempts on lost SMA-X connections. +#ifndef SMAX_PIPE_READ_TIMEOUT_MILLIS +# define SMAX_PIPE_READ_TIMEOUT_MILLIS 3000 ///< (ms) Timeout for pipelined (queued) pull requests +#endif +#ifndef SMAX_RECONNECT_RETRY_SECONDS +# define SMAX_RECONNECT_RETRY_SECONDS 3 ///< (s) Time between reconnection attempts on lost SMA-X connections. +#endif /** @@ -152,16 +164,12 @@ void smaxResetMeta(XMeta *m); int smaxGetMetaCount(const XMeta *m); void smaxSetOrigin(XMeta *m, const char *origin); -// Configuration locks ----------------------------------------> -/// \cond PROTECTED -int smaxLockConfig(); -int smaxUnlockConfig(); -int smaxLockNotify(); -int smaxUnlockNotify(); -/// \endcond - - // Globally available functions provided by SMA-X -------------> +int smaxSetServer(const char *host, int port); +int smaxSetAuth(const char *username, const char *password); +int smaxSetDB(int idx); +int smaxSetTcpBuf(int size); + int smaxConnect(); int smaxConnectTo(const char *server); int smaxDisconnect(); @@ -169,11 +177,10 @@ int smaxIsConnected(); int smaxReconnect(); // Connect/disconnect callback hooks --------------------> -void smaxAddConnectHook(const void (*setupCall)(void)); -void smaxRemoveConnectHook(const void (*setupCall)(void)); -void smaxAddDisconnectHook(const void (*cleanupCall)(void)); -void smaxRemoveDisconnectHook(const void (*cleanupCall)(void)); - +int smaxAddConnectHook(void (*setupCall)(void)); +int smaxRemoveConnectHook(void (*setupCall)(void)); +int smaxAddDisconnectHook(void (*cleanupCall)(void)); +int smaxRemoveDisconnectHook(void (*cleanupCall)(void)); // Basic information exchage routines --------------------> int smaxPull(const char *table, const char *key, XType type, int count, void *value, XMeta *meta); @@ -194,19 +201,18 @@ double *smaxPullDoubles(const char *table, const char *key, XMeta *meta, int *n) char **smaxPullStrings(const char *table, const char *key, XMeta *meta, int *n); XStructure *smaxPullStruct(const char *name, XMeta *meta, int *status); -// Convenience method for structure fields ---------------> + +// Convenience methods for serialized strucures ----------> +boolean smaxGetBooleanField(const XStructure *s, const char *name, boolean defaultValue); long long smaxGetLongField(const XStructure *s, const char *name, long long defaultValue); double smaxGetDoubleField(const XStructure *s, const char *name, double defaultValue); char *smaxGetRawField(const XStructure *s, const char *name, char *defaultValue); int smaxGetArrayField(const XStructure *s, const char *name, void *dst, XType type, int count); -XField *smaxSetScalarField(XStructure *s, const char *name, XType type, const void *value); -XField *smaxSet1DField(XStructure *s, const char *name, XType type, int n, const void *value); - // Pipelined pull requests -------------------------------> int smaxQueue(const char *table, const char *key, XType type, int count, void *value, XMeta *meta); XSyncPoint *smaxCreateSyncPoint(); -int smaxQueueCallback(void (*f)(char *), char *arg); +int smaxQueueCallback(void (*f)(void *), void *arg); void smaxDestroySyncPoint(XSyncPoint *sync); int smaxSync(XSyncPoint *sync, int timeoutMillis); int smaxWaitQueueComplete(int timeoutMillis); @@ -252,8 +258,8 @@ int smaxWaitOnSubscribedGroup(const char *matchTable, char **changedKey, int tim int smaxWaitOnSubscribedVar(const char *matchKey, char **changedTable, int timeout); int smaxWaitOnAnySubscribed(char **changedTable, char **changedKey, int timeout); int smaxReleaseWaits(); -void smaxAddSubscriber(const char *stem, RedisSubscriberCall f); -void smaxRemoveSubscribers(RedisSubscriberCall f); +int smaxAddSubscriber(const char *stem, RedisSubscriberCall f); +int smaxRemoveSubscribers(RedisSubscriberCall f); // Messages ---------------------------------------------------> int smaxSendStatus(const char *msg); @@ -306,6 +312,7 @@ int x2smaxStruct(XStructure *s); int x2smaxField(XField *f); int smax2xStruct(XStructure *s); int smax2xField(XField *f); + XField *smaxCreateScalarField(const char *name, XType type, const void *value); XField *smaxCreate1DField(const char *name, XType type, int size, const void *value); XField *smaxCreateField(const char *name, XType type, int ndim, const int *sizes, const void *value); @@ -331,24 +338,25 @@ char *smaxGetHostName(); void smaxSetHostName(const char *name); char *smaxGetProgramID(); int smaxGetServerTime(struct timespec *t); -void smaxSetTcpBuf(int size); -int smaxGetTcpBuf(); const char *smaxErrorDescription(int code); int smaxError(const char *func, int errorCode); // Low-level utilities ----------------------------------------> -int smaxSplitID(char *id, char **pKey); +char *smaxStringType(XType type); +XType smaxTypeForString(const char *type); +int smaxUnpackStrings(const char *data, int len, int count, char **dst); int smaxStringToValues(const char *str, void *value, XType type, int count, int *parsed); char *smaxValuesToString(const void *value, XType type, int count, char *trybuf, int trylength); int smaxTimestamp(char *buf); int smaxParseTime(const char *timestamp, time_t *secs, long *nanosecs); double smaxGetTime(const char *timestamp); int smaxTimeToString(const struct timespec *time, char *buf); -long smaxGetHash(const char *buf, const int size, const XType type); int smaxSetPipelineConsumer(void (*f)(RESP *)); + #if !(__Lynx__ && __powerpc__) int smaxDeletePattern(const char *pattern); #endif + #endif /* SMAX_H_ */ diff --git a/src/smax-buffers.c b/src/smax-buffers.c deleted file mode 100644 index 68ef196..0000000 --- a/src/smax-buffers.c +++ /dev/null @@ -1,726 +0,0 @@ -/** - * @file - * - * @date Created on Jul 30, 2021 - * @author Attila Kovacs - * - * @brief A set of functions to provide interpolated values, sums, and averages etc., from any numerical SMA-X data. - */ - -#include -#include -#include -#include -#include - -#include "smax.h" -#include "smax-private.h" - -#define INITIAL_BUFFERS 16 /// Initial storage size for buffers - -#ifndef INFINITY -#define INFINITY HUGE_VAL -#endif - -typedef struct { - XMeta meta; - Entry *entry; - int bufferIndex; - int bufferID; -} Incoming; - - -static Buffer *lookup[SMAX_LOOKUP_SIZE]; -static Buffer **buffers; -static int firstBufferID; -static int nBuffers; -static int capacity; -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - - -static void ProcessUpdate(const char *pattern, const char *channel, const char *msg, int len); - - -static int GetFloorIndexAsync(const Buffer *p, double t) { - int i, step; - - if(!p) return -1; - if(!p->n) return -1; - - if(p->entries[p->firstIndex].t > t) return -1; - - step = p->n >> 1; - - for(i = p->firstIndex; step >= 1; step >>= 1) if(p->entries[(i+step) % p->size].t < t) i += step; - return i % p->size; -} - -static int GetCeilIndexAsync(const Buffer *p, double t) { - int i, step; - - if(!p) return -1; - if(!p->n) return -1; - - i = p->firstIndex + p->n - 1; - - if(p->entries[i % p->size].t < t) return -1; - - step = p->n >> 1; - - for(; step >= 1; step >>= 1) if(p->entries[(i-step) % p->size].t > t) i -= step; - return i % p->size; -} - - -static int GetIndexRangeAsync(const Buffer *p, double fromt, double tot, int *fromi, int *toi) { - int status = X_SUCCESS; - - *fromi = GetFloorIndexAsync(p, fromt); - if(*fromi < 0) { - *fromi = p->firstIndex; - status = X_INCOMPLETE; - } - - *toi = GetCeilIndexAsync(p, tot); - if(*toi < 0) { - *toi = (p->firstIndex + p->n); - status = X_INCOMPLETE; - } - else if(*toi < *fromi) *toi += p->size; - - return status; -} - - -static int GetInterpolatedAsync(Buffer *p, double t, double *result) { - Entry *prev, *next; - const double *rates; - double dT, dt; - int i, n; - - i = GetFloorIndexAsync(p, t); - if(i < 0) return X_INCOMPLETE; - - // Make sure there is a 'next' datum. - n = (i + 1) - p->firstIndex; - if(n < 0) n += p->size; - if(n >= p->n) return X_INCOMPLETE; - - rates = (double *) calloc(p->count, sizeof(double)); - if(!rates) return X_FAILURE; - - prev = &p->entries[i]; - next = &p->entries[(i + 1) % p->size]; - - dT = next->t - prev->t; - - dt = t - prev->t; - for(i=p->count; --i >= 0; ) result[i] = prev->values[i] + (next->values[i] - prev->values[i]) * dt / dT; - - return X_SUCCESS; -} - - -static void GetSumAsync(Buffer *p, int fromi, int toi, double *sum) { - int i; - - if(toi < fromi) toi += p->size; - memset(sum, 0, p->count * sizeof(double)); - - for(i = fromi; i < toi; i++) { - Entry *e = &p->entries[i % p->size]; - int k; - for(k=p->count; --k >= 0; ) sum[k] += e->values[k]; - } -} - - -static void GetSquareSumAsync(Buffer *p, int fromi, int toi, double *sum2) { - int i; - - if(toi < fromi) toi += p->size; - memset(sum2, 0, p->count * sizeof(double)); - - for(i = fromi; i < toi; i++) { - Entry *e = &p->entries[i % p->size]; - int k; - for(k=p->count; --k >= 0; ) sum2[k] += e->values[k] * e->values[k]; - } -} - -static void GetAverageAsync(Buffer *p, int fromi, int toi, double *mean, double *rms) { - int i, n; - - if(toi < fromi) toi += p->size; - n = toi - fromi; - - if(rms) for(i=p->count; --i >= 0; ) rms[i] = NAN; - - GetSumAsync(p, fromi, toi, mean); - - for(i=p->count; --i >= 0; ) mean[i] /= n; - - if(rms && n > 1) { - GetSquareSumAsync(p, fromi, toi, rms); - for(i=p->count; --i >= 0; ) rms[i] = sqrt(rms[i] - mean[i] * mean[i]) / (n-1); - } -} - - -static int GetRangeAsync(Buffer *p, int fromi, int toi, double *min, double *max) { - int i, status = X_SUCCESS; - - if(!min && !max) return X_SUCCESS; // Nothing to do... - - if(min) for(i=p->count; --i >= 0; ) min[i] = INFINITY; - if(max) for(i=p->count; --i >= 0; ) max[i] = -INFINITY; - - if(!p->n) return X_INCOMPLETE; - - for(i = fromi; i < toi; i++) { - Entry *e = &p->entries[i % p->size]; - int k; - for(k=p->count; --k >= 0; ) { - if(min) if(e->values[k] < min[k]) min[k] = e->values[k]; - if(max) if(e->values[k] > max[k]) max[k] = e->values[k]; - } - } - - return status; -} - - -static void AddEntry(Buffer *p, Entry *e) { - if(!e) return; - - pthread_mutex_lock(&p->mutex); - - if(p->n < p->size) { - p->entries[(p->firstIndex + p->n) % p->size] = *e; - p->n++; - } - else { - p->entries[p->firstIndex] = *e; - if(++p->firstIndex >= p->size) p->firstIndex = 0; - } - - pthread_mutex_unlock(&p->mutex); - - free(e); -} - -/** - * Returns the hash lookup index for a given update channel. It is the same index as what xGetHashLookupIndex() - * would return for the variable that has been updated in the given notification channel - * - * \param channel The redis notification channel (string) for the update. - * - * \return An integer (0-255) just like for xGetHashLookupIndex(). - * - * \sa xGetHashLookupIndex() - * - */ -static int GetChannelLookupIndex(const char *channel) { - int lGroup; - char *key; - - // Skip the table update prefix for the channel... - if(!strncmp(channel, SMAX_UPDATES, SMAX_UPDATES_LENGTH)) channel += SMAX_UPDATES_LENGTH; - - key = xLastSeparator(channel); - if(!key) return 0; - - lGroup = key - channel; - key += X_SEP_LENGTH; - return smaxGetHashLookupIndex(lGroup ? channel : NULL, lGroup, key, 0); -} - - -static __inline__ int GetBufferLookupIndex(const Buffer *p) { - return smaxGetHashLookupIndex(p->table, 0, p->key, 0); -} - -static int AddBufferAsync(Buffer *p) { - static int serial; - int i; - - if(!p) return X_NULL; - - p->channel = NULL; - - if(!capacity) { - capacity = INITIAL_BUFFERS; - buffers = (Buffer **) calloc(capacity, sizeof(Buffer *)); - if(!buffers) return X_FAILURE; - - p->channel = xGetAggregateID(p->table, p->key); - smaxAddSubscriber("", ProcessUpdate); - } - - if(nBuffers >= capacity) { - Buffer **old = buffers; - int newcap = 2 * capacity; - buffers = (Buffer **) realloc(buffers, newcap * sizeof(Buffer *)); - if(!buffers) { - free(old); - return X_FAILURE; - } - memset(&buffers[capacity], 0, (newcap - capacity) * sizeof(Buffer *)); - capacity = newcap; - } - - p->id = ++serial; - buffers[nBuffers++] = p; - - // Add to lookup table. - i = GetBufferLookupIndex(p); - p->next = lookup[i]; - lookup[i] = p; - - smaxSubscribe(p->table, p->key); - - return X_SUCCESS; -} - -static Buffer *FindBufferAsync(const char *table, const char *key) { - Buffer *p = lookup[smaxGetHashLookupIndex(table, 0, key, 0)]; - for(; p != NULL; p = p->next) if(!strcmp(p->table, table)) if(!strcmp(p->key, key)) return p; - return NULL; -} - -static void ClearBufferAsync(Buffer *p) { - if(!p) return; - p->firstIndex = p->n = 0; -} - -static void DestroyBuffer(Buffer *p) { - if(!p) return; - - smaxUnsubscribe(p->table, p->key); - - pthread_mutex_lock(&p->mutex); - - ClearBufferAsync(p); - if(p->channel) free(p->channel); - if(p->table) free(p->table); - if(p->key) free(p->key); - - pthread_mutex_unlock(&p->mutex); - pthread_mutex_destroy(&p->mutex); - - free(p); -} - -static void ProcessIncoming(char *arg) { - Incoming *in = (Incoming *) arg; - Buffer *p; - - if(!in) return; - - pthread_mutex_lock(&mutex); - - if(in->bufferIndex >= nBuffers) { - pthread_mutex_unlock(&mutex); - return; - } - - p = buffers[in->bufferIndex]; - if(in->bufferID != p->id) { - pthread_mutex_unlock(&mutex); - return; - } - - in->entry->t = in->meta.timestamp.tv_sec + 1e-9 * in->meta.timestamp.tv_nsec; - - AddEntry(p, in->entry); - - pthread_mutex_unlock(&mutex); - - free(in); -} - -static void ProcessUpdate(const char *pattern, const char *channel, const char *msg, int len) { - Buffer *p = lookup[GetChannelLookupIndex(channel)]; - - pthread_mutex_lock(&mutex); - - for( ; p != NULL; p = p->next) if(!strcmp(channel, p->channel)) { - Incoming *in; - Entry *entry = (Entry *) calloc(1, sizeof(Entry)); - - if(!entry) break; - - entry->values = (double *) calloc(p->count, sizeof(double)); - if(!entry->values) { - free(entry); - break; - } - - in = (Incoming *) calloc(1, sizeof(Incoming)); - if(!in) { - free(entry->values); - free(entry); - break; - } - - in->bufferIndex = p->id - firstBufferID; - in->bufferID = p->id; - in->entry = entry; - - smaxQueue(p->table, p->key, X_DOUBLE, p->count, entry->values, &in->meta); - smaxQueueCallback(ProcessIncoming, (char *) in); - - break; - } - - pthread_mutex_unlock(&mutex); -} - - -static Buffer *GetBufferAsync(int id) { - if(id < firstBufferID) return NULL; - if(id > firstBufferID + nBuffers) return NULL; - return buffers[id - firstBufferID]; -} - - -/** - * Starts buffering an SMA-X variable, e.g. for calculating interpolated values; sums, averages, or - * min/max over time windows. The SMA-X variable must be a numberical type (any integer or float type) - * with any number of elements. - * - * @param table Redis hash table name - * @param key Field name - * @param count Number of values we will be requesting for this variable. - * @param lookbackTime (s) Maximum time range of data to hold in store. Old data will be - * discarded as appropriate when new measurements become available. - * - * @return A unique buffer ID (>= 0), that shall be used for accessing data in the buffer, - * or else an error code (< 0), such as: - * - * X_NAME_INVALID if they key argument is NULL, - * X_SIZE_INVALID if the count and/or lookbackTime argument is 0 or negative, - * X_TYPE_INVALID if the SMA-X data for the specified table/key is non-numerical. - * X_FAILURE if the buffer could not be allocated. - * - * @sa smaxFlushBuffer() - * @sa smaxEndBuffers() - * @sa smaxGetWindowAverage() - * @sa smaxGetWindowSum() - * @sa smaxGetWindowRange() - * @sa smaxGetInterpolated() - * @sa smaxGetBufferedRange() - * @sa smaxGetBufferSize() - */ -int smaxBufferData(const char *table, const char *key, int count, int size) { - Buffer *p; - - if(!key) return X_NAME_INVALID; - if(count <= 0) return X_SIZE_INVALID; - if(size <= 0) return X_SIZE_INVALID; - - // Check to make sure SMA-X has numerical data under the specified table/key - switch(smaxPullTypeDimension(table, key, NULL, NULL)) { - case X_BYTE: - case X_SHORT: - case X_INT: - case X_LONG: - case X_FLOAT: - case X_DOUBLE: break; - default: return X_TYPE_INVALID; - } - - pthread_mutex_lock(&mutex); - - // check existing buffer and update as needed. - p = FindBufferAsync(table, key); - - if(!p) { - p = (Buffer *) calloc(1, sizeof(Buffer)); - if(!p) { - pthread_mutex_unlock(&mutex); - return X_FAILURE; - } - - pthread_mutex_init(&p->mutex, NULL); - pthread_mutex_lock(&p->mutex); - - AddBufferAsync(p); - pthread_mutex_unlock(&mutex); - - p->table = xStringCopyOf(table); - p->key = xStringCopyOf(key); - p->entries = (Entry *) calloc(size, sizeof(Entry)); - } - else if(size > p->size) { - pthread_mutex_lock(&p->mutex); - pthread_mutex_unlock(&mutex); - - p->entries = (Entry *) realloc(p->entries, size * sizeof(Entry)); - if(count > p->count) ClearBufferAsync(p); // ... If count increases wipe buffer - } - - if(!p->entries) { - p->size = 0; - pthread_mutex_unlock(&p->mutex); - return X_FAILURE; - } - - p->size = size; - p->count = count; - pthread_mutex_unlock(&p->mutex); - - return p->id; -} - - -/** - * Stops local buffering of SMA-X data, flushing all buffered data and discarding all buffer - * resources. - * - * @return X_SUCCESS (0). - * - * @sa smaxBufferData() - */ -int smaxEndBuffers() { - int n; - Buffer **list; - - smaxRemoveSubscribers(ProcessUpdate); - - pthread_mutex_lock(&mutex); - list = buffers; - n = firstBufferID = nBuffers; - - buffers = NULL; - nBuffers = capacity = 0; - - memset(lookup, 0, SMAX_LOOKUP_SIZE * sizeof(Buffer *)); - - pthread_mutex_unlock(&mutex); - - while(--n >= 0) DestroyBuffer(list[n]); - if(list) free(list); - - return X_SUCCESS; -} - -/** - * Flushes all existing data from the specified data buffer. - * - * @param id Buffer ID, as returned by smaxBufferData(). - * @return X_SUCCESS (0) if successful or else X_NAME_INVALID if there is no buffer currently with the specified ID. - * - * @sa smaxBufferData() - */ -int smaxFlushBuffer(int id) { - Buffer *p; - - pthread_mutex_lock(&mutex); - p = GetBufferAsync(id); - if(p) pthread_mutex_lock(&p->mutex); - pthread_mutex_unlock(&mutex); - - if(!p) return X_NAME_INVALID; - - ClearBufferAsync(p); - pthread_mutex_unlock(&p->mutex); - - return X_SUCCESS; -} - -/** - * Gets (linear) interpolated data from a specific local data buffer, for a specific time. - * - * @param[in] id Buffer ID, as returned by smaxBufferData(). - * @param[in] t (s) UNIX time value for which we want interpolated data. - * @param[out] data Array of doubles to hold the interpolkated values. The data should - * be sized to hold the same number of doubles as was specified when - * starting the data buffer with smax_buffer_data(). - * - * @return X_SUCCESS (0) if successful or else an error code, such as: - * X_NAME_INVALID if there is no buffer currently with the specified ID, - * X_INCOMPLETE if the time is not bracketed by existing data. - */ -int smaxGetInterpolated(int id, double t, double *data) { - Buffer *p; - int s; - - pthread_mutex_lock(&mutex); - p = GetBufferAsync(id); - if(p) pthread_mutex_lock(&p->mutex); - pthread_mutex_unlock(&mutex); - - if(!p) return X_NAME_INVALID; - - s = GetInterpolatedAsync(p, t, data); - pthread_mutex_unlock(&p->mutex); - - return s; -} - -/** - * Calculates the sum of data from a specific local data buffer, for a specific time window. - * - * @param[in] id Buffer ID, as returned by smaxBufferData(). - * @param[in] fromt (s) UNIX time for beginning of time window. - * @param[in] tot (s) UNIX time for end of time window. - * @param[out] sum Array of doubles to hold the sums. The array should - * be sized to hold the same number of doubles as was specified when - * starting the data buffer with smax_buffer_data(). - * @param[out] n Pointer to integer to hold the number of points summed, ot NULL if - * not requested. - * - * @return X_SUCCESS (0) if successful or else an error code, such as: - * X_NAME_INVALID if there is no buffer currently with the specified ID, - * X_INCOMPLETE if the time window is not bracketed by existing data. - */ -int smaxGetWindowSum(int id, double fromt, double tot, double *data, int *n) { - Buffer *p; - int s, fromi, toi; - - pthread_mutex_lock(&mutex); - p = GetBufferAsync(id); - if(p) pthread_mutex_lock(&p->mutex); - pthread_mutex_unlock(&mutex); - - if(!p) return X_NAME_INVALID; - - s = GetIndexRangeAsync(p, fromt, tot, &fromi, &toi); - GetSumAsync(p, fromi, toi, data); - pthread_mutex_unlock(&p->mutex); - - return s; -} - -/** - * Calculates the averages of data from a specific local data buffer, for a specific time window. - * - * @param[in] id Buffer ID, as returned by smaxBufferData(). - * @param[in] fromt (s) UNIX time for beginning of time window. - * @param[in] tot (s) UNIX time for end of time window. - * @param[out] mean Array of doubles to hold the averages values. The array should - * be sized to hold the same number of doubles as was specified when - * starting the data buffer with smax_buffer_data(). - * @param[out] rms Optional array of doubles to hold the rms values, or NULL if RMS - * is not requested. The array should be sized to hold the same number - * of doubles as was specified when starting the data buffer with - * smax_buffer_data(). - * - * @return X_SUCCESS (0) if successful or else an error code, such as: - * X_NAME_INVALID if there is no buffer currently with the specified ID, - * X_INCOMPLETE if the time window is not bracketed by existing data. - */ -int smaxGetWindowAverage(int id, double fromt, double tot, double *mean, double *rms) { - Buffer *p; - int s, fromi, toi; - - pthread_mutex_lock(&mutex); - p = GetBufferAsync(id); - if(p) pthread_mutex_lock(&p->mutex); - pthread_mutex_unlock(&mutex); - - if(!p) return X_NAME_INVALID; - - s = GetIndexRangeAsync(p, fromt, tot, &fromi, &toi); - GetAverageAsync(p, fromi, toi, mean, rms); - pthread_mutex_unlock(&p->mutex); - - return s; -} - - -/** - * Determines the range of data from a specific local data buffer, for a specific time window. - * - * @param[in] id Buffer ID, as returned by smaxBufferData(). - * @param[in] fromt (s) UNIX time for beginning of time window. - * @param[in] tot (s) UNIX time for end of time window. - * @param[out] min Optional array of doubles to hold the minimum values, or NULL if minima - * are not requested. The array should be sized to hold the same number - * of doubles as was specified when starting the data buffer with - * smax_buffer_data(). - * @param[out] max Optional array of doubles to hold the maximum values, or NULL if maxima - * are not requested. The array should be sized to hold the same number - * of doubles as was specified when starting the data buffer with - * smax_buffer_data(). - * - * @return X_SUCCESS (0) if successful or else an error code, such as: - * X_NAME_INVALID if there is no buffer currently with the specified ID, - * X_INCOMPLETE if the time window is not bracketed by existing data. - */ -int smaxGetWindowRange(int id, double fromt, double tot, double *min, double *max) { - Buffer *p; - int s, fromi, toi; - - pthread_mutex_lock(&mutex); - p = GetBufferAsync(id); - if(p) pthread_mutex_lock(&p->mutex); - pthread_mutex_unlock(&mutex); - - if(!p) return X_NAME_INVALID; - - s = GetIndexRangeAsync(p, fromt, tot, &fromi, &toi); - GetRangeAsync(p, fromi, toi, min, max); - pthread_mutex_unlock(&p->mutex); - - return s; -} - - -/** - * Gets the time range of data currently available in a specific local data buffer. - * - * @param[in] id Buffer ID, as returned by smaxBufferData(). - * @param[out] fromt (s) UNIX time for earliest buffered data. - * @param[out] tot (s) UNIX time for last buffered data. - * - * @return X_SUCCESS (0) if successful or else an error code, such as: - * X_NAME_INVALID if there is no buffer currently with the specified ID, - */ -int smaxGetBufferedTimeRange(int id, double *fromt, double *tot) { - Buffer *p; - - pthread_mutex_lock(&mutex); - p = GetBufferAsync(id); - if(p) pthread_mutex_lock(&p->mutex); - pthread_mutex_unlock(&mutex); - - if(!p) return X_NAME_INVALID; - - if(!p->n) *fromt = *tot = NAN; - else { - *fromt = p->entries[p->firstIndex].t; - *tot = p->entries[(p->firstIndex + p->n - 1) % p->size].t; - } - pthread_mutex_unlock(&p->mutex); - - return X_SUCCESS; -} - - -/** - * Gets the number of data entries currently available in a specific local buffer. - * - * @param[in] id Buffer ID, as returned by smaxBufferData(). - * - * @return The number of buffered entries (>=0) or else - * X_NAME_INVALID if there is no buffer currently with the specified ID. - */ -int smaxGetBufferSize(int id) { - Buffer *p; - int n = 0; - - pthread_mutex_lock(&mutex); - p = GetBufferAsync(id); - if(p) pthread_mutex_lock(&p->mutex); - pthread_mutex_unlock(&mutex); - - if(!p) return X_NAME_INVALID; - - n = p->n; - - pthread_mutex_unlock(&p->mutex); - - return n; -} diff --git a/src/smax-easy.c b/src/smax-easy.c index 8add790..486ea99 100644 --- a/src/smax-easy.c +++ b/src/smax-easy.c @@ -16,13 +16,11 @@ #include #include -#include "redisx.h" -#include "smax.h" +#include "smax-private.h" // Local prototypes ------------------------------------> static int WaitOn(const char *table, const char *key, int timeout, ...); - /** * Returns a dynamically allocated buffer with the raw string value stored in SMA-X. * This call can also be used to get single string values from SMA-X, since for single @@ -40,13 +38,21 @@ static int WaitOn(const char *table, const char *key, int timeout, ...); * */ char *smaxPullRaw(const char *table, const char *key, XMeta *meta, int *status) { - char *ptr; + static const char *fn = "smaxPullRaw"; + + char *ptr = NULL; + + if(!status) { + x_error(0, EINVAL, fn, "output 'status' parameter is NULL"); + return NULL; + } + *status = smaxPull(table, key, X_RAW, 1, &ptr, meta); - if(*status) smaxError("smaxPullRaw()", *status); + if(*status) x_trace_null(fn, NULL); + return ptr; } - /** * Returns a dynamically allocated XStrucure for the specified hashtable in SMA-X. * @@ -60,22 +66,23 @@ char *smaxPullRaw(const char *table, const char *key, XMeta *meta, int *status) * @sa xDestroyStruct() */ XStructure *smaxPullStruct(const char *id, XMeta *meta, int *status) { - static const char *funcName = "smaxPullStruct()"; + static const char *fn = "smaxPullStruct"; XStructure *s; - if(id == NULL) { - *status = smaxError(funcName, X_NAME_INVALID); + if(!status) { + x_error(0, EINVAL, fn, "output 'status' parameter is NULL"); return NULL; } s = (XStructure *) calloc(1, sizeof(XStructure)); + x_check_alloc(s); + *status = smaxPull(id, NULL, X_STRUCT, 1, s, meta); - if(*status) smaxError(funcName, *status); + if(*status) x_trace_null(fn, NULL); return s; } - /** * Returns a dynamically allocated buffer with the values stored in SMA-X cast to the specified type. * @@ -89,55 +96,58 @@ XStructure *smaxPullStruct(const char *id, XMeta *meta, int *status) { * */ static void *smaxPullDynamic(const char *table, const char *key, XType type, XMeta *meta, int *n) { - static const char *funcName = "smaxPullType()"; + static const char *fn = "smaxPullType"; int eSize, pos; char *raw; XMeta m = X_META_INIT; void *array; - eSize = xElementSizeOf(type); - if(eSize < 1) { - smaxError(funcName, X_TYPE_INVALID); + if(!n) { + x_error(0, EINVAL, fn, "output parameter 'n' is NULL"); return NULL; } - *n = smaxPull(table, key, X_RAW, 1, &raw, &m); - if(raw == NULL) { - if(!*n) *n = X_NULL; + eSize = xElementSizeOf(type); + if(eSize < 1) { + *n = x_error(X_TYPE_INVALID, EINVAL, fn, "invalid type: %d", type); return NULL; } + *n = smaxPull(table, key, X_RAW, 1, &raw, &m); if(*n) { - free(raw); - smaxError(funcName, *n); - return NULL; + if(raw) free(raw); + return x_trace_null(fn, NULL); } + if(!raw) return NULL; if(meta != NULL) *meta = m; *n = smaxGetMetaCount(&m); if(*n < 1) { free(raw); + x_error(0, ERANGE, fn, "invalid store count: %d", *n); return NULL; } array = calloc(*n, eSize); + if(!array) { + *n = x_error(0, errno, fn, "calloc() error (%d x %d)", *n, eSize); + free(raw); + return NULL; + } *n = smaxStringToValues(raw, array, type, *n, &pos); - free(raw); if(*n < 0) { free(array); - smaxError(funcName, *n); - return NULL; + return x_trace_null(fn, NULL); } return array; } - /** * Returns a dynamically allocated array of integers stored in an SMA-X variable. * @@ -153,7 +163,11 @@ static void *smaxPullDynamic(const char *table, const char *key, XType type, XMe * @sa smaxPullInt() */ int *smaxPullInts(const char *table, const char *key, XMeta *meta, int *n) { - return (int *) smaxPullDynamic(table, key, X_INT, meta, n); + int stat; + int *ptr = (int *) smaxPullDynamic(table, key, X_INT, meta, &stat); + if(n) *n = stat; + if(stat < 0) x_trace_null("smaxPullInts", NULL); + return ptr; } /** @@ -172,10 +186,13 @@ int *smaxPullInts(const char *table, const char *key, XMeta *meta, int *n) { * */ long long *smaxPullLongs(const char *table, const char *key, XMeta *meta, int *n) { - return (long long *) smaxPullDynamic(table, key, X_INT, meta, n); + int stat; + long long *ptr = (long long *) smaxPullDynamic(table, key, X_INT, meta, &stat); + if(n) *n = stat; + if(stat < 0) x_trace_null("smaxPullLongs", NULL); + return ptr; } - /** * Returns a dynamically allocated array of doubles stored in an SMA-X variable. * @@ -190,7 +207,11 @@ long long *smaxPullLongs(const char *table, const char *key, XMeta *meta, int *n * @sa smaxPullFloats() */ double *smaxPullDoubles(const char *table, const char *key, XMeta *meta, int *n) { - return (double *) smaxPullDynamic(table, key, X_DOUBLE, meta, n); + int stat; + double *ptr = (double *) smaxPullDynamic(table, key, X_DOUBLE, meta, &stat); + if(n) *n = stat; + if(stat < 0) x_trace_null("smaxPullDoubles", NULL); + return ptr; } /** @@ -210,9 +231,9 @@ char *smaxPullString(const char *table, const char *key) { int status; status = smaxPull(table, key, X_STRING, 1, &str, NULL); - if(status) { + if(status < 0) { if(str) free(str); - return NULL; + return x_trace_null("smaxPullString", NULL); } return str; @@ -243,27 +264,41 @@ char *smaxPullString(const char *table, const char *key) { * @sa smaxPullRaw() */ char **smaxPullStrings(const char *table, const char *key, XMeta *meta, int *n) { + static const char *fn = "smaxPullStrings"; + int i, offset = 0; char *str; XMeta m = X_META_INIT; char **array; + if(!n) { + x_error(0, EINVAL, fn, "output parameter 'n' is NULL"); + return NULL; + } + str = smaxPullRaw(table, key, &m, n); - if(str == NULL) return NULL; if(*n < 0) { free(str); - smaxError("smaxPullStrings()", *n); - return NULL; + return x_trace_null(fn, NULL); } + if(str == NULL) return NULL; + if(meta != NULL) *meta = m; *n = smaxGetMetaCount(&m); - if(*n < 1) return NULL; + if(*n < 1) { + x_error(0, ERANGE, fn, "invalid store count: %d", *n); + return NULL; + } array = (char **) calloc(*n, sizeof(char *)); + if(!array) { + x_error(0, errno, fn, "calloc() error (%d char*)", *n); + return NULL; + } - for(i=0; i<(*n); i++) { + for(i = 0; i < (*n); i++) { array[i] = &str[offset]; offset += strlen(array[i]) + 1; if(offset > m.storeBytes) break; @@ -292,7 +327,6 @@ int smaxPullInt(const char *table, const char *key, int defaultValue) { return status ? defaultValue : i; } - /** * Returns a single integer value for a given SMA-X variable, or a default value if the * value could not be retrieved. @@ -329,7 +363,6 @@ double smaxPullDouble(const char *table, const char *key) { return smaxPullDoubleDefault(table, key, NAN); } - /** * Returns a single floating-point value for a given SMA-X variable, or a specified * default value if the SMA-X value could not be retrieved. @@ -349,8 +382,6 @@ double smaxPullDoubleDefault(const char *table, const char *key, double defaultV return status ? defaultValue : d; } - - /** * Shares a single integer value to SMA-X. * @@ -358,16 +389,16 @@ double smaxPullDoubleDefault(const char *table, const char *key, double defaultV * \param key Variable name under which the data is stored. * \param value Integer value. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS (0), or else an appropriate error code (<0) from smaxShare(). * * \sa smaxShareHex() * @sa smaxShareInts() */ int smaxShareInt(const char *table, const char *key, long long value) { - return smaxShareLongs(table, key, &value, 1); + prop_error("smaxShareInt", smaxShareLongs(table, key, &value, 1)); + return X_SUCCESS; } - /** * Shares a single integer value to SMA-X in a hexadecimal representatin. * @@ -375,15 +406,15 @@ int smaxShareInt(const char *table, const char *key, long long value) { * \param key The variable name under which the data is stored. * \param value Integer value. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS (0), or else an appropriate error code (<0) from smaxShare(). * * \sa smaxShareInt() */ int smaxShareHex(const char *table, const char *key, long long value) { - return smaxShare(table, key, &value, X_LONG_HEX, 1); + prop_error("smaxShareHex", smaxShare(table, key, &value, X_LONG_HEX, 1)); + return X_SUCCESS; } - /** * Shares a single boolean value to SMA-X. All non-zero values are mapped * to "1". @@ -392,12 +423,13 @@ int smaxShareHex(const char *table, const char *key, long long value) { * \param key The variable name under which the data is stored. * \param value A boolean value. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS (0), or else an appropriate error code (<0) from smaxShare(). * * \sa smaxShareBooleans() */ int smaxShareBoolean(const char *table, const char *key, boolean value) { - return smaxShareBooleans(table, key, &value, 1); + prop_error("smaxShareBoolean", smaxShareBooleans(table, key, &value, 1)); + return X_SUCCESS; } /** @@ -407,13 +439,14 @@ int smaxShareBoolean(const char *table, const char *key, boolean value) { * \param key The variable name under which the data is stored. * \param value floating-point value. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS (0), or else an appropriate error code (<0) from smaxShare(). * * @sa smaxShareDoubles() * @sa smaxShareFloats() */ int smaxShareDouble(const char *table, const char *key, double value) { - return smaxShareDoubles(table, key, &value, 1); + prop_error("smaxShareDouble", smaxShareDoubles(table, key, &value, 1)); + return X_SUCCESS; } /** @@ -423,15 +456,15 @@ int smaxShareDouble(const char *table, const char *key, double value) { * \param key The variable name under which the data is stored. * \param sValue Pointer to string. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS (0), or else an appropriate error code (<0) from smaxShare(). * * @sa smaxShareStrings() */ int smaxShareString(const char *table, const char *key, const char *sValue) { - return smaxShare(table, key, &sValue, X_RAW, 1); + prop_error("smaxShareString", smaxShare(table, key, &sValue, X_RAW, 1)); + return X_SUCCESS; } - /** * Shares a binary sequence to SMA-X. * @@ -440,7 +473,7 @@ int smaxShareString(const char *table, const char *key, const char *sValue) { * \param values pointer to the byte buffer. * \param n Number of bytes in buffer to share. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS (0), or else an appropriate error code (<0) from smaxShare(). * * @sa smaxShareShorts() * @sa smaxShareInts() @@ -448,7 +481,8 @@ int smaxShareString(const char *table, const char *key, const char *sValue) { * @sa smaxShareInt() */ int smaxShareBytes(const char *table, const char *key, const char *values, int n) { - return smaxShare(table, key, values, X_BYTE, n); + prop_error("smaxShareBytes", smaxShare(table, key, values, X_BYTE, n)); + return X_SUCCESS; } /** @@ -459,7 +493,7 @@ int smaxShareBytes(const char *table, const char *key, const char *values, int n * \param values Pointer to short[] array. * \param n Number of elements in array to share. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS(0), or else an appropriate error code (<0) from smaxShare(). * * @sa smaxShareInt() * @sa smaxShareBytes() @@ -468,10 +502,10 @@ int smaxShareBytes(const char *table, const char *key, const char *values, int n * */ int smaxShareShorts(const char *table, const char *key, const short *values, int n) { - return smaxShare(table, key, values, X_SHORT, n); + prop_error("smaxShareShorts", smaxShare(table, key, values, X_SHORT, n)); + return X_SUCCESS; } - /** * Shares an array of wide integers to SMA-X. * @@ -480,7 +514,7 @@ int smaxShareShorts(const char *table, const char *key, const short *values, int * \param values Pointer to long long[] array. * \param n Number of elements in array to share. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS (0), or else an appropriate error code (<0) from smaxShare(). * * @sa smaxShareInts() * @sa smaxShareShorts() @@ -488,10 +522,10 @@ int smaxShareShorts(const char *table, const char *key, const short *values, int * @sa smaxShareInt() */ int smaxShareLongs(const char *table, const char *key, const long long *values, int n) { - return smaxShare(table, key, values, X_LONG, n); + prop_error("smaxShareLongs", smaxShare(table, key, values, X_LONG, n)); + return X_SUCCESS; } - /** * Shares an array of long integers to SMA-X. * @@ -500,7 +534,7 @@ int smaxShareLongs(const char *table, const char *key, const long long *values, * \param values Pointer to int[] array. * \param n Number of elements in array to share. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS (0), or else an appropriate error code (<0) from smaxShare(). * * @sa smaxShareLongs() * @sa smaxShareShorts() @@ -508,11 +542,10 @@ int smaxShareLongs(const char *table, const char *key, const long long *values, * @sa smaxShareInt() */ int smaxShareInts(const char *table, const char *key, const int *values, int n) { - return smaxShare(table, key, values, X_INT, n); + prop_error("smaxShareInts", smaxShare(table, key, values, X_INT, n)); + return X_SUCCESS; } - - /** * Shares an array of boolean values to SMA-X. All non-zero values are mapped * to "1". @@ -522,15 +555,15 @@ int smaxShareInts(const char *table, const char *key, const int *values, int n) * \param values Pointer to boolean[] array. * \param n Number of elements in array to share. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS (0), or else an appropriate error code (<0) from smaxShare(). * * @sa smaxShareBoolean() */ int smaxShareBooleans(const char *table, const char *key, const boolean *values, int n) { - return smaxShare(table, key, values, X_BOOLEAN, n); + prop_error("smaxShareBooleans", smaxShare(table, key, values, X_BOOLEAN, n)); + return X_SUCCESS; } - /** * Shares an array of floats to SMA-X. * @@ -539,13 +572,14 @@ int smaxShareBooleans(const char *table, const char *key, const boolean *values, * \param values Pointer to float[] array. * \param n Number of elements in array to share. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS (0), or else an appropriate error code (<0) from smaxShare(). * * @sa smaxShareDouble() * @sa smaxShareDoubles() */ int smaxShareFloats(const char *table, const char *key, const float *values, int n) { - return smaxShare(table, key, values, X_FLOAT, n); + prop_error("smaxShareFloats", smaxShare(table, key, values, X_FLOAT, n)); + return X_SUCCESS; } /** @@ -556,13 +590,14 @@ int smaxShareFloats(const char *table, const char *key, const float *values, int * \param values Pointer to double[] array. * \param n Number of elements in array to share. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS (0), or else an appropriate error code (<0) from smaxShare(). * * @sa smaxShareDouble() * @sa smaxShareFloats() */ int smaxShareDoubles(const char *table, const char *key, const double *values, int n) { - return smaxShare(table, key, values, X_DOUBLE, n); + prop_error("smaxShareDoubles", smaxShare(table, key, values, X_DOUBLE, n)); + return X_SUCCESS; } /** @@ -573,17 +608,20 @@ int smaxShareDoubles(const char *table, const char *key, const double *values, i * \param sValues Pointer to array of string pointers. * \param n Number of elements in array to share. * - * \return X_SUCCESS, or else an appropriate error code from smaxShare(). + * \return X_SUCCESS (0), or else an appropriate error code (<0) from smaxShare(). * * @sa smaxShareString() */ int smaxShareStrings(const char *table, const char *key, const char **sValues, int n) { + static const char *fn = "smaxShareStrings"; + char *buf; int i, *l, L = 0; - if(sValues == NULL) return X_NULL; + if(sValues == NULL) return x_error(X_NULL, EINVAL, fn, "input 'sValues' is NULL"); l = (int *) calloc(n, sizeof(int)); + if(!l) return x_error(X_NULL, errno, fn, "calloc() error (%d int)", n); for(i=0; ivalue, NULL); + if(b < 0) return defaultValue; + return b; +} + +/** + * Returns the first value in a structure's field as an integer, or the specified default + * value if there is no such field in the structure, or the content cannot be parse into an integer. + * + * @param s Pointer to the XStructure. + * @param name Field name + * @param defaultValue Value to return if no corresponding integer field value. + * @return The (first) field value as a long long, or the default value if there is no such field. + * + * @sa xGetField() + */ +long long smaxGetLongField(const XStructure *s, const char *name, long long defaultValue) { + int i; + char *end; + const XField *f = xGetField(s, name); + + if(!f) return defaultValue; + + i = strtol(f->value, &end, 0); + if(end == f->value) return defaultValue; + if(errno == ERANGE) return defaultValue; + return i; +} + +/** + * Returns the first value in a structure's field as a double precision float, or the specified + * default value if there is no such field in the structure, or the content cannot be parse into an double. + * + * @param s Pointer to the XStructure. + * @param name Field name + * @param defaultValue Value to return if no corresponding integer field value. + * @return The (first) field value as a double, or the specified default if there is no such field. + * + * @sa xGetField() + */ +double smaxGetDoubleField(const XStructure *s, const char *name, double defaultValue) { + double d; + char *end; + const XField *f = xGetField(s, name); + + if(!f) return defaultValue; + + d = strtod(f->value, &end); + if(end == f->value) return defaultValue; + if(errno == ERANGE) return defaultValue; + return d; +} + +/** + * Returns the string value in a structure's field, or the specified default value if there is no + * such field in the structure. + * + * @param s Pointer to the XStructure. + * @param name Field name + * @param defaultValue Value to return if no corresponding integer field value. + * @return The field's string (raw) value, or the specified default if there is no such field. + * + * @sa xGetField() + */ +char *smaxGetRawField(const XStructure *s, const char *name, char *defaultValue) { + const XField *f = xGetField(s, name); + if(!f) return defaultValue; + return f->value; +} + /** * Gets the data of an SMA-X structure field as an array of values of the specified type and element count. * The field's data will be truncated or padded with zeroes to provide the requested element count always. * * @param s Pointer to SMA-X structure * @param name Field name - * @param dst Array to return values in. + * @param[out] dst Array to return values in. * @param type Type of data. * @param count Number of elements in return array. The field data will be truncated or padded as necessary. * @return X_SUCCESS (0) if successful, or @@ -730,22 +869,25 @@ XField *smaxCreateStringField(const char *name, const char *value) { * or else an error returned by smaxStringtoValues(). */ int smaxGetArrayField(const XStructure *s, const char *name, void *dst, XType type, int count) { - int i, pos; - const XField *f = xGetField(s, name); + static const char *fn = "smaxGetArrayField"; + + int pos; + const XField *f; - if(!s) return X_STRUCT_INVALID; - if(!dst) return X_NULL; - if(count < 1) return X_SIZE_INVALID; + if(!s) return x_error(X_STRUCT_INVALID, EINVAL, fn, "input structure is NULL"); + if(!name) return x_error(X_NAME_INVALID, EINVAL, fn, "field name is NULL"); + if(!name[0]) return x_error(X_NAME_INVALID, EINVAL, fn, "field name is empty"); + if(!dst) return x_error(X_NULL, EINVAL, fn, "output 'dst' buffer is NULL"); + if(count < 1) return x_error(X_SIZE_INVALID, EINVAL, fn, "invalid count: %d", count); + f = xGetField(s, name); if(!f) return X_NAME_INVALID; - i = smaxStringToValues(f->value, dst, type, count, &pos); - if(i != X_SUCCESS) return i; + prop_error(fn, smaxStringToValues(f->value, dst, type, count, &pos)); return X_SUCCESS; } - /** * Waits for a specific pushed entry. There must be an active subscription that includes the specified * group & variable, or else the call will block indefinitely. @@ -768,12 +910,16 @@ int smaxGetArrayField(const XStructure *s, const char *name, void *dst, XType ty * @sa smaxReleaseWaits() */ int smaxWaitOnSubscribed(const char *table, const char *key, int timeout) { - if(table == NULL) return X_GROUP_INVALID; - if(key == NULL) return X_NAME_INVALID; - return WaitOn(table, key, timeout); -} + static const char *fn = "smaxWaitOnSubscribed"; + if(table == NULL) return x_error(X_GROUP_INVALID, EINVAL, fn, "table is NULL"); + if(!table[0]) return x_error(X_GROUP_INVALID, EINVAL, fn, "table is empty"); + if(key == NULL) return x_error(X_NAME_INVALID, EINVAL, fn, "key is NULL"); + if(!key[0]) return x_error(X_NAME_INVALID, EINVAL, fn, "key is empty"); + prop_error(fn, WaitOn(table, key, timeout)); + return X_SUCCESS; +} /** * Waits for changes on a specific group. The must be an active subscription including that group, or else the @@ -787,7 +933,7 @@ int smaxWaitOnSubscribed(const char *table, const char *key, int timeout) { * * \return X_SUCCESS (0) if a variable was updated on the host. * X_NO_INIT if the SMA-X sharing was not initialized via smaxConnect(). - * X_HOST_INVALID if the host (owner ID) is NULL. + * X_GROUP_INVALID if the table name to match is invalid. * X_REL_PREMATURE if smaxReleaseWaits() was called. * * \sa smaxSubscribe() @@ -797,10 +943,14 @@ int smaxWaitOnSubscribed(const char *table, const char *key, int timeout) { * @sa smaxReleaseWaits() */ int smaxWaitOnSubscribedGroup(const char *matchTable, char **changedKey, int timeout) { - if(matchTable == NULL) return X_GROUP_INVALID; - return WaitOn(matchTable, NULL, timeout, changedKey); -} + static const char *fn = "smaxWaitOnSubscrivedGroup"; + + if(matchTable == NULL) return x_error(X_GROUP_INVALID, EINVAL, fn, "matchTable parameter is NULL"); + if(!matchTable[0]) return x_error(X_GROUP_INVALID, EINVAL, fn, "matchTable parameter is empty"); + prop_error(fn, WaitOn(matchTable, NULL, timeout, changedKey)); + return X_SUCCESS; +} /** * Waits for a specific pushed variable from any group/table. There must be an active subscription that includes the specified @@ -824,26 +974,29 @@ int smaxWaitOnSubscribedGroup(const char *matchTable, char **changedKey, int tim * @sa smaxReleaseWaits() */ int smaxWaitOnSubscribedVar(const char *matchKey, char **changedTable, int timeout) { - if(matchKey == NULL) return X_NAME_INVALID; - return WaitOn(NULL, matchKey, timeout, changedTable); -} + static const char *fn = "smaxWaitOnSubscribedVar"; + if(matchKey == NULL) return x_error(X_NAME_INVALID, EINVAL, fn, "matchKey parameter is NULL"); + if(!matchKey[0]) return x_error(X_NAME_INVALID, EINVAL, fn, "matchKey parameter is empty"); + prop_error(fn, WaitOn(NULL, matchKey, timeout, changedTable)); + return X_SUCCESS; +} /** * Waits for an update from the specified SMA-X table (optional) and/or specified variable (optional). For example: * \code - * sma_wait_on("myTable", "myVar"); + * smax_wait_on("myTable", "myVar"); * \endcode * will wait until "myVar" is changed in "myTable". * \code * char *fromTable; - * sma_wait_on(NULL, "myVar", &fromTable); + * smax_wait_on(NULL, "myVar", &fromTable); * \endcode * will wait until "myVar" is published to any SMA-X table. The triggering table name will be stored in the supplied 3rd argument. * \code * char *changedKey; - * sma_wait_on("myTable", NULL, &changedKey); + * smax_wait_on("myTable", NULL, &changedKey); * \endcode * will wait until any field is changed in "myTable". The triggering variable name will be store in the supplied 3rd argument. * @@ -860,7 +1013,7 @@ int smaxWaitOnSubscribedVar(const char *matchKey, char **changedTable, int timeo * @sa smaxReleaseWaits() */ static int WaitOn(const char *table, const char *key, int timeout, ...) { - const static char *funcName = "WaitOn"; + static const char *fn = "WaitOn"; char *gotTable, *gotKey; va_list args; @@ -873,19 +1026,19 @@ static int WaitOn(const char *table, const char *key, int timeout, ...) { status = smaxWaitOnAnySubscribed(&gotTable, &gotKey, timeout); if(status) { va_end(args); - return status; + return x_trace(fn, NULL, status); } if(table != NULL) { if(!gotTable) { - smaxError(funcName, X_NULL); + xdprintf("WARNING! %s: got NULL table.\n", fn); continue; } if(strcmp(gotTable, table)) continue; } if(key != NULL) { if(!gotKey) { - smaxError(funcName, X_NULL); + xdprintf("WARNING! %s: got NULL key.\n", fn); continue; } if(strcmp(gotKey, key)) continue; @@ -905,136 +1058,3 @@ static int WaitOn(const char *table, const char *key, int timeout, ...) { return X_SUCCESS; } } - - - - -/** - * Returns the first value in a structure's field as an integer, or the specified default - * value if there is no such fiield in the structure, or the content cannot be parse into an integer. - * - * @param s Pointer to the XStructure. - * @param name Field name - * @param defaultValue Value to return if no corresponding integer field value. - * @return The (first) field value as a long long, or the default value if there is no such field. - * - * @sa xGetField() - */ -long long smaxGetLongField(const XStructure *s, const char *name, long long defaultValue) { - int i; - char *end; - const XField *f = xGetField(s, name); - - if(!f) return defaultValue; - - i = strtol(f->value, &end, 0); - if(end == f->value) return defaultValue; - if(errno == ERANGE) return defaultValue; - return i; -} - - -/** - * Returns the first value in a structure's field as a double precision float, or the specified - * default value if there is no such fiield in the structure, or the content cannot be parse into an double. - * - * @param s Pointer to the XStructure. - * @param name Field name - * @param defaultValue Value to return if no corresponding integer field value. - * @return The (first) field value as a double, or the specified default if there is no such field. - * - * @sa xGetField - */ -double smaxGetDoubleField(const XStructure *s, const char *name, double defaultValue) { - double d; - char *end; - const XField *f = xGetField(s, name); - - if(!f) return defaultValue; - - d = strtod(f->value, &end); - if(end == f->value) return defaultValue; - if(errno == ERANGE) return defaultValue; - return d; -} - - -/** - * Returns the string value in a structure's field, or the specified default value if there is no - * such fiield in the structure. - * - * @param s Pointer to the XStructure. - * @param name Field name - * @param defaultValue Value to return if no corresponding integer field value. - * @return The field's string (raw) value, or the specified default if there is no such field. - * - * @sa xGetField() - */ -char *smaxGetRawField(const XStructure *s, const char *name, char *defaultValue) { - const XField *f = xGetField(s, name); - if(!f) return defaultValue; - return f->value; -} - - -/** - * Sets the a field in a structure to a scalar (single) value. If there was a prior field that is - * replaced, then the pointer to the prior field is returned. - * - * @param s Pointer to the XStructure. - * @param name Field name - * @param type Data type, such as X_INT (see xchange.h). - * @param value Value to return if no corresponding integer field value. - * - * @return Pointer to the prior XField by or NULL if new or there was an error. - * - * @sa smaxSet1DField() - * @sa smaxCreateField() - * @sa xSetField() - * - */ -XField *smaxSetScalarField(XStructure *s, const char *name, XType type, const void *value) { - XField *f; - - if(!s) return NULL; - if(!name) return NULL; - if(!name[0]) return NULL; - - f = smaxCreateField(name, type, 0, NULL, value); - if(!f) return NULL; - - return xSetField(s, f); -} - - -/** - * Sets the a field in a structure to the contents of a 1D array. If there was a prior field that is - * replaced, then the pointer to the prior field is returned. - * - * @param s Pointer to the XStructure. - * @param name Field name - * @param type Data type, such as X_INT (see xchange.h). - * @param n Number of elements - * @param value Value to return if no corresponding integer field value. - * - * @return Pointer to the prior XField by or NULL if new or there was an error. - * - * @sa smaxSetScalarField() - * @sa smaxCreateField() - * @sa xSetField() - * - */ -XField *smaxSet1DField(XStructure *s, const char *name, XType type, int n, const void *value) { - XField *f; - - if(!s) return NULL; - if(!name) return NULL; - if(!name[0]) return NULL; - - f = smaxCreateField(name, type, 1, &n, value); - if(!f) return NULL; - - return xSetField(s, f); -} - - diff --git a/src/smax-lazy.c b/src/smax-lazy.c index 43b89be..1fca83a 100644 --- a/src/smax-lazy.c +++ b/src/smax-lazy.c @@ -21,10 +21,10 @@ #include #include -#include "redisx.h" -#include "smax.h" #include "smax-private.h" +/// \cond PRIVATE + #define MAX_UNPULLED_LAZY_UPDATES 10 ///< Number of unprocessed updates, before unsubscribing from notifications... typedef struct LazyMonitor { @@ -44,6 +44,7 @@ typedef struct LazyMonitor { struct LazyMonitor *next; } LazyMonitor; +/// \endcond static int nMonitors; ///< Number of lazy variables monitored. @@ -51,15 +52,13 @@ static LazyMonitor *monitorTable[SMAX_LOOKUP_SIZE]; ///< hashed moni static pthread_mutex_t monitorLock = PTHREAD_MUTEX_INITIALIZER; ///< Mutex for accessing monitor tables static pthread_mutex_t dataLock = PTHREAD_MUTEX_INITIALIZER; ///< mutex for accessing monitor data/metadata - static LazyMonitor *CreateMonitorAsync(const char *table, const char *key, XType type, boolean withMeta); static boolean DestroyMonitorAsync(LazyMonitor *m); static int GetChannelLookupIndex(const char *channel); static __inline__ int GetTableIndex(const LazyMonitor *m); static LazyMonitor *GetMonitorAsync(const char *table, const char *key); static LazyMonitor *GetSpecificMonitorAsync(const char *table, const char *key); -static void ProcessLazyUpdates(const char *pattern, const char *channel, const char *msg, int length); - +static void ProcessLazyUpdates(const char *pattern, const char *channel, const char *msg, long length); /** * Decrements the number of concurrent user calls that currently need access to the specific @@ -88,6 +87,8 @@ static void ReleaseAsync(LazyMonitor *m) { * @sa ReleaseAsync() */ static int Release(LazyMonitor *m) { + if(!m) return x_error(X_NULL, EINVAL, "Release", "NULL argument"); + pthread_mutex_lock(&monitorLock); ReleaseAsync(m); pthread_mutex_unlock(&monitorLock); @@ -131,7 +132,7 @@ static void ApplyUpdateAsync(LazyMonitor *update, LazyMonitor *m) { * @param arg Pointer to the update, usually created by CreateStaging(). * */ -static void ApplyUpdate(char *arg) { +static void ApplyUpdate(void *arg) { LazyMonitor *update = (LazyMonitor *) arg; LazyMonitor *m; @@ -163,10 +164,7 @@ static LazyMonitor *CreateStaging(const LazyMonitor *m) { // If we update in the background, create temporary storage in which // we can stage the update. s = (LazyMonitor *) calloc(1, sizeof(*s)); - if(!s) { - perror("ERROR! smax-lazy::CreateStaging(): alloc"); - exit(1); - } + x_check_alloc(s); // Copy table/key s->table = xStringCopyOf(m->table); @@ -181,24 +179,25 @@ static LazyMonitor *CreateStaging(const LazyMonitor *m) { return s; } - /** * Updates the monitored data in the cache, by pulling from SMA-X. The update is essentially atomic * as it happens with a single reassignment of a pointer. * * @param m Pointer to a lazy monitor datum. - * @return X_SUCCESS (0) if successfull or else an error from smaxPull(). + * @return X_SUCCESS (0) if successfull or else an error (<0) from smaxPull(). */ static int UpdateCachedAsync(LazyMonitor *m, boolean background) { + static const char *fn = "UpdateCachedAsync"; + LazyMonitor *staging; XType type; int status = X_SUCCESS; void *ptr; - if(!m) return X_NULL; + if(!m) return x_error(X_NULL, EINVAL, fn, "input parameter 'm' is NULL"); staging = CreateStaging(m); - if(!staging) return X_NULL; + if(!staging) return x_trace(fn, NULL, X_NULL); if(m->key) { type = X_RAW; @@ -209,9 +208,9 @@ static int UpdateCachedAsync(LazyMonitor *m, boolean background) { ptr = staging->data; } - if(background && smaxIsPipelined() && 0) { + if(background && smaxIsPipelined()) { status = smaxQueue(m->table, m->key, type, 1, ptr, staging->meta); - if(!status) smaxQueueCallback(ApplyUpdate, (char *) staging); + if(!status) smaxQueueCallback(ApplyUpdate, staging); } else { status = smaxPull(m->table, m->key, type, 1, ptr, staging->meta); @@ -219,10 +218,10 @@ static int UpdateCachedAsync(LazyMonitor *m, boolean background) { DestroyMonitorAsync(staging); } - return status; + prop_error(fn, status); + return X_SUCCESS; } - /** * Returns the currently cached value of a lazy monitor point into the supplied buffer as * the requested type and element count. @@ -236,11 +235,15 @@ static int UpdateCachedAsync(LazyMonitor *m, boolean background) { * @return X_SUCCESS (0) if successfull, or else an error code from smaxStringToValues() */ static int GetCachedAsync(const LazyMonitor *m, XType type, int count, void *value) { + static const char *fn = "GetCachedAsync"; + int n, status; + if(!m) return x_error(X_NULL, EINVAL, fn, "monitor point 'm' is NULL"); + if(!m->data) { if(m->meta) return m->meta->status; - return X_NULL; + return x_error(X_NULL, EINVAL, fn, "m->data is NULL"); } switch(type) { @@ -248,45 +251,53 @@ static int GetCachedAsync(const LazyMonitor *m, XType type, int count, void *val XStructure *tmp = xCopyOfStruct((XStructure *) m->data); XStructure *dst = (XStructure *) value; - if(!m->table) return X_TYPE_INVALID; + if(!m->table) return x_error(X_TYPE_INVALID, EINVAL, fn, "m->table is NULL"); xClearStruct(dst); dst->firstField = tmp->firstField; free(tmp); break; } - case X_RAW: - if(!m->meta) return X_NULL; + case X_RAW: { + char *str; + if(!m->meta) return x_error(X_NULL, EINVAL, fn, "m->meta is NULL"); if(!m->meta->storeBytes) return X_SUCCESS; - *(char **) value = malloc(m->meta->storeBytes); - memcpy(*(char **) value, m->data, m->meta->storeBytes); + + str = (char *) malloc(m->meta->storeBytes); + if(!str) return x_error(X_NULL, errno, fn, "malloc() error (%d bytes)", m->meta->storeBytes); + + memcpy(str, m->data, m->meta->storeBytes); + *(char **) value = str; break; + } case X_STRING: - if(!m->meta) return X_NULL; - if(m->meta->storeType != X_STRING) return X_TYPE_INVALID; - xUnpackStrings(m->data, m->meta->storeBytes, count, (char **) value); + if(!m->meta) return x_error(X_NULL, EINVAL, fn, "m->meta is NULL"); + if(m->meta->storeType != X_STRING) return x_error(X_TYPE_INVALID, EINVAL, fn, "wring m->type (not X_STRING): %d", m->meta->storeType); + smaxUnpackStrings(m->data, m->meta->storeBytes, count, (char **) value); break; default: status = smaxStringToValues(m->data, value, type, count, &n); - if(status <= 0) return status; + if(status <= 0) return x_trace(fn, NULL, status); } return X_SUCCESS; } -LazyMonitor *GetCreateMonitor(const char *table, const char *key, XType type, boolean withMeta) { +static LazyMonitor *GetCreateMonitor(const char *table, const char *key, XType type, boolean withMeta) { + static const char *fn = "GetCreateMonitor"; + LazyMonitor *m; char *lazytab = (char *) table; - if(!table && !key) return NULL; - if(type == X_STRUCT) { lazytab = xGetAggregateID(table, key); key = NULL; } + if(!lazytab) return x_trace_null(fn, NULL); + pthread_mutex_lock(&monitorLock); m = GetSpecificMonitorAsync(lazytab, key); @@ -294,7 +305,7 @@ LazyMonitor *GetCreateMonitor(const char *table, const char *key, XType type, bo pthread_mutex_unlock(&monitorLock); - if(!m) return NULL; + if(!m) return x_trace_null(fn, NULL); if(withMeta && !m->meta) m->meta = smaxCreateMeta(); if(lazytab != table) free(lazytab); @@ -303,12 +314,15 @@ LazyMonitor *GetCreateMonitor(const char *table, const char *key, XType type, bo } static int FetchData(LazyMonitor *m, XType type, int count, void *value, XMeta *meta) { + static const char *fn = "FetchData"; + int status = X_SUCCESS; if(meta && !m->meta) { // Update monitor to include metadata and pull to set it. m->meta = (XMeta *) calloc(1, sizeof(XMeta)); - if(m->meta) status = UpdateCachedAsync(m, FALSE); + x_check_alloc(m->meta); + status = UpdateCachedAsync(m, FALSE); } else if(!m->isCurrent && !m->isCached) status = UpdateCachedAsync(m, FALSE); @@ -330,14 +344,32 @@ static int FetchData(LazyMonitor *m, XType type, int count, void *value, XMeta * Release(m); - return status; + prop_error(fn, status); + return X_SUCCESS; } +/** + * Specify that a specific variable should be cached for minimum overhead lazy access. When a variable is lazy cached + * its local copy is automatically updated in the background so that accessing it is always nearly instantaneous. + * Lazy caching is a good choice for variables that change less frequently than they are polled typically. For + * variables that change frequently (ans used less frequently), lazy caching is not a great choice since it consumes + * network bandwidth even when the variable is not being accessed. + * + * Once a variable is lazy cached, it can be accessed instantaneously via smaxGetLazyCached() without any blocking + * network operations. + * + * @param table The hash table name. + * @param key The variable name under which the data is stored. + * @param type The SMA-X variable type, e.g. X_FLOAT or X_CHARS(40), of the buffer. + * @return X_SUCCESS (0) or X_NO_SERVICE. + * + * @sa smaxGetLazyCached() + */ int smaxLazyCache(const char *table, const char *key, XType type) { LazyMonitor *m; m = GetCreateMonitor(table, key, type, TRUE); - if(!m) return X_NO_SERVICE; + if(!m) return x_trace("smaxLazyCache", NULL, X_NO_SERVICE); m->isCached = TRUE; UpdateCachedAsync(m, FALSE); @@ -346,21 +378,39 @@ int smaxLazyCache(const char *table, const char *key, XType type) { return X_SUCCESS; } - +/** + * Retrieve a variable from the local cache (if available), or else pull from the SMA-X database. If local caching was not + * previously eanbled, it will be enabled with this call, so that subsequent calls will always return data from the locally + * updated cache with minimal overhead and effectively no latency. + * + * @param table The hash table name. + * @param key The variable name under which the data is stored. + * @param type The SMA-X variable type, e.g. X_FLOAT or X_CHARS(40), of the buffer. + * @param count The number of elements to retrieve + * @param value Pointer to the native data buffer in which to restore values + * @param meta Optional metadata pointer, or NULL if metadata is not required. + * @return X_SUCCESS (0), or X_NO_SERVICE is SMA-X is not accessible, or another error (<0) + * from smax.h or xchange.h. + * + * @sa sa smaxLazyCache() + * @sa sa smaxLaxyPull() + */ int smaxGetLazyCached(const char *table, const char *key, XType type, int count, void *value, XMeta *meta) { + static const char *fn = "smaxGetLazyCached"; + LazyMonitor *m; int status; m = GetCreateMonitor(table, key, type, meta != NULL); - if(!m) return X_NO_SERVICE; + if(!m) return x_trace(fn, NULL, X_NO_SERVICE); status = FetchData(m, type, count, value, meta); m->isCached = TRUE; // Set after the first non-mirrored fetch... - return status; + prop_error(fn, status); + return X_SUCCESS; } - /** * Poll an infrequently changing variable without stressing out the network * or the SMA-X database. The first lazy pull for a variable will fetch its value from SMA-X and @@ -378,7 +428,7 @@ int smaxGetLazyCached(const char *table, const char *key, XType type, int count, * \param value Pointer to the buffer to which the data is to be retrieved. * \param meta Pointer to metadata or NULL if no metadata is needed. * - * \return X_SUCCESS (0) on success, or else an error code like smaxPull(). + * \return X_SUCCESS (0) on success, or else an error code (<0) of smaxPull(). * * \sa smaxLazyEnd() * \sa smaxLazyFlush() @@ -387,18 +437,19 @@ int smaxGetLazyCached(const char *table, const char *key, XType type, int count, * */ int smaxLazyPull(const char *table, const char *key, XType type, int count, void *value, XMeta *meta) { + static const char *fn = "smaxLazyPull"; + LazyMonitor *m; - if(!value) return X_NULL; - if(!table && !key) return X_NAME_INVALID; + if(!value) return x_error(X_NULL, EINVAL, fn, "value is NULL"); m = GetCreateMonitor(table, key, type, meta != NULL); - if(!m) return X_NO_SERVICE; + if(!m) return x_trace(fn, NULL, X_NO_SERVICE); - return FetchData(m, type, count, value, meta); + prop_error(fn, FetchData(m, type, count, value, meta)); + return X_SUCCESS; } - /** * Returns a single integer value for a given SMA-X variable, or a default value if the * value could not be retrieved. @@ -437,7 +488,6 @@ double smaxLazyPullDouble(const char *table, const char *key) { return smaxLazyPullDoubleDefault(table, key, NAN); } - /** * Returns a single double-precision value for a given SMA-X variable, or a default value if the * value could not be retrieved. @@ -464,10 +514,11 @@ double smaxLazyPullDoubleDefault(const char *table, const char *key, double defa * @param key The variable name under which the data is stored. * @param buf Buffer to fill with stored data * @param n Number of bytes to fill in buffer. The retrieved data will be truncated as necessary. - * @return X_SUCCESS (0) if successful, or the error code retubrned by smaxLazyPull(). + * @return X_SUCCESS (0) if successful, or the error code (<0) returned by smaxLazyPull(). */ int smaxLazyPullChars(const char *table, const char *key, char *buf, int n) { - return smaxLazyPull(table, key, X_CHARS(n), 1, buf, NULL); + prop_error("smaxLazyPullChars", smaxLazyPull(table, key, X_CHARS(n), 1, buf, NULL)); + return X_SUCCESS; } /** @@ -488,7 +539,7 @@ char *smaxLazyPullString(const char *table, const char *key) { status = smaxLazyPull(table, key, X_STRING, 1, &str, NULL); if(status) { if(str) free(str); - return NULL; + return x_trace_null("smaxLazyPullString", NULL); } return str; @@ -499,14 +550,14 @@ char *smaxLazyPullString(const char *table, const char *key) { * * @param[in] id Aggregate structure ID. * @param[out] s Destination structure to populate with the retrieved fields - * @return X_SUCCESS (0) if successful, or the error code retubrned by smaxLazyPull(). + * @return X_SUCCESS (0) if successful, or the error code (<0) returned by smaxLazyPull(). * * @sa smaxPullStruct() * @sa xCreateStruct() */ int smaxLazyPullStruct(const char *id, XStructure *s) { - if(!s) return X_STRUCT_INVALID; - return smaxLazyPull(id, NULL, X_STRUCT, 1, s, NULL); + prop_error("smaxLazyPullStruct", smaxLazyPull(id, NULL, X_STRUCT, 1, s, NULL)); + return X_SUCCESS; } /** @@ -533,12 +584,12 @@ static void RemoveMonitorAsync(LazyMonitor *m) { if(--nMonitors == 0) smaxRemoveSubscribers(ProcessLazyUpdates); } - /** * Stops processing lazy updates in the background for a given variable. * * \param table The hash table name. * \param key The variable name under which the data is stored. + * \return X_SUCCESS (0) * * \sa smaxLazyFlush() * @sa smaxLazyPull() @@ -660,6 +711,8 @@ int smaxGetLazyUpdateCount(const char *table, const char *key) { * \sa Release() */ static LazyMonitor *CreateMonitorAsync(const char *table, const char *key, XType type, boolean withMeta) { + static const char *fn = "CreateMonitorAsync"; + LazyMonitor *m; char *id; int i; @@ -668,13 +721,13 @@ static LazyMonitor *CreateMonitorAsync(const char *table, const char *key, XType // so we need metadata for these no matter what... if(type == X_STRING || type == X_RAW) withMeta = TRUE; - if(smaxSubscribe(table, key) != X_SUCCESS) return NULL; + if(smaxSubscribe(table, key) != X_SUCCESS) return x_trace_null(fn, NULL); // For structs subscribe to leaf updates also - if(!key) if(smaxSubscribe(table, "*") != X_SUCCESS) return NULL; + if(!key) if(smaxSubscribe(table, "*") != X_SUCCESS) return x_trace_null(fn, NULL); m = (LazyMonitor *) calloc(1, sizeof(LazyMonitor)); - if(!m) return NULL; + x_check_alloc(m); m->users = 1; m->table = xStringCopyOf(table); @@ -682,12 +735,14 @@ static LazyMonitor *CreateMonitorAsync(const char *table, const char *key, XType if(withMeta) { m->meta = calloc(1, sizeof(XMeta)); - if(m->meta) m->meta->storeType = type; + x_check_alloc(m->meta); + m->meta->storeType = type; } id = xGetAggregateID(table, key); m->channel = calloc(1, sizeof(SMAX_UPDATES) + strlen(id)); if(!m->channel) { + x_error(0, errno, fn, "calloc() error (%d bytes)", sizeof(SMAX_UPDATES) + strlen(id)); free(m); return NULL; } @@ -714,7 +769,6 @@ static LazyMonitor *CreateMonitorAsync(const char *table, const char *key, XType return m; } - /** * Attempts to destroy (deallocate) a monitor point structure. It should be called only if the monitor point * is not in the monitor list, and assumed that the monitor'structure mutex is unlocked. If the monitor still has @@ -752,8 +806,6 @@ static boolean DestroyMonitorAsync(LazyMonitor *m) { return TRUE; } - - /** * Returns the hash lookup index for a given update channel. It is the same index as what xGetHashLookupIndex() * would return for the variable that has been updated in the given notification channel @@ -789,7 +841,6 @@ static __inline__ int GetTableIndex(const LazyMonitor *m) { return GetLookupIndex(m->table, m->key); } - /** * Returns the monitor point for a given variable, or NULL if the variable is not currently monitored. * You must call Release() on the monitor point returned after you are done using it. @@ -844,7 +895,6 @@ static LazyMonitor *GetMonitorAsync(const char *table, const char *key) { return m; } - // --------------------------------------------------------------------------- // Handling of PUB/SUB update notifications: // --------------------------------------------------------------------------- @@ -857,7 +907,7 @@ static LazyMonitor *GetMonitorAsync(const char *table, const char *key) { * \sa smaxAddSubscriber() * */ -static void ProcessLazyUpdates(const char *pattern, const char *channel, const char *msg, int length) { +static void ProcessLazyUpdates(const char *pattern, const char *channel, const char *msg, long length) { LazyMonitor *m; char *id; boolean checkParents = TRUE; @@ -893,7 +943,7 @@ static void ProcessLazyUpdates(const char *pattern, const char *channel, const c RemoveMonitorAsync(m); DestroyMonitorAsync(m); } - else if(m->isCached) UpdateCachedAsync(m, TRUE); // queue for a background update. + else if(m->isCached) UpdateCachedAsync(m, TRUE); // queue for a background update. // We found the match and dealt with it. Done with this particular ID. break; @@ -904,7 +954,7 @@ static void ProcessLazyUpdates(const char *pattern, const char *channel, const c if(!checkParents) break; // If there is no parent structure, we are done checking. - if(smaxSplitID(id, NULL) != X_SUCCESS) break; + if(xSplitID(id, NULL) != X_SUCCESS) break; } pthread_mutex_unlock(&monitorLock); diff --git a/src/smax-messages.c b/src/smax-messages.c index c1c31bb..b68db9f 100644 --- a/src/smax-messages.c +++ b/src/smax-messages.c @@ -14,8 +14,7 @@ #include #include -#include "redisx.h" -#include "smax.h" +#include "smax-private.h" #define MESSAGES_ID "messages" ///< Redis PUB_SUB channel head used for program messages #define MESSAGES_PREFIX MESSAGES_ID X_SEP ///< Prefix for Redis PUB/SUB channel for program messages (e.g. "messages:") @@ -38,25 +37,29 @@ static pthread_mutex_t listMutex = PTHREAD_MUTEX_INITIALIZER; static int nextID; -static void ProcessMessage(const char *pattern, const char *channel, const char *msg, int length); +static void ProcessMessage(const char *pattern, const char *channel, const char *msg, long length); static int SendMessage(const char *type, const char *text) { + static const char *fn = "SendMessage"; + const char *id = senderID ? senderID : smaxGetProgramID(); char *channel; char *tsmsg; int n; - if(!type) return X_NULL; - if(!text) return X_NULL; + if(!type) return x_error(X_NULL, EINVAL, fn, "type parameter is NULL"); + if(!text) return x_error(X_NULL, EINVAL, fn, "text parameter is NULL"); - channel = malloc(sizeof(MESSAGES_PREFIX) + strlen(id) + X_SEP_LENGTH + strlen(type)); - if(!channel) return X_NULL; + n = sizeof(MESSAGES_PREFIX) + strlen(id) + X_SEP_LENGTH + strlen(type); + channel = malloc(n); + if(!channel) return x_error(X_NULL, errno, fn, "malloc() error (%d bytes)", n); - tsmsg = malloc(strlen(text) + X_TIMESTAMP_LENGTH + 3); + n = strlen(text) + X_TIMESTAMP_LENGTH + 3; + tsmsg = malloc(n); if(!tsmsg) { free(channel); - return X_NULL; + return x_error(X_NULL, errno, fn, "malloc() error (%d bytes)", n); } sprintf(channel, MESSAGES_PREFIX "%s" X_SEP "%s", id, type); @@ -64,11 +67,14 @@ static int SendMessage(const char *type, const char *text) { smaxTimestamp(&tsmsg[n]); n = redisxNotify(smaxGetRedis(), channel, tsmsg); + if(n > 0) n = X_FAILURE; free(channel); free(tsmsg); - return n; + prop_error(fn, n); + + return X_SUCCESS; } /** @@ -100,7 +106,8 @@ void smaxSetMessageSenderID(const char *id) { * @sa sendInfo() */ int smaxSendStatus(const char *msg) { - return SendMessage(SMAX_MSG_STATUS, msg); + prop_error("smaxSendStatus", SendMessage(SMAX_MSG_STATUS, msg)); + return X_SUCCESS; } /** @@ -115,7 +122,8 @@ int smaxSendStatus(const char *msg) { * @sa sendStatus() */ int smaxSendInfo(const char *msg) { - return SendMessage(SMAX_MSG_INFO, msg); + prop_error("smaxSendInfo", SendMessage(SMAX_MSG_INFO, msg)); + return X_SUCCESS; } /** @@ -125,10 +133,10 @@ int smaxSendInfo(const char *msg) { * @return X_SUCCESS (0), or else an X error. */ int smaxSendDetail(const char *msg) { - return SendMessage(SMAX_MSG_DETAIL, msg); + prop_error("smaxSendDetail", SendMessage(SMAX_MSG_DETAIL, msg)); + return X_SUCCESS; } - /** * Broadcast a debugging message via SMA-X (e.g. program traces). * @@ -136,7 +144,8 @@ int smaxSendDetail(const char *msg) { * @return X_SUCCESS (0), or else an X error. */ int smaxSendDebug(const char *msg) { - return SendMessage(SMAX_MSG_DEBUG, msg); + prop_error("smaxSendDEbug", SendMessage(SMAX_MSG_DEBUG, msg)); + return X_SUCCESS; } /** @@ -151,7 +160,8 @@ int smaxSendDebug(const char *msg) { * */ int smaxSendWarning(const char *msg) { - return SendMessage(SMAX_MSG_WARNING, msg); + prop_error("smaxSendWarning", SendMessage(SMAX_MSG_WARNING, msg)); + return X_SUCCESS; } /** @@ -166,7 +176,8 @@ int smaxSendWarning(const char *msg) { * */ int smaxSendError(const char *msg) { - return SendMessage(SMAX_MSG_ERROR, msg); + prop_error("smaxSendError", SendMessage(SMAX_MSG_ERROR, msg)); + return X_SUCCESS; } /** @@ -177,23 +188,28 @@ int smaxSendError(const char *msg) { * @return X_SUCCESS (0), or else an X error. */ int smaxSendProgress(double fraction, const char *msg) { + static const char *fn = "smaxSendProgress"; + char *progress; int result; - if(!msg) return X_NULL; + if(!msg) { + x_error(0, EINVAL, fn, "'msg' is NULL"); + return X_NULL; + } progress = malloc(10 + strlen(msg)); - if(!progress) return X_NULL; + if(!progress) return x_error(X_NULL, errno, fn, "malloc() error (%d bytes)", 10 + strlen(msg)); sprintf(progress, "%.1f %s", (100.0 * fraction), msg); result = SendMessage(SMAX_MSG_DETAIL, progress); free(progress); - return result; + prop_error(fn, result); + return X_SUCCESS; } - /** * Adds a message processor function for a specific host (or all hosts), a specific program * (or all programs), and a specific message type (or all message types). @@ -209,29 +225,21 @@ int smaxSendProgress(double fraction, const char *msg) { * @sa smaxRemoveMessageProcessor() */ int smaxAddMessageProcessor(const char *host, const char *prog, const char *type, void (*f)(XMessage *)) { + static const char *fn = "smaxAddMessageProcessor"; + MessageProcessor *p; int L = sizeof(MESSAGES_PREFIX) + 2 * X_SEP_LENGTH + 1; // Empty pattern, e.g. "messages:::" - int result; + int result = X_SUCCESS; - if(!f) return X_NULL; + if(!f) return x_error(X_NULL, EINVAL, fn, "processor function is NULL"); p = (MessageProcessor *) calloc(1, sizeof(MessageProcessor)); - if(!p) return X_FAILURE; + x_check_alloc(p); p->call = f; p->id = ++nextID; p->prior = NULL; - pthread_mutex_lock(&listMutex); - - if(firstProc) firstProc->prior = p; - else redisxAddSubscriber(smaxGetRedis(), MESSAGES_PREFIX, ProcessMessage); - - p->next = firstProc; - firstProc = p; - - pthread_mutex_unlock(&listMutex); - if(host) if(strcmp(host, "*")) p->host = xStringCopyOf(host); if(prog) if(strcmp(prog, "*")) p->prog = xStringCopyOf(prog); if(type) if(strcmp(type, "*")) p->type = xStringCopyOf(type); @@ -244,22 +252,47 @@ int smaxAddMessageProcessor(const char *host, const char *prog, const char *type p->pattern = malloc(L); if(!p->pattern) { free(p); - return X_NULL; + return x_error(X_NULL, errno, fn, "malloc() error (%d bytes)", L); } sprintf(p->pattern, MESSAGES_PREFIX "%s" X_SEP "%s" X_SEP "%s", host, prog, type); - result = redisxSubscribe(smaxGetRedis(), p->pattern); - return result < 0 ? result : p->id; + pthread_mutex_lock(&listMutex); + + if(firstProc) firstProc->prior = p; + else result = redisxAddSubscriber(smaxGetRedis(), MESSAGES_PREFIX, ProcessMessage); + + if(result == X_SUCCESS) { + p->next = firstProc; + firstProc = p; + } + + pthread_mutex_unlock(&listMutex); + + // If so far-so good, subscribe to notifications/ + if(result == X_SUCCESS) result = redisxSubscribe(smaxGetRedis(), p->pattern); + + if(result != X_SUCCESS) { + // in case of error, remove the added message processor func, and return with an error. + smaxRemoveMessageProcessor(p->id); + // cppcheck-suppress memleak + return x_trace(fn, NULL, result); + } + + // cppcheck-suppress memleak + return p->id; } +// cppcheck-suppress constParameterCallback static void DefaultProcessor(XMessage *m) { + if(!m) return; + if(!strcmp(m->type, SMAX_MSG_ERROR)) fprintf(stderr, "ERROR! %s(%s): %s.\n", m->prog, m->host, m->text); - if(!strcmp(m->type, SMAX_MSG_WARNING)) fprintf(stderr, "WARNING! %s(%s): %s.\n", m->prog, m->host, m->text); - if(!strcmp(m->type, SMAX_MSG_INFO)) printf(" %s(%s): %s.\n", m->prog, m->host, m->text); - if(!strcmp(m->type, SMAX_MSG_DETAIL)) printf(" ... %s(%s): %s.\n", m->prog, m->host, m->text); - if(!strcmp(m->type, SMAX_MSG_DEBUG)) printf("DEBUG> %s(%s): %s.\n", m->prog, m->host, m->text); - if(!strcmp(m->type, SMAX_MSG_PROGRESS)) { + else if(!strcmp(m->type, SMAX_MSG_WARNING)) fprintf(stderr, "WARNING! %s(%s): %s.\n", m->prog, m->host, m->text); + else if(!strcmp(m->type, SMAX_MSG_INFO)) printf(" %s(%s): %s.\n", m->prog, m->host, m->text); + else if(!strcmp(m->type, SMAX_MSG_DETAIL)) printf(" ... %s(%s): %s.\n", m->prog, m->host, m->text); + else if(!strcmp(m->type, SMAX_MSG_DEBUG)) printf("DEBUG> %s(%s): %s.\n", m->prog, m->host, m->text); + else if(!strcmp(m->type, SMAX_MSG_PROGRESS)) { char *tail; double d = strtod(m->text, &tail); if(errno == ERANGE || tail == m->text) printf(" %s(%s): %s\r", m->prog, m->host, m->text); @@ -277,10 +310,11 @@ static void DefaultProcessor(XMessage *m) { * @return Serial ID number (> 0) of the message processor, or X_NULL. */ int smaxAddDefaultMessageProcessor(const char *host, const char *prog, const char *type) { - return smaxAddMessageProcessor(host, prog, type, DefaultProcessor); + int n = smaxAddMessageProcessor(host, prog, type, DefaultProcessor); + prop_error("smaxAddDefaultMessageProcessor", n); + return n; } - /** * Stops a running message processor. * @@ -328,7 +362,7 @@ int smaxRemoveMessageProcessor(int id) { * * @sa redisxAddSubscriber() */ -static void ProcessMessage(const char *pattern, const char *channel, const char *msg, int length) { +static void ProcessMessage(const char *pattern, const char *channel, const char *msg, long length) { MessageProcessor *p; XMessage m; char *ts; @@ -353,7 +387,7 @@ static void ProcessMessage(const char *pattern, const char *channel, const char ts = strrchr(msg, '@'); if(ts) { m.timestamp = smaxGetTime(&ts[1]); - if(m.timestamp) *ts = '\0'; + if(m.timestamp && !isnan(m.timestamp)) *ts = '\0'; } m.text = xStringCopyOf(msg); diff --git a/src/smax-meta.c b/src/smax-meta.c index 28abcfe..80bc4ba 100644 --- a/src/smax-meta.c +++ b/src/smax-meta.c @@ -14,12 +14,8 @@ #include #include -#include "redisx.h" -#include "smax.h" #include "smax-private.h" - - /** * Adds/updates metadata associated with an SMA-X variable. The data will be pushed via the * Redis pipeline channel. @@ -36,21 +32,20 @@ * \sa smaxPullMeta(), redisxSetValue() */ int smaxPushMeta(const char *meta, const char *table, const char *key, const char *value) { - static const char *funcName = "smaxPushMeta()"; + static const char *fn = "smaxPushMeta"; int status; Redis *redis = smaxGetRedis(); char *var, *channel; - if(redis == NULL) return smaxError(funcName, X_NO_INIT); + if(meta == NULL) return x_error(X_GROUP_INVALID, EINVAL, fn, "input 'meta' is NULL"); + if(!meta[0]) return x_error(X_GROUP_INVALID, EINVAL, fn, "input 'meta' is empty"); + if(value == NULL) return x_error(X_NULL, EINVAL, fn, "int value is NULL"); - if(meta == NULL) return smaxError(funcName, X_GROUP_INVALID); - if(table == NULL) return smaxError(funcName, X_GROUP_INVALID); - if(key == NULL) return smaxError(funcName, X_NAME_INVALID); - if(value == NULL) return smaxError(funcName, X_NULL); + if(redis == NULL) return x_error(X_NO_INIT, ENOTCONN, fn, "not connected"); var = xGetAggregateID(table, key); - if(var == NULL) return smaxError(funcName, X_NULL); + if(var == NULL) return x_trace(fn, NULL, X_NULL); // Use the interactive channel to ensure the notification // strictly follows the update itself. The extra metadata should @@ -62,15 +57,14 @@ int smaxPushMeta(const char *meta, const char *table, const char *key, const cha free(var); - if(channel == NULL) return X_INCOMPLETE; + if(channel == NULL) return x_trace(fn, NULL, X_INCOMPLETE); if(!status) status = redisxNotify(redis, channel, smaxGetProgramID()); free(channel); - return status ? X_INCOMPLETE : X_SUCCESS; + return status ? x_trace(fn, NULL, X_INCOMPLETE) : X_SUCCESS; } - /** * Retrieves a metadata string value for a given variable from the database * @@ -84,45 +78,35 @@ int smaxPushMeta(const char *meta, const char *table, const char *key, const cha * \sa setPushMeta() */ char *smaxPullMeta(const char *meta, const char *table, const char *key, int *status) { - static const char *funcName = "smaxPullMeta()"; + static const char *fn = "smaxPullMeta"; Redis *redis = smaxGetRedis(); - RESP *reply; - char *var; + char *var, *value; if(redis == NULL) { - smaxError(funcName, X_NO_INIT); + x_error(X_NO_INIT, EINVAL, fn, "not initialized"); return NULL; } if(meta == NULL) { - smaxError(funcName, X_GROUP_INVALID); + x_error(X_GROUP_INVALID, EINVAL, fn, "meta name is NULL"); return NULL; } - if(key == NULL) { - smaxError(funcName, X_NAME_INVALID); + if(!meta[0]) { + x_error(X_GROUP_INVALID, EINVAL, fn, "meta name is empty"); return NULL; } var = xGetAggregateID(table, key); - if(var == NULL) { - smaxError(funcName, X_NULL); - return NULL; - } - - reply = redisxGetValue(redis, meta, var, status); + if(var == NULL) return x_trace_null(fn, NULL); + value = redisxGetStringValue(redis, meta, var, status); free(var); - *status = redisxCheckDestroyRESP(reply, RESP_BULK_STRING, 0); - if(*status) return NULL; + if(status) x_trace_null(fn, NULL); - var = reply->value; - reply->value = NULL; - redisxDestroyRESP(reply); - - return var; + return value; } /** @@ -142,7 +126,7 @@ double smaxPullTime(const char *table, const char *key) { double ts; if(status) { - xError("smaxPullTime()", status); + x_trace_null("smaxPullTime", NULL); if(str) free(str); return NAN; } @@ -155,7 +139,6 @@ double smaxPullTime(const char *table, const char *key) { return ts; } - /** * Retrieves the timestamp for a given variable from the database. * @@ -170,12 +153,19 @@ double smaxPullTime(const char *table, const char *key) { * \sa setPushMeta() */ XType smaxPullTypeDimension(const char *table, const char *key, int *ndim, int *sizes) { + static const char *fn = "smaxPullTYpeDimension"; + XType type; int status; char *str = smaxPullMeta(SMAX_TYPES, table, key, &status); - if(status) type = X_UNKNOWN; - else type = xTypeForString(str); + if(status) { + type = x_trace(fn, NULL, X_UNKNOWN); + } + else { + type = smaxTypeForString(str); + if(type == X_UNKNOWN) x_trace(fn, NULL, X_UNKNOWN); + } if(str) free(str); @@ -202,10 +192,10 @@ XType smaxPullTypeDimension(const char *table, const char *key, int *ndim, int * * \sa smaxSetDescription(), smaxPushMeta() */ int smaxSetDescription(const char *table, const char *key, const char *description) { - return smaxPushMeta(META_DESCRIPTION, table, key, description); + prop_error("smaxSetDescription", smaxPushMeta(META_DESCRIPTION, table, key, description)); + return X_SUCCESS; } - /** * Returns a concise description of a variable. * @@ -217,11 +207,12 @@ int smaxSetDescription(const char *table, const char *key, const char *descripti * \sa smaxSetDescription() */ char *smaxGetDescription(const char *table, const char *key) { - int status; - return smaxPullMeta(META_DESCRIPTION, table, key, &status); + int status = X_SUCCESS; + char *desc = smaxPullMeta(META_DESCRIPTION, table, key, &status); + if(status) x_trace_null("smaxGetDescription", NULL); + return desc; } - /** * Sets the physical unit name for a given SMA-X variable. * @@ -235,10 +226,10 @@ char *smaxGetDescription(const char *table, const char *key) { * \sa smaxGetUnits(), smaxPushMeta() */ int smaxSetUnits(const char *table, const char *key, const char *unit) { - return smaxPushMeta(META_UNIT, table, key, unit); + prop_error("smaxSetUnits", smaxPushMeta(META_UNIT, table, key, unit)); + return X_SUCCESS; } - /** * Returns the physical unit name, if any, for the given variable. * @@ -250,11 +241,12 @@ int smaxSetUnits(const char *table, const char *key, const char *unit) { * \sa smaxSetUnits() */ char *smaxGetUnits(const char *table, const char *key) { - int status; - return smaxPullMeta(META_UNIT, table, key, &status); + int status = X_SUCCESS; + char *unit = smaxPullMeta(META_UNIT, table, key, &status); + if(status) x_trace_null("smaxGetUnits", NULL); + return unit; } - /** * Defines the n'th coordinate axis for a given SMA-X coordinate system table id. * @@ -263,19 +255,21 @@ char *smaxGetUnits(const char *table, const char *key) { * \param axis Pointer to the structure describing the coordinate axis. * * \return X_SUCCESS (0) if the coordinate axis was successfully set in the database. - * or else re return value of redisxMultiSet(). + * or else the return value of redisxMultiSet(). * * \sa smaxSetCoordinateAxis(), redisxMultiSet() * */ int smaxSetCoordinateAxis(const char *id, int n, const XCoordinateAxis *axis) { + static const char *fn = "smaxSetCoordinateAxis"; + RedisEntry fields[5]; char cidx[30], ridx[30], rval[30], step[30]; int status; sprintf(cidx, "%d", n+1); id = xGetAggregateID(id, cidx); - if(!id) return X_FAILURE; + if(!id) return x_trace(fn, NULL, X_FAILURE); sprintf(ridx, "%g", axis->refIndex); sprintf(rval, "%g", axis->refValue); @@ -296,10 +290,11 @@ int smaxSetCoordinateAxis(const char *id, int n, const XCoordinateAxis *axis) { fields[4].key = "step"; fields[4].value = step; - status = redisxMultiSet(smaxGetRedis(), id, fields, 5, TRUE); + status = redisxMultiSet(smaxGetRedis(), id, fields, 5, FALSE); free((char *) id); - return status; + prop_error(fn, status); + return X_SUCCESS; } /** @@ -308,36 +303,42 @@ int smaxSetCoordinateAxis(const char *id, int n, const XCoordinateAxis *axis) { * \param id Fully qualified SMA-X coordinate system ID. * \param n The (0-based) index of the coordinate axis * - * \return Pointer to a newlt allocated CoordinateAxis structure or NULL if + * \return Pointer to a newly allocated XCoordinateAxis structure or NULL if * the axis is undefined, or could not be retrieved from the database. * * \sa smaxSetCoordinateAxis() * */ XCoordinateAxis *smaxGetCoordinateAxis(const char *id, int n) { + static const char *fn = "smaxGetCoordinateAxis"; + RedisEntry *fields; XCoordinateAxis *axis; char *axisName, idx[20]; int i; if(n < 0) { - smaxError("xSetCoordinateAxis()", X_SIZE_INVALID); + x_error(0, EINVAL, fn, "invalid coordinate index: %d", n); return NULL; } sprintf(idx, "%d", (n+1)); axisName = xGetAggregateID(id, idx); + if(!axisName) return x_trace_null(fn, NULL); fields = redisxGetTable(smaxGetRedis(), axisName, &n); free(axisName); - if(fields == NULL) return NULL; if(n <= 0) { - free(fields); - return NULL; + if(fields) free(fields); + return x_trace_null(fn, NULL); } + if(fields == NULL) return x_trace_null(fn, NULL); + axis = (XCoordinateAxis *) calloc(1, sizeof(XCoordinateAxis)); + x_check_alloc(axis); + axis->step = 1.0; for(i=0; inAxis; i++) { int status = smaxSetCoordinateAxis(id, i, &coords->axis[i]); if(status) if(!firstError) firstError = status; } - return firstError; -} + free(id); + prop_error(fn, firstError); + + return X_SUCCESS; +} /** * Returns the coordinate system, if any, associated to a given SMA-X variable. @@ -423,34 +433,44 @@ int smaxSetCoordinateSystem(const char *table, const char *key, const XCoordinat * \sa smaxGetCoordinateAxis() */ XCoordinateSystem *smaxGetCoordinateSystem(const char *table, const char *key) { + static const char *fn = "smaxGetCoordinateSystem"; + XCoordinateSystem *s; XCoordinateAxis *a[X_MAX_DIMS]; char *var, *id; int n; var = xGetAggregateID(table, key); + if(!var) return x_trace_null(fn, NULL); + id = xGetAggregateID(META_COORDS, var); + free(var); + if(!id) return x_trace_null(fn, NULL); for(n=0; nnAxis = n; s->axis = (XCoordinateAxis *) calloc(n, sizeof(XCoordinateAxis)); + x_check_alloc(s->axis); while(--n >= 0) s->axis[n] = *a[n]; return s; } - /** * Creates a coordinate system with the desired dimension, and standard Cartesian coordinates * with no labels, or units specified (NULL). @@ -465,16 +485,16 @@ XCoordinateSystem *smaxGetCoordinateSystem(const char *table, const char *key) { XCoordinateSystem *smaxCreateCoordinateSystem(int nAxis) { XCoordinateSystem *coords; - if(nAxis <= 0) return NULL; + if(nAxis <= 0 || nAxis > X_MAX_DIMS) { + x_error(0, EINVAL, "smaxCreateCoordinateSystem", "invalid dimension: %d", nAxis); + return NULL; + } coords = (XCoordinateSystem *) calloc(1, sizeof(XCoordinateSystem)); - if(!coords) return NULL; + x_check_alloc(coords); coords->axis = (XCoordinateAxis *) calloc(nAxis, sizeof(XCoordinateAxis)); - if(!coords->axis) { - free(coords); - return NULL; - } + x_check_alloc(coords->axis); coords->nAxis = nAxis; @@ -486,7 +506,6 @@ XCoordinateSystem *smaxCreateCoordinateSystem(int nAxis) { return coords; } - /** * Deallocates a coordinate system structure. * diff --git a/src/smax-queue.c b/src/smax-queue.c index ffca099..ca056c2 100644 --- a/src/smax-queue.c +++ b/src/smax-queue.c @@ -16,8 +16,6 @@ #include #include -#include "redisx.h" -#include "smax.h" #include "smax-private.h" /// \cond PRIVATE @@ -28,7 +26,6 @@ #define X_CALLBACK 111112 /// \endcond - // Queued (pipelined) pulls ------------------------------> typedef struct XQueue { void *first; // Pointer to PullRequest list... @@ -36,7 +33,6 @@ typedef struct XQueue { int status; } XQueue; - static pthread_mutex_t qLock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t qComplete = PTHREAD_COND_INITIALIZER; @@ -50,16 +46,14 @@ static void Sync(); static void RemoveQueueHead(); static void DiscardQueuedAsync(); - // The variables below should be accessed only after an exclusive lock on pipeline->channelLock -// e.g. via lockChannel(PIPELINE_CHANNEL); +// e.g. via lockChannel(REDISX_PIPELINE_CHANNEL); static XQueue queued; static int nQueued = 0; static int maxQueued = SMAX_DEFAULT_MAX_QUEUED; static boolean isQueueInitialized = FALSE; - /** * Creates a synchronization point that can be waited upon until all elements queued prior to creation * are processed (retrieved from the database. @@ -72,11 +66,14 @@ static boolean isQueueInitialized = FALSE; */ XSyncPoint *smaxCreateSyncPoint() { XSyncPoint *s = (XSyncPoint *) calloc(1, sizeof(XSyncPoint)); + x_check_alloc(s); s->lock = (pthread_mutex_t *) calloc(1, sizeof(pthread_mutex_t)); + x_check_alloc(s->lock); pthread_mutex_init(s->lock, NULL); s->isComplete = (pthread_cond_t *) calloc(1, sizeof(pthread_cond_t)); + x_check_alloc(s->isComplete); pthread_cond_init(s->isComplete, NULL); if(queued.first == NULL) { @@ -86,6 +83,8 @@ XSyncPoint *smaxCreateSyncPoint() { else { // Otherwise put the synchronization point onto the queue. PullRequest *req = (PullRequest *) calloc(1, sizeof(PullRequest)); + x_check_alloc(req); + req->type = X_SYNCPOINT; req->value = s; @@ -99,7 +98,6 @@ XSyncPoint *smaxCreateSyncPoint() { return s; } - /** * Destroys a synchronization point, releasing the memory space allocated to it. * @@ -122,7 +120,6 @@ void smaxDestroySyncPoint(XSyncPoint *s) { free(s); } - /** * Adds a callback function to the queue to be called with the specified argument once all prior * requests in the queue have been fullfilled (retrieved from the database). @@ -136,12 +133,14 @@ void smaxDestroySyncPoint(XSyncPoint *s) { * \param f The callback function that takes a pointer argument * \param arg Argument to call the specified function with. * - * \return X_SUCCESS (0) + * \return X_SUCCESS (0) or else X_NULL if the function parameter is NULL. * * @sa smaxCreateSyncPoint() * @sa smaxQueue() */ -int smaxQueueCallback(void (*f)(char *), char *arg) { +int smaxQueueCallback(void (*f)(void *), void *arg) { + if(!f) return x_error(X_NULL, EINVAL, "smaxQueueCallback", "function parameter is NULL"); + if(queued.first == NULL) { // If nothing is queued, just call back right away... f(arg); @@ -149,9 +148,12 @@ int smaxQueueCallback(void (*f)(char *), char *arg) { else { // Otherwise, place the callback request onto the queue... PullRequest *req = (PullRequest *) calloc(1, sizeof(PullRequest)); + + x_check_alloc(req); + req->type = X_CALLBACK; req->value = f; - req->key = arg; + req->key = (char *) arg; pthread_mutex_lock(&qLock); QueueAsync(req); @@ -161,8 +163,6 @@ int smaxQueueCallback(void (*f)(char *), char *arg) { return X_SUCCESS; } - - /** * Start pipelined read operations. Pipelined reads are much faster but change the behavior slightly. * @@ -183,9 +183,6 @@ static void InitQueueAsync() { else fprintf(stderr, "WARNING! SMA-X : failed to set pipeline consumer.\n"); } - - - /** * Configures how many pull requests can be queued in when piped pulls are enabled. If the * queue reaches the specified limit, no new pull requests can be submitted until responses @@ -196,7 +193,7 @@ static void InitQueueAsync() { * \return TRUE if the argument was valid, and the queue size was set to it, otherwise FALSE */ int smaxSetMaxPendingPulls(int n) { - if(n < 1) return X_FAILURE; + if(n < 1) return x_error(X_FAILURE, EINVAL, "smaxSetMaxPendingPulls", "invalid limit: %d", n); maxQueued = n; return X_SUCCESS; } @@ -212,7 +209,7 @@ static void ResubmitQueueAsync() { if(p->type == X_SYNCPOINT) continue; if(p->type == X_CALLBACK) continue; - status = smaxRead(p, PIPELINE_CHANNEL); + status = smaxRead(p, REDISX_PIPELINE_CHANNEL); if(status) { //smaxZero(p->value, p->type, p->count); smaxError("xResubmitQueueAsync()", status); @@ -220,8 +217,6 @@ static void ResubmitQueueAsync() { } } - - /** * Waits for the queue to reach the specified sync point, up to an optional timeout limit. * @@ -230,7 +225,7 @@ static void ResubmitQueueAsync() { * The call will be guaranteed to return in the specified interval, * whether or not the pipelined reads all succeeded. The return value * can be used to check for errors or if the call timed out before - * all data were collected. If X_TIMEDOUT is returned, sma_end_bulk_pulls() + * all data were collected. If X_TIMEDOUT is returned, smax_end_bulk_pulls() * may be called again to allow more time for the queued read operations * to complete. * 0 or negative timeout values will cause the call to wait indefinitely @@ -250,14 +245,14 @@ static void ResubmitQueueAsync() { * @sa smaxWaitQueueComplete() */ int smaxSync(XSyncPoint *sync, int timeoutMillis) { - static const char *funcName = "smaxSync()"; + static const char *fn = "smaxSync"; struct timespec end; int status = 0; - if(sync == NULL) return smaxError(funcName, X_NULL); - if(sync->lock == NULL) return smaxError(funcName, X_NULL); - if(sync->isComplete == NULL) return smaxError(funcName, X_NULL); + if(sync == NULL) return x_error(X_NULL, EINVAL, fn, "synchronization point argument is NULL"); + if(sync->lock == NULL) return x_error(X_NULL, EINVAL, fn, "sync->lock is NULL"); + if(sync->isComplete == NULL) return x_error(X_NULL, EINVAL, fn, "sync->isComplete is NULL"); if(timeoutMillis > 0) { clock_gettime(CLOCK_REALTIME, &end); @@ -268,14 +263,14 @@ int smaxSync(XSyncPoint *sync, int timeoutMillis) { if(pthread_mutex_lock(sync->lock)) { xvprintf("SMA-X> Sync lock error.\n"); - return smaxError(funcName, X_FAILURE); + return x_error(X_FAILURE, errno, fn, "mutex lock error"); } // Check if there is anything to actually wait for... if(sync->status != X_INCOMPLETE && queued.first == NULL) { xvprintf("SMA-X> Already synchronized.\n"); pthread_mutex_unlock(sync->lock); - return smaxError(funcName, sync->status); + return x_error(sync->status, EALREADY, fn, "already synched"); } xvprintf("SMA-X> Waiting to reach synchronization...\n"); @@ -292,19 +287,17 @@ int smaxSync(XSyncPoint *sync, int timeoutMillis) { xvprintf("SMA-X> End wait for synchronization.\n"); // If timeout with an incomplete sync, then return X_TIMEDOUT - if(status == ETIMEDOUT) return smaxError(funcName, X_TIMEDOUT); + if(status == ETIMEDOUT) return x_error(X_TIMEDOUT, status, fn, "timed out"); // If the queue is in an erroneous state, then set the sync status to indicate potential issues. // (The error may not have occured at any point prior to the synchronization, and even prior to // the batch of pulls we care about... - if(queued.status) smaxError(funcName, queued.status); + if(queued.status) x_trace(fn, NULL, queued.status); - return smaxError(funcName, sync->status); + prop_error(fn, sync->status); + return X_SUCCESS; } - - - /** * Waits until all queued pull requests have been retrieved from the database, or until the specified * timeout it reached. @@ -313,7 +306,7 @@ int smaxSync(XSyncPoint *sync, int timeoutMillis) { * The call will be guaranteed to return in the specified interval, * whether or not the pipelined reads all succeeded. The return value * can be used to check for errors or if the call timed out before - * all data were collected. If X_TIMEDOUT is returned, sma_end_bulk_pulls() + * all data were collected. If X_TIMEDOUT is returned, smax_end_bulk_pulls() * may be called again to allow more time for the queued read operations * to complete. * 0 or negative timeout values will cause the call to wait indefinitely @@ -335,12 +328,10 @@ int smaxWaitQueueComplete(int timeoutMillis) { sync.isComplete = &qComplete; sync.lock = &qLock; - return smaxSync(&sync, timeoutMillis); + prop_error("smaxWaitQueueComplete", smaxSync(&sync, timeoutMillis)); + return X_SUCCESS; } - - - /** * Wait until no more than the specified number of pending reads remain in the queue, or until the timeout limit is reached. * @@ -352,14 +343,14 @@ int smaxWaitQueueComplete(int timeoutMillis) { * X_NO_SERVICE if the connection closed while waiting... */ static int DrainQueueAsync(int maxRemaining, int timeoutMicros) { - static const char *funcName = "xDrainQueue()"; + static const char *fn = "xDrainQueue"; int totalSleep = 0; xvprintf("SMA-X> read queue full. Waiting to drain...\n"); while(nQueued > maxRemaining) { int sleepMicros; - if(!redisxHasPipeline(smaxGetRedis())) return smaxError(funcName, X_NO_SERVICE); - if(timeoutMicros > 0) if(totalSleep > timeoutMicros) return smaxError(funcName, X_TIMEDOUT); + if(!redisxHasPipeline(smaxGetRedis())) return x_error(X_NO_SERVICE, ENOTCONN, fn, "no pipeline client"); + if(timeoutMicros > 0) if(totalSleep > timeoutMicros) return x_error(X_TIMEDOUT, ETIMEDOUT, fn, "timed out"); sleepMicros = 1 + nQueued - maxRemaining; totalSleep += sleepMicros; usleep(sleepMicros); @@ -369,8 +360,6 @@ static int DrainQueueAsync(int maxRemaining, int timeoutMicros) { return X_SUCCESS; } - - /** * The listener function that processes pipelined responses in the background. * @@ -406,7 +395,6 @@ static void ProcessPipeResponseAsync(RESP *reply) { else smaxProcessPipedWritesAsync(reply); } - /** * Processes timely synchronizations, whether callbacks, or synchronization points. * @@ -441,10 +429,6 @@ static void Sync() { } } - - - - /** * Discard all piped reads, setting values to zeroes. * @@ -466,7 +450,6 @@ static void DiscardQueuedAsync() { pthread_cond_broadcast(&qComplete); } - /** * Queues a pull requests for pipelined data retrieval. Because pipelined pulls are executed on * a separate Redis client from the one used for sharing values, e.g. via smaxShare(), there is @@ -480,8 +463,8 @@ static void DiscardQueuedAsync() { * \param key Variable name under which the data is stored. * \param type SMA-X variable type, e.g. X_FLOAT or X_CHARS(40), of the buffer. * \param count Number of points to retrieve into the buffer. - * \param value Pointer to the buffer to which the data is to be retrieved. - * \param meta Pointer to the corresponding metadata structure, or NULL. + * \param[out] value Pointer to the buffer to which the data is to be retrieved. + * \param[out] meta Pointer to the corresponding metadata structure, or NULL. * * \return X_SUCCESS (0) if successful * X_NAME_INVALID if the table and key are both NULL @@ -495,13 +478,20 @@ static void DiscardQueuedAsync() { * */ int smaxQueue(const char *table, const char *key, XType type, int count, void *value, XMeta *meta) { + static const char *fn = "smaxQueue"; + PullRequest *req, *last; int status; - if(table == NULL && key == NULL) return X_NAME_INVALID; - if(value == NULL) return X_NULL; + if(table == NULL) return x_error(X_GROUP_INVALID, EINVAL, fn, "table is NULL"); + if(!table[0]) return x_error(X_GROUP_INVALID, EINVAL, fn, "table is empty"); + if(key == NULL) return x_error(X_NAME_INVALID, EINVAL, fn, "key is NULL"); + if(!key[0]) return x_error(X_NAME_INVALID, EINVAL, fn, "key is empty"); + if(value == NULL) return x_error(X_NULL, EINVAL, fn, "outut value is NULL"); req = (PullRequest *) calloc(1, sizeof(PullRequest)); + x_check_alloc(req); + req->group = xStringCopyOf(table); req->key = xStringCopyOf(key); req->value = value; @@ -516,7 +506,7 @@ int smaxQueue(const char *table, const char *key, XType type, int count, void *v if(status != X_NO_SERVICE) fprintf(stderr, "ERROR! SMA-X : piped read timed out on %s:%s.\n", (req->group == NULL ? "" : req->group), req->key); free(req); - return smaxError("smaxQueue()", status); + return x_trace(fn, NULL, status); } } @@ -526,7 +516,7 @@ int smaxQueue(const char *table, const char *key, XType type, int count, void *v QueueAsync(req); // Send the pull request to Redis for this queued entry - status = smaxRead(req, PIPELINE_CHANNEL); + status = smaxRead(req, REDISX_PIPELINE_CHANNEL); // If the pull request was not submitted to SMA-X, then undo the queuing... if(status) queued.last = last; @@ -538,9 +528,10 @@ int smaxQueue(const char *table, const char *key, XType type, int count, void *v // smaxZero(req->value, req->type, req->count); } - return status; -} + prop_error(fn, status); + return X_SUCCESS; +} /** * Queues a pull request for pipelined retrieval. Apart from retrieving regular variables, the pull @@ -567,7 +558,6 @@ static void QueueAsync(PullRequest *req) { nQueued++; } - static void RemoveQueueHead() { PullRequest *req = NULL; diff --git a/src/smax-resilient.c b/src/smax-resilient.c index 7f7f4e2..3663877 100644 --- a/src/smax-resilient.c +++ b/src/smax-resilient.c @@ -18,7 +18,6 @@ * * \sa smaxSetResilient() * \sa smaxIsResilient() - * */ @@ -26,11 +25,9 @@ #include #include #include +#include -#include "smax.h" #include "smax-private.h" -#include "redisx.h" - typedef struct PushRequest { char *group; @@ -38,11 +35,9 @@ typedef struct PushRequest { struct PushRequest *next; } PushRequest; - static PushRequest *table[SMAX_LOOKUP_SIZE]; static pthread_mutex_t tableLock = PTHREAD_MUTEX_INITIALIZER; - static void SendStoredPushRequests(); static void UpdatePushRequest(const char *table, const XField *field); static void DestroyPushRequest(PushRequest *req); @@ -110,7 +105,6 @@ void smaxSetResilientExit(boolean value) { exitAfterSync = value ? TRUE : FALSE; } - /** * \cond PROTECTED * @@ -130,19 +124,28 @@ void smaxSetResilientExit(boolean value) { * X_NAME_INVALID if the field's name is NULL or empty string. */ int smaxStorePush(const char *group, const XField *field) { - if(field == NULL) return X_NULL; - if(group == NULL && field->name == NULL) return X_NULL; - if(field->name == NULL) return X_NAME_INVALID; - if(*field->name == '\0') return X_NAME_INVALID; + static const char *fn = "smaxStorePush"; + + if(field == NULL) return x_error(X_NULL, EINVAL, fn, "field is NULL"); if(field->type == X_STRUCT) { + const XStructure *s = (XStructure *) field->value; - XField *f; - char *id = xGetAggregateID(group, field->name); + const XField *f; + char *id; + int status = X_SUCCESS; - for(f = s->firstField; f != NULL; f = f->next) smaxStorePush(id, f); + id = xGetAggregateID(group, field->name); + if(!id) return x_trace(fn, NULL, X_NULL); + + for(f = s->firstField; f != NULL; f = f->next) if(smaxStorePush(id, f) != X_SUCCESS) { + status = X_INCOMPLETE; + break; + } free(id); + + prop_error(fn, status); } else UpdatePushRequest(group, field); @@ -150,7 +153,6 @@ int smaxStorePush(const char *group, const XField *field) { } /// \endcond - /** * Sends all previously undelivered and stored push requests to Redis, and exists the program with X_FAILURE (-1), * unless smaxSetResilientExit(FALSE) was previously set, after all penbding updates were successfully propagated. @@ -202,11 +204,9 @@ static void SendStoredPushRequests() { pthread_mutex_unlock(&tableLock); } - /** * IMPORTANT: Do not call with structure... * - * */ static void UpdatePushRequest(const char *group, const XField *field) { PushRequest *req; @@ -219,8 +219,11 @@ static void UpdatePushRequest(const char *group, const XField *field) { if(req == NULL) { req = (PushRequest *) calloc(1, sizeof(PushRequest)); + x_check_alloc(req); + req->group = xStringCopyOf(group); req->field = (XField *) calloc(1, sizeof(XField)); + x_check_alloc(req->field); if(table[idx] == NULL) table[idx] = req; else { diff --git a/src/smax-util.c b/src/smax-util.c index e77a2d1..02c3cd7 100644 --- a/src/smax-util.c +++ b/src/smax-util.c @@ -18,7 +18,6 @@ #include #include -#include "smax.h" #include "smax-private.h" #if SMAX_LEGACY @@ -26,12 +25,9 @@ #endif - - // Local prototypes -----------------------------------> static void *SMAXReconnectThread(void *arg); - // Local variables ------------------------------------> /// A lock for ensuring exlusive access for pipeline configuraton changes... @@ -74,7 +70,8 @@ int smaxUnlockConfig() { */ XMeta *smaxCreateMeta() { XMeta *m = (XMeta *) malloc(sizeof(XMeta)); - if(m) smaxResetMeta(m); + x_check_alloc(m); + smaxResetMeta(m); return m; } @@ -101,7 +98,9 @@ void smaxResetMeta(XMeta *m) { * @return the total number of elements represented by the metadata */ int smaxGetMetaCount(const XMeta *m) { - return xGetElementCount(m->storeDim, m->storeSizes); + int n = xGetElementCount(m->storeDim, m->storeSizes); + prop_error("smaxGetMetaCount", n); + return n; } /** @@ -133,7 +132,8 @@ void smaxSetOrigin(XMeta *m, const char *origin) { * @sa smaxSetResilient() * @sa redisxSetTrasmitErrorHandler() */ -void smaxTransmitErrorHandler(Redis *redis, int channel, const char *op) { +// cppcheck-suppress constParameterPointer +void smaxTransmitErrorHandler(Redis *redis, enum redisx_channel channel, const char *op) { pthread_t tid; if(redis != smaxGetRedis()) { @@ -248,7 +248,6 @@ int smaxError(const char *func, int errorCode) { return redisxError(func, errorCode); } - /** * Returns a string description for one of the RM error codes. * @@ -266,7 +265,6 @@ const char *smaxErrorDescription(int code) { return redisxErrorDescription(code); } - /** * \cond PROTECTED * @@ -280,11 +278,8 @@ void smaxDestroyPullRequest(PullRequest *p) { if(p->key != NULL) free(p->key); free(p); } -/// \endcond - /** - * \cond PROTECTED * * Returns a hash table lookup index for the given table (group) name and * Redis field (key) name. @@ -314,8 +309,9 @@ unsigned char smaxGetHashLookupIndex(const char *table, int lTab, const char *ke } /// \endcond - /** + * \cond PROTECTED + * * A quick 32-bit integer hashing algorithm. It uses a combination of 32-bit XOR products and summing to * obtain something reasonably robust at detecting changes. The returned hash is unique for data that fits in * 4-bytes. @@ -347,7 +343,7 @@ long smaxGetHash(const char *buf, const int size, const XType type) { return sum; } - +/// \endcond /** * Gets the SHA1 script ID for the currently loaded script with the specified name. @@ -361,15 +357,24 @@ long smaxGetHash(const char *buf, const int size, const XType type) { * */ char *smaxGetScriptSHA1(const char *scriptName, int *status) { - static const char *funcName = "smaxGetScriptSHA1()"; + static const char *fn = "smaxGetScriptSHA1"; Redis *redis = smaxGetRedis(); RESP *reply; char *sha1; + if(!status) { + x_error(0, EINVAL, fn, "output status is NULL"); + return NULL; + } if(scriptName == NULL) { - *status = smaxError(funcName, X_NAME_INVALID); + *status = x_error(X_NAME_INVALID, EINVAL, fn, "script name is NULL"); + return NULL; + } + + if(!scriptName[0]) { + *status = x_error(X_NAME_INVALID, EINVAL, fn, "script name is empty"); return NULL; } @@ -377,15 +382,11 @@ char *smaxGetScriptSHA1(const char *scriptName, int *status) { if(*status) { redisxDestroyRESP(reply); - smaxError(funcName, *status); - return NULL; + return x_trace_null(fn, NULL); } *status = redisxCheckDestroyRESP(reply, RESP_BULK_STRING, 0); - if(*status) { - smaxError(funcName, *status); - return NULL; - } + if(*status) return x_trace_null(fn, NULL); sha1 = (char *) reply->value; reply->value = NULL; @@ -395,8 +396,9 @@ char *smaxGetScriptSHA1(const char *scriptName, int *status) { return sha1; } +/// \cond PROTECTED + /** - * \cond PROTECTED * * \return TRUE (non-zero) if SMA-X is currently diabled (e.g. to reconnect), or else * FALSE (zero). @@ -427,6 +429,7 @@ static void *SMAXReconnectThread(void *arg) { return NULL; } +/// \endcond /** * Prints the given UNIX time into the supplied buffer with subsecond precision. @@ -434,27 +437,32 @@ static void *SMAXReconnectThread(void *arg) { * \param[in] time Pointer to time value. * \param[out] buf Pointer to string buffer, must be at least X_TIMESTAMP_LENGTH in size. * - * \return Number of characters printed, not including the terminating '\0'; + * \return Number of characters printed, not including the terminating '\\0', or else + * an error code (<0) if the `buf` argument is NULL. * */ __inline__ int smaxTimeToString(const struct timespec *time, char *buf) { - if(!buf) return X_NULL; + if(!buf) return x_error(X_NULL, EINVAL, "smaxTimeToString", "output buffer is NULL"); return sprintf(buf, "%lld.%06ld", (long long) time->tv_sec, (time->tv_nsec / 1000)); } - /** * Prints the current time into the supplied buffer with subsecond precision. * * \param[out] buf Pointer to string buffer, must be at least X_TIMESTAMP_LENGTH in size. * - * \return Number of characters printed, not including the terminating '\0'; + * \return Number of characters printed, not including the terminating '\\0', or else + * an error code (<0) if the `buf` argument is NULL. * */ int smaxTimestamp(char *buf) { struct timespec ts; + int n; + clock_gettime(CLOCK_REALTIME, &ts); - return smaxTimeToString(&ts, buf); + n = smaxTimeToString(&ts, buf); + prop_error("smaxTimestamp", n); + return n; } /** @@ -465,20 +473,23 @@ int smaxTimestamp(char *buf) { * \param[out] nanosecs Pointer to the retuned sub-second remainder as nanoseconds, or NULL if nor requested. * * \return X_SUCCESS(0) if the timestamp was successfully parsed. - * -1 if there was no timestamp (empty or invalid string) + * X_NULL if there was no timestamp (empty or invalid string), or the `secs` argument is NULL. + * X_PARSE_ERROR if the seconds could not be parsed. * 1 if there was an error parsing the nanosec part. * X_NULL if the secs arhument is NULL */ int smaxParseTime(const char *timestamp, time_t *secs, long *nanosecs) { + static const char *fn = "smaxParseTime"; + char *next; - if(!timestamp) return -1; - if(!secs) return X_NULL; + if(!timestamp) return x_error(X_NULL, EINVAL, fn, "input timestamp is NULL"); + if(!secs) return x_error(X_NULL, EINVAL, fn, "output seconds is NULL"); *secs = (time_t) strtoll(timestamp, &next, 10); if(errno == ERANGE || next == timestamp) { *nanosecs = 0; - return -1; + return x_error(X_PARSE_ERROR, ENOMSG, fn, "cannot parse seconds: '%s'", timestamp); } if(*next == '.') { @@ -495,27 +506,35 @@ int smaxParseTime(const char *timestamp, time_t *secs, long *nanosecs) { return X_SUCCESS; } - /** * Returns the a sub-second precision UNIX time value for the given SMA-X timestamp * * \param timestamp The string timestamp returned by SMA-X * - * \return Corresponding UNIX time with sub-second precision. + * \return Corresponding UNIX time with sub-second precision, or NAN if the input could not be parsed. */ double smaxGetTime(const char *timestamp) { + static const char *fn = "smaxGetTime"; + time_t sec; long nsec; - if(timestamp == NULL) return 0.0; // TODO NAN? + if(timestamp == NULL) { + x_error(X_NULL, EINVAL, fn, "input timestamp is NULL"); + return NAN; + } + + if(smaxParseTime(timestamp, &sec, &nsec) < 0) { + x_trace(fn, NULL, 0); + return NAN; + } - if(smaxParseTime(timestamp, &sec, &nsec) < 0) return 0.0; return sec + 1e-9 * nsec; } - /** * Creates a generic field of a given name and type and dimensions using the specified native values. + * It is like `xCreateField()` except that the field is created in serialized form for SMA-X. * * \param name Field name * \param type Storage type, e.g. X_INT. @@ -529,30 +548,22 @@ double smaxGetTime(const char *timestamp) { * @sa xSetField() */ XField *smaxCreateField(const char *name, XType type, int ndim, const int *sizes, const void *value) { - const char *funcName = "smaxCreateField()"; + static const char *fn = "smaxCreateField"; int n; XField *f; - if(type != X_RAW && type != X_STRING) if(xStringElementSizeOf(type) < 1) { - smaxError(funcName, X_TYPE_INVALID); - return NULL; - } - - if(type == X_RAW || type == X_STRUCT) return xCreateField(name, type, ndim, sizes, value); + if(type != X_RAW && type != X_STRING) if(xStringElementSizeOf(type) < 1) return x_trace_null(fn, NULL); - if(type != X_STRING) if(xStringElementSizeOf(type) < 1) { - xError(funcName, X_TYPE_INVALID); - return NULL; + if(type == X_RAW || type == X_STRUCT) { + f = xCreateField(name, type, ndim, sizes, value); + return f ? f : x_trace_null(fn, NULL); } n = xGetElementCount(ndim, sizes); - if(n < 1) { - xError(funcName, X_SIZE_INVALID); - return NULL; - } + if(n < 1) return x_trace_null(fn, NULL); f = xCreateField(name, type, ndim, sizes, NULL); - if(!f) return NULL; + if(!f) return x_trace_null(fn, NULL); f->value = smaxValuesToString(value, type, n, NULL, 0); f->isSerialized = TRUE; @@ -564,7 +575,7 @@ XField *smaxCreateField(const char *name, XType type, int ndim, const int *sizes * Converts a standard xchange field (with a native value storage) to an SMA-X field with * serialized string value storage. * - * @param f Pointer to field to convert + * @param[in, out] f Pointer to field to convert * @return X_SUCCESS (0) if successful, or * X_NULL if the input field or the serialized value is NULL. * @@ -572,14 +583,17 @@ XField *smaxCreateField(const char *name, XType type, int ndim, const int *sizes * @sa x2smaxStruct() */ int x2smaxField(XField *f) { + static const char *fn = "x2smaxField"; + void *value; - if(!f) return X_NULL; + if(!f) return x_error(X_NULL, EINVAL, fn, "field is NULL"); if(!f->value) return X_SUCCESS; if(f->type == X_RAW) return X_SUCCESS; if(f->type == X_STRUCT) { f->isSerialized = TRUE; - return x2smaxStruct((XStructure *) f->value); + prop_error(fn, x2smaxStruct((XStructure *) f->value)); + return X_SUCCESS; } if(f->isSerialized) return X_SUCCESS; @@ -589,7 +603,7 @@ int x2smaxField(XField *f) { f->isSerialized = TRUE; - if(!f->value) return X_NULL; + if(!f->value) return x_trace(fn, NULL, X_NULL); return X_SUCCESS; } @@ -608,29 +622,32 @@ int x2smaxField(XField *f) { * @sa smax2xStruct() */ int smax2xField(XField *f) { + static const char *fn = "smax2xField"; + void *str; int pos = 0, result, count, eSize; - if(!f) return X_NULL; + if(!f) return x_error(X_NULL, EINVAL, fn, "field is NULL"); if(!f->value) return X_SUCCESS; if(f->type == X_RAW) return X_SUCCESS; if(f->type == X_STRUCT) { f->isSerialized = FALSE; - return smax2xStruct((XStructure *) f->value); + prop_error(fn, smax2xStruct((XStructure *) f->value)); + return X_SUCCESS; } if(!f->isSerialized) return X_SUCCESS; eSize = xElementSizeOf(f->type); - if(eSize <= 0) return X_TYPE_INVALID; + if(eSize <= 0) return x_trace(fn, NULL, X_TYPE_INVALID); count = xGetFieldCount(f); - if(count <= 0) return X_SIZE_INVALID; + if(count <= 0) return x_trace(fn, NULL, X_SIZE_INVALID); str = f->value; f->value = calloc(count, eSize); if(!f->value) { free(str); - return X_NULL; + return x_error(X_NULL, errno, fn, "calloc() error (%d x %d)", count, eSize); } result = smaxStringToValues(str, f->value, f->type, count, &pos); @@ -638,7 +655,8 @@ int smax2xField(XField *f) { f->isSerialized = FALSE; - return result; + prop_error(fn, result); + return X_SUCCESS; } /** @@ -654,17 +672,20 @@ int smax2xField(XField *f) { * @sa x2smaxField() */ int x2smaxStruct(XStructure *s) { + static const char *fn = "x2smaxStruct"; + XField *f; int status = X_SUCCESS; - if(!s) return X_STRUCT_INVALID; + if(!s) return x_error(X_STRUCT_INVALID, EINVAL, fn, "input structure is NULL"); for(f = s->firstField; f; f = f->next) { int res = x2smaxField(f); if(!status) status = res; } - return status; + prop_error(fn, status); + return X_SUCCESS; } /** @@ -680,54 +701,22 @@ int x2smaxStruct(XStructure *s) { * @sa smax2xField() */ int smax2xStruct(XStructure *s) { + static const char *fn = "smax2xStruct"; + XField *f; int status = X_SUCCESS; - if(!s) return X_STRUCT_INVALID; + if(!s) return x_error(X_STRUCT_INVALID, EINVAL, fn, "input structure is NULL"); for(f = s->firstField; f; f = f->next) { int res = smax2xField(f); if(!status) status = res; } + prop_error(fn, status); return status; } - -/** - * Splits the id into two strings (sharing the same input buffer) for SMA-X table and field. - * The original input id is string terminated after the table name. And the pointer to the key - * part that follows after the last separator is returned in the second (optional argument). - * - * \param[in,out] id String containing an aggregate SMA-X ID (table:field), which will be terminated after the table part. - * \param[out] pKey Returned pointer to the second component after the separator within the same buffer. This is - * not an independent pointer. Use smaxStringCopyOf() if you need an idependent string - * on which free() can be called! The returned value pointed to may be NULL if the ID - * could not be split. The argument may also be null, in which case the input string is - * just terminated at the stem, without returning the second part. - * - * \return X_SUCCESS (0) if the ID was successfully split into two components. - * X_NULL if the id argument is NULL. - * X_NAME_INVALID if no separator was found - * - */ -int smaxSplitID(char *id, char **pKey) { - char *s; - - if(id == NULL) return X_NULL; - - // Default NULL return for the second component. - if(pKey) *pKey = NULL; - - s = xLastSeparator(id); - if(s) *s = '\0'; - else return X_NAME_INVALID; - - if(pKey) *pKey = s + X_SEP_LENGTH; - - return X_SUCCESS; -} - /** * Returns the current time on the Redis server instance. * @@ -737,13 +726,12 @@ int smaxSplitID(char *id, char **pKey) { * or another error returned by redisxCheckRESP(). */ int smaxGetServerTime(struct timespec *t) { - if(!smaxIsConnected()) return X_NO_INIT; - return redisxGetTime(smaxGetRedis(), t); + prop_error("smaxGetServerTime", redisxGetTime(smaxGetRedis(), t)); + return X_SUCCESS; } - /** - * Serializes binary values into a string representation (for REDIS). + * Serializes binary values into a string representation (for Redis). * * \param[in] value Pointer to an array of values, or NULL to produce all zeroes. * If type is X_STRING value should be a pointer to a char** (array of string @@ -767,6 +755,8 @@ int smaxGetServerTime(struct timespec *t) { * use. */ char *smaxValuesToString(const void *value, XType type, int eCount, char *trybuf, int trylength) { + static const char *fn = "smaxValuedToString"; + int eSize=1, k, stringSize = 1; char *sValue, *next; @@ -779,7 +769,10 @@ char *smaxValuesToString(const void *value, XType type, int eCount, char *trybuf char **S = (char **) value; if(value == NULL) type = X_UNKNOWN; // Print zero(es) for null value. - if(type == X_STRUCT) return NULL; // structs are not serialized by this function. + if(type == X_STRUCT) { + x_error(0, EINVAL, fn, "structures not allowed"); + return NULL; // structs are not serialized by this function. + } if(type == X_RAW) if(value) return *(char **) value; // Figure out how big the serialized string might be... @@ -790,7 +783,7 @@ char *smaxValuesToString(const void *value, XType type, int eCount, char *trybuf } else { eSize = xElementSizeOf(type); - if(eSize <= 0) return NULL; // Unsupported element type... + if(eSize <= 0) return x_trace_null(fn, NULL); // Unsupported element type... stringSize = eCount * xStringElementSizeOf(type); } @@ -798,7 +791,10 @@ char *smaxValuesToString(const void *value, XType type, int eCount, char *trybuf // Use the supplied buffer if large enough, or dynamically allocate one. if(trybuf != NULL && stringSize <= trylength) sValue = trybuf; - else sValue = (char *) malloc(stringSize); + else { + sValue = (char *) malloc(stringSize); + x_check_alloc(sValue); + } // If we got this far with raw type, it's because it was a null value, so return an empty string... if(type == X_RAW) { @@ -905,7 +901,7 @@ static __inline__ void CheckParseError(char **next, int *status) { /** * Deserializes a string to binary values. * - * \param[in] str Serialized ASCII representation of the data (as stored by REDIS). + * \param[in] str Serialized ASCII representation of the data (as stored by Redis). * * \param[out] value Pointer to the buffer that will hold the binary values. The caller is responsible * for ensuring the buffer is sufficiently sized for holding the data for the @@ -926,6 +922,8 @@ static __inline__ void CheckParseError(char **next, int *status) { * X_PARSE_ERROR If the tokens could not be parsed in the format expected */ int smaxStringToValues(const char *str, void *value, XType type, int eCount, int *pos) { + static const char *fn = "smaxStringToValues"; + char *next; int status = 0, eSize, k; @@ -937,15 +935,19 @@ int smaxStringToValues(const char *str, void *value, XType type, int eCount, int char *c = (char *) value; boolean *b = (boolean *) value; - if(value == NULL) return X_NULL; - if(eCount <= 0) return X_SIZE_INVALID; + if(value == NULL) return x_error(X_NULL, EINVAL, fn, "value is NULL"); + if(eCount <= 0) return x_error(X_SIZE_INVALID, EINVAL, fn, "invalid count: %d", eCount); - if(type == X_RAW || type == X_STRUCT) return X_TYPE_INVALID; + if(type == X_RAW || type == X_STRUCT) return x_error(X_TYPE_INVALID, EINVAL, fn, "X_RAW or X_STRUCT not allowed"); - if(type == X_STRING) return xUnpackStrings(str, strlen(str), eCount, (char **) value); + if(type == X_STRING) { + int n = smaxUnpackStrings(str, strlen(str), eCount, (char **) value); + prop_error(fn, n); + return n; + } eSize = xElementSizeOf(type); - if(eSize <= 0) return X_SIZE_INVALID; + if(eSize <= 0) return x_trace(fn, NULL, X_SIZE_INVALID); if(str == NULL) { xZero(value, type, eCount); @@ -1020,7 +1022,7 @@ int smaxStringToValues(const char *str, void *value, XType type, int eCount, int CheckParseError(&next, &status); } break; - default: return X_TYPE_INVALID; // Unknown type... + default: return x_error(X_TYPE_INVALID, EINVAL, fn, "unsupported type: %d", type); // Unknown type... } // Zero out the remaining elements... @@ -1029,9 +1031,121 @@ int smaxStringToValues(const char *str, void *value, XType type, int eCount, int *pos = next - str; - return status ? status : k; + prop_error(fn, status); + + return k; } +/** + * Returns the string type for a given XType argument as a constant expression. For examples X_LONG -> "int64". + * + * \param type SMA-X type, e.g. X_FLOAT + * + * \return Corresponding string type, e.g. "float". (Default is "string" -- since typically + * anything can be represented as strings.) + * + * \sa smaxTypeForString() + */ +char *smaxStringType(XType type) { + if(type < 0) return "string"; // X_CHAR(n), legacy fixed size strings. + + switch(type) { + case X_BOOLEAN: return "boolean"; + case X_BYTE: + case X_BYTE_HEX: return "int8"; + case X_SHORT: + case X_SHORT_HEX: return "int16"; + case X_INT: + case X_INT_HEX: return "int32"; + case X_LONG: + case X_LONG_HEX: return "int64"; + case X_FLOAT: return "float"; + case X_DOUBLE: return "double"; + case X_STRING: return "string"; + case X_RAW: return "raw"; + case X_STRUCT: return "struct"; + case X_UNKNOWN: + default: + x_error(0, EINVAL, "smaxStringType", "invalid SMA-X type: %d", type); + return "unknown"; + } +} + +/** + * Returns the XType for a given case-sensitive type string. For example "float" -> X_FLOAT. The value "raw" will + * return X_RAW. + * + * \param type String type, e.g. "struct". + * + * \return Corresponding XType, e.g. X_STRUCT. (The default return value is X_RAW, since all Redis + * values can be represented as raw strings.) + * + * \sa smaxStringType() + */ +XType smaxTypeForString(const char *type) { + if(!type) return X_RAW; + if(!strcmp("int", type) || !strcmp("integer", type)) return X_INT; + if(!strcmp("boolean", type) || !strcmp("bool", type)) return X_BOOLEAN; + if(!strcmp("int8", type)) return X_BYTE; + if(!strcmp("int16", type)) return X_SHORT; + if(!strcmp("int32", type)) return X_INT; + if(!strcmp("int64", type)) return X_LONG; + if(!strcmp("float", type)) return X_FLOAT; + if(!strcmp("float32", type)) return X_FLOAT; + if(!strcmp("float64", type)) return X_DOUBLE; + if(!strcmp("double", type)) return X_DOUBLE; + if(!strcmp("string", type) || !strcmp("str", type)) return X_STRING; + if(!strcmp("struct", type)) return X_STRUCT; + if(!strcmp("raw", type)) return X_RAW; + + return x_error(X_UNKNOWN, EINVAL, "smaxTypeForString", "invalid SMA-X type: '%s'", type); +} + +/** + * Returns an array of dynamically allocated strings from a packed buffer of consecutive 0-terminated + * or '\\r'-separated string elements. + * + * \param[in] data Pointer to the packed string data buffer. + * \param[in] len length of packed string (excl. termination). + * \param[in] count Number of string elements expected. If fewer than that are found + * in the packed data, then the returned array of pointers will be + * padded with NULL. + * \param[out] dst An array of string pointers (of size 'count') which will point to + * dynamically allocated string (char*) elements. The array is assumed + * to be uninitialized, and elements will be allocated as necessary. + * + * \return X_SUCCESS (0) if successful, or X_NULL if one of the argument + * pointers is NULL, or else X_INCOMPLETE if some of the components + * were too large to unpack (alloc error). + */ +int smaxUnpackStrings(const char *data, int len, int count, char **dst) { + static const char *fn = "smaxUnpackStrings"; + + int i, offset = 0; + + if(!data) return x_error(X_NULL, EINVAL, fn, "input packed string 'data' is NULL"); + if(!dst) return x_error(X_NULL, EINVAL, fn, "output unpacked string 'dst' is NULL"); + + // Make sure the data has proper string termination at its end, so we don't overrun... + //data[len] = '\0'; + + for(i=0; i" X_SEP "%s", pattern); redisxDeleteEntries(smaxGetRedis(), metaPattern); diff --git a/src/smax.c b/src/smax.c index efbf585..33fd762 100644 --- a/src/smax.c +++ b/src/smax.c @@ -7,7 +7,7 @@ * \brief * SMA-X is a software implementation for SMA shared data, and is the base layer for the software * reflective memory (RM) emulation, and DSM replacement. - * It works by communicating TCP/IP messages to a central REDIS server. + * It works by communicating TCP/IP messages to a central Redis server. * * There is also extra functionality, for configuring, performance tweaking, verbosity control, * and some convenience methods (e.g. data serialization/deserialization). @@ -23,9 +23,8 @@ #include #include #include +#include -#include "redisx.h" -#include "smax.h" #include "smax-private.h" #if __Lynx__ @@ -62,9 +61,8 @@ char *HMSET_WITH_META; ///< SHA1 key for calling HMSetWithMeta LUA script char *GET_STRUCT; ///< SHA1 key for calling HGetStruct LUA script - // Local prototypes -------------------> -static void ProcessUpdateNotificationAsync(const char *pattern, const char *channel, const char *msg, int length); +static void ProcessUpdateNotificationAsync(const char *pattern, const char *channel, const char *msg, long length); static int ProcessStructRead(RESP **component, PullRequest *req); static int ParseStructData(XStructure *s, RESP *names, RESP *data, XMeta *meta); @@ -74,7 +72,7 @@ static int SendStructDataAsync(RedisClient *cl, const char *id, const XStructure static void InitScriptsAsync(); static boolean usePipeline = TRUE; -static int tcpBufSize = REDIS_TCP_BUF; +static int tcpBufSize = REDISX_TCP_BUF_SIZE; // A lock for ensuring exlusive access for the monitor list... // and the variables that it controls, e.g. via lockNotify() @@ -85,10 +83,99 @@ static pthread_cond_t notifyBlock = PTHREAD_COND_INITIALIZER; static char *notifyID; static int notifySize; +static char *server; +static int serverPort = REDISX_TCP_PORT; +static char *user; +static char *auth; +static int dbIndex; + static Redis *redis; +static char *hostName; static char *programID; +/** + * Configures the SMA-X server before connecting. + * + * @param host The SMA-X REdis server host name or IP address. + * @param port The Redis port number on the SMA-X server, or <=0 to use the default + * @return X_SUCCESS (0) if successful, or X_ALREADY_OPEN if cannot alter the server configuration + * because we are already in a connected state. + * + * @sa smaxSetAuth() + * @sa smaxSetDB() + * @sa smaxConnect() + */ +int smaxSetServer(const char *host, int port) { + smaxLockConfig(); + + if(smaxIsConnected()) { + smaxUnlockConfig(); + return x_error(X_ALREADY_OPEN, EALREADY, "smaxSetServer", "already in connected state"); + } + + if(server) free(server); + server = xStringCopyOf(host); + + serverPort = port > 0 ? port : REDISX_TCP_PORT; + + smaxUnlockConfig(); + return X_SUCCESS; +} + +/** + * Sets the SMA-X database authentication parameters (if any) before connecting to the SMA-X server. + * + * @param username Redis ACL user name (if any), or NULL for no user-based authentication + * @param password Redis database password (if any), or NULL if the database is not password protected + * @return X_SUCCESS (0) if successful, or X_ALREADY_OPEN if cannot alter the server configuration + * because we are already in a connected state. + * + * @sa smaxSetServer() + * @sa smaxConnect() + */ +int smaxSetAuth(const char *username, const char *password) { + smaxLockConfig(); + + if(smaxIsConnected()) { + smaxUnlockConfig(); + return x_error(X_ALREADY_OPEN, EALREADY, "smaxSetAuth", "already in connected state"); + } + + if(user) free(user); + user = xStringCopyOf(username); + + if(auth) free(auth); + auth = xStringCopyOf(password); + + smaxUnlockConfig(); + return X_SUCCESS; +} + +/** + * Sets a non-default Redis database index to use for SMA-X before connecting to the SMA-X server. + * + * @param idx The Redis database index to use (if not the default one) + * @return X_SUCCESS (0) if successful, or X_ALREADY_OPEN if cannot alter the server configuration + * because we are already in a connected state. + * + * @sa smaxSetServer() + * @sa smaxConnect() + */ +int smaxSetDB(int idx) { + smaxLockConfig(); + + if(smaxIsConnected()) { + smaxUnlockConfig(); + return x_error(X_ALREADY_OPEN, EALREADY, "smaxSetDB", "already in connected state"); + } + + dbIndex = idx > 0 ? idx : 0; + + smaxUnlockConfig(); + return X_SUCCESS; +} + /** * Enable or disable verbose reporting of all SMA-X operations (and possibly some details of them). * Reporting is done on the standard output (stdout). It may be useful when debugging programs @@ -103,7 +190,6 @@ void smaxSetVerbose(boolean value) { redisxSetVerbose(value); } - /** * Checks id verbose reporting is enabled. * @@ -115,13 +201,12 @@ boolean smaxIsVerbose() { return redisxIsVerbose(); } - /** - * Enable or disable pipelined write operations. When pipelining, shares calls will return - * as soon as the request is sent to the REDIS server, without waiting for a response. + * Enable or disable pipelined write operations (enabled by default). When pipelining, share calls + * will return as soon as the request is sent to the Redis server, without waiting for a response. * Instead, responses are consumed asynchronously by a dedicated thread, which will report * errors to stderr. Pipelined writes can have a significant performance advantage over - * handshaking at the cost of one extra socket connection to REDIS (dedicated to pipelining) + * handshaking at the cost of one extra socket connection to Redis (dedicated to pipelining) * and the extra thread consuming responses. * * The default state of pipelined writes might vary by platform (e.g. enabled on Linux, @@ -129,26 +214,30 @@ boolean smaxIsVerbose() { * * __IMPORTANT__: calls to smaxSetPipelined() must precede the call to smaxConnect(). * - * \param isEnabled TRUE to enable pipelined writes, FALSE to disable. + * @param isEnabled TRUE to enable pipelined writes, FALSE to disable (default is enabled). * - * \return X_SUCCESS if the setting was successful - * X_FAILURE if the state could not be changed - * (e.g. because smaxOpen() was called previously). + * @return X_SUCCESS (0) if successful, or X_ALREADY_OPEN if cannot alter the server configuration + * because we are already in a connected state. * * @sa smaxIsPipelined() * @sa smaxSetPipelineConsumer() */ int smaxSetPipelined(boolean isEnabled) { if(usePipeline == isEnabled) return X_SUCCESS; + + smaxLockConfig(); + if(smaxIsConnected()) { - fprintf(stderr, "WARNING! SMA-X : Cannot change pipeline state after smaxOpen().\n"); - return X_FAILURE; + smaxUnlockConfig(); + return x_error(X_ALREADY_OPEN, EALREADY, "smaxSetPipelined", "Cannot change pipeline state after connecting"); } + usePipeline = isEnabled; + smaxUnlockConfig(); + return X_SUCCESS; } - /** * Check if SMA-X is configured with pipeline mode enabled. * @@ -160,51 +249,69 @@ boolean smaxIsPipelined() { return usePipeline; } - /** * Set the size of the TCP/IP buffers (send and receive) for future client connections. * * @param size (bytes) requested buffer size, or <= 0 to use default value * - * @sa smaxGetTcpBuf() + * @sa smaxConnect; */ -void smaxSetTcpBuf(int size) { +int smaxSetTcpBuf(int size) { + smaxLockConfig(); + + if(smaxIsConnected()) { + smaxUnlockConfig(); + return x_error(X_ALREADY_OPEN, EALREADY, "smaxSetTcpBuf", "Cannot change pipeline state after connecting"); + } + tcpBufSize = size; -} + smaxUnlockConfig(); -/** - * Returns the current TCP/IP buffer size (send and receive) to be used for future client connections. - * - * @return (bytes) future TCP/IP buffer size, 0 if system default. - * - * @sa smaxSetTcpBuf() - */ -int smaxGetTcpBuf() { - return tcpBufSize > 0 ? tcpBufSize : 0; + return X_SUCCESS; } - /** - * Returns the host name on which we are running. It returns a reference to the same + * Returns the host name on which this program is running. It returns a reference to the same * static variable every time. As such you should never call free() on the returned value. + * Note, that only the leading part of the host name is returned, so for a host + * that is registered as 'somenode.somedomain' only 'somenode' is returned. * - * \return The host computer/node name. + * \return The host name string (leading part only). + * + * \sa smaxSetHostName() * - * @sa smaxSetHostName() */ char *smaxGetHostName() { - return redisxGetHostName(); + if(hostName == NULL) { + struct utsname u; + int i; + uname(&u); + + // Keep only the leading part only... + for(i=0; u.nodename[i]; i++) if(u.nodename[i] == '.') { + u.nodename[i] = '\0'; + break; + } + + hostName = xStringCopyOf(u.nodename); + } + return hostName; } /** - * Sets a user-specified host name, to use instead of the system default. + * Changes the host name to the user-specified value instead of the default (leading component + * of the value returned by gethostname()). Subsequent calls to smaxGetHostName() will return + * the newly set value. An argument of NULL resets to the default. * - * @param name The user specified host name, or NULL to restore the system default. + * @param name the host name to use, or NULL to revert to the default (leading component + * of gethostname()). * * @sa smaxGetHostName() */ void smaxSetHostName(const char *name) { - redisxSetHostName(name); + char *oldName = hostName; + hostName = xStringCopyOf(name); + if(oldName) free(oldName); } /** @@ -214,7 +321,7 @@ void smaxSetHostName(const char *name) { * */ char *smaxGetProgramID() { -#if __Lynx__ +#if __Lynx__ && __powerpc__ char procName[40]; #else const char *procName; @@ -225,7 +332,7 @@ char *smaxGetProgramID() { if(programID) return programID; -#if __Lynx__ +#if __Lynx__ && __powerpc__ getProcessName(getpid(), procName, 40); #else procName = __progname; @@ -237,7 +344,6 @@ char *smaxGetProgramID() { return programID; } - /** * Returns the Redis connection information for SMA-X * @@ -251,7 +357,6 @@ Redis *smaxGetRedis() { return redis; } - /** * Checks whether SMA-X sharing is currently open (by a preceding call to smaxConnect() call. * @@ -265,49 +370,55 @@ int smaxIsConnected() { } + /** - * Initializes the SMA-X sharing library in this runtime instance, with the default Redis server address. + * Initializes the SMA-X sharing library in this runtime instance with the specified Redis server. SMA-X is + * initialized in resilient mode, so that we'll automatically attempt to reconnect to the Redis server if + * the connection is severed (once it was established). If that is not the desired behavior, you should + * call smaxSetResilient(FALSE) after connecting. * + * \param server SMA-X Redis server name or IP address, e.g. "127.0.0.1". * * \return X_SUCCESS If the library was successfully initialized - * X_ALREADY_OPEN If SMA-X sharing was already open. * X_NO_SERVICE If the there was an issue establishing the necessary network connection(s). - * X_NAME_INVALID If the default redis name lookup failed. - * X_NULL If the Redis IP address is NULL * - * @sa smaxConnectTo() - * \sa smaxDisconnect() + * @sa smaxConnect() + * @sa smaxDisconnect() * @sa smaxReconnect() * @sa smaxIsConnected() + * @sa smaxSetResilient() + * */ -int smaxConnect() { - return smaxConnectTo(SMAX_DEFAULT_HOSTNAME); -} +int smaxConnectTo(const char *server) { + static const char *fn = "smaxConnectTo"; + + prop_error(fn, smaxSetServer(server, -1)); + prop_error(fn, smaxConnect()); + return X_SUCCESS; +} /** - * Initializes the SMA-X sharing library in this runtime instance with the specified Redis server. SMA-X is - * initialized in resilient mode, so that we'll automatically attempt to reconnect to the Redis server if - * the connection is severed (once it was established). If that is not the desired behavior, you should - * call smaxSetResilient(FALSE) after connecting. + * Initializes the SMA-X sharing library in this runtime instance. * - * \param server SMA-X Redis server name or IP address, e.g. "127.0.0.1". * * \return X_SUCCESS If the library was successfully initialized + * X_ALREADY_OPEN If SMA-X sharing was already open. * X_NO_SERVICE If the there was an issue establishing the necessary network connection(s). + * X_NAME_INVALID If the redis server name lookup failed. + * X_NULL If the Redis IP address is NULL * - * @sa smaxConnect() + * @sa smaxSetServer() + * @sa smaxSetAuth() + * @sa smaxConnectTo() * @sa smaxDisconnect() * @sa smaxReconnect() * @sa smaxIsConnected() - * @sa smaxSetResilient() - * */ -int smaxConnectTo(const char *server) { +int smaxConnect() { + static const char *fn = "smaxConnect"; static int isInitialized = FALSE; - const char *funcName = "smaxConnectTo()"; - int status; smaxLockConfig(); @@ -325,12 +436,18 @@ int smaxConnectTo(const char *server) { xvprintf("SMA-X> program ID: %s\n", programID); redisxSetTcpBuf(tcpBufSize); - redis = redisxInit(server); + + redis = redisxInit(server ? server : SMAX_DEFAULT_HOSTNAME); if(redis == NULL) { smaxUnlockConfig(); - return smaxError(funcName, X_NO_INIT); + return x_trace(fn, NULL, X_NO_INIT); } + redisxSetPort(redis, serverPort); + redisxSetUser(redis, user); + redisxSetPassword(redis, auth); + redisxSelectDB(redis, dbIndex); + redisxSetTransmitErrorHandler(redis, smaxTransmitErrorHandler); smaxSetPipelineConsumer(smaxProcessPipedWritesAsync); @@ -339,6 +456,7 @@ int smaxConnectTo(const char *server) { // Initial sotrage for update notifications notifySize = 80; notifyID = (char *) calloc(1, notifySize); + x_check_alloc(notifyID); isInitialized = TRUE; } @@ -352,10 +470,13 @@ int smaxConnectTo(const char *server) { // Flush lazy cache after disconnecting from Redis. smaxAddDisconnectHook((void (*)) smaxLazyFlush); + // Release pending waits if disconnected + smaxAddDisconnectHook((void (*)) smaxReleaseWaits); + status = redisxConnect(redis, usePipeline); if(status) { smaxUnlockConfig(); - return smaxError(funcName, status); + return x_trace(fn, NULL, status); } // By default, we'll try to reconnect to Redis if the connection is severed. @@ -365,10 +486,9 @@ int smaxConnectTo(const char *server) { xvprintf("SMA-X> opened & ready.\n"); - return status; + return X_SUCCESS; } - /** * Disables the SMA-X sharing capability, closing underlying network connections. * @@ -381,7 +501,7 @@ int smaxConnectTo(const char *server) { * @sa smaxIsConnected() */ int smaxDisconnect() { - if(!smaxIsConnected()) return X_NO_INIT; + if(!smaxIsConnected()) return x_error(X_NO_INIT, ENOTCONN, "smaxDisconnect", "not connected"); redisxDisconnect(redis); @@ -390,7 +510,6 @@ int smaxDisconnect() { return X_SUCCESS; } - /** * Reconnects to the SMA-X server. It will try connecting repeatedly at regular intervals until the * connection is made. If resilient mode is enabled, then locally accumulated shares will be sent to @@ -411,7 +530,7 @@ int smaxDisconnect() { * @sa smaxAddConnectHook() */ int smaxReconnect() { - if(redis == NULL) return X_NO_INIT; + if(redis == NULL) return x_error(X_NO_INIT, ENOTCONN, "smaxReconnect", "not connected"); xvprintf("SMA-X> reconnecting.\n"); @@ -425,51 +544,58 @@ int smaxReconnect() { * Add a callback function for when SMA-X is connected. It's a wrapper to redisxAddConnectHook(). * * @param setupCall Callback function + * @return X_SUCCESS (0) or an error code (<0) from redisxAddConnectHook(). * * @sa smaxRemoveConnectHook() * @sa smaxConnect() * @sa smaxConnectTo() */ -void smaxAddConnectHook(const void (*setupCall)(void)) { - redisxAddConnectHook(smaxGetRedis(), setupCall); +int smaxAddConnectHook(void (*setupCall)(void)) { + prop_error("smaxAddConnectHook", redisxAddConnectHook(smaxGetRedis(), (void (*)(Redis *)) setupCall)); + return X_SUCCESS; } /** * Remove a post-connection callback function. It's a wrapper to redisxRemoveConnectHook(). * * @param setupCall Callback function + * @return X_SUCCESS (0) or an error code (<0) from redisxAddConnectHook(). * * @sa smaxAddConnectHook() * @sa smaxConnect() * @sa smaxConnectTo() */ -void smaxRemoveConnectHook(const void (*setupCall)(void)) { - redisxRemoveConnectHook(smaxGetRedis(), setupCall); +int smaxRemoveConnectHook(void (*setupCall)(void)) { + prop_error("smaxRemoveConnectHook", redisxRemoveConnectHook(smaxGetRedis(), (void (*)(Redis *)) setupCall)); + return X_SUCCESS; } - /** * Add a callback function for when SMA-X is disconnected. It's a wrapper to redisxAddDisconnectHook(). * * @param cleanupCall Callback function + * @return X_SUCCESS (0) or an error code (<0) from redisxAddConnectHook(). * * @sa smaxRemoveDisconnectHook() * @sa smaxDisconnect() */ -void smaxAddDisconnectHook(const void (*cleanupCall)(void)) { - redisxAddDisconnectHook(smaxGetRedis(), cleanupCall); +int smaxAddDisconnectHook(void (*cleanupCall)(void)) { + prop_error("smaxAddDisconnectHook", redisxAddDisconnectHook(smaxGetRedis(), (void (*)(Redis *)) cleanupCall)); + return X_SUCCESS; } /** * Remove a post-cdisconnect callback function. It's a wrapper to redisxRemiveDisconnectHook(). * * @param cleanupCall Callback function + * @return X_SUCCESS (0) or an error code (<0) from redisxAddConnectHook(). * * @sa smaxAddDisconnectHook() * @sa smaxDisconnect() */ -void smaxRemoveDisconnectHook(const void (*cleanupCall)(void)) { - redisxRemoveDisconnectHook(smaxGetRedis(), cleanupCall); +int smaxRemoveDisconnectHook(void (*cleanupCall)(void)) { + prop_error("smaxRemoveDisconnectHook", redisxRemoveDisconnectHook(smaxGetRedis(), (void (*)(Redis *)) cleanupCall)); + return X_SUCCESS; } /** @@ -477,13 +603,14 @@ void smaxRemoveDisconnectHook(const void (*cleanupCall)(void)) { * for redisxSetPipelineConsumer(). * * @param f The function to process ALL pipeline responses from Redis. - * @return X_SUCCESS if successful, or else an error by redisxSetPipelineConsumer() + * @return X_SUCCESS (0) if successful, or else an error by redisxSetPipelineConsumer() * * @sa smaxSetPipelined() * @sa smaxIsPipelined() */ int smaxSetPipelineConsumer(void (*f)(RESP *)) { - return redisxSetPipelineConsumer(smaxGetRedis(), f); + prop_error("smaxSetPipelineConsumer", redisxSetPipelineConsumer(smaxGetRedis(), f)); + return X_SUCCESS; } /** @@ -498,9 +625,9 @@ int smaxSetPipelineConsumer(void (*f)(RESP *)) { * * \return X_SUCCESS (0) if successful, or * X_NO_INIT if the SMA-X library was not initialized. - * X_HOST_INVALID if the host (owner ID) is NULL. - * X_NAME_INVALID if the 'key' argument is NULL. - * X_NULL_VALUE if the 'value' argument is NULL. + * X_GROUP_INVALID if the 'table' argument is invalid. + * X_NAME_INVALID if the 'key' argument is invalid. + * X_NULL if an essential argument is NULL or contains NULL. * X_NO_SERVICE if there was no connection to the Redis server. * X_FAILURE if there was an underlying failure. * @@ -508,15 +635,24 @@ int smaxSetPipelineConsumer(void (*f)(RESP *)) { * @sa smaxQueue() */ int smaxPull(const char *table, const char *key, XType type, int count, void *value, XMeta *meta) { + static const char *fn = "smaxPull"; + PullRequest *data; + char *id = NULL; int status; + if(type == X_STRUCT) { + id = xGetAggregateID(table, key); + if(!id) return x_trace(fn, NULL, X_NULL); + } + data = (PullRequest *) calloc(1, sizeof(PullRequest)); + x_check_alloc(data); // Make sure structures are retrieved all the same no matter how their names are split // into group + key. if(type == X_STRUCT) { - data->group = xGetAggregateID(table, key); + data->group = id; data->key = NULL; } else { @@ -529,17 +665,16 @@ int smaxPull(const char *table, const char *key, XType type, int count, void *va data->count = count; data->meta = meta; - status = smaxRead(data, INTERACTIVE_CHANNEL); + status = smaxRead(data, REDISX_INTERACTIVE_CHANNEL); //if(status) smaxZero(value, type, count); smaxDestroyPullRequest(data); - if(status) return smaxError("smaxPull()", status); + prop_error(fn, status); return X_SUCCESS; } - /** * Share the data into a Redis hash table over the interactive Redis client. It's a fire-and-forget * type implementation, which sends the data to Redis, without waiting for confirmation of its arrival. @@ -559,10 +694,10 @@ int smaxPull(const char *table, const char *key, XType type, int count, void *va * * \return X_SUCCESS (0) if successful, or * X_NO_INIT if the SMA-X library was not initialized. - * X_HOST_INVALID if the host (owner ID) is NULL. - * X_NAME_INVALID if the 'key' argument is NULL. + * X_GROUP_INVALID if the table name is invalid. + * X_NAME_INVALID if the 'key' argument is invalid. * X_SIZE_INVALID if count < 1 or count > X_MAX_ELEMENTS - * X_NULL_VALUE if the 'value' argument is NULL. + * X_NULL if the 'value' argument is NULL. * X_NO_SERVICE if there was no connection to the Redis server. * X_FAILURE if there was an underlying failure. * @@ -571,48 +706,48 @@ int smaxPull(const char *table, const char *key, XType type, int count, void *va * \sa smaxShareStruct() */ int smaxShare(const char *table, const char *key, const void *value, XType type, int count) { - return smaxShareArray(table, key, value, type, 1, &count); + prop_error("smaxShare", smaxShareArray(table, key, value, type, 1, &count)); + return X_SUCCESS; } - /** - * Share a multidimensional array, such as an int[][][], or float[][], in a single atomic + * Share a multidimensional array, such as an `int[][][]`, or `float[][]`, in a single atomic * transaction. * * \param table Hash table in which to write entry. * \param key Variable name under which the data is stored. - * \param ptr Pointer to the data buffer, such as an int[][][] or float[][]. + * \param ptr Pointer to the data buffer, such as an `int[][][]` or `float[][]`. * \param type SMA-X variable type, e.g. X_FLOAT or X_CHARS(40), of the buffer. - * \param ndim Dimensionality of the data (0 <= ndim <= X_MAX_DIMS). + * \param ndim Dimensionality of the data (0 <= `ndim` <= X_MAX_DIMS). * \param sizes An array of ints containing the sizes along each dimension. * * \return X_SUCCESS (0) if successful, or * X_NO_INIT if the SMA-X library was not initialized. - * X_HOST_INVALID if the host (owner ID) is NULL. - * X_NAME_INVALID if the 'key' argument is NULL. + * X_GROUP_INVALID if the table name is invalid. + * X_NAME_INVALID if the 'key' argument is invalid. * X_SIZE_INVALID if ndim or sizes are invalid. - * X_NULL_VALUE if the 'value' argument is NULL. + * X_NULL if the 'value' argument is NULL. * X_NO_SERVICE if there was no connection to the Redis server. * X_FAILURE if there was an underlying failure. * * @sa smaxShare() */ int smaxShareArray(const char *table, const char *key, const void *ptr, XType type, int ndim, const int *sizes) { - const char *funcName = "smaxShareArray()"; + static const char *fn = "smaxShareArray"; - XField f = {0}; - char trybuf[REDIS_CMDBUF_SIZE]; + XField f = {}; + char trybuf[REDISX_CMDBUF_SIZE]; int count, status; - if(ndim < 0 || ndim > X_MAX_DIMS) return smaxError(funcName, X_SIZE_INVALID); count = xGetElementCount(ndim, sizes); - if(count < 1 || count > X_MAX_ELEMENTS) return smaxError(funcName, X_SIZE_INVALID); + prop_error(fn, count); + + if(count < 1 || count > X_MAX_ELEMENTS) return x_error(X_SIZE_INVALID, EINVAL, fn, "invalid element count: %d", count); f.value = (type == X_RAW || type == X_STRUCT) ? - (char *) ptr : - smaxValuesToString(ptr, type, count, trybuf, REDIS_CMDBUF_SIZE); + (char *) ptr : smaxValuesToString(ptr, type, count, trybuf, REDISX_CMDBUF_SIZE); - if(f.value == NULL) return smaxError(funcName, X_NULL); + if(f.value == NULL) return x_trace(fn, NULL, X_NULL); f.isSerialized = TRUE; f.name = (char *) key; @@ -624,9 +759,10 @@ int smaxShareArray(const char *table, const char *key, const void *ptr, XType ty if(f.value != trybuf) if(type != X_RAW && type != X_STRUCT) free(f.value); - return status; -} + prop_error(fn, status); + return X_SUCCESS; +} /** * Share a field object, which may contain any SMA-X data type. @@ -636,10 +772,10 @@ int smaxShareArray(const char *table, const char *key, const void *ptr, XType ty * * \return X_SUCCESS (0) if successful, or * X_NO_INIT if the SMA-X library was not initialized. - * X_HOST_INVALID if the host (owner ID) is NULL. - * X_NAME_INVALID if the 'key' argument is NULL. + * X_GROUP_INVALID if the table name is invalid. + * X_NAME_INVALID if the 'key' argument is invalid. * X_SIZE_INVALID if ndim or sizes are invalid. - * X_NULL_VALUE if the 'value' argument is NULL. + * X_NULL if the 'value' argument is NULL. * X_NO_SERVICE if there was no connection to the Redis server. * X_FAILURE if there was an underlying failure. * @@ -650,25 +786,26 @@ int smaxShareArray(const char *table, const char *key, const void *ptr, XType ty * @sa xGetField() */ int smaxShareField(const char *table, const XField *f) { + static const char *fn = "smaxShareField"; + int status; if(f->type == X_STRUCT) { char *id = xGetAggregateID(table, f->name); status = smaxShareStruct(id, (XStructure *) f->value); if(id != NULL) free(id); - return status; + return x_trace(fn, NULL, status); } status = smaxWrite(table, f); if(status) { - if(status == X_NO_SERVICE) smaxStorePush(table, f); - return smaxError("smaxShareField()", status); + if(status == X_NO_SERVICE) status = smaxStorePush(table, f); + return x_trace(fn, NULL, status); } return X_SUCCESS; } - /** * Sends a structure to Redis, and all its data including recursive * sub-structures, in a single atromic transaction. @@ -678,9 +815,9 @@ int smaxShareField(const char *table, const XField *f) { * * \return X_SUCCESS (0) if successful, or * X_NO_INIT if the SMA-X library was not initialized. - * X_HOST_INVALID if the host (owner ID) is NULL. + * X_GROUP_INVALID if the table name is invalid. * X_NAME_INVALID if the 'key' argument is NULL. - * X_NULL_VALUE if the 'value' argument is NULL. + * X_NULL if the 'value' argument is NULL. * X_NO_SERVICE if there was no connection to the Redis server. * X_FAILURE if there was an underlying failure. * \sa smaxShare() @@ -688,16 +825,12 @@ int smaxShareField(const char *table, const XField *f) { * \sa xCreateStruct() */ static int SendStruct(const char *id, const XStructure *s) { - const char *funcName = "SendStruct()"; + static const char *fn = "SendStruct"; RedisClient *cl = redis->interactive; int status; - if(id == NULL) return smaxError(funcName, X_GROUP_INVALID); - if(s == NULL) return smaxError(funcName, X_NULL); - if(!redisxIsConnected(redis)) return smaxError(funcName, X_NO_SERVICE); - - status = redisxLockEnabled(cl); + status = redisxLockConnected(cl); if(!status) { // TODO the following should be done atomically, but multi/exec blocks don't work // with evalsha(?)... @@ -707,7 +840,9 @@ static int SendStruct(const char *id, const XStructure *s) { redisxUnlockClient(cl); } - return status ? smaxError(funcName, status) : X_SUCCESS; + prop_error(fn, status); + + return X_SUCCESS; } /** @@ -719,9 +854,9 @@ static int SendStruct(const char *id, const XStructure *s) { * * \return X_SUCCESS (0) if successful, or * X_NO_INIT if the SMA-X library was not initialized. - * X_HOST_INVALID if the host (owner ID) is NULL. - * X_NAME_INVALID if the 'key' argument is NULL. - * X_NULL_VALUE if the 'value' argument is NULL. + * X_GROUP_INVALID if the table name is invalid. + * X_NAME_INVALID if the 'key' argument is invalid. + * X_NULL if the 'value' argument is NULL. * X_NO_SERVICE if there was no connection to the Redis server. * X_FAILURE if there was an underlying failure. * \sa smaxShare() @@ -729,18 +864,22 @@ static int SendStruct(const char *id, const XStructure *s) { * \sa xCreateStruct() */ int smaxShareStruct(const char *id, const XStructure *s) { + static const char *fn = "smaxShareStruct"; + int status = SendStruct(id, s); if(status == X_NO_SERVICE) { XField *f = smaxCreateField(id, X_STRUCT, 0, NULL, s); if(f) { - smaxStorePush(NULL, f); + status = smaxStorePush(NULL, f); free(f); } } - return status; + prop_error(fn, status); + + return X_SUCCESS; } /** @@ -772,11 +911,10 @@ int smaxSubscribe(const char *table, const char *key) { char *p = smaxGetUpdateChannelPattern(table, key); int status = redisxSubscribe(redis, p); free(p); - if(status) return smaxError("smaxSubscribe()", status); + prop_error("smaxSubscribe", status); return X_SUCCESS; } - /** * Unsubscribes from a specific key(s) in specific group(s). Both the group and key names may contain Redis * subscription patterns, e.g. '*' or '?', or bound characters in square-brackets, e.g. '[ab]'. Unsubscribing @@ -800,12 +938,10 @@ int smaxUnsubscribe(const char *table, const char *key) { char *p = smaxGetUpdateChannelPattern(table, key); int status = redisxUnsubscribe(redis, p); free(p); - if(status) return smaxError("smaxUnsubscribe()", status); + prop_error("smaxUnsubscribe", status); return X_SUCCESS; } - - /** * Add a subcriber (callback) function to process incoming PUB/SUB messages for a given SMA-X table (or id). The * function should itself check that the channel receiving notification is indeed what it expectes before @@ -826,10 +962,12 @@ int smaxUnsubscribe(const char *table, const char *key) { * * @sa smaxSubscribe() */ -void smaxAddSubscriber(const char *idStem, RedisSubscriberCall f) { +int smaxAddSubscriber(const char *idStem, RedisSubscriberCall f) { char *stem = xGetAggregateID(SMAX_UPDATES_ROOT, idStem); - redisxAddSubscriber(smaxGetRedis(), stem, f); + int status = redisxAddSubscriber(smaxGetRedis(), stem, f); free(stem); + prop_error("smaxAddSubscriber", status); + return X_SUCCESS; } /** @@ -839,15 +977,15 @@ void smaxAddSubscriber(const char *idStem, RedisSubscriberCall f) { * for variables that no longer have associated callbacks. * * @param f Function to remove - * @return X_SUCCESS if successful, or else an error returned by redisxRemoveSubscriber(). + * @return X_SUCCESS (0) if successful, or else an error (<0) returned by redisxRemoveSubscriber(). * * @sa smaxUnsubscribe() */ -void smaxRemoveSubscribers(RedisSubscriberCall f) { - redisxRemoveSubscribers(smaxGetRedis(), f); +int smaxRemoveSubscribers(RedisSubscriberCall f) { + prop_error("smaxRemoveSubscribers", redisxRemoveSubscribers(smaxGetRedis(), f)); + return X_SUCCESS; } - /** * \cond PROTECTED * @@ -866,10 +1004,12 @@ char *smaxGetUpdateChannelPattern(const char *table, const char *key) { if(table == NULL) table = "*"; if(key == NULL) { p = (char *) malloc(sizeof(SMAX_UPDATES) + strlen(table)); + x_check_alloc(p); sprintf(p, SMAX_UPDATES "%s", table); } else { p = (char *) malloc(sizeof(SMAX_UPDATES) + strlen(table) + X_SEP_LENGTH + strlen(key)); + x_check_alloc(p) sprintf(p, SMAX_UPDATES "%s" X_SEP "%s", table, key); } return p; @@ -889,7 +1029,8 @@ char *smaxGetUpdateChannelPattern(const char *table, const char *key) { * * \return X_SUCCESS (0) if a variable was pushed on a host. * X_NO_INIT if the SMA-X sharing was not initialized via smaxConnect(). - * X_HOST_INVALID if the buffer for the returned host name is NULL. + * X_NO_SERVICE if the connection was broken + * X_GROUP_INVALID if the buffer for the returned table name is NULL. * X_NAME_INVALID if the buffer for the returned variable name is NULL. * X_INTERRUPTED if smaxReleaseWaits() was called. * X_INCOMPLETE if the wait timed out. @@ -901,12 +1042,12 @@ char *smaxGetUpdateChannelPattern(const char *table, const char *key) { * */ int smaxWaitOnAnySubscribed(char **changedTable, char **changedKey, int timeout) { - const char *funcName = "smaxWaitOnAnySubscribed()"; + static const char *fn = "smaxWaitOnAnySubscribed"; int status = X_SUCCESS; struct timespec endTime; - if(changedTable == NULL) return smaxError(funcName, X_GROUP_INVALID); - if(changedKey == NULL) return smaxError(funcName, X_NAME_INVALID); + if(changedTable == NULL) return x_error(X_GROUP_INVALID, EINVAL, fn, "'changedTable' parameter is NULL"); + if(changedKey == NULL) return x_error(X_NAME_INVALID, EINVAL, fn, "'changedKey' parameter is NULL"); xvprintf("SMA-X> waiting for notification...\n"); @@ -927,24 +1068,28 @@ int smaxWaitOnAnySubscribed(char **changedTable, char **changedKey, int timeout) status = timeout > 0 ? pthread_cond_timedwait(¬ifyBlock, ¬ifyLock, &endTime) : pthread_cond_wait(¬ifyBlock, ¬ifyLock); if(status) { // If the wait returns with an error, the mutex is uncloked. - if(status == ETIMEDOUT) return X_INCOMPLETE; - - fprintf(stderr, "WARNING! SMA-X : pthread_cond_wait() error %d. Ignored.\n", status); + if(status == ETIMEDOUT) return x_error(X_INCOMPLETE, status, fn, "wait timed out"); + xdprintf("WARNING! SMA-X : pthread_cond_wait() error %d. Ignored.\n", status); continue; } + if(!smaxIsConnected()) { + smaxUnlockNotify(); + return x_error(X_NO_SERVICE, EPIPE, fn, "wait aborted due to broken connection"); + } + // Check for premature release... if(!strcmp(notifyID, RELEASEID)) { smaxUnlockNotify(); - return smaxError(funcName, X_INTERRUPTED); + return x_error(X_INTERRUPTED, EINTR, fn, "wait interrupted"); } if(notifyID[0] == '\0') { - fprintf(stderr, "WARNING! SMA-X : published message contained NULL. Ignored.\n"); + xdprintf("WARNING! SMA-X : published message contained NULL. Ignored.\n"); continue; } - xvprintf("SMA-X> %s: got %s.\n", funcName, notifyID); + xvprintf("SMA-X> %s: got %s.\n", fn, notifyID); // Find the last separator sep = xLastSeparator(notifyID); @@ -963,17 +1108,15 @@ int smaxWaitOnAnySubscribed(char **changedTable, char **changedKey, int timeout) smaxUnlockNotify(); - if(status) return smaxError(funcName, status); + prop_error(fn, status); return X_SUCCESS; } - /** - * Unblocks all sma_wait*() calls, which will return X_REL_PREMATURE, as a result. + * Unblocks all smax_wait*() calls, which will return X_REL_PREMATURE, as a result. * - * \return X_SUCCESS (0) if a variable was pushed on a host. - * X_NO_INIT if the SMA-X sharing was not initialized, e.g. via smaxConnect(). + * \return X_SUCCESS (0) * * \sa smaxWaitOnAnySubscribed() * @@ -987,23 +1130,23 @@ int smaxReleaseWaits() { char *oldid = notifyID; notifyID = realloc(notifyID, sizeof(RELEASEID)); if(!notifyID) { - perror("WARNING! realloc notifyID"); + perror("ERROR! alloc error"); free(oldid); + exit(errno); } - notifySize = notifyID ? sizeof(RELEASEID) : 0; + notifySize = sizeof(RELEASEID); } if(notifyID) { strcpy(notifyID, RELEASEID); pthread_cond_broadcast(¬ifyBlock); } + smaxUnlockNotify(); return X_SUCCESS; } - - /** * Retrieve the current number of variables stored on host (or owner ID). * @@ -1011,64 +1154,65 @@ int smaxReleaseWaits() { * * \return The number of keys (fields) in the specified table (>= 0), or an error code (<0), such as: * X_NO_INIT if the SMA-X sharing was not initialized, e.g. via smaConnect(). - * X_HOST_INVALID if the host (owner ID) is NULL. - * or one of the errors returned by redisxArrayRequest(). + * X_GROUP_INVALID if the table name is invalid. + * or one of the errors (<0) returned by redisxRequest(). * * @sa smaxGetKeys() */ int smaxKeyCount(const char *table) { - const char *funcName = "smaNumVars()"; + static const char *fn = "smaxKeyCount"; RESP *reply; int status; - if(table == NULL) return smaxError(funcName, X_GROUP_INVALID); + if(table == NULL) return x_error(X_GROUP_INVALID, EINVAL, fn, "table is NULL"); + if(!table[0]) return x_error(X_GROUP_INVALID, EINVAL, fn, "table is empty"); reply = redisxRequest(redis, "HLEN", table, NULL, NULL, &status); if(status) { redisxDestroyRESP(reply); - return smaxError(funcName, status); + return x_trace(fn, NULL, status); } status = redisxCheckRESP(reply, RESP_INT, 0); - if(status) return redisxError(funcName, status); - - status = reply->n; + if(!status) status = reply->n; redisxDestroyRESP(reply); - if(status < 0) return smaxError(funcName, status); - - xvprintf("SMA-X> Get number of variables: %d.\n", reply->n); + prop_error(fn, status); - return reply->n; + xvprintf("SMA-X> Get number of variables: %d.\n", status); + return status; } - /** * Returns a snapshot of the key names stored in a given Redis hash table, ot NULL if there * was an error. * - * \param[out] table Host name or owner ID whose variable to count. + * \param table Host name or owner ID whose variable to count. * \param[out] n Pointer to which the number of keys (>=0) or an error (<0) is returned. * An error returned by redisxGetKeys(), or else: * * X_NO_INIT if the SMA-X sharing was not initialized, e.g. via smaxConnect(). - * X_HOST_INVALID if the host (owner ID) is NULL. + * X_GROUP_INVALID if the table name is invalid. + * X_NULL if the output 'n' pointer is NULL. * * \return An array of pointers to the names of Redis keys. * * @sa smaxKeyCount() */ char **smaxGetKeys(const char *table, int *n) { + static const char *fn = "smaxGetKeys"; + char **keys; - if(table == NULL) { - *n = X_GROUP_INVALID; + if(n == NULL) { + x_error(0, EINVAL, fn, "parameter 'n' is NULL"); return NULL; } xvprintf("SMA-X> get variable names.\n"); keys = redisxGetKeys(redis, table, n); + if(*n > 0) return keys; // CLEANUP --- There was an error. @@ -1077,41 +1221,48 @@ char **smaxGetKeys(const char *table, int *n) { for(i=*n; --i >= 0; ) if(keys[i]) free(keys[i]); free(keys); } + + if(*n < 0) x_trace_null(fn, NULL); + *n = 0; + return NULL; } - /** * \cond PROTECTED * - * Retrieves data from the SMA-X database, interatively or as a pipelined request. + * Retrieves data from the SMA-X database, interactively or as a pipelined request. * * \param[in,out] req Pull request - * \param[in] channel INTERACTIVE_CHANNEL or PIPELINE_CHANNEL + * \param[in] channel REDISX_INTERACTIVE_CHANNEL or REDISX_PIPELINE_CHANNEL * * \return X_SUCCESS (0) if successful, or * X_NULL if the request or its value field is NULL - * X_N_INIT if the SMA-X library was not initialized. - * X_HOST_INVALID if the host (owner ID) is NULL. - * X_GROUP_INVALID if the 'group' argument is NULL. + * X_NO_INIT if the SMA-X library was not initialized. + * X_GROUP_INVALID if the table name is invalid. + * X_NAME_INVALID if the 'key argument is invalid. * X_NO_SERVICE if there was no connection to the Redis server. * X_FAILURE if there was an underlying failure. */ int smaxRead(PullRequest *req, int channel) { - const char *funcName = "smaxRead()"; + static const char *fn = "smaxRead"; + char *args[5], *script = NULL; RESP *reply = NULL; RedisClient *cl; int status, n = 0; - if(req == NULL) return smaxError(funcName, X_NULL); - if(req->group == NULL) return smaxError(funcName, X_GROUP_INVALID); - if(req->value == NULL) return smaxError(funcName, X_NULL); - if(redis == NULL) smaxError(funcName, X_NO_INIT); - if(!redisxIsConnected(redis)) return smaxError(funcName, X_NO_SERVICE); + if(req == NULL) return x_error(X_NULL, EINVAL, fn, "'req' is NULL"); + if(req->group == NULL) return x_error(X_GROUP_INVALID, EINVAL, fn, "req->group is NULL"); + if(!req->group[0]) return x_error(X_GROUP_INVALID, EINVAL, fn, "req->group is empty"); + if(req->value == NULL) return x_error(X_NULL, EINVAL, fn, "req->value is NULL"); + if(req->type != X_STRUCT) { + if(req->key == NULL) return x_error(X_NAME_INVALID, EINVAL, fn, "req->group is NULL"); + if(!req->key[0]) return x_error(X_NAME_INVALID, EINVAL, fn, "req->group is empty"); + } - xvprintf("SMA-X> read %s:%s.\n", (req->group == NULL ? "" : req->group), req->key); + xvprintf("SMA-X> read %s:%s.\n", (req->group ? req->group : ""), (req->key ? req->key : "")); script = (req->type == X_STRUCT) ? GET_STRUCT : HGET_WITH_META; @@ -1138,43 +1289,38 @@ int smaxRead(PullRequest *req, int channel) { args[n++] = req->key; } - cl = redisxGetClient(redis, channel); - if(cl == NULL) return smaxError(funcName, X_NO_SERVICE); - - status = redisxLockEnabled(cl); - if(status) return smaxError(funcName, status); + cl = redisxGetLockedConnectedClient(redis, channel); + if(cl == NULL) return x_trace(fn, NULL, X_NO_SERVICE); // Call script status = redisxSendArrayRequestAsync(cl, args, NULL, n); - if(channel != PIPELINE_CHANNEL) if(!status) reply = redisxReadReplyAsync(cl); + if(channel != REDISX_PIPELINE_CHANNEL) if(!status) reply = redisxReadReplyAsync(cl); redisxUnlockClient(cl); // Process reply as needed... - if(channel != PIPELINE_CHANNEL) if(!status) { + if(reply) { // Process the value status = smaxProcessReadResponse(reply, req); redisxDestroyRESP(reply); } - if(status) return smaxError(funcName, status); + prop_error(fn, status); return X_SUCCESS; } /// \endcond - /** * Private error handling for xProcessReadResponse(). Not used otherwise. * */ static int RequestError(const PullRequest *req, int status) { if(req->meta) req->meta->status = status; - return smaxError("xProcessReadResponse()", status); + return status; } - /** * \cond PROTECTED * @@ -1183,16 +1329,18 @@ static int RequestError(const PullRequest *req, int status) { * \param[in] reply String content of the Redis response to a read. * \param[in,out] req Pointer to a PullRequest structure to be completed with the data. * - * \return X_SUCCESS (0) if successful, or else an appropriate error - * such as expected for sma_pull(). + * \return X_SUCCESS (0) if successful, or else an appropriate error code (<0) + * such as expected for smax_pull(). * */ int smaxProcessReadResponse(RESP *reply, PullRequest *req) { + static const char *fn = "smaxProcessReadResponse"; + int status = X_SUCCESS; RESP *data = NULL; - if(req == NULL) return smaxError("smaxProcessReadResponse()", X_NULL); - if(reply == NULL) return RequestError(req, REDIS_NULL); + if(reply == NULL) return x_error(RequestError(req, REDIS_NULL), ENOENT, fn, "Redis NULL response"); + if(req == NULL) return x_error(X_NULL, EINVAL, fn, "'req' is NULL"); // Clear metadata if requested. if(req->meta != NULL) smaxResetMeta(req->meta); @@ -1201,7 +1349,7 @@ int smaxProcessReadResponse(RESP *reply, PullRequest *req) { if(req->type == X_RAW || req->type == X_STRUCT) req->count = 1; // Check that request is not crazy. - if(req->count <= 0) return RequestError(req, X_SIZE_INVALID); + if(req->count <= 0) return x_error(RequestError(req, X_SIZE_INVALID), ERANGE, fn, "invalid req->count: %d", req->count); // (nil) if(reply->n < 0) { @@ -1211,7 +1359,7 @@ int smaxProcessReadResponse(RESP *reply, PullRequest *req) { } // If we had a NULL value without n < 0, then it's an error. - if(!req->value) return RequestError(req, X_NULL); + if(!req->value) return x_error(RequestError(req, X_NULL), ENOENT, fn, "unexpected NULL value"); xvprintf("SMA-X> received %s:%s.\n", req->group == NULL ? "" : req->group, req->key); // If expecting metadata, then initialize the destination metadata to defaults. @@ -1239,7 +1387,7 @@ int smaxProcessReadResponse(RESP *reply, PullRequest *req) { XMeta *m = req->meta; m->storeBytes = component[0]->n; - if(reply->n > 1) m->storeType = xTypeForString((char *) component[1]->value); + if(reply->n > 1) m->storeType = smaxTypeForString((char *) component[1]->value); if(reply->n > 2) m->storeDim = xParseDims((char *) component[2]->value, m->storeSizes); if(reply->n > 3) smaxParseTime((char *) component[3]->value, &m->timestamp.tv_sec, &m->timestamp.tv_nsec); if(reply->n > 4) smaxSetOrigin(m, (char *) component[4]->value); @@ -1264,26 +1412,26 @@ int smaxProcessReadResponse(RESP *reply, PullRequest *req) { } else if(req->type == X_STRING) { // Unpack strings - xUnpackStrings((char *) data->value, data->n, req->count, (char **) req->value); + smaxUnpackStrings((char *) data->value, data->n, req->count, (char **) req->value); } else { // Unpack fixed-sized types. int parsed; status = smaxStringToValues((char *) data->value, req->value, req->type, req->count, &parsed); - if(status >= 0) status = X_SUCCESS; // Keeps errors only, not the number of elements parsed. } } if(reply->type == RESP_ERROR) if(strstr("NOSCRIPT", (char *) reply->value)) return smaxScriptError("smaxProcessReadResponse()", X_NULL); - if(status) return RequestError(req, status); + prop_error(fn, RequestError(req, status)); + // Keeps errors only, not the number of elements parsed. return X_SUCCESS; } /// \endcond static int ProcessStructRead(RESP **component, PullRequest *req) { - const char *funcName = "xProcessStructRead()"; + static const char *fn = "xProcessStructRead"; XStructure *base = (XStructure *) req->value; XStructure **s; @@ -1293,11 +1441,17 @@ static int ProcessStructRead(RESP **component, PullRequest *req) { nStructs = component[0]->n; - if(nStructs <= 0) return smaxError(funcName, X_STRUCT_INVALID); + if(nStructs <= 0) return x_error(X_STRUCT_INVALID, EINVAL, fn, "invalid number of structures: %d", nStructs); // Allocate temporary storage names = (char **) calloc(nStructs, sizeof(char *)); + if(!names) return x_error(X_NULL, errno, fn, "calloc() error (%d char *)", nStructs); + metas = (XMeta *) calloc(nStructs, sizeof(XMeta)); + if(!metas) { + free(names); + return x_error(X_NULL, errno, fn, "calloc() error (%d char *)", nStructs); + } if(req->meta != NULL) { smaxResetMeta(req->meta); @@ -1308,6 +1462,11 @@ static int ProcessStructRead(RESP **component, PullRequest *req) { // Parse all structure data (for embedded structures) s = (XStructure **) calloc(nStructs, sizeof(XStructure *)); + if(!s) { + free(metas); + free(names); + return x_error(X_NULL, errno, fn, "calloc() error (%d XStructure)", nStructs); + } for(i=0; ivalue; @@ -1376,29 +1535,28 @@ static int ProcessStructRead(RESP **component, PullRequest *req) { free(metas); free(s); - if(status) return smaxError(funcName, status); + prop_error(fn, status); return X_SUCCESS; } - static int ParseStructData(XStructure *s, RESP *names, RESP *data, XMeta *meta) { - const char *funcName = "xParseStructData()"; + static const char *fn = "xParseStructData"; RESP **component, **keys, **values, **types, **dims, **timestamps, **origins, **serials; int i; - if(names == NULL) return smaxError(funcName, X_NULL); - if(data == NULL) return smaxError(funcName, X_NULL); - if(names->type != RESP_ARRAY) return smaxError(funcName, REDIS_UNEXPECTED_RESP); - if(data->type != RESP_ARRAY) return smaxError(funcName, REDIS_UNEXPECTED_RESP); - if(data->n != HMGET_COMPONENTS) return smaxError(funcName, X_NOT_ENOUGH_TOKENS); + if(names == NULL) return x_error(X_NULL, EINVAL, fn, "RESP names is NULL"); + if(data == NULL) return x_error(X_NULL, EINVAL, fn, "RESP data is NULL"); + if(names->type != RESP_ARRAY) return x_error(REDIS_UNEXPECTED_RESP, EINVAL, fn, "RESP names is not an array: '%c'", names->type); + if(data->type != RESP_ARRAY) return x_error(REDIS_UNEXPECTED_RESP, EINVAL, fn, "RESP data is not an array: '%c'", data->type); + if(data->n != HMGET_COMPONENTS) return x_error(X_NOT_ENOUGH_TOKENS, ERANGE, fn, "RESP data size: expected %d, got %d", HMGET_COMPONENTS, data->n); component = (RESP **) data->value; for(i=HMGET_COMPONENTS; --i >= 0; ) { - if(component[i]->type != RESP_ARRAY) return smaxError(funcName, REDIS_UNEXPECTED_RESP); - if(component[i]->n != names->n) return smaxError(funcName, X_NOT_ENOUGH_TOKENS); + if(component[i]->type != RESP_ARRAY) return x_error(REDIS_UNEXPECTED_RESP, EINVAL, fn, "RESP component[%d] is not an array: '%c'", i, component[i]->type); + if(component[i]->n != names->n) return x_error(X_NOT_ENOUGH_TOKENS, ERANGE, fn, "RESP component[%d] wrong size: expected %d, got %d", i, names->n, component[i]->n); } if(meta != NULL) smaxResetMeta(meta); @@ -1422,7 +1580,7 @@ static int ParseStructData(XStructure *s, RESP *names, RESP *data, XMeta *meta) f->value = (char *) values[i]->value; values[i]->value = NULL; // Dereference the RESP data so it does not get destroyed with RESP. - f->type = xTypeForString((char *) types[i]->value); + f->type = smaxTypeForString((char *) types[i]->value); f->ndim = xParseDims((char *) dims[i]->value, f->sizes); xSetField(s, f); @@ -1446,12 +1604,10 @@ static int ParseStructData(XStructure *s, RESP *names, RESP *data, XMeta *meta) return X_SUCCESS; } - /** - * * \cond PROTECTED * - * Sends a write request to REDIS over the specified communication channel. The caller + * Sends a write request to Redis over the specified communication channel. The caller * is responsible for granting exclusive access to that channel before calling this * function in order to avoid clobber in a parallel environment. * @@ -1468,32 +1624,30 @@ static int ParseStructData(XStructure *s, RESP *names, RESP *data, XMeta *meta) * \param f XField value to write (it cannot be an XStructure!) * * \return X_SUCCESS if successful, or one of the errors returned by setRedisValue(), or - * X_NO_INIT if the SMA-X sharing was not initialized (via sma_x_open()). - * X_HOST_INVALID if the host (owner ID) is NULL. - * X_NAME_INVALID if the 'key' arhument is NULL. + * X_NO_INIT if the SMA-X sharing was not initialized (via smax_x_open()). + * X_GROUP_INVALID if the table name is invalid. + * X_NAME_INVALID if the field's name is invalid. * X_SIZE_INVALID if ndim or sizes is invalid or if the total element count exceeds * X_MAX_ELEMENTS. * X_NULL if the 'value' argument is NULL. */ int smaxWrite(const char *table, const XField *f) { - const char *funcName = "smaxWrite()"; + static const char *fn = "smaxWrite"; int status; char *args[9]; char dims[X_MAX_STRING_DIMS]; RedisClient *cl; - if(table == NULL) return smaxError(funcName, X_GROUP_INVALID); - if(f->name == NULL) return smaxError(funcName, X_NAME_INVALID); - if(f->value == NULL) return smaxError(funcName, X_NULL); - if(f->ndim < 0 || f->ndim > X_MAX_DIMS) return smaxError(funcName, X_SIZE_INVALID); - if(xGetFieldCount(f) <= 0) return smaxError(funcName, X_SIZE_INVALID); - if(redis == NULL) return smaxError(funcName, X_NO_INIT); - if(!redisxIsConnected(redis)) return smaxError(funcName, X_NO_SERVICE); + if(table == NULL) return x_error(X_GROUP_INVALID, EINVAL, fn, "table is NULL"); + if(!table[0]) return x_error(X_GROUP_INVALID, EINVAL, fn, "table is empty"); + if(f->name == NULL) return x_error(X_NAME_INVALID, EINVAL, fn, "field->name is NULL"); + if(!f->name[0]) return x_error(X_NAME_INVALID, EINVAL, fn, "field->name is empty"); + if(f->value == NULL) return x_error(X_NAME_INVALID, EINVAL, fn, "field->value is NULL"); if(HSET_WITH_META == NULL) return smaxScriptError("HSetWithMeta", X_NULL); // Create timestamped string values. - if(f->type == X_STRUCT) return smaxError(funcName, X_TYPE_INVALID); + if(f->type == X_STRUCT) return x_error(X_TYPE_INVALID, EINVAL, fn, "structures not supported"); xPrintDims(dims, f->ndim, f->sizes); @@ -1504,42 +1658,42 @@ int smaxWrite(const char *table, const XField *f) { args[4] = smaxGetProgramID(); args[5] = f->name; // hash field name. args[6] = f->value; // Value. If not serialized, we'll deal with it below... - args[7] = xStringType(f->type); + args[7] = smaxStringType(f->type); args[8] = dims; - cl = redisxGetClient(redis, INTERACTIVE_CHANNEL); - if(cl == NULL) return smaxError(funcName, X_NO_SERVICE); - if(!f->isSerialized) { + int count = xGetFieldCount(f); + prop_error(fn, count); + args[6] = smaxValuesToString(f->value, f->type, xGetFieldCount(f), NULL, 0); - if(!args[6]) return smaxError(funcName, X_NULL); + if(!args[6]) return x_trace(fn, NULL, X_NULL); } - status = redisxLockEnabled(cl); + cl = redisxGetLockedConnectedClient(redis, REDISX_INTERACTIVE_CHANNEL); + if(cl == NULL) { + if(!f->isSerialized) if(f->type != X_RAW) free(args[6]); + return x_trace(fn, NULL, X_NO_SERVICE); + } + + // Writes not to request reply. + status = redisxSkipReplyAsync(cl); if(!status) { - // Writes not to request reply. - status = redisxSkipReplyAsync(cl); + int L[9] = {0}; // Call script - if(!status) { - int L[9] = {0}; - status = redisxSendArrayRequestAsync(cl, args, L, 9); - } - - if(status) smaxError(funcName, status); - - redisxUnlockClient(cl); + status = redisxSendArrayRequestAsync(cl, args, L, 9); } + redisxUnlockClient(cl); + if(!f->isSerialized) if(f->type != X_RAW) free(args[6]); - if(status) return smaxError(funcName, status); + prop_error(fn, status); return X_SUCCESS; } /// \endcond - /** * Writes the structure data, recursively for nested sub-structures, into the database, by calling * the HMGetWithMeta for setting all fields of each component structure. @@ -1552,18 +1706,18 @@ int smaxWrite(const char *table, const XField *f) { * should usually set this TRUE, and as the call processes nested * substructures, the recursion will make those calls with the parameter set to FALSE. * - * \return X_SUCCESS, or else an appropriate error code. + * \return X_SUCCESS (0), or else an appropriate error code (<0). */ static int SendStructDataAsync(RedisClient *cl, const char *id, const XStructure *s, boolean isTop) { - const char *funcName = "xSendStructDataAsync()"; + static const char *fn = "xSendStructDataAsync"; int status = X_SUCCESS, nFields = 0, *L, n, next; char **args; XField *f; - if(id == NULL) return smaxError(funcName, X_GROUP_INVALID); - if(s == NULL) return smaxError(funcName, X_NULL); - if(!redisxIsConnected(redis)) return smaxError(funcName, X_NO_SERVICE); + if(id == NULL) return x_error(X_GROUP_INVALID, EINVAL, fn, "'id' is NULL"); + if(!id[0]) return x_error(X_GROUP_INVALID, EINVAL, fn, "'id' is empty"); + if(s == NULL) return x_error(X_NULL, EINVAL, fn, "input structure is NULL"); if(HMSET_WITH_META == NULL) return smaxScriptError("HMSetWithMeta", X_NULL); for(f = s->firstField; f != NULL; f = f->next) { @@ -1574,13 +1728,13 @@ static int SendStructDataAsync(RedisClient *cl, const char *id, const XStructure if(nFields == 0) return X_SUCCESS; // Empty struct, nothing to do... n = 6 + nFields * HMSET_COMPONENTS; - args = (char **) calloc(n, sizeof(char *)); - if(!args) return smaxError(funcName, X_FAILURE); + args = (char **) malloc(n * sizeof(char *)); + if(!args) return x_error(X_FAILURE, errno, fn, "malloc() error (%d char *)", n); L = (int *) calloc(n, sizeof(int)); if(!L) { free(args); - return smaxError(funcName, X_FAILURE); + return x_error(X_FAILURE, errno, fn, "malloc() error (%d int)", n); } args[0] = "EVALSHA"; @@ -1595,7 +1749,7 @@ static int SendStructDataAsync(RedisClient *cl, const char *id, const XStructure if(!xIsFieldValid(f)) continue; args[next + HMSET_NAME_OFFSET] = f->name; - args[next + HMSET_TYPE_OFFSET] = xStringType(f->type); + args[next + HMSET_TYPE_OFFSET] = smaxStringType(f->type); if(f->type == X_STRUCT) { args[next + HMSET_VALUE_OFFSET] = xGetAggregateID(id, f->name); @@ -1615,12 +1769,9 @@ static int SendStructDataAsync(RedisClient *cl, const char *id, const XStructure if(!status) { // Don't want a reply. status = redisxSkipReplyAsync(cl); - if(status) smaxError(funcName, status); // Call script - status = redisxSendArrayRequestAsync(cl, args, NULL, n); - - if(status) smaxError(funcName, status); + if(!status) status = redisxSendArrayRequestAsync(cl, args, NULL, n); } next = 5; @@ -1636,12 +1787,11 @@ static int SendStructDataAsync(RedisClient *cl, const char *id, const XStructure free(args); free(L); - if(status) return smaxError(funcName, status); + prop_error(fn, status); return X_SUCCESS; } - /** * Loads the script SHA1 ids for a given SMA-X script name, and checks to make sure * that the corresponding script is in fact loaded into the Redis database. @@ -1653,12 +1803,14 @@ static int SendStructDataAsync(RedisClient *cl, const char *id, const XStructure * * X_NULL if either argument is NULL, or if there is no script SHA available * in Redis for the given name, or - * X_NAME_INVALID if the name is empty, or + * X_NAME_INVALID if the name is invalid, or * X_NO_SERVICE if the script with the given SHA1 id is not loaded into Redis. * * @sa InitScriptsAsync() */ static int InitScript(const char *name, char **pSHA1) { + static const char *fn = "InitScript"; + RESP *reply; char *sha1 = NULL; int status = X_SUCCESS; @@ -1667,8 +1819,11 @@ static int InitScript(const char *name, char **pSHA1) { *pSHA1 = NULL; sha1 = smaxGetScriptSHA1(name, &status); - if(status) return status; - if(!sha1) return X_NULL; + if(status) { + if(sha1) free(sha1); + return x_trace(fn, name, status); + } + if(!sha1) return x_trace(fn, name, X_NULL); reply = redisxRequest(redis, "SCRIPT", "EXISTS", sha1, NULL, &status); if(!status) status = redisxCheckRESP(reply, RESP_ARRAY, 1); @@ -1679,11 +1834,12 @@ static int InitScript(const char *name, char **pSHA1) { redisxDestroyRESP(reply); - if(!status) *pSHA1 = sha1; + if(status) return x_trace(fn, name, status); - return status; -} + *pSHA1 = sha1; + return X_SUCCESS; +} /** * Initializes the SHA1 script IDs for the essential LUA helpers. @@ -1704,6 +1860,7 @@ static void InitScriptsAsync() { } if(status != X_SUCCESS) { + x_trace_null("InitScriptsAsync", NULL); if(!smaxIsDisabled()) { if(first) fprintf(stderr, "ERROR! SMA-X: Missing LUA script(s) in Redis.\n"); @@ -1716,8 +1873,8 @@ static void InitScriptsAsync() { } } - /// \cond PROTECTED + /** * Get exclusive access for accessing or updating notifications. * @@ -1731,7 +1888,6 @@ int smaxLockNotify() { return status; } - /** * Relinquish exclusive access notifications. * @@ -1746,7 +1902,7 @@ int smaxUnlockNotify() { } -static void ProcessUpdateNotificationAsync(const char *pattern, const char *channel, const char *msg, int length) { +static void ProcessUpdateNotificationAsync(const char *pattern, const char *channel, const char *msg, long length) { xdprintf("{message} %s %s\n", channel, msg); if(strncmp(channel, SMAX_UPDATES, SMAX_UPDATES_LENGTH)) return; // Wrong message prefix @@ -1773,7 +1929,6 @@ static void ProcessUpdateNotificationAsync(const char *pattern, const char *chan smaxUnlockNotify(); } - /** * * Process responses to pipelined HSET calls (integer RESP). @@ -1781,6 +1936,7 @@ static void ProcessUpdateNotificationAsync(const char *pattern, const char *chan * \param reply The RESP reply received from Redis on its pipeline channel. * */ +// cppcheck-suppress constParameterCallback void smaxProcessPipedWritesAsync(RESP *reply) { if(reply->type == RESP_INT) { xvprintf("pipe RESP: %d\n", reply->n); diff --git a/tests/src/benchmarkRM.c b/tests/src/benchmarkRM.c index de0c4ba..d42fcda 100644 --- a/tests/src/benchmarkRM.c +++ b/tests/src/benchmarkRM.c @@ -35,7 +35,7 @@ int main(int argc, const char *argv[]) { smaxSetVerbose(FALSE); if(rm_open(antlist)) { - fprintf(stderr, "ERROR connecting to REDIS. Exiting...\n"); + fprintf(stderr, "ERROR connecting to Redis. Exiting...\n"); exit(1); } diff --git a/tools/src/smaxScrub.c b/tools/src/smaxScrub.c deleted file mode 100644 index 8b85b0f..0000000 --- a/tools/src/smaxScrub.c +++ /dev/null @@ -1,18 +0,0 @@ -/* - * smaxScrub.c - * - * Created on: Jul 4, 2020 - * Author: Attila Kovacs - */ - - -// I. Obsolete data -// 1. Iterate through keys using SCAN command -// - Ignore metadata and 'scripts' -// - Iterate through fields using HSCAN command -// > Check corresponding time stamp. If no timestamp or too old, UNLINK -// -// II. Orphaned metadata -// 1. Iterate through meta tables using SCAN -// - Iterate through keys using HSCAN -// > Check for corresponding entry in DB. If not UNLINK diff --git a/tools/src/smaxValue.c b/tools/src/smaxValue.c index d0e695f..9c0365c 100644 --- a/tools/src/smaxValue.c +++ b/tools/src/smaxValue.c @@ -63,7 +63,7 @@ int main(int argc, char *argv[]) { if(showList) return printValue(id, NULL); else { - if(smaxSplitID(id, &key)) return -1; + if(xSplitID(id, &key)) return -1; return printValue(id, key); } } diff --git a/tools/src/smaxWrite.c b/tools/src/smaxWrite.c index 60dc0c2..fae32c0 100644 --- a/tools/src/smaxWrite.c +++ b/tools/src/smaxWrite.c @@ -113,7 +113,7 @@ int main(int argc, char *argv[]) { } table = xStringCopyOf(id); - status = smaxSplitID(table, &f.name); + status = xSplitID(table, &f.name); if(status) { fprintf(stderr, "ERROR! Invalid table:key argument: %s\n", id); exit(1);