1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

Replaced Spawn/Terminate functions with experimental ConsoleProcess Class.

Small tweaks to Rakefile for new gem release.

git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@621 19e92222-5c0b-0410-8929-a290d50e31e9
This commit is contained in:
luislavena 2007-09-24 05:57:44 +00:00
parent 5a948694ba
commit 4cf7422cd9
7 changed files with 130 additions and 504 deletions

View file

@ -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.
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.

View file

@ -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

View file

@ -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"

View file

@ -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.")

View file

@ -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

View file

@ -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

View file

@ -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__