Skip to content
This repository has been archived by the owner on Sep 2, 2021. It is now read-only.

Linux: Startup thread that launches node subprocess #278

Closed
wants to merge 7 commits into from
176 changes: 167 additions & 9 deletions appshell/appshell_node_process_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,55 +21,213 @@
*
*/


#include "appshell_node_process.h"
#include "appshell_node_process_internal.h"

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>


#ifndef OS_LINUX
#define OS_LINUX 1
#endif
#include "config.h"

#define BRACKETS_NODE_BUFFER_SIZE 4096
#define MAX_PATH 128

// init mutex
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// write & read references to subprocess
FILE* streamTo;
FILE* streamFrom;

// Threads should hold mutex before using these
static int nodeState = BRACKETS_NODE_NOT_YET_STARTED;
static int nodeStartTime = 0;

// Forward declarations
//DWORD WINAPI NodeThread(LPVOID);
void* nodeThread(void*);
void* nodeReadThread(void*);
void restartNode(bool);

// Creates the thread that starts Node and then monitors the state
// of the node process.
void startNodeProcess() {

pthread_t thread_id;
if (pthread_create(&thread_id, NULL, &nodeThread, NULL) != 0)
nodeState = BRACKETS_NODE_FAILED;
}


// Thread function for the thread that starts the node process
// and monitors that process's state
void* nodeThread(void* unused) {

// get mutex
if (pthread_mutex_lock(&mutex)) {
fprintf(stderr,
"failed to acquire mutex for Node subprocess start: %s\n",
strerror(errno));
return NULL;
}

// TODO nodeStartTime = get time();

char executablePath[MAX_PATH];
char bracketsShellPath[MAX_PATH];
char nodeExecutablePath[MAX_PATH];
char nodecorePath[MAX_PATH];

// get path to Brackets
if (readlink("/proc/self/exe", executablePath, MAX_PATH) == -1) {
fprintf(stderr, "cannot find Brackets path: %s\n", strerror(errno));
pthread_mutex_unlock(&mutex);
return NULL;
}

// strip off 'out/Release/Brackets' for path to brackets-shell
size_t pathLength = strlen(executablePath);
memcpy(bracketsShellPath, executablePath, pathLength - 20);

// create node exec and node-core paths
strcpy(nodeExecutablePath, bracketsShellPath);
strcat(nodeExecutablePath, NODE_EXECUTABLE_PATH);
strcpy(nodecorePath, bracketsShellPath);
strcat(nodecorePath, NODE_CORE_PATH);

// create pipes for node process stdin/stdout
int toNode[2];
int fromNode[2];

pipe(toNode);
pipe(fromNode);

// create the Node process
pid_t child_pid = fork();
if (child_pid == 0) { // child (node) process
// close our copy of write end and
// connect read end to stdin
close(toNode[1]);
dup2(toNode[0], STDIN_FILENO);

// close our copy of read end and
// connect write end to stdout
close(fromNode[0]);
dup2(fromNode[1], STDOUT_FILENO);

// run node executable
char* arg_list[] = { nodeExecutablePath, nodecorePath, NULL};
execvp(arg_list[0], arg_list);

fprintf(stderr, "the Node process failed to start: %s\n", strerror(errno));
abort();
}
else { // parent

// close our reference of toNode's read end
// and fromNode's write end
close(toNode[0]);
close(fromNode[1]);

// convert subprocess write & read to FILE objects
streamTo = fdopen(toNode[1], "w");
streamFrom = fdopen(fromNode[0], "r");

nodeState = BRACKETS_NODE_PORT_NOT_YET_SET;

// done launching process so release mutex
if (pthread_mutex_unlock(&mutex)) {
fprintf(stderr,
"failed to release mutex for Node subprocess startup: %s\n",
strerror(errno));
}

// start pipe read thread
pthread_t readthread_id;
if (pthread_create(&readthread_id, NULL, &nodeReadThread, NULL) != 0)
nodeState = BRACKETS_NODE_FAILED;
// ugly - need to think more about what to do if read thread fails

}

return NULL;
}


// Thread function for the thread that reads from the Node pipe
// Reads on anonymous pipes are always blocking (OVERLAPPED reads
// are not possible) So, we need to do this in a separate thread
//

// TODO: This code first reads to a character buffer, and then
// copies it to a std::string. The code could be optimized to avoid
// this double-copy
//DWORD WINAPI NodeReadThread(LPVOID lpParam) {
//}
void* nodeReadThread(void* unused) {

char charBuf[BRACKETS_NODE_BUFFER_SIZE];
std::string strBuf("");
while (fgets(charBuf, BRACKETS_NODE_BUFFER_SIZE, streamFrom) != NULL) {
strBuf.assign(charBuf);
processIncomingData(strBuf);
}
}

// Thread function for the thread that starts the node process
// and monitors that process's state
//DWORD WINAPI NodeThread(LPVOID lpParam) {
//}

// Determines whether the current node process has run long
// enough that a restart is warranted, and initiates the startup
// if so. Can also optionally terminate the running process.
void restartNode(bool terminateCurrentProcess) {

}

// Sends data to the node process. If the write fails completely,
// calls restartNode.
void sendData(const std::string &data) {

if (pthread_mutex_lock(&mutex)) {
fprintf(stderr,
"failed to acquire mutex for write to Node subprocess: %s\n",
strerror(errno));
return;
}

// write to pipe
fprintf(streamTo, "%s", data.c_str());

if (pthread_mutex_unlock(&mutex)) {
fprintf(stderr,
"failed to release mutex for write to Node subprocess: %s\n",
strerror(errno));
}
}

// Thread-safe way to access nodeState variable
// Returns nodeState variable
int getNodeState() {

return nodeState;
}

// Thread-safe way to set nodeState variable
void setNodeState(int newState) {

if (pthread_mutex_lock(&mutex)) {
fprintf(stderr,
"failed to acquire mutex for setting Node state: %s\n",
strerror(errno));
return;
}
nodeState = newState;
if (pthread_mutex_unlock(&mutex)) {
fprintf(stderr,
"failed to release mutex for Node set state: %s\n",
strerror(errno));
}
}
8 changes: 6 additions & 2 deletions appshell/cefclient_gtk.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights
// Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

Expand All @@ -13,6 +13,7 @@
#include "include/cef_frame.h"
#include "include/cef_runnable.h"
#include "client_handler.h"
#include "appshell_node_process.h"

static std::string APPICONS[] = {"appshell32.png","appshell48.png","appshell128.png","appshell256.png"};
char szWorkingDir[512]; // The current working directory
Expand All @@ -24,7 +25,7 @@ bool isReallyClosing = false;
// The global ClientHandler reference.
extern CefRefPtr<ClientHandler> g_handler;

//Application startup time
// Application startup time
time_t g_appStartupTime;

void destroy(void) {
Expand Down Expand Up @@ -247,6 +248,9 @@ int main(int argc, char* argv[]) {
// Install an signal handler so we clean up after ourselves.
signal(SIGINT, TerminationSignalHandler);
signal(SIGTERM, TerminationSignalHandler);

// Start the node server process
startNodeProcess();

CefRunMessageLoop();

Expand Down
13 changes: 13 additions & 0 deletions appshell/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@
#define NODE_CORE_PATH @"/Contents/node-core"

#endif
#ifdef OS_LINUX
//#define GROUP_NAME @""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comments?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. Nevermind, I see this is a placeholder for OS-specific preferences. I'll add a TODO.

//#define APP_NAME @"Brackets"
//#define WINDOW_TITLE APP_NAME

// Path for node resources is in dependencies dir and relative to the location of the appshell executable
#define NODE_EXECUTABLE_PATH "deps/node/bin/Brackets-node"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can update appshell.gyp and the grunt installer task so that this matches mac and win. Mind if I start a new pull requests with those changes? Your commits (and authorship) will stay intact.

#define NODE_CORE_PATH "appshell/node-core"

#endif




#define REMOTE_DEBUGGING_PORT 9234

Expand Down