/* WebROaR - Ruby Application Server - http://webroar.in/
* Copyright (C) 2009 Goonj LLC
*
* This file is part of WebROaR.
*
* WebROaR 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 3 of the License, or
* (at your option) any later version.
*
* WebROaR is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WebROaR. If not, see .
*/
#include
#include
#include
#include
#include
#include
#define DEFAULT_LOWER_LIMIT 1024
#define DEFAULT_UPPER_LIMIT 10485760
static wkr_t *worker = NULL;
config_t *Config = NULL;
struct ev_loop *loop; // Event loop
struct ev_idle idle_watcher;
int is_alive = 1;
/** Usage */
void print_usage(char *appname) {
printf("usage: \n%s [-c ] [-i ] [-n ]",appname);
printf("[-l ] [-f ] [-o ] [-k ]\n");
printf(" = Control port number (control sock path in case [-i 1])\n");
printf(" = Unix domain socket flag. Value should be 0 or 1.\n");
printf(" = Name of application\n");
printf(" = Logging Level\n");
printf(" = Name of the log file\n");
printf(" = Root directory path\n");
printf(" = Keep Alive flag value must be 'y'/'n'\n");
}
void sigproc() {
// file_log("/tmp/too_many_worker.log","Webroar-Worker of %s getting close\n", worker->tmp->name.str);
LOG_DEBUG(4,"**************Caught Interrupt Signal************");
is_alive = 0;
}
void cleanup() {
LOG_FUNCTION
ev_idle_stop(loop, &idle_watcher);
LOG_DEBUG(DEBUG,"stoping event loop");
ev_unloop(loop,EVUNLOOP_ALL);
//TODO: send worker stopping signal
worker_free(&worker);
wr_worker_config_free(Config);
LOG_INFO("Worker stopped and exiting gracefully.");
close_logger();
exit(0);
}
/** Handle segmentation fault */
void crash_handler(int sig) {
void *array[Config->Worker.stack_tace];
size_t size;
char **bt_symbols;
char bt_string[Config->Worker.stack_tace * STR_SIZE256];
int i;
LOG_ERROR(FATAL, "Got %d signal, trying to create core file.", sig);
signal(sig, SIG_DFL);
//kill(getpid(), sig);
if(fork() == 0) { // child
char cmd[64], core_file_name[48], timestamp[24];
int rv;
signal(SIGCHLD, SIG_DFL);
//TODO: add application name
sprintf(core_file_name, "/tmp/webroar-worker");
if ( get_timestamp(timestamp) == 0 ) {
strcat(core_file_name, "-");
strcat(core_file_name, timestamp);
}
#ifdef __APPLE__
sprintf(cmd, "gcore -c %s %ld", core_file_name, (long) getppid() );
#else
sprintf(cmd, "gcore -o %s %ld", core_file_name, (long) getppid() );
#endif
rv = system(cmd);
if ( rv < 0 ) {
LOG_ERROR(FATAL, "Core file creation failed, gcore might be missing... rv = %d, errno = %d, error = %s", rv, errno, strerror(errno));
} else {
LOG_INFO("Core file - %s created", core_file_name);
}
exit(0);
}
sleep(5);
// get void*'s for all entries on the stack
size = backtrace(array, Config->Worker.stack_tace);
bt_symbols = backtrace_symbols(array, size);
strcpy(bt_string, "\n");
for(i = 0; i < size; i++) {
strcat(bt_string, bt_symbols[i]);
strcat(bt_string, "\n");
}
LOG_ERROR(FATAL, "Obtained %zd stack frames.%s", size, bt_string);
free(bt_symbols);
//TODO: carefully dump worker state, current request
cleanup();
}
/** Read and set Worker Configuration **/
void wr_worker_config_read(){
LOG_FUNCTION
node_t *root;
LOG_DEBUG(4,"YAML file path %s", Config->Worker.File.internal_config);
root = yaml_parse(Config->Worker.File.internal_config.str);
if(!root) {
LOG_ERROR(SEVERE, "Config file found with erroneous entries. Please correct it.");
printf("Config file found with erroneous entries. Please correct it.\n");
return;
}
wr_set_numeric_value(root, "Worker/maximum_request_body_size", &Config->Worker.max_body_size, FALSE);
node_free(root);
Config->Worker.Compress.lower_limit = DEFAULT_LOWER_LIMIT;
Config->Worker.Compress.upper_limit = DEFAULT_UPPER_LIMIT;
}
/** Parse command line arguments */
wkr_tmp_t* parse_args(int argc, char **argv) {
int option;
extern char *optarg;
size_t len;
char *str;
int invalid_arg_flag = 0, log_level = INFO;
wkr_tmp_t *tmp = wkr_tmp_new();
if(tmp == NULL)
return NULL;
while ( (option=getopt(argc,argv,"l:f:c:i:n:o:k:")) != -1 ) {
str = optarg;
len = strlen(str);
switch ( option ) {
case 'l': // Logging level
log_level = atoi(optarg);
break;
case 'f': // Log file name
wr_string_free(tmp->log_file);
wr_string_new(tmp->log_file, str, len);
break;
case 'c': // Control path
wr_string_new(tmp->ctl_path, str, len);
break;
case 'i': // Unix domain socket flag
if(strcmp(optarg, "y")==0) {
tmp->is_uds = TRUE;
}
break;
case 'n': // Application name
wr_string_new(tmp->name, str, len);
wr_string_free(tmp->log_file);
tmp->log_file.str = (char*) malloc(sizeof(char)*(strlen(optarg)+8));
tmp->log_file.len = sprintf(tmp->log_file.str,"%s.log", optarg);
break;
case 'o': // Server root path
wr_string_new(tmp->root_path, str, len);
break;
case 'k':
if(strcmp(optarg, "n")==0) {
tmp->keep_alive = FALSE;
}
break;
default:
invalid_arg_flag++;
}
}
Config = wr_worker_config_init(tmp->root_path.str);
wr_worker_config_read();
if(tmp->log_file.str){
initialize_logger(tmp->log_file.str, Config->Worker.Server.name.str, Config->Worker.Server.version.str);
redirect_standard_io();
#ifdef L_DEBUG
set_log_severity(DEBUG);
#else
set_log_severity(log_level);
#endif
}else{
perror("Log file is not specified.");
}
if (invalid_arg_flag > 0 || tmp->root_path.str == NULL || Config == NULL) {
print_usage(argv[0]);
LOG_ERROR(SEVERE, "Either argument is invalid or application/root path is not passed.");
wkr_tmp_free(&tmp);
wr_worker_config_free(Config);
return NULL;
}
if(strcmp(tmp->name.str, Config->Worker.static_server.str) == 0){
tmp->is_static = 1;
}
return tmp;
}
void start_idle_watcher() {
LOG_FUNCTION
if(!ev_is_active(&idle_watcher)) {
ev_idle_start (loop, &idle_watcher);
}
}
void idle_cb (struct ev_loop *loop, struct ev_idle *w, int revents) {
LOG_FUNCTION
/* Calling libev's blocking call ev_loop() between TRAP_* macros were working on Ruby 1.8,
* but didn't worked on Ruby 1.9, looks we need to schedule ruby threads on our own
*/
if(rb_thread_alone()) {
/* Stop scheduling ruby threads, there is only one! */
ev_idle_stop(loop, &idle_watcher);
} else {
/* TODO: Found following three api to schedule ruby threads
* rb_thread_schedule() was getting called infinitely and eating most of the CPU.
* rb_thread_polling() was getting called, for approximately 16 times in a second on 1.8
* and 10 times on 1.9
* rb_thread_select() takes delay(in struct timeval) as last argument, useful to control
* number of call in a second, but need to checkout best value for that argument
* Ruby 1.9 have rb_thread_blocking_region() to execute C blocking call, need to explore it
* Checkout the best strategy to combine ruby thread scheduling with libev.
* Currently rb_thread_polling looks suitable to use */
rb_thread_polling();
//rb_thread_schedule();
}
}
void init_idle_watcher(wkr_t *w){
if(!w->http->stat){
ev_idle_init (&idle_watcher, idle_cb);
start_idle_watcher();
}
}
int main(int argc, char **argv) {
wkr_t* w = NULL;
if(argc == 1) {
print_usage(argv[0]);
return -1;
}
signal(SIGSEGV, crash_handler); /* set our handler for segfault */
// signal(SIGQUIT, crash_handler);
// signal(SIGILL, crash_handler);
// signal(SIGABRT, crash_handler);
// signal(SIGFPE, crash_handler);
wkr_tmp_t *tmp = parse_args(argc, argv);
if(tmp == NULL)
return -1;
loop = ev_default_loop (0);
w = worker_new(loop, tmp);
if(w==NULL){
wr_worker_config_free(Config);
return -1;
}
worker = w;
//TODO: Windows Portability?
signal(SIGHUP, sigproc); /* catch hangup signal */
signal(SIGINT, sigproc);
signal(SIGTERM, sigproc);
// signal(SIGCHLD, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
while(is_alive ==1) {
/* TODO: wrapping ev_loop() between TARP_* macros didn't worked in Ruby 1.9 */
//TRAP_BEG;
ev_loop(loop,EVLOOP_ONESHOT);
//TRAP_END;
}
cleanup();
return 0;
}