Tests about a simple python3 fastcgi runner using libfcgi and the Python-C API.
python
c
wsgi
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

monitor.c 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. #include "monitor.h"
  2. static pyfcgi_monitor_t pyfcgi_mon;
  3. static void clean_exit(int status)
  4. {
  5. pyfcgi_wd_stop();
  6. pyfcgi_log(LOG_DEBUG, "Exiting with status %d", status);
  7. if(pyfcgi_mon.sockserv)
  8. {
  9. if(pyfcgi_mon.sockcli)
  10. {
  11. if(shutdown(pyfcgi_mon.sockcli, SHUT_RDWR) < 0)
  12. {
  13. pyfcgi_log(LOG_WARNING, "Unable to shutdown clisocket : %s",
  14. strerror(errno));
  15. }
  16. if(close(pyfcgi_mon.sockcli) < 0)
  17. {
  18. pyfcgi_log(LOG_WARNING, "Unable to close clisocket : %s",
  19. strerror(errno));
  20. }
  21. }
  22. pyfcgi_log(LOG_INFO, "Closing listening socket...");
  23. if(shutdown(pyfcgi_mon.sockserv, SHUT_RDWR) < 0)
  24. {
  25. pyfcgi_log(LOG_WARNING, "Unable to shutdown socket : %s",
  26. strerror(errno));
  27. }
  28. if(close(pyfcgi_mon.sockserv) < 0)
  29. {
  30. pyfcgi_log(LOG_WARNING, "Unable to close socket : %s",
  31. strerror(errno));
  32. }
  33. }
  34. exit(status);
  35. }
  36. pid_t pyfcgi_spawn_monitor()
  37. {
  38. pid_t res;
  39. struct sigaction act, actdrop;
  40. struct timespec wd_delay;
  41. int err;
  42. wd_delay.tv_sec = 2;
  43. wd_delay.tv_nsec = 0;
  44. act.sa_handler = pyfcgi_monitor_sighandler;
  45. sigemptyset(&act.sa_mask);
  46. act.sa_flags = 0;
  47. act.sa_restorer = NULL;
  48. actdrop.sa_handler = pyfcgi_sighandler_drop;
  49. sigemptyset(&act.sa_mask);
  50. act.sa_flags = 0;
  51. act.sa_restorer = NULL;
  52. if(!PyFCGI_conf.mon_socket)
  53. {
  54. pyfcgi_log(LOG_ERR, "No socket url set but pyfcgi_spawn_monitor called !");
  55. return -1;
  56. }
  57. memset(&pyfcgi_mon, 0, sizeof(pyfcgi_mon));
  58. res = fork();
  59. if(res == -1)
  60. {
  61. pyfcgi_log(LOG_ALERT, "Unable to fork into monitoring server process : %s",
  62. strerror(errno));
  63. sleep(1);
  64. }
  65. if(!res)
  66. { //child process
  67. pyfcgi_logger_set_ident("StatServ");
  68. if(sigaction(SIGTERM, &act, NULL))
  69. {
  70. err = errno;
  71. pyfcgi_log(LOG_ERR, "Unable to run sigaction for SIGTERM : %s",
  72. strerror(err));
  73. exit(PYFCGI_FATAL);
  74. }
  75. if(sigaction(SIGINT, &actdrop, NULL))
  76. {
  77. pyfcgi_log(LOG_WARNING, "Unable run sigaction for SIGINT sigaction : %s",
  78. strerror(errno));
  79. }
  80. if(sigaction(SIGALRM, &actdrop, NULL))
  81. {
  82. pyfcgi_log(LOG_WARNING, "Unable to restore ALARM sigaction : %s",
  83. strerror(errno));
  84. }
  85. if(pyfcgi_wd_init(pyfcgi_monitor_timeout, &wd_delay) < 0)
  86. {
  87. pyfcgi_log(LOG_ALERT,
  88. "Unable to start watchdog, sleep 1s then exiting...");
  89. sleep(1);
  90. exit(PYFCGI_FATAL);
  91. }
  92. pyfcgi_monitor_loop();
  93. pyfcgi_log(LOG_ALERT, "Monitor loop should never return but just did it...");
  94. clean_exit(PYFCGI_FATAL);
  95. }
  96. return res;
  97. }
  98. int pyfcgi_monitor_IPC_init()
  99. {
  100. int ret;
  101. ret = 0;
  102. if(!PyFCGI_SEM_OPEN(SEM_WREQS))
  103. {
  104. if(pyfcgi_IPC_init(IPC_WREQS)) { ret = -1; }
  105. }
  106. if(!PyFCGI_SEM_OPEN(SEM_STATS))
  107. {
  108. if(pyfcgi_IPC_init(IPC_SEMST)) { ret = -1; }
  109. }
  110. if(!PyFCGI_conf.shm.ptr)
  111. {
  112. if(pyfcgi_IPC_init(IPC_SHMST)) { ret = -1; }
  113. }
  114. return ret;
  115. }
  116. void pyfcgi_monitor_loop()
  117. {
  118. pyfcgi_monitor_addr_t addr;
  119. int *sockargs, reuse;
  120. socklen_t addrlen;
  121. sockargs = pyfcgi_mon.sockargs;
  122. if(pyfcgi_monitor_parse_sock(PyFCGI_conf.mon_socket, sockargs,
  123. &addr) < 0)
  124. {
  125. pyfcgi_log(LOG_WARNING,
  126. "Unable to parse socket URL, monitoring server not starting...");
  127. free(PyFCGI_conf.mon_socket);
  128. PyFCGI_conf.mon_socket = NULL;
  129. clean_exit(PYFCGI_ERR);
  130. }
  131. if( (pyfcgi_mon.sockserv = socket(sockargs[0], sockargs[1],
  132. sockargs[2])) < 0)
  133. {
  134. pyfcgi_log(LOG_ERR, "Unable to create socket : %s",
  135. strerror(errno));
  136. sleep(1);
  137. clean_exit(PYFCGI_ERR);
  138. }
  139. if(sockargs[0] == AF_INET)
  140. {
  141. addrlen = sizeof(struct sockaddr_in);
  142. }
  143. else if(sockargs[0] == AF_INET6)
  144. {
  145. addrlen = sizeof(struct sockaddr_in6);
  146. }
  147. else
  148. {
  149. addrlen = sizeof(struct sockaddr_un);
  150. }
  151. reuse = 1;
  152. if(setsockopt(pyfcgi_mon.sockserv, SOL_SOCKET, SO_REUSEADDR, &reuse,
  153. sizeof(int)) < 0)
  154. {
  155. pyfcgi_log(LOG_WARNING, "Unable to set socket option : %s",
  156. strerror(errno));
  157. }
  158. if(bind(pyfcgi_mon.sockserv, (struct sockaddr*)&addr, addrlen) < 0)
  159. {
  160. pyfcgi_log(LOG_ERR, "Unable to bind socket... : %s",
  161. strerror(errno));
  162. pyfcgi_log(LOG_INFO, "Retrying in 10s");
  163. close(pyfcgi_mon.sockserv);
  164. sleep(10);
  165. clean_exit(PYFCGI_ERR);
  166. }
  167. pyfcgi_log(LOG_INFO, "Listening on %s", PyFCGI_conf.mon_socket);
  168. //bind successfull, sending to appropriate response loop
  169. if(pyfcgi_monitor_IPC_init() < 0)
  170. {
  171. pyfcgi_log(LOG_ERR, "Unable to initialize IPC");
  172. sleep(1);
  173. exit(PYFCGI_ERR);
  174. }
  175. pyfcgi_stats_init();
  176. if(pyfcgi_mon.sockargs[1] == SOCK_DGRAM)
  177. {
  178. pyfcgi_monitor_dgram_loop();
  179. }
  180. else
  181. {
  182. pyfcgi_monitor_stream_loop();
  183. }
  184. }
  185. void pyfcgi_monitor_stream_loop(pyfcgi_monitor_addr_t addr_serv)
  186. {
  187. pyfcgi_monitor_addr_t cliaddr;
  188. socklen_t addrlen;
  189. int sockcli, err, ret;
  190. char ipstr[64];
  191. const char *buff;
  192. size_t bufflen;
  193. addrlen = sizeof(cliaddr);
  194. if(listen(pyfcgi_mon.sockserv, PYFCGI_MONITOR_STREAM_BACKLOG) < 0)
  195. {
  196. pyfcgi_log(LOG_ERR, "Unable to listen on socket : %s",
  197. strerror(errno));
  198. clean_exit(PYFCGI_ERR);
  199. }
  200. while(1)
  201. {
  202. sockcli = pyfcgi_mon.sockcli = accept(pyfcgi_mon.sockserv,
  203. (struct sockaddr*)&cliaddr, &addrlen);
  204. pyfcgi_wd_arm(); //watchdog
  205. if(sockcli < 0)
  206. {
  207. err = errno;
  208. if(err == EINTR) { continue; }
  209. break;
  210. }
  211. //pyfcgi_log(LOG_DEBUG, "New client");
  212. if(!inet_ntop(pyfcgi_mon.sockargs[0], (void*)&cliaddr, ipstr, 64))
  213. {
  214. pyfcgi_log(LOG_WARNING,
  215. "inet_ntop fails to represent client address : %s",
  216. strerror(errno));
  217. strcpy(ipstr, "XXX");
  218. }
  219. if(!pyfcgi_stats_format())
  220. {
  221. pyfcgi_log(LOG_ERR, "Unable to format stats...");
  222. close(sockcli);
  223. continue;
  224. }
  225. pyfcgi_stats_buff(&buff, &bufflen);
  226. pyfcgi_log(LOG_INFO, "Sending stats to %s", ipstr);
  227. do
  228. {
  229. ret = send(sockcli, buff, bufflen, 0);
  230. err = errno;
  231. }while(ret == -1 && err == EINTR);
  232. if(ret == -1)
  233. {
  234. pyfcgi_log(LOG_WARNING, "Unable to send stats to client : %s",
  235. strerror(err));
  236. }
  237. shutdown(sockcli, SHUT_RDWR);
  238. close(sockcli);
  239. pyfcgi_wd_pause(); //watchdog
  240. pyfcgi_mon.sockcli = 0;
  241. addrlen = sizeof(cliaddr);
  242. }
  243. pyfcgi_log(LOG_ERR, "Unable to accept new connection on socket : %s",
  244. strerror(errno));
  245. }
  246. void pyfcgi_monitor_dgram_loop()
  247. {
  248. pyfcgi_log(LOG_ERR, "Dgram server not implemented.... exiting server");
  249. free(PyFCGI_conf.mon_socket);
  250. PyFCGI_conf.mon_socket = NULL;
  251. clean_exit(PYFCGI_ERR);
  252. }
  253. int pyfcgi_monitor_check_sock(const char* sockurl)
  254. {
  255. const char *port;
  256. short tcp;
  257. const char *urlorig;
  258. urlorig = sockurl;
  259. if(!(tcp = strncasecmp("tcp://", sockurl, 6)) ||
  260. !strncasecmp("udp://", sockurl, 6))
  261. {
  262. sockurl += 6; //first addr chr
  263. }
  264. else if(!strncasecmp("unix://", sockurl, 7))
  265. {
  266. sockurl += 7;
  267. if(!strlen(sockurl))
  268. {
  269. dprintf(2, "UNIX socket missing file path : '%s'\n",
  270. urlorig);
  271. return -1;
  272. }
  273. if(strlen(sockurl) > UNIX_SOCKPATH_MAX)
  274. {
  275. dprintf(2, "UNIX socket support only path with length <= %d but given path is %ld bytes long : '%s'",
  276. UNIX_SOCKPATH_MAX, strlen(sockurl), urlorig);
  277. return -1;
  278. }
  279. return 0;
  280. }
  281. else
  282. {
  283. dprintf(2, "Invalid protocol in '%s'\n", sockurl);
  284. return -1;
  285. }
  286. do { sockurl++; } while(*sockurl && *sockurl != ':');
  287. if(!sockurl)
  288. {
  289. dprintf(2, "%s protocol choosen but not port given : '%s'\n",
  290. tcp?"TCP":"UDP", sockurl);
  291. return -1;
  292. }
  293. if(!*sockurl)
  294. {
  295. dprintf(2, "Port missing in socket URL '%s'\n", urlorig);
  296. return -1;
  297. }
  298. sockurl++;
  299. port = sockurl;
  300. while(*sockurl && *sockurl >= '0' && *sockurl <= '9')
  301. {
  302. sockurl++;
  303. }
  304. if(*sockurl)
  305. {
  306. dprintf(2, "Invalid port '%s' in socket URL '%s'\n",
  307. port, urlorig);
  308. return -1;
  309. }
  310. return 0;
  311. }
  312. int pyfcgi_monitor_parse_sock(const char *sockurl, int sockargs[3],
  313. pyfcgi_monitor_addr_t *listen_addr)
  314. {
  315. const char *addr_ptr;
  316. short tcp;
  317. int *domain, *type, *protocol;
  318. struct sockaddr_un *addr_un;
  319. domain = &sockargs[0];
  320. type = &sockargs[1];
  321. protocol = &sockargs[2];
  322. addr_un = &(listen_addr->un);
  323. if(!strncasecmp("unix://", sockurl, 7))
  324. {
  325. addr_ptr = sockurl + 7;
  326. *domain = AF_UNIX;
  327. *type = SOCK_STREAM;
  328. *protocol = 0;
  329. addr_un->sun_family = AF_UNIX;
  330. strncpy(addr_un->sun_path, addr_ptr, UNIX_SOCKPATH_MAX);
  331. unlink(addr_ptr); // if socket exists delete it
  332. return 0;
  333. }
  334. if((tcp = strncasecmp("tcp://", sockurl, 6)) &&
  335. strncasecmp("udp://", sockurl, 6))
  336. { //Unchecked URL??!!
  337. pyfcgi_log(LOG_ERR, "Invalid protocol in URL : '%s'",
  338. sockurl);
  339. return -1;
  340. }
  341. addr_ptr = sockurl + 6;
  342. *type = tcp ? SOCK_DGRAM : SOCK_STREAM;
  343. *protocol = 0;
  344. if(pyfcgi_monitor_parse_inet_addr(addr_ptr, *type, listen_addr, domain))
  345. {
  346. return -1;
  347. }
  348. return 0;
  349. }
  350. int pyfcgi_monitor_parse_inet_addr(const char* addr_str, int socktype,
  351. pyfcgi_monitor_addr_t *listen_addr, int* domain)
  352. {
  353. char *addr, *port, *ptr, ipstr[64];
  354. struct addrinfo *infos, hints, *info;
  355. short v4, v6;
  356. int ret;
  357. // initialize temporary address & port pointers
  358. addr = strdup(addr_str);
  359. if(!addr)
  360. {
  361. pyfcgi_log(LOG_ALERT, "strdup() failed to copy socket addr : %s",
  362. strerror(errno));
  363. return -1;
  364. }
  365. ptr = addr;
  366. do { ptr++; }while(*ptr && *ptr != ':');
  367. if(!ptr)
  368. {
  369. pyfcgi_log(LOG_ERR, "No port found in INET url : %s",
  370. addr_str);
  371. goto free_err;
  372. }
  373. *ptr = '\0';
  374. port = ptr+1;
  375. memset(&hints, 0, sizeof(struct addrinfo));
  376. v4 = PyFCGI_conf.ipv4;
  377. v6 = PyFCGI_conf.ipv6;
  378. hints.ai_family = (v4?AF_INET:(v6?AF_INET6:PF_UNSPEC));
  379. hints.ai_socktype = socktype;
  380. hints.ai_flags = AI_CANONNAME;
  381. if((ret = getaddrinfo(addr, port, &hints, &infos)))
  382. {
  383. pyfcgi_log(LOG_ALERT, "getaddrinfo fails on '%s' : %s",
  384. gai_strerror(ret));
  385. goto free_err;
  386. }
  387. for(info = infos; info != NULL; info = info->ai_next)
  388. {
  389. if(info->ai_family == AF_INET)
  390. {
  391. memcpy(&listen_addr->in, info->ai_addr,
  392. info->ai_addrlen);
  393. }
  394. else if(info->ai_family == AF_INET6)
  395. {
  396. memcpy(&listen_addr->in6, info->ai_addr,
  397. info->ai_addrlen);
  398. }
  399. else
  400. {
  401. continue;
  402. }
  403. *domain = info->ai_family;
  404. if(!inet_ntop(*domain, info->ai_addr, ipstr, 64))
  405. {
  406. pyfcgi_log(LOG_ERR, "Unable to format IP in string : %s",
  407. strerror(errno));
  408. strcpy(ipstr, "IP");
  409. }
  410. pyfcgi_log(LOG_DEBUG, "Listen addr resolved to %s(%s)",
  411. info->ai_canonname, ipstr);
  412. freeaddrinfo(infos);
  413. free(addr);
  414. return 0;
  415. }
  416. pyfcgi_log(LOG_ERR, "Unable to resolve to a valid AF_INET[6] address");
  417. freeaddrinfo(infos);
  418. free_err:
  419. free(addr);
  420. return -1;
  421. }
  422. void pyfcgi_monitor_sighandler(int signum)
  423. {
  424. pyfcgi_log(LOG_NOTICE, "Received signal %s, exiting",
  425. strsignal(signum));
  426. clean_exit(0);
  427. }
  428. void pyfcgi_monitor_timeout(int signum)
  429. {
  430. pyfcgi_log(LOG_ALERT, "Monitor server timeout, killed by watchdog");
  431. clean_exit(PYFCGI_TIMEOUT);
  432. }