2015-03-13 18 views
3

具體細節過程重生和信號處理在PHP

我在PHP中,一個問題,當重生過程不處理的信號,而在此之前重生,裝卸工作正常。我縮小我的代碼非常簡單:

declare(ticks=1); 

register_shutdown_function(function() { 
    if ($noRethrow = ob_get_contents()) { 
     ob_end_clean(); 
     exit; 
    } 
    system('/usr/bin/nohup /usr/bin/php '.__FILE__. ' 1>/dev/null 2>/dev/null &'); 
}); 

function handler($signal) 
{ 
    switch ($signal) { 
     case SIGTERM: 
      file_put_contents(__FILE__.'.log', sprintf('Terminated [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND); 
      ob_start(); 
      echo($signal); 
      exit; 
     case SIGCONT: 
      file_put_contents(__FILE__.'.log', sprintf('Restarted [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND); 
      exit; 
    } 
} 

pcntl_signal(SIGTERM, 'handler'); 
pcntl_signal(SIGCONT, 'handler'); 

while(1) { 
    if (time() % 5 == 0) { 
     file_put_contents(__FILE__.'.log', sprintf('Idle [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND); 
    } 
    sleep(1); 
} 

正如你所看到的,它下面:

  • 註冊關機功能,在重生與nohup(這樣的過程,忽略SIGHUP時父母進程死亡)
  • 註冊處理程序通過pcntl_signal()SIGTERMSIGCONT。首先只會記錄一個消息,表明進程已終止,而第二會導致進程重新生成。它是通過ob_*函數實現的,所以要傳遞一個標誌,關閉函數應該做什麼 - 退出或重新生成。
  • 記錄一些腳本爲「活動」的信息以記錄文件。

發生了什麼

於是,我開始腳本:

/usr/bin/nohup /usr/bin/php script.php 1>/dev/null 2>/dev/null & 

然後,在日誌文件中,也有類似的條目:

Idle [ppid=7171] [pid=8849] 
Idle [ppid=7171] [pid=8849] 

讓我們說,然後我做kill 8849

Terminated [ppid=7171] [pid=8849] 

因此,它成功處理了SIGTERM(而且腳本確實退出)。現在,如果我不是做kill -18 8849,然後我看到(圖18是SIGCONT數值):和

Idle [ppid=7171] [pid=8849] 
Restarted [ppid=7171] [pid=8849] 
Idle [ppid=1] [pid=8875] 
Idle [ppid=1] [pid=8875] 

,因此:第一,SIGCONT也被正確處理,並通過接下來的「空閒」的消息來看,新生成的腳本實例運行良好。

更新#1:我在想的東西用ppid=1(因此,init全球進程)和孤立進程的信號處理,但它並非如此。這是log part,這表明孤兒(ppid=1)進程不是原因:當工作人員通過控制應用程序啓動時,它也通過system()命令調用它 - 與工作人員重新生成本身一樣。但是,在控制應用程序調用worker之後,它具有ppid=1並正確響應信號,而如果worker自己重新生成,則新副本不響應,除SIGKILL之外。所以,只有當工人重新出現時纔會出現問題。

更新#2:我試圖分析strace發生了什麼。現在,這裏有兩個街區。

  1. 如果是尚未重生工人 - strace output。看看行45,這是我發送SIGCONT,因此kill -18一個進程。然後觸發所有鏈:寫入文件,system()調用並退出當前進程。
  2. 當工人已經自己重生 - strace output。在這裏,看看行89 - 他們出現後收到SIGCONT。第一個:看起來過程仍然以某種方式接收信號,其次,它忽略了信號。沒有采取任何行動,但系統通知SIGCONT已發送進程。那麼爲什麼這個過程忽略它 - 是一個問題(因爲如果SIGCONT的用戶處理程序的安裝失敗,那麼它應該結束執行,而過程沒有結束)。至於SIGKILL,那麼對於已經重生工人的產出是這樣的:

    nanosleep({1, 0}, <unfinished ...> 
    +++ killed by SIGKILL +++ 
    

這表明,該信號被接收,並做了它應該做的。

問題

由於過程是重生,它不反應既不SIGTERM,也不SIGCONT。但是,仍然可以用SIGKILL來結束它(所以,kill -9 PID確實結束了該過程)。例如,對於上述過程,kill 8875kill -18 8875都不會執行任何操作(進程將忽略信號並繼續記錄消息)。然而,我不會說註冊信號完全失敗 - 因爲它重新定義了至少SIGTERM(通常導致終止,而在這種情況下它被忽略)。但是,我不會說註冊信號完全失敗 - 因爲它重新定義了至少SIGTERM(通常導致終止,而在這種情況下它被忽略)。我還懷疑ppid = 1指出了一些錯誤的東西,但我現在無法確定。

而且,我想任何其他種類的信號(實際上,這並不重要,什麼是信號代碼,結果總是相同的)

問題

可能是什麼這種行爲的原因?我正在重建一個過程的方式是否正確?如果不是,那麼其他哪些選項將允許新產生的進程正確使用用戶定義的信號處理程序?

回答

1

解決方案:最終,strace有助於理解問題。這是如下:

nanosleep({1, 0}, {0, 294396497})  = ? ERESTART_RESTARTBLOCK (Interrupted by signal) 
restart_syscall(<... resuming interrupted call ...>) = 0 

因此,它顯示了信號被接收,但忽略。要完全回答這個問題,我需要弄清楚,爲什麼過程中加入的信號忽略列表,但pcntl_sigprocmask()有力疏導他們正在做的事情:

pcntl_sigprocmask(SIG_UNBLOCK, [SIGTERM, SIGCONT]); 

然後一切順利,並重生過程中接收/處理信號如其意圖。例如,我試圖僅添加SIGCONT進行解鎖,然後正確處理它,而SIGTERM被阻止,這指出了這一點,這正是發送信號失敗的原因。

分辨率:由於某些原因,當進程產生自己的信號處理程序時,新實例會將這些信號屏蔽以忽略。揭露它們有力地解決了這個問題,但爲什麼信號在新的情況下被掩蓋 - 這是一個尚未解決的問題。

0

這是由於你通過執行system(foo)產生了一個子進程,然後繼續死於當前進程。因此,這個過程變成一個孤兒,並且它的父母變成PID 1(init)。

您可以使用pstree命令查看更改。

前:

init─┬─cron 
(...) 
    └─screen─┬─zsh───pstree 
       ├─3*[zsh] 
       ├─zsh───php 
       └─zsh───vim 

後:

init─┬─cron 
(...) 
    └─php 

維基百科指出什麼:

趙氏孤兒過程是怎麼樣的殭屍進程的情況正好相反,因爲它是指案件一個父進程在其子進程之前終止,在這種情況下,這些孩子被稱爲「孤兒」。

與子進程終止(通過SIGCHLD信號)時發生的異步子對父通知不同,父進程完成後不會立即通知子進程。相反,系統簡單地將子進程數據中的「parent-pid」字段重新定義爲系統中每個其他進程的「始祖」進程,該進程的pid通常具有值1(一),其名稱傳統上是「init」。因此,據說「init」採用「系統上的每個孤兒進程」。

對於你的情況,我建議兩個選項:

  • 使用兩個腳本:一個用於管理孩子,第二個,「工人」,實際執行的工作,
  • 或,使用一個腳本,這將包括兩個:外部部分將管理,內部部分,從外部分叉,將完成這項工作。
+0

我在想這個東西,但事實並非如此。這裏是[log part](http://pastebin.com/mSHL0VZu),它表明孤兒('ppid = 1')進程不是原因:當worker通過控制app啓動時,它也會用' system()'命令 - 就像工人重新生成自己一樣。但是,在控制應用程序調用worker之後,它具有'ppid = 1'並正確響應信號,而如果工作人員自己重新生成,新的副本不會響應它們,除了'SIGKILL'。所以,只有當**工人重新生成**時纔會出現問題。 – 2015-03-16 10:47:46

+0

我已經做了一些使用中間shell腳本的測試,它就像你說的:'ppid = 1'不是問題,因爲第一次產生,第二次沒有。現在我沒有更多的想法。 – leafnode 2015-03-16 10:56:15

+0

無論如何,我加了一些分析過程在做什麼。 – 2015-03-16 12:17:07