thin_service 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE +53 -0
- data/README.md +26 -0
- data/Rakefile +17 -0
- data/bin/thin_service +17 -0
- data/lib/thin_service.rb +15 -0
- data/lib/thin_service/command.rb +343 -0
- data/lib/thin_service/logger.rb +74 -0
- data/lib/thin_service/service_manager.rb +78 -0
- data/lib/thin_service/version.rb +3 -0
- data/resource/thin_service_wrapper.exe +0 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/thin_service_spec.rb +43 -0
- data/src/ServiceFB/ServiceFB.bas +651 -0
- data/src/ServiceFB/ServiceFB.bi +109 -0
- data/src/ServiceFB/ServiceFB_Utils.bas +495 -0
- data/src/ServiceFB/ServiceFB_Utils.bi +70 -0
- data/src/ServiceFB/_internals.bi +50 -0
- data/src/ServiceFB/_utils_internals.bi +51 -0
- data/src/thin_service/_debug.bi +61 -0
- data/src/thin_service/boolean.bi +18 -0
- data/src/thin_service/console_process.bas +397 -0
- data/src/thin_service/console_process.bi +75 -0
- data/src/thin_service/thin_service.bas +199 -0
- data/src/thin_service/thin_service.bi +61 -0
- data/tasks/native_lib.rake +40 -0
- data/tasks/native_service.rake +43 -0
- data/tasks/tests.rake +55 -0
- data/tests/all_tests.bas +18 -0
- data/tests/fixtures/mock_process.bas +92 -0
- data/tests/test_console_process.bas +402 -0
- data/tests/test_helpers.bas +35 -0
- data/tests/test_helpers.bi +8 -0
- data/thin_service.gemspec +21 -0
- data/tools/freebasic.rb +359 -0
- metadata +106 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
'#--
|
2
|
+
'# Copyright (c) 2006-2007 Luis Lavena, Multimedia systems
|
3
|
+
'#
|
4
|
+
'# This source code is released under the MIT License.
|
5
|
+
'# See MIT-LICENSE file for details
|
6
|
+
'#++
|
7
|
+
|
8
|
+
#ifndef __CONSOLE_PROCESS_BI__
|
9
|
+
#define __CONSOLE_PROCESS_BI__
|
10
|
+
|
11
|
+
#include once "windows.bi"
|
12
|
+
#include once "boolean.bi"
|
13
|
+
|
14
|
+
enum ProcessStdEnum
|
15
|
+
ProcessStdOut = 1
|
16
|
+
ProcessStdErr = 2
|
17
|
+
ProcessStdBoth = 3
|
18
|
+
end enum
|
19
|
+
|
20
|
+
type ConsoleProcess
|
21
|
+
'# this class provide basic functionality
|
22
|
+
'# to control child processes
|
23
|
+
|
24
|
+
'# new ConsoleProcess(Filename, Parameters)
|
25
|
+
declare constructor(byref as string = "", byref as string = "")
|
26
|
+
|
27
|
+
'# delete
|
28
|
+
declare destructor()
|
29
|
+
|
30
|
+
'# properties (only getters)
|
31
|
+
declare property filename as string
|
32
|
+
declare property filename(byref as string)
|
33
|
+
|
34
|
+
declare property arguments as string
|
35
|
+
declare property arguments(byref as string)
|
36
|
+
|
37
|
+
'# stdout and stderr allow you redirect
|
38
|
+
'# console output and errors to files
|
39
|
+
declare property redirected_stdout as string
|
40
|
+
declare property redirected_stderr as string
|
41
|
+
|
42
|
+
'# evaluate if the process is running
|
43
|
+
declare property running as boolean
|
44
|
+
|
45
|
+
'# pid will return the current Process ID, or 0 if no process is running
|
46
|
+
declare property pid as uinteger
|
47
|
+
|
48
|
+
'# exit_code is the value set by the process prior exiting.
|
49
|
+
declare property exit_code as uinteger
|
50
|
+
|
51
|
+
'# methods
|
52
|
+
declare function redirect(byval as ProcessStdEnum, byref as string) as boolean
|
53
|
+
declare function start() as boolean
|
54
|
+
declare function terminate(byval as boolean = false) as boolean
|
55
|
+
|
56
|
+
private:
|
57
|
+
_filename as string
|
58
|
+
_arguments as string
|
59
|
+
_pid as uinteger
|
60
|
+
_process_info as PROCESS_INFORMATION
|
61
|
+
_show_console as boolean = false
|
62
|
+
|
63
|
+
_redirect_stdout as boolean
|
64
|
+
_stdout_filename as string
|
65
|
+
|
66
|
+
_redirect_stderr as boolean
|
67
|
+
_stderr_filename as string
|
68
|
+
|
69
|
+
'# this fake console handler
|
70
|
+
'# is used to trap ctrl-c
|
71
|
+
declare static function _console_handler(byval as DWORD) as BOOL
|
72
|
+
|
73
|
+
end type 'ConsoleProcess
|
74
|
+
|
75
|
+
#endif '__CONSOLE_PROCESS_BI__
|
@@ -0,0 +1,199 @@
|
|
1
|
+
'##################################################################
|
2
|
+
'#
|
3
|
+
'# mongrel_service: Win32 native implementation for thin
|
4
|
+
'# (using ServiceFB and FreeBASIC)
|
5
|
+
'#
|
6
|
+
'# Copyright (c) 2006 Multimedia systems
|
7
|
+
'# (c) and code by Luis Lavena
|
8
|
+
'#
|
9
|
+
'# mongrel_service (native) and mongrel_service gem_pluing are licensed
|
10
|
+
'# in the same terms as mongrel, please review the mongrel license at
|
11
|
+
'# http://thin.rubyforge.org/license.html
|
12
|
+
|
13
|
+
'#
|
14
|
+
'##################################################################
|
15
|
+
|
16
|
+
'##################################################################
|
17
|
+
'# Requirements:
|
18
|
+
'# - FreeBASIC 0.18
|
19
|
+
'#
|
20
|
+
'##################################################################
|
21
|
+
|
22
|
+
#include once "thin_service.bi"
|
23
|
+
#define DEBUG_LOG_FILE EXEPATH + "\thin_service.log"
|
24
|
+
#include once "_debug.bi"
|
25
|
+
|
26
|
+
namespace thin_service
|
27
|
+
constructor SingleThin()
|
28
|
+
dim redirect_path as string = EXEPATH
|
29
|
+
dim redirect_file as string = "thin.default.log"
|
30
|
+
dim flag as string
|
31
|
+
dim idx as integer = 1
|
32
|
+
|
33
|
+
'# determine supplied logfile
|
34
|
+
flag = command(idx)
|
35
|
+
do while (len(flag) > 0)
|
36
|
+
'# application directory
|
37
|
+
if (flag = "-c") or (flag = "--chdir") then
|
38
|
+
redirect_path = command(idx + 1)
|
39
|
+
end if
|
40
|
+
|
41
|
+
'# log file
|
42
|
+
if (flag = "-l") or (flag = "--log") then
|
43
|
+
redirect_file = command(idx + 1)
|
44
|
+
end if
|
45
|
+
idx += 1
|
46
|
+
|
47
|
+
flag = command(idx)
|
48
|
+
loop
|
49
|
+
|
50
|
+
with this.__service
|
51
|
+
.name = "single"
|
52
|
+
.description = "Thin Single Process service"
|
53
|
+
|
54
|
+
'# disabling shared process
|
55
|
+
.shared_process = FALSE
|
56
|
+
|
57
|
+
'# TODO: fix inheritance here
|
58
|
+
.onInit = @single_onInit
|
59
|
+
.onStart = @single_onStart
|
60
|
+
.onStop = @single_onStop
|
61
|
+
end with
|
62
|
+
|
63
|
+
with this.__console
|
64
|
+
debug("redirecting to: " + redirect_path + "/" + redirect_file)
|
65
|
+
.redirect(ProcessStdBoth, (redirect_path + "/" + redirect_file))
|
66
|
+
end with
|
67
|
+
|
68
|
+
'# TODO: fix inheritance here
|
69
|
+
single_thin_ref = @this
|
70
|
+
end constructor
|
71
|
+
|
72
|
+
destructor SingleThin()
|
73
|
+
'# TODO: fin inheritance here
|
74
|
+
end destructor
|
75
|
+
|
76
|
+
function single_onInit(byref self as ServiceProcess) as integer
|
77
|
+
dim result as integer
|
78
|
+
dim thin_cmd as string
|
79
|
+
|
80
|
+
debug("single_onInit()")
|
81
|
+
|
82
|
+
'# ruby.exe must be in the path, which we guess is already there.
|
83
|
+
'# because thin_service executable (.exe) is located in the same
|
84
|
+
'# folder than thin_rails ruby script, we complete the path with
|
85
|
+
'# EXEPATH + "\thin_rails" to make it work.
|
86
|
+
'# FIXED ruby installation outside PATH and inside folders with spaces
|
87
|
+
thin_cmd = !"\"" + EXEPATH + !"\\ruby.exe" + !"\" " + !"\"" + EXEPATH + !"\\thin_service" + !"\""
|
88
|
+
|
89
|
+
'# due lack of inheritance, we use single_thin_ref as pointer to
|
90
|
+
'# SingleThin instance. now we should call StillAlive
|
91
|
+
self.StillAlive()
|
92
|
+
if (len(self.commandline) > 0) then
|
93
|
+
'# assign the program
|
94
|
+
single_thin_ref->__console.filename = thin_cmd
|
95
|
+
single_thin_ref->__console.arguments = self.commandline
|
96
|
+
|
97
|
+
'# fix commandline, it currently contains params to be passed to
|
98
|
+
'# thin_rails, and not ruby.exe nor the script to be run.
|
99
|
+
self.commandline = thin_cmd + " " + self.commandline
|
100
|
+
|
101
|
+
'# now launch the child process
|
102
|
+
debug("starting child process with cmdline: " + self.commandline)
|
103
|
+
single_thin_ref->__child_pid = 0
|
104
|
+
if (single_thin_ref->__console.start() = true) then
|
105
|
+
single_thin_ref->__child_pid = single_thin_ref->__console.pid
|
106
|
+
end if
|
107
|
+
self.StillAlive()
|
108
|
+
|
109
|
+
'# check if pid is valid
|
110
|
+
if (single_thin_ref->__child_pid > 0) then
|
111
|
+
'# it worked
|
112
|
+
debug("child process pid: " + str(single_thin_ref->__child_pid))
|
113
|
+
result = not FALSE
|
114
|
+
end if
|
115
|
+
else
|
116
|
+
'# if no param, no service!
|
117
|
+
debug("no parameters was passed to this service!")
|
118
|
+
result = FALSE
|
119
|
+
end if
|
120
|
+
|
121
|
+
debug("single_onInit() done")
|
122
|
+
return result
|
123
|
+
end function
|
124
|
+
|
125
|
+
sub single_onStart(byref self as ServiceProcess)
|
126
|
+
debug("single_onStart()")
|
127
|
+
|
128
|
+
do while (self.state = Running) or (self.state = Paused)
|
129
|
+
'# instead of sitting idle here, we must monitor the pid
|
130
|
+
'# and re-spawn a new process if needed
|
131
|
+
if not (single_thin_ref->__console.running = true) then
|
132
|
+
'# check if we aren't terminating
|
133
|
+
if (self.state = Running) or (self.state = Paused) then
|
134
|
+
debug("child process terminated!, re-spawning a new one")
|
135
|
+
|
136
|
+
single_thin_ref->__child_pid = 0
|
137
|
+
if (single_thin_ref->__console.start() = true) then
|
138
|
+
single_thin_ref->__child_pid = single_thin_ref->__console.pid
|
139
|
+
end if
|
140
|
+
|
141
|
+
if (single_thin_ref->__child_pid > 0) then
|
142
|
+
debug("new child process pid: " + str(single_thin_ref->__child_pid))
|
143
|
+
end if
|
144
|
+
end if
|
145
|
+
end if
|
146
|
+
|
147
|
+
'# wait for 5 seconds
|
148
|
+
sleep 5000
|
149
|
+
loop
|
150
|
+
|
151
|
+
debug("single_onStart() done")
|
152
|
+
end sub
|
153
|
+
|
154
|
+
sub single_onStop(byref self as ServiceProcess)
|
155
|
+
debug("single_onStop()")
|
156
|
+
|
157
|
+
'# now terminates the child process
|
158
|
+
if not (single_thin_ref->__child_pid = 0) then
|
159
|
+
debug("trying to kill pid: " + str(single_thin_ref->__child_pid))
|
160
|
+
if not (single_thin_ref->__console.terminate() = true) then
|
161
|
+
debug("Terminate() reported a problem when terminating process " + str(single_thin_ref->__child_pid))
|
162
|
+
else
|
163
|
+
debug("child process terminated with success.")
|
164
|
+
single_thin_ref->__child_pid = 0
|
165
|
+
end if
|
166
|
+
end if
|
167
|
+
|
168
|
+
debug("single_onStop() done")
|
169
|
+
end sub
|
170
|
+
|
171
|
+
sub application()
|
172
|
+
dim simple as SingleThin
|
173
|
+
dim host as ServiceHost
|
174
|
+
dim ctrl as ServiceController = ServiceController("Thin Windows Service", "version " + VERSION, _
|
175
|
+
"(c) 2006-2010 The Mongrel development team.")
|
176
|
+
|
177
|
+
'# add SingleThin (service)
|
178
|
+
host.Add(simple.__service)
|
179
|
+
select case ctrl.RunMode()
|
180
|
+
'# call from Service Control Manager (SCM)
|
181
|
+
case RunAsService:
|
182
|
+
debug("ServiceHost RunAsService")
|
183
|
+
host.Run()
|
184
|
+
|
185
|
+
'# call from console, useful for debug purposes.
|
186
|
+
case RunAsConsole:
|
187
|
+
debug("ServiceController Console")
|
188
|
+
ctrl.Console()
|
189
|
+
|
190
|
+
case else:
|
191
|
+
ctrl.Banner()
|
192
|
+
print "thin_service is not designed to run form commandline,"
|
193
|
+
print "please use thin_rails service:: commands to create a win32 service."
|
194
|
+
end select
|
195
|
+
end sub
|
196
|
+
end namespace
|
197
|
+
|
198
|
+
'# MAIN: start native thin_service here
|
199
|
+
thin_service.application()
|
@@ -0,0 +1,61 @@
|
|
1
|
+
'##################################################################
|
2
|
+
'#
|
3
|
+
'# thin_service: Win32 native implementation for thin
|
4
|
+
'# (using ServiceFB and FreeBASIC)
|
5
|
+
'#
|
6
|
+
'# Copyright (c) 2006 Multimedia systems
|
7
|
+
'# (c) and code by Luis Lavena
|
8
|
+
'#
|
9
|
+
'# thin_service (native) and thin_service gem_pluing are licensed
|
10
|
+
'# in the same terms as thin, please review the thin license at
|
11
|
+
'# http://thin.rubyforge.org/license.html
|
12
|
+
'#
|
13
|
+
'##################################################################
|
14
|
+
|
15
|
+
'##################################################################
|
16
|
+
'# Requirements:
|
17
|
+
'# - FreeBASIC 0.18.
|
18
|
+
'#
|
19
|
+
'##################################################################
|
20
|
+
|
21
|
+
#define SERVICEFB_INCLUDE_UTILS
|
22
|
+
#include once "ServiceFB.bi"
|
23
|
+
#include once "console_process.bi"
|
24
|
+
|
25
|
+
'# use for debug versions
|
26
|
+
#if not defined(GEM_VERSION)
|
27
|
+
#define GEM_VERSION (debug mode)
|
28
|
+
#endif
|
29
|
+
|
30
|
+
'# preprocessor stringize
|
31
|
+
#define PPSTR(x) #x
|
32
|
+
|
33
|
+
namespace thin_service
|
34
|
+
const VERSION as string = PPSTR(GEM_VERSION)
|
35
|
+
|
36
|
+
'# namespace include
|
37
|
+
using fb.svc
|
38
|
+
using fb.svc.utils
|
39
|
+
|
40
|
+
declare function single_onInit(byref as ServiceProcess) as integer
|
41
|
+
declare sub single_onStart(byref as ServiceProcess)
|
42
|
+
declare sub single_onStop(byref as ServiceProcess)
|
43
|
+
|
44
|
+
'# SingleThin
|
45
|
+
type SingleThin
|
46
|
+
declare constructor()
|
47
|
+
declare destructor()
|
48
|
+
|
49
|
+
'# TODO: replace for inheritance here
|
50
|
+
'declare function onInit() as integer
|
51
|
+
'declare sub onStart()
|
52
|
+
'declare sub onStop()
|
53
|
+
|
54
|
+
__service as ServiceProcess
|
55
|
+
__console as ConsoleProcess
|
56
|
+
__child_pid as uinteger
|
57
|
+
end type
|
58
|
+
|
59
|
+
'# TODO: replace with inheritance here
|
60
|
+
dim shared single_thin_ref as SingleThin ptr
|
61
|
+
end namespace
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../tools/freebasic'
|
2
|
+
|
3
|
+
# ServiceFB namespace (lib)
|
4
|
+
namespace :lib do
|
5
|
+
lib_options = {
|
6
|
+
:debug => false,
|
7
|
+
:profile => false,
|
8
|
+
:errorchecking => :ex,
|
9
|
+
:mt => true,
|
10
|
+
:pedantic => true
|
11
|
+
}
|
12
|
+
|
13
|
+
lib_options[:debug] = true if ENV['DEBUG']
|
14
|
+
lib_options[:profile] = true if ENV['PROFILE']
|
15
|
+
lib_options[:errorchecking] = :exx if ENV['EXX']
|
16
|
+
lib_options[:pedantic] = false if ENV['NOPEDANTIC']
|
17
|
+
|
18
|
+
project_task 'servicefb' do
|
19
|
+
lib 'ServiceFB'
|
20
|
+
build_to 'builds'
|
21
|
+
|
22
|
+
define 'SERVICEFB_DEBUG_LOG' if ENV['LOG']
|
23
|
+
source 'src/ServiceFB/ServiceFB.bas'
|
24
|
+
|
25
|
+
option lib_options
|
26
|
+
end
|
27
|
+
|
28
|
+
project_task 'servicefb_utils' do
|
29
|
+
lib 'ServiceFB_Utils'
|
30
|
+
build_to 'builds'
|
31
|
+
|
32
|
+
define 'SERVICEFB_DEBUG_LOG' if ENV['LOG']
|
33
|
+
source 'src/ServiceFB/ServiceFB_Utils.bas'
|
34
|
+
|
35
|
+
option lib_options
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
task :native_lib => ["lib:build"]
|
40
|
+
task :clean => ["lib:clobber"]
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative '../tools/freebasic'
|
2
|
+
require_relative '../lib/thin_service/version'
|
3
|
+
|
4
|
+
# thin_service (native)
|
5
|
+
namespace :native do
|
6
|
+
exe_options = {
|
7
|
+
:debug => false,
|
8
|
+
:profile => false,
|
9
|
+
:errorchecking => :ex,
|
10
|
+
:mt => true,
|
11
|
+
:pedantic => true
|
12
|
+
}
|
13
|
+
|
14
|
+
exe_options[:debug] = true if ENV['DEBUG']
|
15
|
+
exe_options[:profile] = true if ENV['PROFILE']
|
16
|
+
exe_options[:errorchecking] = :exx if ENV['EXX']
|
17
|
+
exe_options[:pedantic] = false if ENV['NOPEDANTIC']
|
18
|
+
|
19
|
+
project_task 'thin_service' do
|
20
|
+
executable 'thin_service_wrapper'
|
21
|
+
build_to 'resource'
|
22
|
+
|
23
|
+
define 'DEBUG_LOG' if ENV['LOG']
|
24
|
+
define "GEM_VERSION=\"#{ThinService::VERSION}\""
|
25
|
+
|
26
|
+
main 'src/thin_service/thin_service.bas'
|
27
|
+
source 'src/thin_service/console_process.bas'
|
28
|
+
|
29
|
+
search_path 'src/ServiceFB'
|
30
|
+
|
31
|
+
lib_path 'builds'
|
32
|
+
library 'ServiceFB', 'ServiceFB_Utils'
|
33
|
+
library 'user32', 'advapi32', 'psapi'
|
34
|
+
|
35
|
+
option exe_options
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
task :clean => ['native:clobber']
|
40
|
+
task :native_service => [:native_lib, 'native:build']
|
41
|
+
|
42
|
+
desc "Compile native code"
|
43
|
+
task :compile => [:native_service]
|
data/tasks/tests.rake
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative '../tools/freebasic'
|
2
|
+
|
3
|
+
# global options shared by all the project in this Rakefile
|
4
|
+
options = {
|
5
|
+
:debug => false,
|
6
|
+
:profile => false,
|
7
|
+
:errorchecking => :ex,
|
8
|
+
:mt => true,
|
9
|
+
:pedantic => true
|
10
|
+
}
|
11
|
+
|
12
|
+
options[:debug] = true if ENV['DEBUG']
|
13
|
+
options[:profile] = true if ENV['PROFILE']
|
14
|
+
options[:errorchecking] = :exx if ENV['EXX']
|
15
|
+
options[:pedantic] = false if ENV['NOPEDANTIC']
|
16
|
+
|
17
|
+
project_task :mock_process do
|
18
|
+
executable :mock_process
|
19
|
+
build_to 'tests'
|
20
|
+
|
21
|
+
main 'tests/fixtures/mock_process.bas'
|
22
|
+
|
23
|
+
option options
|
24
|
+
end
|
25
|
+
|
26
|
+
task "all_tests:build" => ["lib:build"]
|
27
|
+
|
28
|
+
project_task :all_tests do
|
29
|
+
executable :all_tests
|
30
|
+
build_to 'tests'
|
31
|
+
|
32
|
+
search_path 'src/mongrel_service'
|
33
|
+
lib_path 'builds'
|
34
|
+
|
35
|
+
main 'tests/all_tests.bas'
|
36
|
+
|
37
|
+
# this temporally fix the inverse namespace ctors of FB
|
38
|
+
source Dir.glob("tests/test_*.bas").reverse
|
39
|
+
|
40
|
+
library 'testly'
|
41
|
+
|
42
|
+
source 'src/mongrel_service/console_process.bas'
|
43
|
+
|
44
|
+
option options
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Run all the internal tests for the library"
|
48
|
+
task "all_tests:run" => ["mock_process:build", "all_tests:build"] do
|
49
|
+
Dir.chdir('tests') do
|
50
|
+
sh %{all_tests}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "Run all the test for this project"
|
55
|
+
task :test => "all_tests:run"
|