├── .gitignore ├── node_main.c ├── mkmf.js ├── node_embed.h ├── README.md └── node.cc /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | *.o 3 | node_main 4 | -------------------------------------------------------------------------------- /node_main.c: -------------------------------------------------------------------------------- 1 | #include "node_embed.h" 2 | 3 | int main(int argc, char** argv) { 4 | node_context *context = nodeSetup(argc, argv); 5 | 6 | if (context) { 7 | nodeExecuteString(context, "let foo = 1", "__init__"); 8 | nodeExecuteString(context, "foo = 2", "__init__"); 9 | nodeExecuteString(context, "console.log(foo)", "__init__"); 10 | 11 | return nodeTeardown(context); 12 | } else { 13 | return 12; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /mkmf.js: -------------------------------------------------------------------------------- 1 | /* 2 | The node build process is a bit, ahem, complex. Thie extracts the necessary 3 | bits from the generated makefiles to build a standalone Makefile, 4 | */ 5 | 6 | var fs = require("fs"); 7 | var srcdir = process.argv[2] || process.env.HOME + "/git/node"; 8 | 9 | var input = fs.readFileSync( 10 | srcdir + "/out/node_lib.target.mk", 11 | "utf8" 12 | ); 13 | 14 | var out = "srcdir := " + srcdir + "\n" + "builddir := $(srcdir)/out/Release\n" + 15 | "obj := $(builddir)/obj\n" + "\n"; 16 | out += input.match(/DEFS_Release.*?\n\n/s)[0]; 17 | out += input.match(/INCS_Release.*?\n\n/s)[0]; 18 | out += input.match(/CFLAGS_Release.*?\n\n/s)[0]; 19 | out += input.match(/CFLAGS_CC_Release.*?\n\n/s)[0]; 20 | 21 | input = fs.readFileSync(srcdir + "/out/node.target.mk", "utf8"); 22 | out += input.match(/LDFLAGS_Release.*?\n\n/s)[0]; 23 | out += input.match(/LIBS.*?\n\n/s)[0]; 24 | out += input.match(/LD_INPUTS.*?\n/)[0]; 25 | 26 | out += ` 27 | node_main: node_main.o node.o 28 | \tc++ $(LDFLAGS_Release) -o $@ $+ $(LIBS) $(LD_INPUTS) 29 | 30 | node.o: node.cc node_embed.h 31 | \tc++ $(DEFS_Release) $(INCS_Release) $(CFLAGS_Release) \\ 32 | \t$(CFLAGS_CC_Release) -c $< -o $@ 33 | 34 | node_main.o: node_main.c node_embed.h 35 | \tcc $(CFLAGS_Release) -c $< -o $@ 36 | 37 | test: node_main 38 | \ttest \`./node_main\` = "2" && echo success 39 | 40 | clean: 41 | \trm -f *.o nodemain 42 | `; 43 | 44 | fs.writeFileSync("Makefile", out) 45 | -------------------------------------------------------------------------------- /node_embed.h: -------------------------------------------------------------------------------- 1 | // node_embed.h : an API for Node embedders 2 | 3 | /* 4 | 5 | ______ _______ _ _______ _________ _______ _______ 6 | ( __ \ ( ___ ) ( ( /|( ___ )\__ __/ |\ /|( ____ \( ____ \ 7 | | ( \ )| ( ) | | \ ( || ( ) | ) ( | ) ( || ( \/| ( \/ 8 | | | ) || | | | | \ | || | | | | | | | | || (_____ | (__ 9 | | | | || | | | | (\ \) || | | | | | | | | |(_____ )| __) 10 | | | ) || | | | | | \ || | | | | | | | | | ) || ( 11 | | (__/ )| (___) | | ) \ || (___) | | | | (___) |/\____) || (____/\ 12 | (______/ (_______) |/ )_)(_______) )_( (_______)\_______)(_______/ 13 | 14 | This header is beyond experimental status at this point. Change is not only 15 | likely, it is all but certain. At a minimum, it is planned to become 16 | N-API'ized, but more significantly the immediate plans are to focus on teasing 17 | apart the per-process setup and teardown away from the per isolate setup and 18 | teardown, and that likely will require substantial changes. 19 | 20 | */ 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | typedef struct node_context_struct node_context; 27 | 28 | node_context *nodeSetup(int argc, char** argv); 29 | 30 | void nodeExecuteString(node_context *context, const char* string, 31 | const char *fileName); 32 | 33 | int nodeTeardown(node_context *context); 34 | 35 | #ifdef __cplusplus 36 | } 37 | 38 | #include "node.h" 39 | 40 | struct node_context_struct { 41 | node::Environment *env; 42 | node::ArrayBufferAllocator *allocator; 43 | }; 44 | #endif 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The goal of this codebase is to help anchor a discussion of what an 2 | embeddable interface to Node.js would look like with running code. 3 | 4 | Current state: 5 | ------------- 6 | 7 | * Embedding node.js today is not only a desirable goal, it is actively being 8 | done by a number of projects despite the lack of support. 9 | * Lack of support starts with lack of documentation. 10 | * The one stable interface `node::Start()` is a misnomer as it not only starts 11 | node, but it also executes, and terminates node. This rarely is what an embedder wants. 12 | * Embedding node today requires extracting key bits from the source files, and 13 | knowledge of the build system, platforms, and dependencies (v8, openssl, etc). 14 | 15 | Quick start: 16 | ----------- 17 | 18 | * To start with, one needs to clone and build node from GitHub. This code 19 | has been tested on Mac OS/X and Ubuntu, and is likely to work on other 20 | Unix like operating systems. 21 | * The next step is to run the provided `mkmf.js` and specify the directory 22 | for the built node tree. This builds a standalone Makefile for your platform. 23 | * The final step is to run `make`. This will build an executable named 24 | `./node_main` which you can run. 25 | 26 | How it works: 27 | ------------ 28 | 29 | * The [`mkmf.js`](./mkmf.js) is pretty self explanatory. 30 | * [`node.cc`](./node.cc) is an updated version of `src/node.cc` from the node repository 31 | with the following changes: 32 | * First, it is a subset so as to not cause duplicate symbols when linked 33 | with `libnode.a`. This part is done for demo purposes only, the intent 34 | is that this will be abandoned, and the results of the next two steps 35 | will be folded into node.js itself. 36 | * Second, `node::Start` has been split into `node:Setup`, 37 | `node::ExecuteString` and `node::Teardown`. 38 | * Third, a `C` interface to invoking these functions is provided. 39 | * [`node_embed.h`](./node_embed.h) contains the C API 40 | * [`node_main.c`](./node_main.c) is a demo program that calls `nodeSetup`, successively calls 41 | `nodeExecuteString` and then finally `nodeTeardown`. 42 | 43 | Why was this demo created? 44 | ------------------------- 45 | 46 | * I believe that creating a standalone Makefile and application should be 47 | a part of the CI for node.js. This demo shows how it could be done for 48 | discussion purposes before it is implemented "for real". 49 | 50 | 51 | Discussion starters: 52 | ------------------- 53 | 54 | * Should we retire `node::Start` or implement it by calling these new wrappers? 55 | * Is `argc`/`argv` the right interface for an API? Should the defaults be 56 | different (example: should no arguments trigger a REPL) when called by an API? 57 | * Splitting `node::Start` into three steps means that some data is allocated 58 | on the heap instead of the stack, and some things (most noteabley locks) 59 | are obtained and released multiple times. My intuition is that this 60 | is negligible when compared to the overhead of starting a complete node 61 | executable, but that needs to be measured. 62 | * Currently, `nodeExecuteString` is synchronous is that it waits until the 63 | event loop is emptied before returning. Should an asynchronous version 64 | be created, and if so, what is the desired semantics? 65 | 66 | Future plans: 67 | ------------ 68 | 69 | * This demo only shows incrementally updating a single execution context. 70 | It should be expanded to include creating multiple contexts (either 71 | concurrently or serially). 72 | * The interface should be NAPIized, and make to work on Microsoft Windows. 73 | Help from the ChakraCore team would be greatly appreciated. 74 | * While I suspect that only the `C` interface needs to be officially maintained, 75 | providing examples in Go, Java, Python, TCL, Ruby, and others would be valuable and straightforward. 76 | -------------------------------------------------------------------------------- /node.cc: -------------------------------------------------------------------------------- 1 | // Copyright Joyent, Inc. and other Node contributors. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | ///////////////////////////////////////////////////////////////////////// 23 | #include "node_embed.h" 24 | ///////////////////////////////////////////////////////////////////////// 25 | 26 | #include "tracing/traced_value.h" 27 | 28 | #if HAVE_OPENSSL 29 | #include "node_crypto.h" 30 | #endif 31 | 32 | #include "ares.h" 33 | #include "nghttp2/nghttp2ver.h" 34 | #include "tracing/node_trace_writer.h" 35 | #include "zlib.h" 36 | 37 | #if defined(__POSIX__) 38 | #include 39 | #endif 40 | 41 | #define READONLY_PROPERTY(obj, str, var) \ 42 | do { \ 43 | obj->DefineOwnProperty(env->context(), \ 44 | OneByteString(env->isolate(), str), \ 45 | var, \ 46 | ReadOnly).FromJust(); \ 47 | } while (0) 48 | 49 | 50 | namespace node { 51 | 52 | using v8::Context; 53 | using v8::EscapableHandleScope; 54 | using v8::HandleScope; 55 | using v8::Isolate; 56 | using v8::Local; 57 | using v8::Locker; 58 | using v8::MaybeLocal; 59 | using v8::Object; 60 | using v8::ReadOnly; 61 | using v8::Script; 62 | using v8::ScriptOrigin; 63 | using v8::SealHandleScope; 64 | using v8::String; 65 | using v8::TracingController; 66 | using v8::TryCatch; 67 | using v8::V8; 68 | using v8::Value; 69 | 70 | static bool v8_is_profiling = false; 71 | 72 | static Mutex node_isolate_mutex; 73 | static Isolate* node_isolate; 74 | 75 | // Ensures that __metadata trace events are only emitted 76 | // when tracing is enabled. 77 | class NodeTraceStateObserver : 78 | public TracingController::TraceStateObserver { 79 | public: 80 | void OnTraceEnabled() override; 81 | 82 | void OnTraceDisabled() override; 83 | 84 | explicit NodeTraceStateObserver(TracingController* controller) : 85 | controller_(controller) {} 86 | ~NodeTraceStateObserver() override {} 87 | 88 | private: 89 | TracingController* controller_; 90 | }; 91 | 92 | static struct { 93 | #if NODE_USE_V8_PLATFORM 94 | void Initialize(int thread_pool_size) { 95 | tracing_agent_.reset(new tracing::Agent()); 96 | auto controller = tracing_agent_->GetTracingController(); 97 | controller->AddTraceStateObserver(new NodeTraceStateObserver(controller)); 98 | tracing::TraceEventHelper::SetTracingController(controller); 99 | StartTracingAgent(); 100 | // Tracing must be initialized before platform threads are created. 101 | platform_ = new NodePlatform(thread_pool_size, controller); 102 | V8::InitializePlatform(platform_); 103 | } 104 | 105 | void Dispose() { 106 | platform_->Shutdown(); 107 | delete platform_; 108 | platform_ = nullptr; 109 | // Destroy tracing after the platform (and platform threads) have been 110 | // stopped. 111 | tracing_agent_.reset(nullptr); 112 | } 113 | 114 | void DrainVMTasks(Isolate* isolate) { 115 | platform_->DrainTasks(isolate); 116 | } 117 | 118 | void CancelVMTasks(Isolate* isolate) { 119 | platform_->CancelPendingDelayedTasks(isolate); 120 | } 121 | 122 | #if HAVE_INSPECTOR 123 | bool StartInspector(Environment* env, const char* script_path, 124 | std::shared_ptr options) { 125 | // Inspector agent can't fail to start, but if it was configured to listen 126 | // right away on the websocket port and fails to bind/etc, this will return 127 | // false. 128 | return env->inspector_agent()->Start( 129 | script_path == nullptr ? "" : script_path, options, true); 130 | } 131 | 132 | bool InspectorStarted(Environment* env) { 133 | return env->inspector_agent()->IsListening(); 134 | } 135 | #endif // HAVE_INSPECTOR 136 | 137 | void StartTracingAgent() { 138 | if (per_process_opts->trace_event_categories.empty()) { 139 | tracing_file_writer_ = tracing_agent_->DefaultHandle(); 140 | } else { 141 | tracing_file_writer_ = tracing_agent_->AddClient( 142 | ParseCommaSeparatedSet(per_process_opts->trace_event_categories), 143 | std::unique_ptr( 144 | new tracing::NodeTraceWriter( 145 | per_process_opts->trace_event_file_pattern)), 146 | tracing::Agent::kUseDefaultCategories); 147 | } 148 | } 149 | 150 | void StopTracingAgent() { 151 | tracing_file_writer_.reset(); 152 | } 153 | 154 | tracing::AgentWriterHandle* GetTracingAgentWriter() { 155 | return &tracing_file_writer_; 156 | } 157 | 158 | NodePlatform* Platform() { 159 | return platform_; 160 | } 161 | 162 | std::unique_ptr tracing_agent_; 163 | tracing::AgentWriterHandle tracing_file_writer_; 164 | NodePlatform* platform_; 165 | #else // !NODE_USE_V8_PLATFORM 166 | void Initialize(int thread_pool_size) {} 167 | void Dispose() {} 168 | void DrainVMTasks(Isolate* isolate) {} 169 | void CancelVMTasks(Isolate* isolate) {} 170 | 171 | void StartTracingAgent() { 172 | if (!trace_enabled_categories.empty()) { 173 | fprintf(stderr, "Node compiled with NODE_USE_V8_PLATFORM=0, " 174 | "so event tracing is not available.\n"); 175 | } 176 | } 177 | void StopTracingAgent() {} 178 | 179 | tracing::AgentWriterHandle* GetTracingAgentWriter() { 180 | return nullptr; 181 | } 182 | 183 | NodePlatform* Platform() { 184 | return nullptr; 185 | } 186 | #endif // !NODE_USE_V8_PLATFORM 187 | 188 | #if !NODE_USE_V8_PLATFORM || !HAVE_INSPECTOR 189 | bool InspectorStarted(Environment* env) { 190 | return false; 191 | } 192 | #endif // !NODE_USE_V8_PLATFORM || !HAVE_INSPECTOR 193 | } v8_platform; 194 | 195 | 196 | #ifdef __POSIX__ 197 | static const unsigned kMaxSignal = 32; 198 | #endif 199 | 200 | 201 | void RunBeforeExit(Environment* env); 202 | 203 | static void StartInspector(Environment* env, const char* path, 204 | std::shared_ptr debug_options) { 205 | #if HAVE_INSPECTOR 206 | CHECK(!env->inspector_agent()->IsListening()); 207 | v8_platform.StartInspector(env, path, debug_options); 208 | #endif // HAVE_INSPECTOR 209 | } 210 | 211 | static void ReportException(Environment* env, const TryCatch& try_catch) { 212 | ReportException(env, try_catch.Exception(), try_catch.Message()); 213 | } 214 | 215 | static void WaitForInspectorDisconnect(Environment* env) { 216 | #if HAVE_INSPECTOR 217 | if (env->inspector_agent()->IsActive()) { 218 | // Restore signal dispositions, the app is done and is no longer 219 | // capable of handling signals. 220 | #if defined(__POSIX__) && !defined(NODE_SHARED_MODE) 221 | struct sigaction act; 222 | memset(&act, 0, sizeof(act)); 223 | for (unsigned nr = 1; nr < kMaxSignal; nr += 1) { 224 | if (nr == SIGKILL || nr == SIGSTOP || nr == SIGPROF) 225 | continue; 226 | act.sa_handler = (nr == SIGPIPE) ? SIG_IGN : SIG_DFL; 227 | CHECK_EQ(0, sigaction(nr, &act, nullptr)); 228 | } 229 | #endif 230 | env->inspector_agent()->WaitForDisconnect(); 231 | } 232 | #endif 233 | } 234 | 235 | 236 | // Executes a str within the current v8 context. 237 | static MaybeLocal ExecuteString(Environment* env, 238 | Local source, 239 | Local filename) { 240 | EscapableHandleScope scope(env->isolate()); 241 | TryCatch try_catch(env->isolate()); 242 | 243 | // try_catch must be nonverbose to disable FatalException() handler, 244 | // we will handle exceptions ourself. 245 | try_catch.SetVerbose(false); 246 | 247 | ScriptOrigin origin(filename); 248 | MaybeLocal