Skip to content

Commit 312080a

Browse files
committed
round trip most types, and implement hooks to KeyDB
1 parent e267aa5 commit 312080a

File tree

4 files changed

+198
-20
lines changed

4 files changed

+198
-20
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ modjs.so: $(MODULE_OBJS)
1010
$(CXX) -c $(CXX_FLAGS) -o $@ $<
1111

1212
clean:
13+
rm -f userland.js
1314
rm -f *.o
14-
rm -f *.so
15+
rm -f *.so

js.cpp

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
#include "js.h"
22
#include <mutex>
3-
#include <v8.h>
43
#include <libplatform/libplatform.h>
4+
#include <fstream>
5+
#include <streambuf>
6+
7+
void KeyDBExecuteCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
58

69
thread_local v8::Isolate *isolate = nullptr;
10+
thread_local v8::Persistent<v8::ObjectTemplate, v8::CopyablePersistentTraits<v8::ObjectTemplate>> tls_global;
11+
712

813
void javascript_initialize()
914
{
@@ -26,24 +31,75 @@ class Script
2631
v8::Local<v8::Context> context;
2732
};
2833

34+
static void LogCallback(const v8::FunctionCallbackInfo<v8::Value>& args)
35+
{
36+
if (args.Length() < 1) return;
37+
v8::Isolate* isolate = args.GetIsolate();
38+
v8::HandleScope scope(isolate);
39+
v8::Local<v8::Value> arg = args[0];
40+
v8::String::Utf8Value value(isolate, arg);
41+
printf("%s\n", *value);
42+
}
43+
44+
45+
void javascript_hooks_initialize(v8::Local<v8::ObjectTemplate> &keydb_obj)
46+
{
47+
keydb_obj->Set(v8::String::NewFromUtf8(isolate, "log", v8::NewStringType::kNormal)
48+
.ToLocalChecked(),
49+
v8::FunctionTemplate::New(isolate, LogCallback));
50+
51+
keydb_obj->Set(v8::String::NewFromUtf8(isolate, "call", v8::NewStringType::kNormal)
52+
.ToLocalChecked(),
53+
v8::FunctionTemplate::New(isolate, KeyDBExecuteCallback));
54+
}
55+
2956
void javascript_thread_initialize()
3057
{
3158
v8::Isolate::CreateParams create_params;
3259
create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
3360
isolate = v8::Isolate::New(create_params);
61+
62+
v8::HandleScope handle_scope(isolate);
63+
64+
// Create a template for the global object where we set the
65+
// built-in global functions.
66+
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
67+
v8::Local<v8::ObjectTemplate> keydb_obj = v8::ObjectTemplate::New(isolate);
68+
69+
javascript_hooks_initialize(keydb_obj);
70+
71+
global->Set(v8::String::NewFromUtf8(isolate, "keydb", v8::NewStringType::kNormal)
72+
.ToLocalChecked(),
73+
keydb_obj);
74+
global->Set(v8::String::NewFromUtf8(isolate, "redis", v8::NewStringType::kNormal)
75+
.ToLocalChecked(),
76+
keydb_obj);
77+
78+
tls_global = v8::Persistent<v8::ObjectTemplate, v8::CopyablePersistentTraits<v8::ObjectTemplate>>(isolate, global);
3479
}
3580

36-
std::string javascript_run(const char *rgch, size_t cch)
81+
std::string prettyPrintException(v8::TryCatch &trycatch)
3782
{
38-
if (isolate == nullptr)
39-
javascript_thread_initialize();
83+
auto e = trycatch.Exception();
84+
v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate, isolate->GetCurrentContext());
85+
v8::String::Utf8Value estr(isolate, e);
86+
std::string str(*estr);
87+
88+
auto maybeTrace = trycatch.StackTrace(context);
89+
v8::Local<v8::Value> traceV;
90+
91+
if (maybeTrace.ToLocal(&traceV))
92+
{
93+
str += "\n";
94+
str += *v8::String::Utf8Value(isolate, traceV);
95+
}
96+
return str;
97+
}
4098

99+
v8::Local<v8::Value> javascript_run(v8::Local<v8::Context> &context, const char *rgch, size_t cch)
100+
{
41101
v8::TryCatch trycatch(isolate);
42-
v8::Isolate::Scope isolate_scope(isolate);
43-
// Create a stack-allocated handle scope.
44-
v8::HandleScope handle_scope(isolate);
45-
// Create a new context.
46-
v8::Local<v8::Context> context = v8::Context::New(isolate);
102+
47103
// Enter the context for compiling and running the hello world script.
48104
v8::Context::Scope context_scope(context);
49105
// Create a string containing the JavaScript source code.
@@ -60,8 +116,7 @@ std::string javascript_run(const char *rgch, size_t cch)
60116
{
61117
if (trycatch.HasCaught())
62118
{
63-
v8::String::Utf8Value estr(isolate, trycatch.Exception());
64-
throw std::string(*estr);
119+
throw prettyPrintException(trycatch);
65120
}
66121
throw std::nullptr_t();
67122
}
@@ -74,15 +129,13 @@ std::string javascript_run(const char *rgch, size_t cch)
74129
{
75130
if (trycatch.HasCaught())
76131
{
77-
v8::String::Utf8Value estr(isolate, trycatch.Exception());
78-
throw std::string(*estr);
132+
throw prettyPrintException(trycatch);
79133
}
80134
throw std::nullptr_t();
81135
}
82136

83-
// Convert the result to an UTF8 string and print it.
84-
v8::String::Utf8Value utf8(isolate, result);
85-
return std::string(*utf8);
137+
// Convert the result to a KeyDB type and return it
138+
return result;
86139
}
87140

88141

js.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#pragma once
22

33
#include <string>
4+
#include <v8.h>
45

56
void javascript_initialize();
67
void javascript_thread_initialize();
78
void javascript_shutdown();
89
void javascript_thread_shutdown();
910

10-
std::string javascript_run(const char *rgch, size_t cch);
11+
v8::Local<v8::Value> javascript_run(v8::Local<v8::Context> &context, const char *rgch, size_t cch);

module.cpp

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,115 @@
11
#include "js.h"
22
#include "redismodule.h"
3+
#include <vector>
4+
#include <v8.h>
5+
6+
thread_local RedisModuleCtx *g_ctx = nullptr;
7+
extern thread_local v8::Isolate *isolate;
8+
extern thread_local v8::Persistent<v8::ObjectTemplate, v8::CopyablePersistentTraits<v8::ObjectTemplate>> tls_global;
9+
10+
static void ProcessCallReply(v8::Local<v8::Value> &dst, v8::Isolate* isolate, RedisModuleCallReply *reply)
11+
{
12+
const char *rgchReply;
13+
size_t cchReply;
14+
15+
switch (RedisModule_CallReplyType(reply))
16+
{
17+
case REDISMODULE_REPLY_STRING:
18+
rgchReply = RedisModule_CallReplyStringPtr(reply, &cchReply);
19+
dst = v8::String::NewFromUtf8(isolate, rgchReply, v8::NewStringType::kNormal, cchReply).ToLocalChecked();
20+
break;
21+
22+
case REDISMODULE_REPLY_INTEGER:
23+
{
24+
long long val = RedisModule_CallReplyInteger(reply);
25+
dst = v8::BigInt::New(isolate, val);
26+
break;
27+
}
28+
29+
case REDISMODULE_REPLY_ARRAY:
30+
{
31+
size_t celem = RedisModule_CallReplyLength(reply);
32+
33+
auto array = v8::Array::New(isolate, celem);
34+
for (size_t ielem = 0; ielem < celem; ++ielem)
35+
{
36+
RedisModuleCallReply *replyArray = RedisModule_CallReplyArrayElement(reply, ielem);
37+
v8::Local<v8::Value> val;
38+
ProcessCallReply(val, isolate, replyArray);
39+
v8::Maybe<bool> result = array->Set(isolate->GetCurrentContext(), ielem, val);
40+
bool fResult;
41+
if (!result.To(&fResult) || !fResult)
42+
{
43+
RedisModule_Log(g_ctx, "warning", "Failed to process array result");
44+
}
45+
}
46+
dst = array;
47+
break;
48+
}
49+
50+
default:
51+
rgchReply = RedisModule_CallReplyProto(reply, &cchReply);
52+
dst = v8::String::NewFromUtf8(isolate, rgchReply, v8::NewStringType::kNormal, cchReply).ToLocalChecked();
53+
}
54+
}
55+
56+
void KeyDBExecuteCallback(const v8::FunctionCallbackInfo<v8::Value>& args)
57+
{
58+
if (args.Length() < 1) return;
59+
v8::Isolate* isolate = args.GetIsolate();
60+
v8::HandleScope scope(isolate);
61+
v8::Local<v8::Value> vfnName = args[0];
62+
v8::String::Utf8Value fnName(isolate, vfnName);
63+
64+
std::vector<RedisModuleString*> vecstrs;
65+
for (size_t iarg = 1; iarg < args.Length(); ++iarg)
66+
{
67+
v8::String::Utf8Value argument(isolate, args[iarg]);
68+
vecstrs.push_back(RedisModule_CreateString(g_ctx, *argument, argument.length()));
69+
}
70+
71+
RedisModuleCallReply *reply = RedisModule_Call(g_ctx, *fnName, "v", vecstrs.data(), vecstrs.size());
72+
73+
if (reply != nullptr)
74+
{
75+
v8::Local<v8::Value> result;
76+
ProcessCallReply(result, isolate, reply);
77+
args.GetReturnValue().Set(result);
78+
79+
RedisModule_FreeCallReply(reply);
80+
}
81+
else
82+
{
83+
isolate->ThrowException(v8::String::NewFromUtf8(isolate, "Invalid Command").ToLocalChecked());
84+
}
85+
86+
for (auto str : vecstrs)
87+
RedisModule_FreeString(g_ctx, str);
88+
}
89+
90+
91+
static void processResult(RedisModuleCtx *ctx, v8::Local<v8::Context> &v8ctx, v8::Local<v8::Value> &result)
92+
{
93+
if (result->IsArray())
94+
{
95+
v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(result);
96+
RedisModule_ReplyWithArray(g_ctx, array->Length());
97+
for (size_t ielem = 0; ielem < array->Length(); ++ielem)
98+
{
99+
auto maybe = array->Get(v8ctx, ielem);
100+
v8::Local<v8::Value> val;
101+
if (maybe.ToLocal(&val))
102+
processResult(ctx, v8ctx, val);
103+
else
104+
RedisModule_ReplyWithNull(ctx);
105+
}
106+
}
107+
else
108+
{
109+
v8::String::Utf8Value utf8(isolate, result);
110+
RedisModule_ReplyWithCString(ctx, *utf8);
111+
}
112+
}
3113

4114
int evaljs_command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
5115
{
@@ -9,23 +119,36 @@ int evaljs_command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
9119
return REDISMODULE_ERR;
10120
}
11121

122+
if (isolate == nullptr)
123+
javascript_thread_initialize();
124+
12125
size_t cch = 0;
13126
const char *rgch = RedisModule_StringPtrLen(argv[1], &cch);
14127
try
15128
{
16-
std::string strRes = javascript_run(rgch, cch);
17-
RedisModule_ReplyWithCString(ctx, strRes.c_str());
129+
g_ctx = ctx;
130+
v8::Isolate::Scope isolate_scope(isolate);
131+
// Create a stack-allocated handle scope.
132+
v8::HandleScope handle_scope(isolate);
133+
// Create a new context.
134+
v8::Local<v8::ObjectTemplate> global = v8::Local<v8::ObjectTemplate>::New(isolate, tls_global);
135+
v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
136+
v8::Local<v8::Value> result = javascript_run(context, rgch, cch);
137+
processResult(ctx, context, result);
18138
}
19139
catch (std::string strerr)
20140
{
21141
RedisModule_ReplyWithError(ctx, strerr.c_str());
142+
g_ctx = nullptr;
22143
return REDISMODULE_ERR;
23144
}
24145
catch (std::nullptr_t)
25146
{
26147
RedisModule_ReplyWithError(ctx, "Unknown Error");
148+
g_ctx = nullptr;
27149
return REDISMODULE_ERR;
28150
}
151+
g_ctx = nullptr;
29152
return REDISMODULE_OK;
30153
}
31154

0 commit comments

Comments
 (0)