lighttpd cgi不能重启

1. 背景

cgi出现coredump后,lighttpd不能拉动cgi重启。

2. 重现问题

2.1. cgi实现

cpp 复制代码
/*! cgi简单实现 */
#include <stdio.h>
#include <fcgiapp.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, const char *argv)
{
    while (1) {
        FCGX_Request request = {0};
        FCGX_InitRequest(&request, 0, 0);
        FCGX_Accept_r(&request);
        system("sleep 100000000");
        FCGX_Finish_r(&request);
    }

    return 0;
}

2.2. 场景重现

bash 复制代码
# 运行lighttpd,此处将worker线程配置为4个
/smbdir/third_party/sbin/lighttpd -f /smbdir/third_party/lighttpd/etc/lighttpd.conf -D

# 手动kill掉cgi会出现cgi不能正常启动的问题
killall -11 cgi

3. 原因

lighttpd利用mod_facgi模块实现与cgi的交互,而cgi利用标准输入与lighttpd之间的信息互通,当在cgi中执行shell命令时,子进程会继承父进程的一些属性(标准输入也会被继承)。

3.1. lighttpd判断cgi是否正在运行

cpp 复制代码
    gw_fd = fdevent_socket_cloexec(proc->saddr->sa_family, SOCK_STREAM, 0);
    if (-1 == gw_fd) {
        log_perror(errh, __FILE__, __LINE__, "socket()");
        return -1;
    }

    do {
        status = connect(gw_fd, proc->saddr, proc->saddrlen);
        log_perror(errh, __FILE__, __LINE__, "status[%d], reason[%s], errno[%d]", status, strerror(errno), errno);
    } while (-1 == status && errno == EINTR);

    if (-1 == status && errno != ENOENT && proc->unixsocket) {
        log_perror(errh, __FILE__, __LINE__,
          "connect %s", proc->unixsocket->ptr);
        unlink(proc->unixsocket->ptr);
    }

    close(gw_fd);

如果此时cgi进程出现coredump,而由其产生的子进程由于继承了cgi的标准输入,会使上述connect系统调用正常返回0,从而导致lighttpd重启cgi进程失败

3.2. lighttpd重启cgi原理

cpp 复制代码
int status;

/*! lighttpd管理进程会在此处监听子进程状态,如果有子进程终止运行,则fdevent_waitpid_intr会返回 */
if (-1 != (pid = fdevent_waitpid_intr(-1, &status))) {
	log_monotonic_secs = server_monotonic_secs();
	log_epoch_secs = server_epoch_secs(srv);

    /*! 子进程终止运行后,该函数会最终调用gw_spawn_connection重启cgi进程 */
	if (plugins_call_handle_waitpid(srv, pid, status) != HANDLER_GO_ON) {
		if (!timer) alarm((timer = 5));
		continue;
	}
	switch (fdlog_pipes_waitpid_cb(pid)) {
	  default: break;
	  case -1: if (!timer) alarm((timer = 5));
		   __attribute_fallthrough__
	  case  1: continue;
	}
	/** 
	 * check if one of our workers went away
	 */
	for (int n = 0; n < npids; ++n) {
		if (pid == pids[n]) {
			pids[n] = -1;
			num_childs++;
			break;
		}
	}
}

4. 解决方案

自行封装形如popen或system类的接口,内部使用execl系列函数执行shell命令。但在执行命令前**优先关闭标准输入(close(0);)**即可解决该问题。

相关推荐
程序员小崔日记7 小时前
20块钱注册一年 .COM 域名!Spaceship 最新优惠教程,支付宝即可付款(附优惠码)
服务器·腾讯云·域名
Better Bench7 小时前
WSL2 Ubuntu 中 Claude CLI “command not found” 故障排查与修复
linux·ubuntu·claude·wsl·claudecode
实心儿儿8 小时前
Linux —— 进程间关系和守护进程
linux·运维·服务器
林浅不想努力9 小时前
LVS知识点总结
服务器·云原生·lvs
Dawn-bit9 小时前
Linux磁盘管理详解
linux·运维·服务器·计算机网络·云计算
RisunJan10 小时前
Linux命令-sftp(SSH 文件传输协议客户端)
linux·运维
龙仔72512 小时前
人大金仓OS_Core数据库自动备份实施笔记(银河麒麟Linux)
linux·数据库·笔记·备份·人大金仓
老杨聊技术12 小时前
CentOS 7 安装 MySQL 8 保姆级教程
linux·mysql·centos
xiaoye-duck13 小时前
《Linux系统编程》Linux 系统多线程(六):<线程同步与互斥>线程同步(下):POSIX 信号量与环形队列生产者消费者模型详解
linux·线程
三言老师13 小时前
CentOS7.9:Redis‑Cluster集群部署结构化实战教程
linux·运维·服务器·数据库