我有一个sinatra应用程序设置为使用win32-service
gem作为服务运行。应用程序启动正常,但一段时间后它会中断。然而,根据服务状态,它仍在运行,但我似乎无法从外部或计算机本身访问它,修复它的唯一方法是停止它,删除它,重新安装服务并启动它
该应用程序通常是自我部署(由于构建管道,但仅在我推送更新时),由于厨师安装和运行,其设置方式是厨师服务器每隔5分钟运行所有菜谱我认为以某种方式打破了我的应用程序,但我不确定如何。
非常感谢任何见解。
P.S。我知道我没有发布任何代码但只是因为我不确定会有什么帮助,告诉我你需要看什么,我会发布它。
编辑:事实证明它与厨师无关:
感谢一些日志记录,我知道我的应用程序运行了大约57分钟,然后它停止响应5秒健康ping(由清漆完成),厨师运行每30分钟发生一次,他们没有共同内部,问题因此,与厨师无关。
让我说清楚,状态仍然是“正在运行”,因此它没有停止,日志也没有报告会导致守护程序停止运行的错误。
编辑:
这是我的守护程序代码:
class DemoDaemon < Daemon
def service_main
Object.const_get("GG_Web_#{C_NAME}").run! port: PORT, server: 'thin'
while running?
sleep 100
File.open(Logfile, "a"){ |f| f.puts "#{Time.now} - Service is running" }
end
end
def service_stop
File.open(Logfile, "a"){ |f| f.puts "#{Time.now} - Service stopped" }
exit!
end
end
DemoDaemon.mainloop
编辑:
我注意到应用似乎在50分钟左右停止响应,没有记录输出(即例外,服务已停止等)或者应用状态响应为running
。
万一你问,这是100%不是厨师,因为我禁用了盒子上的厨师客户端,应用程序仍然停止响应。
编辑:
此外,它实际上不会将Service is running
或Service stopped
打印到日志文件中。
编辑: 我把它缩小了很多,我知道它不是:
我已经回滚到没有这个问题的早期版本的宝石,它仍然在做它。
只剩下一件事(我能想到的),我用来创建应用程序主循环的win32-system
gem以及它与sinatra的交互。
以下是我用来注册应用的代码:
binary_path = "#{self.rubypath} #{ROOT.gsub('/','\\')}boot_as_service.rb"
Service.create({
service_name: NAME,
service_type: Service::WIN32_OWN_PROCESS,
description: "#{DESCRIPTION}, running on: #{HOST}:#{PORT}",
start_type: Service::AUTO_START,
error_control: Service::ERROR_NORMAL,
binary_path_name: binary_path,
load_order_group: 'Network',
dependencies: ['W32Time', 'Schedule'],
display_name: NAME
})
主要应用类:
class GG_Web_My_APP < Sinatra::Base
set :show_exceptions, true
set :root, ROOT + NAME
set :server, 'thin'
set :scss, {:style => :compact, :debug_info => false}
Compass.add_project_configuration(File.join(settings.root, 'config', 'compass.rb'))
Tilt.register Tilt::ERBTemplate, 'html.erb'
enable :logging
logging_file = File.open((Object.const_defined?('Logfile') ? Logfile : 'C:\\app.log'), "a")
logging_file.sync = true
use Rack::CommonLogger, logging_file
if ENV['APPLICATION_NAME']
set :environment, :production
set :bind, '0.0.0.0'
end
get '/css/:name.css' do
content_type 'text/css', :charset => 'utf-8'
scss :"/assets/css/#{params[:name]}", Compass.sass_engine_options
end
helpers Sinatra::FormHelpers
helpers Sinatra.const_get("GG_Web_#{C_NAME}")::Helpers
register Sinatra.const_get("GG_Web_#{C_NAME}")::Api
register Sinatra.const_get("GG_Web_#{C_NAME}")::Actions
end
并且您已经看到了我用来将应用程序作为守护程序运行的代码。
我真的需要帮助来解决这个问题,我完全没有想法,看看是什么导致了这个以及如何修复它。
编辑: 好吧,我现在已经确认了一件事,我的代码中没有任何东西导致它。它仍然死了,但我在本地机器上运行的版本却没有。它在服务器上运行,导致它无响应。
进一步编辑:
原来这根本不是问题,我什么都没有
编辑:
好吧,我不能让它撒谎,所以我回去了,我设法让我的应用程序作为一个守护进程运行而不使用system32守护进程类并猜测是什么,它仍然悬挂到大约50分钟后(好吧)这次是45分钟),自从我开始没有服务宝石并且它仍悬挂时,我想知道它还能是什么。什么可能导致sinatra挂?
此外,如果我ping localhost
收到回复(0%丢失),但如果我使用curl则无法连接。
编辑:
系统地删除所有内容,直到它只是一个hello world应用程序,我可以肯定地说它与我的ruby代码完全无关。到此为止我是:
get '/' do
'Hello World'
end
毕竟它仍然死了,到目前为止它只是一个纯粹的sinatra'你好世界'应用程序;所以要么sinatra有问题(我怀疑其他人会遇到这个问题),或者很可能服务器上有一些东西导致它。
服务器上有什么可能导致它我不知道。但至少我离这个工作还有一步之遥。
答案 0 :(得分:2)
看起来你有两个问题。
这两者可能与您建议的直接相关。以下是步骤,以便您可以逐位分离每个问题,以便确定问题所在。所以不是答案,但它可能会让你得到答案......
获取Process Explorer并查看ruby.exe
进程正在执行的操作。右键点击ruby.exe
,然后选择Properties...
。
它使用100%CPU吗?是否有任何特殊的计数器让你觉得奇怪?
在Process Explorer中查看该过程的Threads
标签。是否有任何TID被列为100%的运行?对于每个线程,如果Stack
(右下角按钮)提到Init_ffi
,则它们是win32-daemon线程。事件机器线程rubyeventmachine.so
,你猜对了。
通过创建简单的app.rb或rackup文件,在不使用win32/daemon
的情况下运行您的应用。是否会出现同样的问题?
使用Webrick,Puma或Mongrel而非Thin来运行您的应用。是否会出现同样的问题?
您可以通过运行msys.bat
来查看process further with gdb
中卡住的dev kits。
$ gdb -p <PID>
(gdb) thread 1
(gdb) where
#0 0x62e905f9 in setproctitle () from c:\dply\lang\ruby-1.9.3-p551-i386-mingw32\bin\msvcrt-ruby191.dll
#1 0x62e936d1 in select@20 () from c:\dply\lang\ruby-1.9.3-p551-i386-mingw32\bin\msvcrt-ruby191.dll
#2 0x673c8524 in _SelectDataSelect (v=0x22fa38) at ../../../../ext/em.cpp:810
#3 0x62e846b8 in rb_thread_blocking_region () from c:\dply\lang\ruby-1.9.3-p551-i386-mingw32\bin\msvcrt-ruby191.dll
#4 0x673c9307 in _Select (this=0x28d2bc8) at ../../../../ext/em.cpp:822
#5 EventMachine_t::_RunSelectOnce (this=0x28d2bc8) at ../../../../ext/em.cpp:898
#6 0x673ca6c4 in _RunOnce (this=<optimized out>) at ../../../../ext/em.cpp:503
#7 EventMachine_t::Run (this=0x28d2bc8) at ../../../../ext/em.cpp:485
#8 0x673cf4f0 in evma_run_machine () at ../../../../ext/cmain.cpp:88
#9 0x673d3297 in t_run_machine_without_threads (self=40004496) at ../../../../ext/rubymain.cpp:223
#10 0x62e7874a in rb_vm_call () from c:\dply\lang\ruby-1.9.3-p551-i386-mingw32\bin\msvcrt-ruby191.dll
#11 0x62e6e7d8 in rb_vm_localjump_error () from c:\dply\lang\ruby-1.9.3-p551-i386-mingw32\bin\msvcrt-ruby191.dll
#12 0x62e72eb1 in rb_vm_localjump_error () from c:\dply\lang\ruby-1.9.3-p551-i386-mingw32\bin\msvcrt-ruby191.dll
#13 0x62e798ab in rb_iseq_eval_main () from c:\dply\lang\ruby-1.9.3-p551-i386-mingw32\bin\msvcrt-ruby191.dll
#14 0x62d47919 in rb_check_frozen () from c:\dply\lang\ruby-1.9.3-p551-i386-mingw32\bin\msvcrt-ruby191.dll
#15 0x62d49e50 in ruby_run_node () from c:\dply\lang\ruby-1.9.3-p551-i386-mingw32\bin\msvcrt-ruby191.dll
#16 0x0040136f in main (argc=4, argv=0x572f80) at ../ruby_1_9/main.c:38
(gdb) thread 2
(gdb) where...
通过几次运行,您应该能够识别出卡在同一位置的任何线程(而不仅仅是睡眠)。如果你有100%cpu的线程,你不必搜索,只需确定线程正在做什么。
您的应用程序必须始终能够响应Windows所提供的服务请求,无论其状态如何。如果它无法响应,那么您的服务经理最终会处于您所经历的状态。这是管理Windows服务或任何服务的相当常见的陷阱。
多线程程序可以使用 nanny 线程实现 它位于您的正常工作线程旁边,只处理 服务请求。即便如此,你最终还是会得到锁定的线程 不会很好地死(尤其是Java)。
单线程程序,您可以拥有一个保姆进程 顶级和(风险)工作是在儿童流程中完成的。
在Ruby / MRI案例中,它位于两者之间。 win32/daemon
创建一个新的Ruby线程来处理服务控制请求。请注意,此“线程”与我们之前处理的实际线程无关,Ruby线程都在一个MRI进程中运行。 Ruby有全局解释器锁(GIL),它确保在Ruby域中一次只运行一个线程。当线程在正常操作中执行时,线程会将执行交给彼此,但是您的线程会卡住并且永远不会启动service_stop
方法。这个问题可能发生在一个运行本机代码线程的gem中,因为GIL规则最容易弯曲(很可能在Eventmachine,Thin解析器,win32/daemon
或其他任何依赖项被拉入)。 / p>
下次尝试停止服务但失败时,您可以通过右键点击服务下的ruby.exe
和ruby.exe
,使用Process Explorer来终止Kill Process
服务流程或使用 Del kbd&gt;当ruby.exe
突出显示时。您不应该每次都重新创建服务。
回到分离关注点的想法,将保姆和工作者分离成单独的Ruby进程是保持保姆简单和尽可能控制的最可靠方法之一。您可能会修复此应用程序问题并且服务问题将消失,但锁定进程的下一个问题将具有相同的服务问题。如果你分开保姆和工人流程,他们就会有一个操作系统屏障来保持它们的分离。不利的一面是它使得整个系统在Windows上变得更加复杂,因为您必须在当时或某种形式的IPC之间处理spawning子进程和信号处理。你还需要谨慎对待那些留下来的流浪儿童。在此设置中,您最终会得到一个过程树,如:
- services.exe
- ruby.exe daemon.rb - win32/daemon
- ruby.exe app.rb - thin/sinatra
因此,保姆有可能死去,但你仍然有孩子在无人看管的情况下跑来跑去。
Sinatra应用程序在线程模式下启动Thin。如果直接运行Thin,则默认为evented模型。 Sinatra已经这样了几年,但我倾向于信赖Thin on Thin的东西。尝试禁用线程:
set :threaded, false
service_main
中的循环是不必要的。 Sinatras App.run!
为您实现循环,run!
在您告诉应用程序quit
之前不会返回。
class DemoDaemon < Daemon
def service_main
File.open(Logfile, "a"){ |f| f.puts "#{Time.now} - Service is starting" }
Object.const_get("GG_Web_#{C_NAME}").run! port: PORT, server: 'thin'
end
您可以使用App.quit!
要求Sinatra进行清理,然后可以在超时后exit
进行清理。
def service_stop
File.open(Logfile, "a"){ |f| f.puts "#{Time.now} - Service stopping" }
Object.const_get("GG_Web_#{C_NAME}").quit!
File.open(Logfile, "a"){ |f| f.puts "#{Time.now} - Service stopped" }
end
end
您也可以使用rubyw.exe
来删除控制台($ stdout / $ stderr)但不重要的服务,因为win32/daemon
会为您解决这个问题。
答案 1 :(得分:0)
正如您所提到的,在没有win32守护程序的情况下运行时,问题不会发生,请尝试使用任务计划程序而不是Windows服务运行它。