Страницы

воскресенье, 18 августа 2013 г.

Windows forever.

Нет, я не фанат Windows. На мой вкус без разницы какую ось использовать, главное чтобы она позволяла решать поставленные задачи. А заголовок холиварный получился :). Речь пойдет о модуле forever, который я использую для запуска сервисов на Node.js, а также о том, как написать подобный "forever" для Windows. Освещал тему прежде, но в этот раз иной подход - напишем консольный скрипт на примере forever.


Для начала познакомимся с forever.
Запускаем терминал Linux, я использую Ubuntu.
Создаем каталог приложения, переходим в созданный каталог, создаем файл приложения Node.js.
В качестве редактора используем vim.

У кого vim не установлен:
- sudo apt-get install vim
Пишем код приложения:
var http = require('http');
http.createServer(function (req, res) {
 res.writeHead(200, {'Content-Type': 'text/plain'});
 res.end('It will last forever!');
}).listen(3000, '127.0.0.1');
console.log('Server running at http://127.0.0.1:3000');


Устанавливаем forever:
- npm install forever



С целью исключения коллизий предпочитаю не устанавливать модули глобально.
Запускаем приложение с помощью установленного модуля forever:
- node_modules/forever/bin/forever start app.js

Тестируем:

А теперь предлагаю найти в списке запущенных процессов node для того, чтобы понять как устроен forever:
- ps axl | grep node

Становится понятно, что для обеспечения бесперебойной работы приложения forever запускает процесс,
который отслеживает событие падения целевого приложения, после наступления которого запускает приложение повторно. Где-то я это уже видел...

Выполним команду отображения списка процессов, запущенных forever:
- node_modules/forever/bin/forever list

Похоже forever ведет лог, что на мой взгляд вполне логично. 

В завершение остановим все процессы, запущенные forever:
- node_modules/forever/bin/forever stop 0

Резюмируем: forever - очень полезный инструмент. 
На просторах сети я нашел, как мне показалось, еще более "навороченный" инструмент - pm2, в свое время обязательно попробую.

Переходим к Windows. Где же я это уже видел? 
Один мой товарищ арендует серверы Windows, на которых крутятся какие-то биржевые приложения. Не далее как прошло зимой мы пересекались по работе и он попросил меня написать скрипт, который поднимал бы эти самые биржевые приложения в случае падения - а падают они, надо сказать, нередко. 
Я не стал париться с запуском приложений в качестве сервисов, а влегкую написал ему что-то типа:
Set objWMI = GetObject("winmgmts:\\.\Root\CIMV2")
Set colEvents = objWMI.ExecNotificationQuery( _
 "SELECT * FROM __InstanceDeletionEvent WITHIN 2 " & _
 "WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'notepad.exe'")  
Do 
 Set objEvent = colEvents.NextEvent 
 objWMI.Get("Win32_Process").Create("notepad.exe")
Loop

Подобный код пришелся очень даже кстати, до сих пор никаких нареканий, только восторг и овации :).

Вспомнив описанный выше случай после знакомства с forever я решил написать чуть более сложный скрипт, который решает вопрос запуска приложений Windows, что называется, forever:
Set args = WScript.Arguments
' проверяем наличие аргумента
If args.Count = 0 Then
 WScript.Echo "Please enter process name"
 WScript.Quit()
ElseIf args.Count > 2 Then
 WScript.Echo "Too much information"
 WScript.Quit()
End If

If args.Count = 1 Then
 Set f = New Forever
 If f.Run(args(0)) = 0 Then ' запускаем процесс  
  WScript.Echo "Process '" & args(0) & "' is started, pid: " & f.pid
  mon = "wscript.exe " & WScript.ScriptFullName & " /pid:" & f.pid & " /proc:" & args(0)
  If f.Run(mon) = 0 Then ' запускаем процесс мониторинга
   WScript.Echo "Monitor started..."
  Else
   WScript.Echo "Failed to start process '" & mon & "'"
  End If
 Else
  WScript.Echo "Failed to start process '" & args(0) & "'"
 End If
 Set f = Nothing
Else ' запускаем мониторинг
 If IsEmpty(args.Named.Item("pid")) Or IsEmpty(args.Named.Item("proc")) Then
  WScript.Echo "Named arguments are missing"
  WScript.Quit
 End If
 Set f = New Forever
 f.Monitor args.Named.Item("pid"), args.Named.Item("proc")
End If

Class Forever
 Private objWMI, m_pid, m_proc ' команда запуска процесса
 Public Property Get pid ' ProcessId запущенного процесса
  pid = m_pid
 End Property 
 Private Sub Class_Initialize()
  Set objWMI = GetObject("winmgmts:\\.\Root\CIMV2")
 End Sub
 Private Sub Class_Terminate()
  Set objWMI = Nothing
 End Sub
 Public Function Run(proc)  
  ret = objWMI.Get("Win32_Process").Create(proc, , , m_pid)
  If ret = 0 Then   
   Log("Process '" & proc & "' started, pid: " & m_pid)
  Else
   Log("Failed to start process '" & proc & "'")
  End If
  Run = ret
 End Function
 Public Sub Monitor(pid, proc)
  m_pid = pid
  m_proc = proc
  Set colEvents = objWMI.ExecNotificationQuery( _
   "SELECT * FROM __InstanceDeletionEvent WITHIN 2 " & _
   "WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.ProcessId = '" & m_pid & "'")  
  Do 
   Set objEvent = colEvents.NextEvent
   Log("Process '" & m_proc & "' stopped, pid: " & m_pid)
   Do    
    ret = Run(m_proc)
    WScript.Sleep 100
   Loop Until ret = 0 ' пытаемся запустить процесс до победного
   Exit Do
  Loop
  Monitor m_pid, m_proc
 End Sub
 ' логгер
 Private Sub Log(s)
  Const logName = "forever.log"
  Set fso = CreateObject("Scripting.FileSystemObject")
  With fso
   path = .BuildPath(.GetParentFolderName(.GetFile(WScript.ScriptFullName)), logName)
   If Not .FileExists(path) Then
    Set fl = .CreateTextFile(path)
    fl.Close()
    Set fl = Nothing
   End If
   Set fl = .OpenTextFile(path, 8)
   With fl
    .WriteLine Now() & ": " & s
    .Close()
   End With
   Set fl = Nothing
  End With
  Set fso = Nothing
 End Sub
End Class

Сохраняем код в файл с расширением .vbs, я назвал его forever.vbs, запускаем блокнот:

Несколько раз пытаемся закрыть блокнот... что и требовалось.

Убиваем процесс мониторинга:
taskkill /im wscript.exe /f

Закрываем блокнот. Читаем лог:

Вот как-то так. На этом прощаюсь. Покупайте наших слонов :)