2

我有一个 sinatra 应用程序设置为使用win32-servicegem 作为服务运行。该应用程序启动正常,但一段时间后中断。但是根据服务状态,它仍在运行,但我似乎无法从外部或计算机本身访问它,修复它的唯一方法是停止它,删除它,重新安装服务并启动它.

该应用程序通常是自我部署的(由于构建管道,但仅当我推送更新时),由于厨师而安装和运行,其设置方式是厨师服务器每 5 分钟运行一次它的所有食谱,我认为这会中断我的应用程序不知何故,但我不确定如何。

任何见解将不胜感激。

ps 我意识到我没有发布任何代码,但这只是因为我不确定有什么帮助,告诉我你需要看到什么,我会发布它。

编辑:原来它与厨师无关:

多亏了一些日志记录,我知道我的应用程序在停止响应 5 秒健康 ping(由清漆完成)之前运行了大约 57 分钟,并且厨师运行每 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 runningService stopped日志文件中。

编辑:我已经把它缩小了很多,我知道它不是:

  1. 厨师
  2. 源代码加载机制
  3. 执行主要操作的应用程序部分,即处理请求、从数据库加载数据等(我有两个应用程序有相同的问题,无论导致什么都与两者相同)

我已经回滚到没有这个问题的 gem 的早期版本,它仍然在做。

只剩下一件事(我能想到的),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 分钟这个time) ,好吧,因为我在没有服务 gem 的情况下启动它并且它仍然挂起,我想知道它还能是什么。什么可能导致 sinatra 挂起?

此外,如果我ping localhost得到回复(0% 丢失)但如果我使用 curl 则无法连接。

编辑:

在系统地剥离所有内容直到它只是一个 hello world 应用程序之后,我可以肯定地说这与我的 ruby​​ 代码无关。至此,我是:

  1. 使用 rackup 将应用程序作为进程启动(尽管我必须手动终止主线程但应用程序仍然运行),而不是将代码包装在守护程序中并运行它。
  2. 关闭日志记录
  3. 摆脱了指南针/ sass(认为这可能会挂起,因为更少对其他人显然是这样做的,而且我意识到我没有使用它附带的任何功能,所以保留它没有意义)
  4. 注释掉所有模块,只有: get '/' do 'Hello World' end

毕竟它仍然死了,到那时它只是一个纯粹的 sinatra 'hello world' 应用程序;所以要么 sinatra 有问题(我怀疑其他人会遇到这个问题),要么很可能是服务器上的某些东西导致了它。

服务器上可能导致它的原因我不知道。但至少我离解决这个问题更近了一步。

4

2 回答 2

2

看起来你有两个问题。

  1. 应用程序变得无响应
  2. 缺乏对服务的控制

两者可能像您建议的那样直接相关。以下是一些步骤,您可以一点一点地分离出每个问题,以便找出问题所在。所以不是一个确切的答案,但它可能会让你得到一个答案......


应用程序变得无响应

获取Process Explorer并查看您的ruby.exe流程在做什么。右键单击ruby.exe并选择Properties...。它使用 100% 的 CPU 吗?有没有什么特别的计数器让你觉得奇怪?

查看Process ExplorerThreads中流程的选项卡。列出的任何 TID 是否卡在 100% 运行?对于每个线程,在(右下角按钮)中,如果他们提到它们是 win32-daemon 线程。是,你猜对了,eventmachine 线程。StackInit_ffirubyeventmachine.so

无需win32/daemon创建简单的 app.rb 或 rackup 文件即可运行您的应用程序。是否会出现同样的问题?

使用 Webrick、Puma或 Mongrel 而不是 Thin 运行您的应用程序。是否会出现同样的问题?

您可以通过运行. _gdbmsys.bat

$ 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 服务或与此相关的任何服务的一个相当常见的陷阱。

  • 多线程程序可以通过一个位于普通工作线程旁边并且只处理服务请求的保姆线程来实现。即使那样,您最终也可能会得到不会很好地死掉的锁定线程(尤其是 Java)。

  • 单线程程序,您可以有一个位于顶部的保姆进程,并且您的(有风险的)工作在子进程中完成。

在 Ruby/MRI 案例中,它介于两者之间。win32/daemon创建一个新的 Ruby 线程来处理服务控制请求。请注意,这个“线程”与我们之前处理的真实线程无关,Ruby 线程都在一个 MRI 进程中运行。Ruby 具有全局解释器锁 (GIL),可确保在 Ruby 领域一次只运行一个线程。线程在正常操作中可以将执行移交给彼此,但您的线程被卡住并且永远不会启动该service_stop方法。该问题可能发生在运行本机代码线程的 gem 中,因为这是 GIL 规则最受弯曲的地方(很可能在 Eventmachine、Thin 解析器win32/daemon或任何其他引入的依赖项中)。

下次尝试停止服务但失败时,您可以使用Process Explorerruby.exe通过右键单击服务ruby.exe下的服务来终止服务进程,或者在突出显示时Kill Process使用Delkbd> 。ruby.exe您不必每次都重新创建服务。

回到关注点分离的想法,将 nanny 和 worker 分离成单独的 Ruby 进程是保持 nanny 尽可能简单和可控的最可靠方法之一。您可能会修复此应用程序问题并且服务问题将消失,但锁定进程的下一个问题将具有相同的服务问题。如果将 nanny 和 worker 进程分开,它们就会有一个操作系统屏障来保持它们分开。不利的一面是它使整个系统在 Windows 上更加复杂,因为您必须处理生成子进程和在那时或某种形式的 IPC 之间的信号处理。您还需要小心地对待留在附近的流浪儿童。在此设置中,您最终会得到一个流程树,例如:

- services.exe
  - ruby.exe daemon.rb - win32/daemon
    - ruby.exe app.rb - thin/sinatra

所以保姆有可能死去,但你仍然有孩子在无人看管的情况下到处乱跑。


其他事情

Sinatra 应用程序以线程模式启动 Thin。如果您直接运行 Thin,则默认为事件模型。Sinatra 多年来一直这样,但我倾向于相信 Thin on Thin 事物。尝试禁用线程:

set :threaded, false


循环输入service_main是不必要的。SinatrasApp.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的服务,因为它会为您处理。

于 2014-11-28T15:10:02.490 回答
0

正如您所提到的,在没有 win32 守护程序的情况下运行时不会出现问题,请尝试使用任务调度程序而不是作为 Windows 服务运行它。

于 2014-12-10T20:25:16.727 回答