-
Rob Swindell authored
... using uncrustify mod_paren_on_return config
Rob Swindell authored... using uncrustify mod_paren_on_return config
js_queue.c 13.26 KiB
/* Synchronet JavaScript "Queue" Object */
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright Rob Swindell - http://www.synchro.net/copyright.html *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
#include "sbbs.h"
#include "msg_queue.h"
#include "js_request.h"
typedef struct
{
char name[128];
size_t size;
uint64 *value;
} queued_value_t;
link_list_t named_queues;
/* Queue Destructor */
static void js_finalize_queue(JSContext *cx, JSObject *obj)
{
msg_queue_t* q;
list_node_t* n;
if ((q = (msg_queue_t*)JS_GetPrivate(cx, obj)) == NULL)
return;
if (msgQueueDetach(q) == 0 && (n = listFindNode(&named_queues, q, /* length=0 for ptr compare */ 0)) != NULL)
listRemoveNode(&named_queues, n, false);
JS_SetPrivate(cx, obj, NULL);
}
static void js_decode_value(JSContext *cx, JSObject *parent
, queued_value_t* v, jsval* rval)
{
*rval = JSVAL_VOID;
if (v == NULL)
return;
JS_ReadStructuredClone(cx, v->value, v->size, JS_STRUCTURED_CLONE_VERSION, rval, NULL, NULL);
return;
}
/* Queue Object Methods */
extern JSClass js_queue_class;
static JSBool
js_poll(JSContext *cx, uintN argc, jsval *arglist)
{
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
msg_queue_t* q;
queued_value_t* v;
int32 timeout = 0;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((q = (msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class)) == NULL) {
return JS_FALSE;
}
if (argc && JSVAL_IS_NUMBER(argv[0])) { /* timeout specified */
if (!JS_ValueToInt32(cx, argv[0], &timeout))
return JS_FALSE;
}
rc = JS_SUSPENDREQUEST(cx);
v = msgQueuePeek(q, timeout);
JS_RESUMEREQUEST(cx, rc);
if (v == NULL)
JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
else if (v->name[0])
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, v->name)));
else
JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
return JS_TRUE;
}
static JSBool
js_read(JSContext *cx, uintN argc, jsval *arglist)
{
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
msg_queue_t* q;
queued_value_t find_v;
queued_value_t* v;
int32 timeout = 0;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((q = (msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class)) == NULL) {
return JS_FALSE;
}
if (argc && JSVAL_IS_STRING(argv[0])) { /* value named specified */
ZERO_VAR(find_v);
JSVALUE_TO_STRBUF(cx, argv[0], find_v.name, sizeof(find_v.name), NULL);
rc = JS_SUSPENDREQUEST(cx);
v = msgQueueFind(q, &find_v, sizeof(find_v.name));
JS_RESUMEREQUEST(cx, rc);
} else {
if (argc && JSVAL_IS_NUMBER(argv[0])) {
if (!JS_ValueToInt32(cx, argv[0], &timeout))
return JS_FALSE;
}
rc = JS_SUSPENDREQUEST(cx);
v = msgQueueRead(q, timeout);
JS_RESUMEREQUEST(cx, rc);
}
if (v != NULL) {
jsval rval;
js_decode_value(cx, obj, v, &rval);
free(v->value);
free(v);
JS_SET_RVAL(cx, arglist, rval);
}
return JS_TRUE;
}
static JSBool
js_peek(JSContext *cx, uintN argc, jsval *arglist)
{
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
msg_queue_t* q;
queued_value_t* v;
int32 timeout = 0;
jsrefcount rc;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((q = (msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class)) == NULL) {
return JS_FALSE;
}
if (argc && JSVAL_IS_NUMBER(argv[0])) { /* timeout specified */
if (!JS_ValueToInt32(cx, argv[0], &timeout))
return JS_FALSE;
}
rc = JS_SUSPENDREQUEST(cx);
v = msgQueuePeek(q, timeout);
JS_RESUMEREQUEST(cx, rc);
if (v != NULL) {
jsval rval;
js_decode_value(cx, obj, v, &rval);
JS_SET_RVAL(cx, arglist, rval);
}
return JS_TRUE;
}
static queued_value_t* js_encode_value(JSContext *cx, jsval val, char* name)
{
queued_value_t *v;
uint64 * serialized;
if ((v = malloc(sizeof(queued_value_t))) == NULL)
return NULL;
memset(v, 0, sizeof(queued_value_t));
if (name != NULL)
SAFECOPY(v->name, name);
if (!JS_WriteStructuredClone(cx, val, &serialized, &v->size, NULL, NULL)) {
free(v);
return NULL;
}
if ((v->value = (uint64 *)malloc(v->size)) == NULL) {
JS_free(cx, serialized);
free(v);
return NULL;
}
memcpy(v->value, serialized, v->size);
JS_free(cx, serialized);
return v;
}
bool js_enqueue_value(JSContext *cx, msg_queue_t* q, jsval val, char* name)
{
queued_value_t* v;
bool result;
jsrefcount rc;
if ((v = js_encode_value(cx, val, name)) == NULL)
return false;
rc = JS_SUSPENDREQUEST(cx);
result = msgQueueWrite(q, v, sizeof(queued_value_t));
free(v);
JS_RESUMEREQUEST(cx, rc);
return result;
}
static JSBool
js_write(JSContext *cx, uintN argc, jsval *arglist)
{
JSObject * obj = JS_THIS_OBJECT(cx, arglist);
jsval * argv = JS_ARGV(cx, arglist);
uintN argn = 0;
msg_queue_t* q;
jsval val;
char* name = NULL;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if ((q = (msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class)) == NULL) {
return JS_FALSE;
}
val = argv[argn++];
if (argn < argc) {
JSVALUE_TO_MSTRING(cx, argv[argn], name, NULL);
argn++;
HANDLE_PENDING(cx, name);
}
JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(js_enqueue_value(cx, q, val, name)));
if (name)
free(name);
return JS_TRUE;
}
/* Queue Object Properites */
enum {
QUEUE_PROP_NAME
, QUEUE_PROP_DATA_WAITING
, QUEUE_PROP_READ_LEVEL
, QUEUE_PROP_WRITE_LEVEL
, QUEUE_PROP_OWNER
, QUEUE_PROP_ORPHAN
};
#ifdef BUILD_JSDOCS
static const char* queue_prop_desc[] = {
"Name of the queue (if it has one)"
, "<tt>true</tt> if data is waiting to be read from queue"
, "Number of values in the read queue"
, "Number of values in the write queue"
, "<tt>true</tt> if current thread is the owner/creator of the queue"
, "<tt>true</tt> if the owner of the queue has detached from the queue"
, NULL
};
#endif
static JSBool js_queue_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
{
jsval idval;
jsint tiny;
msg_queue_t* q;
jsrefcount rc;
if ((q = (msg_queue_t*)JS_GetPrivate(cx, obj)) == NULL)
return JS_FALSE;
JS_IdToValue(cx, id, &idval);
tiny = JSVAL_TO_INT(idval);
switch (tiny) {
case QUEUE_PROP_NAME:
if (q->name[0])
*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, q->name));
break;
case QUEUE_PROP_DATA_WAITING:
rc = JS_SUSPENDREQUEST(cx);
*vp = BOOLEAN_TO_JSVAL(INT_TO_BOOL(msgQueueReadLevel(q)));
JS_RESUMEREQUEST(cx, rc);
break;
case QUEUE_PROP_READ_LEVEL:
rc = JS_SUSPENDREQUEST(cx);
*vp = INT_TO_JSVAL(msgQueueReadLevel(q));
JS_RESUMEREQUEST(cx, rc);
break;
case QUEUE_PROP_WRITE_LEVEL:
rc = JS_SUSPENDREQUEST(cx);
*vp = INT_TO_JSVAL(msgQueueWriteLevel(q));
JS_RESUMEREQUEST(cx, rc);
break;
case QUEUE_PROP_OWNER:
rc = JS_SUSPENDREQUEST(cx);
*vp = BOOLEAN_TO_JSVAL(INT_TO_BOOL(msgQueueOwner(q)));
JS_RESUMEREQUEST(cx, rc);
break;
case QUEUE_PROP_ORPHAN:
rc = JS_SUSPENDREQUEST(cx);
*vp = BOOLEAN_TO_JSVAL(INT_TO_BOOL(q->flags & MSG_QUEUE_ORPHAN));
JS_RESUMEREQUEST(cx, rc);
break;
}
return JS_TRUE;
}
#define QUEUE_PROP_FLAGS JSPROP_ENUMERATE | JSPROP_READONLY
static jsSyncPropertySpec js_queue_properties[] = {
/* name ,tinyid ,flags, ver */
{ "name", QUEUE_PROP_NAME, QUEUE_PROP_FLAGS, 312 },
{ "data_waiting", QUEUE_PROP_DATA_WAITING, QUEUE_PROP_FLAGS, 312 },
{ "read_level", QUEUE_PROP_READ_LEVEL, QUEUE_PROP_FLAGS, 312 },
{ "write_level", QUEUE_PROP_WRITE_LEVEL, QUEUE_PROP_FLAGS, 312 },
{ "owner", QUEUE_PROP_OWNER, QUEUE_PROP_FLAGS, 316 },
{ "orphan", QUEUE_PROP_ORPHAN, QUEUE_PROP_FLAGS, 31702 },
{0}
};
static jsSyncMethodSpec js_queue_functions[] = {
{"poll", js_poll, 1, JSTYPE_UNDEF, "[<i>number</i> timeout=0]"
, JSDOCSTR("Wait for any value to be written to the queue for up to <i>timeout</i> milliseconds "
"(default: <i>0</i>), returns <tt>true</tt> or the <i>name</i> (string) of "
"the value waiting (if it has one), or <tt>false</tt> if no values are waiting")
, 312},
{"read", js_read, 1, JSTYPE_UNDEF, "[<i>string</i> name] or [<i>number</i>timeout=0]"
, JSDOCSTR("Read a value from the queue, if <i>name</i> not specified, reads next value "
"from the bottom of the queue (waiting up to <i>timeout</i> milliseconds)")
, 313},
{"peek", js_peek, 1, JSTYPE_UNDEF, "[<i>number</i> timeout=0]"
, JSDOCSTR("Peek at the value at the bottom of the queue, "
"wait up to <i>timeout</i> milliseconds for any value to be written "
"(default: <i>0</i>)")
, 313},
{"write", js_write, 1, JSTYPE_BOOLEAN, "value [,name=<i>none</i>]"
, JSDOCSTR("Write a value (optionally named) to the queue")
, 312},
{0}
};
static JSBool js_queue_resolve(JSContext *cx, JSObject *obj, jsid id)
{
char* name = NULL;
JSBool ret;
if (id != JSID_VOID && id != JSID_EMPTY) {
jsval idval;
JS_IdToValue(cx, id, &idval);
if (JSVAL_IS_STRING(idval)) {
JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL);
HANDLE_PENDING(cx, name);
}
}
ret = js_SyncResolve(cx, obj, name, js_queue_properties, js_queue_functions, NULL, 0);
if (name)
free(name);
return ret;
}
static JSBool js_queue_enumerate(JSContext *cx, JSObject *obj)
{
return js_queue_resolve(cx, obj, JSID_VOID);
}
JSClass js_queue_class = {
"Queue" /* name */
, JSCLASS_HAS_PRIVATE /* flags */
, JS_PropertyStub /* addProperty */
, JS_PropertyStub /* delProperty */
, js_queue_get /* getProperty */
, JS_StrictPropertyStub /* setProperty */
, js_queue_enumerate /* enumerate */
, js_queue_resolve /* resolve */
, JS_ConvertStub /* convert */
, js_finalize_queue /* finalize */
};
/* Queue Constructor (creates queue) */
static JSBool
js_queue_constructor(JSContext *cx, uintN argc, jsval *arglist)
{
JSObject * obj;
jsval * argv = JS_ARGV(cx, arglist);
uintN argn = 0;
char* name = NULL;
int32 flags = MSG_QUEUE_BIDIR;
msg_queue_t* q = NULL;
list_node_t* n;
jsrefcount rc;
obj = JS_NewObject(cx, &js_queue_class, NULL, NULL);
JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));
#if 0 /* This doesn't appear to be doing anything but leaking memory */
if ((q = (msg_queue_t*)malloc(sizeof(msg_queue_t))) == NULL) {
JS_ReportError(cx, "malloc failed");
return JS_FALSE;
}
memset(q, 0, sizeof(msg_queue_t));
#endif
if (argn < argc && JSVAL_IS_STRING(argv[argn])) {
JSVALUE_TO_ASTRING(cx, argv[argn], name, sizeof(q->name), NULL);
argn++;
}
if (argn < argc && JSVAL_IS_NUMBER(argv[argn])) {
if (!JS_ValueToInt32(cx, argv[argn++], &flags))
return JS_FALSE;
}
rc = JS_SUSPENDREQUEST(cx);
if (name != NULL) {
listLock(&named_queues);
for (n = named_queues.first; n != NULL; n = n->next)
if ((q = n->data) != NULL && !stricmp(q->name, name))
break;
listUnlock(&named_queues);
if (n == NULL)
q = NULL;
}
if (q == NULL) {
q = msgQueueInit(NULL, flags);
if (name != NULL)
SAFECOPY(q->name, name);
listPushNode(&named_queues, q);
} else
msgQueueAttach(q);
JS_RESUMEREQUEST(cx, rc);
if (!JS_SetPrivate(cx, obj, q)) {
JS_ReportError(cx, "JS_SetPrivate failed");
return JS_FALSE;
}
#ifdef BUILD_JSDOCS
js_DescribeSyncObject(cx, obj, "Class for bi-directional message queues. "
"Used for inter-thread/module communications.", 312);
js_DescribeSyncConstructor(cx, obj, "To create a new (named) Queue object: "
"<tt>var q = new Queue(<i>name</i>)</tt>");
js_CreateArrayOfStrings(cx, obj, "_property_desc_list", queue_prop_desc, JSPROP_READONLY);
#endif
return JS_TRUE;
}
JSObject* js_CreateQueueClass(JSContext* cx, JSObject* parent)
{
JSObject* obj;
obj = JS_InitClass(cx, parent, NULL
, &js_queue_class
, js_queue_constructor
, 0 /* number of constructor args */
, NULL /* props, specified in constructor */
, NULL /* funcs, specified in constructor */
, NULL
, NULL);
return obj;
}
JSObject* js_CreateQueueObject(JSContext* cx, JSObject* parent, char *name, msg_queue_t* q)
{
JSObject* obj;
if (name == NULL)
obj = JS_NewObject(cx, &js_queue_class, NULL, parent);
else
obj = JS_DefineObject(cx, parent, name, &js_queue_class, NULL
, JSPROP_ENUMERATE | JSPROP_READONLY);
if (obj == NULL)
return NULL;
if (!JS_SetPrivate(cx, obj, q))
return NULL;
return obj;
}