diff --git a/projects/mongrel_service/CHANGELOG b/projects/mongrel_service/CHANGELOG index 9806928c..67f0697e 100644 --- a/projects/mongrel_service/CHANGELOG +++ b/projects/mongrel_service/CHANGELOG @@ -1,26 +1,17 @@ -* SVN * - - * Properly display package/gem version for mongrel_service. Closes #13823. - - * Updated ServiceFB to r80 to solve issue when compiling with FB > 0.17. - -* 0.3.2 * - - * Solved detection of parent process at ServiceFB level - (solves the x64 Windows issues). - - * Upgraded to ServiceFB 'trunk' (but pistoned it, just in case). - - * Fixed problems with ruby installations outside PATH or inside folders with spaces. - - * Activate FB pedantic warnings by default (is really useful). - -* 0.3.1 * - - * Single Service (SingleMongrel) object type implemented. - - * Updated Rakefile to reflect the new building steps. - - * Removed SendSignal, too hackish for my taste, replaced with complete FB solution. - - * Added basic Process monitoring and re-spawning. \ No newline at end of file + +v0.3.3. + Properly display package/gem version for mongrel_service; Closes #13823. + Updated ServiceFB to r80 to solve issue when compiling with FB > 0.17. + Replaced Spawn/Terminate functions with experimental ConsoleProcess Class. + +v0.3.2. + Solved detection of parent process at ServiceFB level (solves the x64 Windows issues). + Upgraded to ServiceFB 'trunk' (but pistoned it, just in case). + Fixed problems with ruby installations outside PATH or inside folders with spaces. + Activate FB pedantic warnings by default (is really useful). + +v0.3.1. + Single Service (SingleMongrel) object type implemented. + Updated Rakefile to reflect the new building steps. + Removed SendSignal, too hackish for my taste, replaced with complete FB solution. + Added basic Process monitoring and re-spawning. diff --git a/projects/mongrel_service/Manifest b/projects/mongrel_service/Manifest index c51f20f6..d5351fa2 100644 --- a/projects/mongrel_service/Manifest +++ b/projects/mongrel_service/Manifest @@ -3,10 +3,10 @@ tools/freebasic.rb TODO resources/defaults.yaml README -native/process.bi -native/process.bas native/mongrel_service.bi native/mongrel_service.bas +native/console_process.bi +native/console_process.bas native/_debug.bi LICENSE lib/ServiceFB/ServiceFB_Utils.bi diff --git a/projects/mongrel_service/Rakefile b/projects/mongrel_service/Rakefile index a6b085a2..015fe471 100644 --- a/projects/mongrel_service/Rakefile +++ b/projects/mongrel_service/Rakefile @@ -1,92 +1,87 @@ - -require 'echoe' -require 'tools/freebasic' - -echoe_spec = Echoe.new("mongrel_service") do |p| - p.summary = "Mongrel Native Win32 Service Plugin for Rails" - p.summary += " (debug build)" unless ENV['RELEASE'] - p.description = "This plugin offer native win32 services for rails, powered by Mongrel." - p.author = "Luis Lavena" - p.platform = Gem::Platform::WIN32 - p.dependencies = ['gem_plugin >=0.2.1', 'mongrel >=0.3.12.4', 'win32-service >=0.5.0'] - - p.need_tar_gz = false - p.need_zip = true - p.certificate_chain = ['/Users/eweaver/p/configuration/gem_certificates/mongrel/mongrel-public_cert.pem', - '/Users/eweaver/p/configuration/gem_certificates/evan_weaver-mongrel-public_cert.pem'] - p.require_signed = true -end - -desc "Compile native code" -task :compile => [:native_lib, :native_service] - -task :"bin/mongrel_service.exe" => [:clean, :compile] - -# global options shared by all the project in this Rakefile -OPTIONS = { - :debug => false, - :profile => false, - :errorchecking => :ex, - :mt => true, - :pedantic => true } - -OPTIONS[:debug] = true if ENV['DEBUG'] -OPTIONS[:profile] = true if ENV['PROFILE'] -OPTIONS[:errorchecking] = :exx if ENV['EXX'] -OPTIONS[:pedantic] = false if ENV['NOPEDANTIC'] - -# ServiceFB namespace (lib) -namespace :lib do - project_task 'servicefb' do - lib 'ServiceFB' - build_to 'lib' - - define 'SERVICEFB_DEBUG_LOG' unless ENV['RELEASE'] - source 'lib/ServiceFB/ServiceFB.bas' - - option OPTIONS - end - - project_task 'servicefb_utils' do - lib 'ServiceFB_Utils' - build_to 'lib' - - define 'SERVICEFB_DEBUG_LOG' unless ENV['RELEASE'] - source 'lib/ServiceFB/ServiceFB_Utils.bas' - - option OPTIONS - end -end -# add lib namespace to global tasks -#include_projects_of :lib -task :native_lib => "lib:build" -task :clean => "lib:clobber" - -# mongrel_service (native) -namespace :native do - project_task 'mongrel_service' do - executable 'mongrel_service' - build_to 'bin' - - define 'DEBUG_LOG' unless ENV['RELEASE'] - define "GEM_VERSION=#{echoe_spec.version}" - - main 'native/mongrel_service.bas' - source 'native/process.bas' - - # including the precompiled file show warnings when linking with - # ld, due pial m$ directives in the obj file - # will solve that later, when migrate the asm part of code to gcc - # source 'native/send_signal.o' - - lib_path 'lib' - library 'ServiceFB', 'ServiceFB_Utils' - library 'user32', 'advapi32', 'psapi' - - option OPTIONS - end -end -#include_projects_of :native -task :native_service => "native:build" -task :clean => "native:clobber" - +require 'echoe' +require 'tools/freebasic' + +echoe_spec = Echoe.new("mongrel_service", "0.3.3") do |p| + p.summary = "Mongrel Native Win32 Service Plugin for Rails" + p.summary += " (debug build)" unless ENV['RELEASE'] + p.description = "This plugin offer native win32 services for rails, powered by Mongrel." + p.author = "Luis Lavena" + p.platform = Gem::Platform::WIN32 + p.dependencies = ['gem_plugin >=0.2.2', 'mongrel >=1.0.1', 'win32-service >=0.5.0'] + + p.need_tar_gz = false + p.need_zip = true + p.certificate_chain = ['~/gem_certificates/mongrel-public_cert.pem', + '~/gem_certificates/luislavena-mongrel-public_cert.pem'] + p.require_signed = true +end + +desc "Compile native code" +task :compile => [:native_lib, :native_service] + +#task :"bin/mongrel_service.exe" => [:clean, :compile] + +# global options shared by all the project in this Rakefile +OPTIONS = { + :debug => false, + :profile => false, + :errorchecking => :ex, + :mt => true, + :pedantic => true } + +OPTIONS[:debug] = true if ENV['DEBUG'] +OPTIONS[:profile] = true if ENV['PROFILE'] +OPTIONS[:errorchecking] = :exx if ENV['EXX'] +OPTIONS[:pedantic] = false if ENV['NOPEDANTIC'] + +# ServiceFB namespace (lib) +namespace :lib do + project_task 'servicefb' do + lib 'ServiceFB' + build_to 'lib' + + define 'SERVICEFB_DEBUG_LOG' unless ENV['RELEASE'] + source 'lib/ServiceFB/ServiceFB.bas' + + option OPTIONS + end + + project_task 'servicefb_utils' do + lib 'ServiceFB_Utils' + build_to 'lib' + + define 'SERVICEFB_DEBUG_LOG' unless ENV['RELEASE'] + source 'lib/ServiceFB/ServiceFB_Utils.bas' + + option OPTIONS + end +end + +# add lib namespace to global tasks +#include_projects_of :lib +task :native_lib => "lib:build" +task :clean => "lib:clobber" + +# mongrel_service (native) +namespace :native do + project_task 'mongrel_service' do + executable 'mongrel_service' + build_to 'bin' + + define 'DEBUG_LOG' unless ENV['RELEASE'] + define "GEM_VERSION=#{echoe_spec.version}" + + main 'native/mongrel_service.bas' + source 'native/console_process.bas' + + lib_path 'lib' + library 'ServiceFB', 'ServiceFB_Utils' + library 'user32', 'advapi32', 'psapi' + + option OPTIONS + end +end + +#include_projects_of :native +task :native_service => "native:build" +task :clean => "native:clobber" diff --git a/projects/mongrel_service/native/mongrel_service.bas b/projects/mongrel_service/native/mongrel_service.bas index 9b5be3dc..49caa1bf 100644 --- a/projects/mongrel_service/native/mongrel_service.bas +++ b/projects/mongrel_service/native/mongrel_service.bas @@ -14,7 +14,7 @@ '################################################################## '# Requirements: -'# - FreeBASIC 0.17, Win32 CVS Build (as for November 09, 2006). +'# - FreeBASIC 0.18 '# '################################################################## @@ -23,9 +23,9 @@ #include once "_debug.bi" namespace mongrel_service - using fb.process - constructor SingleMongrel() + dim redirect_file as string + with this.__service .name = "single" .description = "Mongrel Single Process service" @@ -39,6 +39,12 @@ namespace mongrel_service .onStop = @single_onStop end with + with this.__console + redirect_file = EXEPATH + "\mongrel.log" + debug("redirecting to: " + redirect_file) + .redirect(ProcessStdBoth, redirect_file) + end with + '# TODO: fix inheritance here single_mongrel_ref = @this end constructor @@ -64,6 +70,10 @@ namespace mongrel_service '# SingleMongrel instance. now we should call StillAlive self.StillAlive() if (len(self.commandline) > 0) then + '# assign the program + single_mongrel_ref->__console.filename = mongrel_cmd + single_mongrel_ref->__console.arguments = self.commandline + '# fix commandline, it currently contains params to be passed to '# mongrel_rails, and not ruby.exe nor the script to be run. self.commandline = mongrel_cmd + " " + self.commandline @@ -71,7 +81,9 @@ namespace mongrel_service '# now launch the child process debug("starting child process with cmdline: " + self.commandline) single_mongrel_ref->__child_pid = 0 - single_mongrel_ref->__child_pid = Spawn(self.commandline) + if (single_mongrel_ref->__console.start() = true) then + single_mongrel_ref->__child_pid = single_mongrel_ref->__console.pid + end if self.StillAlive() '# check if pid is valid @@ -96,13 +108,15 @@ namespace mongrel_service do while (self.state = Running) or (self.state = Paused) '# instead of sitting idle here, we must monitor the pid '# and re-spawn a new process if needed - if not (Status(single_mongrel_ref->__child_pid) = ProcessStillActive) then + if not (single_mongrel_ref->__console.running = true) then '# check if we aren't terminating if (self.state = Running) or (self.state = Paused) then debug("child process terminated!, re-spawning a new one") single_mongrel_ref->__child_pid = 0 - single_mongrel_ref->__child_pid = Spawn(self.commandline) + if (single_mongrel_ref->__console.start() = true) then + single_mongrel_ref->__child_pid = single_mongrel_ref->__console.pid + end if if (single_mongrel_ref->__child_pid > 0) then debug("new child process pid: " + str(single_mongrel_ref->__child_pid)) @@ -123,8 +137,7 @@ namespace mongrel_service '# now terminates the child process if not (single_mongrel_ref->__child_pid = 0) then debug("trying to kill pid: " + str(single_mongrel_ref->__child_pid)) - 'if not (send_break(single_mongrel_ref->__child_pid) = 0) then - if not (Terminate(single_mongrel_ref->__child_pid) = TRUE) then + if not (single_mongrel_ref->__console.terminate() = true) then debug("Terminate() reported a problem when terminating process " + str(single_mongrel_ref->__child_pid)) else debug("child process terminated with success.") diff --git a/projects/mongrel_service/native/mongrel_service.bi b/projects/mongrel_service/native/mongrel_service.bi index 56fca92b..d5ea0dcd 100644 --- a/projects/mongrel_service/native/mongrel_service.bi +++ b/projects/mongrel_service/native/mongrel_service.bi @@ -14,13 +14,13 @@ '################################################################## '# Requirements: -'# - FreeBASIC 0.17, Win32 CVS Build (as for November 09, 2006). +'# - FreeBASIC 0.18. '# '################################################################## #define SERVICEFB_INCLUDE_UTILS #include once "lib/ServiceFB/ServiceFB.bi" -#include once "process.bi" +#include once "console_process.bi" '# use for debug versions #if not defined(GEM_VERSION) @@ -52,6 +52,7 @@ namespace mongrel_service 'declare sub onStop() __service as ServiceProcess + __console as ConsoleProcess __child_pid as uinteger end type diff --git a/projects/mongrel_service/native/process.bas b/projects/mongrel_service/native/process.bas deleted file mode 100644 index f12713ca..00000000 --- a/projects/mongrel_service/native/process.bas +++ /dev/null @@ -1,316 +0,0 @@ -'################################################################## -'# -'# mongrel_service: Win32 native implementation for mongrel -'# (using ServiceFB and FreeBASIC) -'# -'# Copyright (c) 2006 Multimedia systems -'# (c) and code by Luis Lavena -'# -'# mongrel_service (native) and mongrel_service gem_pluing are licensed -'# in the same terms as mongrel, please review the mongrel license at -'# http://mongrel.rubyforge.org/license.html -'# -'################################################################## - -'################################################################## -'# Requirements: -'# - FreeBASIC 0.17, Win32 CVS Build (as for November 09, 2006). -'# -'################################################################## - -#include once "process.bi" -#define DEBUG_LOG_FILE EXEPATH + "\mongrel_service.log" -#include once "_debug.bi" - -namespace fb -namespace process - '# Spawn(cmdline) will try to create a new process, monitor - '# if it launched successfuly (5 seconds) and then return the - '# new children PID (Process IDentification) or 0 in case of problems - function Spawn(byref cmdline as string) as uinteger - dim result as uinteger - dim success as BOOL - - '# New Process resources - dim child as PROCESS_INFORMATION - dim context as STARTUPINFO - dim child_sa as SECURITY_ATTRIBUTES = type(sizeof(SECURITY_ATTRIBUTES), NULL, TRUE) - dim wait_code as DWORD - - '# StdIn, StdOut, StdErr Read and Write Pipes. - dim as HANDLE StdInRd, StdOutRd, StdErrRd - dim as HANDLE StdInWr, StdOutWr, StdErrWr - - debug("Spawn() init") - - '# to ensure everything will work, we must allocate a console - '# using AllocConsole, even if it fails. - '# This solve the problems when running as service. - if (AllocConsole() = 0) then - debug("Success in AllocConsole()") - else - debug("AllocConsole failed, maybe already allocated, safely to discart.") - end if - - '# presume all worked - success = TRUE - - '# Create the pipes - '# StdIn - if (CreatePipe( @StdInRd, @StdInWr, @child_sa, 0 ) = 0) then - debug("Error creating StdIn pipes.") - success = FALSE - end if - - '# StdOut - if (CreatePipe( @StdOutRd, @StdOutWr, @child_sa, 0 ) = 0) then - debug("Error creating StdOut pipes.") - success = FALSE - end if - - '# StdErr - if (CreatePipe( @StdErrRd, @StdErrWr, @child_sa, 0 ) = 0) then - debug("Error creating StdErr pipes.") - success = FALSE - end if - - '# Ensure the handles to the pipe are not inherited. - if (SetHandleInformation( StdInWr, HANDLE_FLAG_INHERIT, 0) = 0) then - debug("Error disabling StdInWr handle.") - success = FALSE - end if - - if (SetHandleInformation( StdOutRd, HANDLE_FLAG_INHERIT, 0) = 0) then - debug("Error disabling StdOutRd handle.") - success = FALSE - end if - - if (SetHandleInformation( StdErrRd, HANDLE_FLAG_INHERIT, 0) = 0) then - debug("Error disabling StdErrRd handle.") - success = FALSE - end if - - '# without the pipes, we shouldn't continue! - if (success = TRUE) then - '# Set the Std* handles ;-) - with context - .cb = sizeof( context ) - .hStdError = StdErrWr - .hStdOutput = StdOutWr - .hStdInput = StdInRd - .dwFlags = STARTF_USESTDHANDLES - end with - - '# now creates the process - debug("Creating child process with cmdline: " + cmdline) - if (CreateProcess(NULL, _ - strptr(cmdline), _ - NULL, _ - NULL, _ - TRUE, _ - 0, _ - NULL, _ - NULL, _ - @context, _ - @child) = 0) then - - debug("Error creating child process, error #" + str(GetLastError())) - result = 0 - else - '# close the Std* handles - debug("Closing handles.") - CloseHandle(StdInRd) - CloseHandle(StdInWr) - CloseHandle(StdOutRd) - CloseHandle(StdOutWr) - CloseHandle(StdErrRd) - CloseHandle(StdErrWr) - - '# close children main Thread handle - if (CloseHandle(child.hThread) = 0) then - debug("Problem closing children main thread.") - end if - - '# now wait 2 seconds if the children process unexpectly quits - wait_code = WaitForSingleObject(child.hProcess, 2000) - debug("wait_code: " + str(wait_code)) - if (wait_code = WAIT_TIMEOUT) then - '# the process is still running, we are good - '# save a reference to the pid - result = child.dwProcessId - debug("New children PID: " + str(result)) - - '# now close the handle - CloseHandle(child.hProcess) - else - '# the process failed or terminated earlier - debug("failed, the process terminate earlier.") - result = 0 - end if '# (wait_code = WAIT_OBJECT_0) - end if '# (CreateProcess() = 0) - else - debug("problem preparing environment for child process, no success") - result = 0 - end if '# (success = TRUE) - - debug("Spawn() done") - return result - end function - - - '# Terminate(PID) will hook the special console handler (_child_console_handler) - '# and try sending CTRL_C_EVENT, CTRL_BREAK_EVENT and TerminateProcess - '# in case of the first two fails. - function Terminate(byval pid as uinteger) as BOOL - dim result as BOOL - dim success as BOOL - dim exit_code as DWORD - dim wait_code as DWORD - - '# process resources - dim child as HANDLE - - debug("Terminate() init") - - '# is pid valid? - if (pid > 0) then - '# hook our custom console handler - debug("hooking console handler") - if (SetConsoleCtrlHandler(@_child_console_handler, TRUE) = 0) then - debug("error hooking our custom error handler") - end if - - '# get a handle to Process - debug("OpenProcess() with Terminate and Query flags") - child = OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_TERMINATE or SYNCHRONIZE, FALSE, pid) - if not (child = NULL) then - '# process is valid, perform actions - success = FALSE - - '# send CTRL_C_EVENT and wait for result - debug("sending Ctrl-C to child pid " + str(pid)) - if not (GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) = 0) then - '# it worked, wait 5 seconds terminates. - debug("send worked, waiting 5 seconds to terminate...") - wait_code = WaitForSingleObject(child, 5000) - debug("wait_code: " + str(wait_code)) - if not (wait_code = WAIT_TIMEOUT) then - debug("child process terminated properly.") - success = TRUE - end if - else - debug("cannot generate event, error " + str(GetLastError())) - success = FALSE - end if - - '# Ctrl-C didn't work, try Ctrl-Break - if (success = FALSE) then - '# send CTRL_BREAK_EVENT and wait for result - debug("sending Ctrl-Break to child pid " + str(pid)) - if not (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0) = 0) then - '# it worked, wait 5 seconds terminates. - debug("send worked, waiting 5 seconds to terminate...") - wait_code = WaitForSingleObject(child, 5000) - debug("wait_code: " + str(wait_code)) - if not (wait_code = WAIT_TIMEOUT) then - debug("child process terminated properly.") - success = TRUE - end if - else - debug("cannot generate event, error " + str(GetLastError())) - success = FALSE - end if - end if - - '# still no luck? we should do a hard kill then - if (success = FALSE) then - debug("doing kill using TerminateProcess") - if (TerminateProcess(child, 3) = 0) then - debug("TerminateProcess failed, error " + str(GetLastError())) - else - success = TRUE - end if - end if - - '# now get process exit code - if not (GetExitCodeProcess(child, @exit_code) = 0) then - debug("getting child process exit code: " + str(exit_code)) - if (exit_code = 0) then - debug("process terminated ok.") - result = TRUE - elseif (exit_code = STILL_ACTIVE) then - debug("process still active") - result = FALSE - else - debug("process terminated with exit_code: " + str(exit_code)) - result = TRUE - end if - else - debug("error getting child process exit code, value: " + str(exit_code) + ", error " + str(GetLastError())) - result = FALSE - end if - else - '# invalid process handler - result = FALSE - end if - - '# remove hooks - SetConsoleCtrlhandler(@_child_console_handler, FALSE) - - '# clean up all open handles - CloseHandle(child) - end if '# (pid > 0) - - return result - end function - - - '# StillActive(PID) will return FALSE (0) in case the process no longer - '# exist of get terminated with error. - function Status(byval pid as uinteger) as ProcessStateEnum - dim result as ProcessStateEnum - dim exit_code as DWORD - - '# process reference - dim child as HANDLE - - '# presume error? - result = ProcessQueryError - - '# is pid valid? - if (pid > 0) then - '# try getting a handler to the process - child = OpenProcess(PROCESS_QUERY_INFORMATION or SYNCHRONIZE, FALSE, pid) - if not (child = NULL) then - '# the process reference is valid, get the exit_code - if not (GetExitCodeProcess(child, @exit_code) = 0) then - '# no error in the query, get result - if (exit_code = STILL_ACTIVE) then - result = ProcessStillActive - end if '# (exit_code = STILL_ACTIVE) - end if '# not (GetExitCodeProcess()) - - '# closes the process handle - CloseHandle(child) - end if '# not (child = NULL) - end if '# (pid > 0) - - return result - end function - - '# Special hook used to avoid the process calling Terminate() - '# respond to CTRL_*_EVENTS when terminating child process - private function _child_console_handler(byval dwCtrlType as DWORD) as BOOL - dim result as BOOL - - debug("_child_console_handler, dwCtrlType: " + str(dwCtrlType)) - if (dwCtrlType = CTRL_C_EVENT) then - result = TRUE - elseif (dwCtrlType = CTRL_BREAK_EVENT) then - result = TRUE - end if - - return result - end function -end namespace '# fb.process -end namespace '# fb diff --git a/projects/mongrel_service/native/process.bi b/projects/mongrel_service/native/process.bi deleted file mode 100644 index 0173131c..00000000 --- a/projects/mongrel_service/native/process.bi +++ /dev/null @@ -1,58 +0,0 @@ -'################################################################## -'# -'# mongrel_service: Win32 native implementation for mongrel -'# (using ServiceFB and FreeBASIC) -'# -'# Copyright (c) 2006 Multimedia systems -'# (c) and code by Luis Lavena -'# -'# mongrel_service (native) and mongrel_service gem_pluing are licensed -'# in the same terms as mongrel, please review the mongrel license at -'# http://mongrel.rubyforge.org/license.html -'# -'################################################################## - -'################################################################## -'# Requirements: -'# - FreeBASIC 0.17, Win32 CVS Build (as for November 09, 2006). -'# -'################################################################## - -#ifndef __Process_bi__ -#define __Process_bi__ - -#include once "windows.bi" - -namespace fb -namespace process - '# fb.process functions that allow creation/graceful termination - '# of child process. - - '# Process Status Enum - enum ProcessStateEnum - ProcessQueryError = 0 - ProcessStillActive = STILL_ACTIVE - end enum - - - '# Spawn(cmdline) will try to create a new process, monitor - '# if it launched successfuly (5 seconds) and then return the - '# new children PID (Process IDentification) or 0 in case of problems - declare function Spawn(byref as string) as uinteger - - '# Terminate(PID) will hook the special console handler (_child_console_handler) - '# and try sending CTRL_C_EVENT, CTRL_BREAK_EVENT and TerminateProcess - '# in case of the first two fails. - declare function Terminate(byval as uinteger) as BOOL - - '# StillActive(PID) will return FALSE (0) in case the process no longer - '# exist of get terminated with error. - declare function Status(byval as uinteger) as ProcessStateEnum - - '# Special hook used to avoid the process calling Terminate() - '# respond to CTRL_*_EVENTS when terminating child process - declare function _child_console_handler(byval as DWORD) as BOOL -end namespace '# fb.process -end namespace '# fb - -#endif '# __Process_bi__