Browse Source

Implement on demand commits

Yann Weber 4 years ago
parent
commit
d38ab9dacb
5 changed files with 199 additions and 44 deletions
  1. 3
    3
      src/main.c
  2. 44
    28
      src/pyworker.c
  3. 2
    10
      src/pyworker.h
  4. 136
    3
      src/responder.c
  5. 14
    0
      src/responder.h

+ 3
- 3
src/main.c View File

@@ -29,7 +29,7 @@
29 29
 #include "responder.h"
30 30
 
31 31
 #define IDENT_FMT "pyfcgi[%d]"
32
-#define MAX_REQS 5
32
+#define MAX_REQS 50
33 33
 
34 34
 #define EARLY_ERR(err_str) write(2, err_str, strlen(err_str))
35 35
 
@@ -76,8 +76,8 @@ int main(int argc, char **argv)
76 76
 		}
77 77
 		else if(!child)
78 78
 		{
79
-			responder_loop(py_entrypoint, MAX_REQS, 1, 1);
80
-			exit(-1);
79
+			responder_loop(py_entrypoint, MAX_REQS, 1, 20);
80
+			exit((unsigned char)-1);
81 81
 		}
82 82
 		waitpid(child, &child_ret, 0);
83 83
 		if(child_ret)

+ 44
- 28
src/pyworker.c View File

@@ -21,36 +21,18 @@
21 21
 
22 22
 static int worker_piper_sigrcv = 0;
23 23
 
24
-pid_t spawn(char* py_entrypoint, int wrk_id, int semid, int max_reqs)
25
-{
26
-	pid_t res;
27
-
28
-	res = fork();
29
-	if(res == -1)
30
-	{
31
-		syslog(	LOG_ERR, "Fork fails for worker #%d : %s",
32
-			wrk_id, strerror(errno));
33
-		return -1;
34
-	}
35
-	else if(!res)
36
-	{
37
-		// Child process
38
-		exit(work(py_entrypoint, wrk_id, semid, max_reqs));
39
-	}
40
-	syslog(	LOG_INFO,
41
-		"Worker #%d spawned with PID %d", wrk_id, res);
42
-	return res;
43
-}
44
-
45 24
 int work(char* py_entrypoint, int wrk_id, int semid, int max_reqs)
46 25
 {
47 26
 	PyObject *entry_fun, *pystdout_flush, *pystderr_flush,
48 27
 	         *py_osmod;
49 28
 	int count, pipe_out[2], pipe_err[2], pipe_ctl[2], err, piper_status;
29
+	struct sembuf sop;
50 30
 	struct sigaction act;
51 31
 	sigset_t emptyset;
52 32
 	char buf[PIPE_BUF];
53 33
 
34
+	sop.sem_num = 0;
35
+	sop.sem_flg = 0;
54 36
 
55 37
 	// preparing sigaction for piper
56 38
 	if(sigemptyset(&emptyset))
@@ -96,12 +78,22 @@ int work(char* py_entrypoint, int wrk_id, int semid, int max_reqs)
96 78
 	count = 0;
97 79
 	while ((!count || count != max_reqs) && FCGI_Accept() >= 0)
98 80
 	{
81
+		sop.sem_op = -1; // decrementing sem to show worker busy
82
+		if(semop(semid, &sop, 1) < 0)
83
+		{
84
+			err = errno;
85
+			syslog(LOG_ERR,
86
+			       "Worker[%d] error decrementing the semaphore : %s",
87
+			       wrk_id, strerror(err));
88
+		}
89
+
99 90
 		count++;
91
+		/*
100 92
 		syslog(	LOG_INFO,
101 93
 			"Worker[%d] request %d", wrk_id, count);
94
+		*/
102 95
 		worker_piper_sigrcv = 0;
103 96
 		pipe(pipe_ctl); //TODO : check for pipe error
104
-		//PyOS_BeforeFork();
105 97
 		pid_t pid = fork();
106 98
 		if(pid < 0)
107 99
 		{
@@ -128,7 +120,6 @@ int work(char* py_entrypoint, int wrk_id, int semid, int max_reqs)
128 120
 			//printf("Content-type: text/html\r\n\r\nHello world !\n");
129 121
 			exit(1);
130 122
 		}
131
-		//PyOS_AfterFork_Parent();
132 123
 		update_pyenv(py_osmod);
133 124
 		//TODO : check if pipe_ctl lock is really needed anymore
134 125
 		close(pipe_ctl[1]);
@@ -139,14 +130,16 @@ int work(char* py_entrypoint, int wrk_id, int semid, int max_reqs)
139 130
 		}
140 131
 		else
141 132
 		{
133
+			/*
142 134
 			syslog(LOG_DEBUG, "Worker[%d] request %d funcall [OK]",
143 135
 				wrk_id, count);
136
+			*/
144 137
 		}
145 138
 		PyObject_CallObject(pystdout_flush, NULL);
146 139
 		PyObject_CallObject(pystderr_flush, NULL);
147 140
 		read(pipe_ctl[0], &buf, 1); // unblock when child ready
148
-syslog(LOG_DEBUG, "PIPER UNLOCK");
149
-		kill(pid, WPIPER_SIG);
141
+
142
+		kill(pid, WPIPER_SIG); //indicate child python call ended
150 143
 		waitpid(pid, &piper_status, 0);
151 144
 		if(piper_status)
152 145
 		{
@@ -154,6 +147,17 @@ syslog(LOG_DEBUG, "PIPER UNLOCK");
154 147
 			       "Woker[%d] req #%d piper exited with error status %d",
155 148
 			       wrk_id, count, piper_status);
156 149
 		}
150
+
151
+		//Increase sem showing the worker is idle
152
+		sop.sem_op = 1;
153
+		if(semop(semid, &sop, 1) < 0)
154
+		{
155
+			err = errno;
156
+			syslog(LOG_ERR,
157
+			       "Worker[%d] error incrementing the semaphore : %s",
158
+			       wrk_id, strerror(err));
159
+		}
160
+
157 161
 		syslog(LOG_DEBUG, "Worker[%d] request %d END [OK]",
158 162
 			wrk_id, count);
159 163
 	}
@@ -168,7 +172,7 @@ void worker_piper(int wrk_id, int req_id, int pystdout, int pystderr,
168 172
 	short revents;
169 173
 	char buf[PIPE_BUF];
170 174
 
171
-	syslog(LOG_DEBUG, "Worker[%d] req #%d piper", wrk_id, req_id);
175
+	//syslog(LOG_DEBUG, "Worker[%d] req #%d piper", wrk_id, req_id);
172 176
 
173 177
 	fds[0].fd = pystdout;
174 178
 	fds[1].fd = pystderr;
@@ -180,7 +184,7 @@ void worker_piper(int wrk_id, int req_id, int pystdout, int pystderr,
180 184
 	while(cont)
181 185
 	{
182 186
 		poll_ret = poll(fds, 2, 10);
183
-syslog(LOG_DEBUG, "Worler[%d] req #%d poll_ret = %d", wrk_id, req_id, poll_ret);
187
+//syslog(LOG_DEBUG, "Worker[%d] req #%d poll_ret = %d", wrk_id, req_id, poll_ret);
184 188
 		if(poll_ret < 0)
185 189
 		{
186 190
 			err = errno;
@@ -200,16 +204,22 @@ syslog(LOG_DEBUG, "Worler[%d] req #%d poll_ret = %d", wrk_id, req_id, poll_ret);
200 204
 		}
201 205
 		if(poll_ret && (revents = fds[0].revents))
202 206
 		{
207
+			/*
203 208
 			syslog(LOG_DEBUG, "Worker[%d] req #%d STDOUT evt !",
204 209
 			       wrk_id, req_id);
210
+			*/
205 211
 			poll_ret--;
206 212
 			if(revents & POLLIN)
207 213
 			{
214
+				/*
208 215
 				syslog(LOG_DEBUG, "Worker[%d] req #%d POLLIN STDOUT !",
209 216
 				       wrk_id, req_id);
217
+				*/
210 218
 				ret = read(pystdout, buf, PIPE_BUF);
219
+				/*
211 220
 				syslog(LOG_DEBUG, "Worker[%d] req #%d read(stdout) ret %d",
212 221
 				       wrk_id, req_id, ret);
222
+				*/
213 223
 				if(ret < 0)
214 224
 				{
215 225
 					err = errno;
@@ -229,12 +239,16 @@ syslog(LOG_DEBUG, "Worler[%d] req #%d poll_ret = %d", wrk_id, req_id, poll_ret);
229 239
 		}
230 240
 		if(poll_ret && (revents = fds[1].revents))
231 241
 		{
242
+			/*
232 243
 			syslog(LOG_DEBUG, "Worker[%d] req #%d STDERR evt !",
233 244
 			       wrk_id, req_id);
245
+			*/
234 246
 			if(revents & POLLIN)
235 247
 			{
248
+				/*
236 249
 				syslog(LOG_DEBUG, "Worker[%d] req #%d POLLIN STDERR !",
237 250
 				       wrk_id, req_id);
251
+				*/
238 252
 				while(1)
239 253
 				{
240 254
 					ret = read(pystderr, buf, PIPE_BUF);
@@ -261,8 +275,10 @@ syslog(LOG_DEBUG, "Worler[%d] req #%d poll_ret = %d", wrk_id, req_id, poll_ret);
261 275
 		}
262 276
 		fds[0].revents = fds[1].revents = 0;
263 277
 	}
278
+	/*
264 279
 	syslog(LOG_DEBUG, "Worker[%d] req #%d piper exiting...",
265 280
 	       wrk_id, req_id);
281
+	*/
266 282
 	exit(0);
267 283
 }
268 284
 
@@ -620,7 +636,7 @@ void update_pyenv(PyObject *py_osmod)
620 636
 		}
621 637
 		value++;
622 638
 		*(value-1) = '\0'; // dirty modification of **environ
623
-syslog(LOG_DEBUG, "PySetEnv '%s'='%s'", key, value);
639
+//syslog(LOG_DEBUG, "PySetEnv '%s'='%s'", key, value);
624 640
 		pykey = PyUnicode_DecodeLocale(key, "surrogateescape");
625 641
 		if(!pykey)
626 642
 		{

+ 2
- 10
src/pyworker.h View File

@@ -34,6 +34,8 @@
34 34
 #include <limits.h>
35 35
 #include <poll.h>
36 36
 #include <sys/types.h>
37
+#include <sys/ipc.h>
38
+#include <sys/sem.h>
37 39
 #include <sys/wait.h>
38 40
 
39 41
 #define EXIT_PYERR 42
@@ -44,16 +46,6 @@ extern char **environ;
44 46
 
45 47
 typedef unsigned long int pywrkid_t;
46 48
 
47
-/**@brief Spawn a worker given an entrypoint
48
- *
49
- * Spawn a new worker process and prepare ENV & request forwarding
50
- * @param char* python_entrypoint a path to a python entrypoint
51
- * @param int worker uid
52
- * @param int semid for FCGI access
53
- * @param int max request before worker restart
54
- * @return child PID
55
- */
56
-pid_t spawn(char*, int, int, int);
57 49
 
58 50
 /**@brief the function that initialize the python worker
59 51
  * @param char* python_entrypoint a path to a python entrypoint

+ 136
- 3
src/responder.c View File

@@ -28,10 +28,19 @@ void init_context()
28 28
 int responder_loop(char *py_entrypoint, unsigned int max_reqs,
29 29
 	unsigned int min_wrk, unsigned int max_wrk)
30 30
 {
31
-	unsigned int n_wrk;
32
-	int *wrk_pids;
31
+	unsigned int n_wrk, wanted_n, n;
32
+	pid_t *wrk_pids;
33 33
 	int semid, err;
34 34
 	int status;
35
+	pid_t ret;
36
+	struct sembuf sop;
37
+	struct timespec timeout;
38
+
39
+	sop.sem_num = 0;
40
+	sop.sem_op = 0;
41
+	sop.sem_flg = 0;
42
+	timeout.tv_sec = 0;
43
+	timeout.tv_nsec = 100000000;
35 44
 
36 45
 	syslog(LOG_INFO, "Preparing workers");
37 46
 
@@ -50,10 +59,99 @@ int responder_loop(char *py_entrypoint, unsigned int max_reqs,
50 59
 
51 60
 	semid = new_semaphore();
52 61
 	
53
-	for(n_wrk=0; n_wrk != min_wrk; n_wrk++)
62
+	wanted_n = min_wrk;
63
+	n_wrk = 0;
64
+	// prespawning minimum worker count
65
+	for(n_wrk=0; n_wrk < wanted_n; n_wrk++)
54 66
 	{
55 67
 		wrk_pids[n_wrk] = spawn(py_entrypoint, n_wrk, semid, max_reqs);
56 68
 	}
69
+
70
+	// main loop, taking care to restart terminated workers, 
71
+	// spawn new one if needed, etc.
72
+	while(1)
73
+	{
74
+		if( (ret = waitpid(0, &status, WNOHANG)) )
75
+		{
76
+			if(ret < 0)
77
+			{
78
+				//TODO : error
79
+			}
80
+			if(!ret)
81
+			{
82
+				continue;
83
+			}
84
+			for(n=0; n<n_wrk; n++)
85
+			{
86
+				if(wrk_pids[n] == ret)
87
+				{
88
+					break;
89
+				}
90
+			}
91
+			if(n == n_wrk)
92
+			{
93
+				syslog(LOG_WARNING,
94
+				       "Child %d stopped but was notregistered",
95
+				       ret);
96
+				continue;
97
+			}
98
+			if(status)
99
+			{
100
+				syslog(LOG_WARNING,
101
+				       "Worker[%d] exited with status %d",
102
+				       n, status);
103
+			}
104
+			else
105
+			{
106
+				syslog(LOG_INFO,
107
+				       "Worker[%d] PID %d exited normally",
108
+				       n, wrk_pids[n]);
109
+			}
110
+			// child stopped, looking for it
111
+			if(wanted_n < n_wrk)
112
+			{	// need to shift the list and dec n_wrk
113
+				memmove(wrk_pids+n, wrk_pids+n+1,
114
+				        sizeof(pid_t) * (n_wrk - n));
115
+				n_wrk--;
116
+			}
117
+			else
118
+			{	// respawn on same slot
119
+				wrk_pids[n] = spawn(py_entrypoint, n,
120
+				                    semid, max_reqs);
121
+			}
122
+		}
123
+		if(n_wrk == max_wrk)
124
+		{
125
+			nanosleep(&timeout, NULL);
126
+			continue;
127
+		}
128
+		ret = semtimedop(semid, &sop, 1, &timeout);
129
+		if(ret < 0)
130
+		{
131
+			err = errno;
132
+			if(err == EAGAIN)
133
+			{
134
+				// workers idle
135
+				if(wanted_n > min_wrk)
136
+				{
137
+					wanted_n--;
138
+				}
139
+				continue;
140
+			}
141
+			syslog(LOG_ERR, "Unable to read semaphore : %s",
142
+			       strerror(err));
143
+		}
144
+		if(!ret)
145
+		{
146
+			syslog( LOG_DEBUG,
147
+				"All workers busy, spawning a new one");
148
+			n = n_wrk;
149
+			n_wrk++;
150
+			wanted_n = n_wrk;
151
+			wrk_pids[n] = spawn(py_entrypoint, n,
152
+					    semid, max_reqs);
153
+		}
154
+	}
57 155
 	
58 156
 	//Debug wait & exit
59 157
 	for(n_wrk=0; n_wrk != min_wrk; n_wrk++)
@@ -67,6 +165,41 @@ int responder_loop(char *py_entrypoint, unsigned int max_reqs,
67 165
 	exit(0);
68 166
 }
69 167
 
168
+pid_t spawn(char* py_entrypoint, int wrk_id, int semid, int max_reqs)
169
+{
170
+	pid_t res;
171
+	struct sembuf sop;
172
+	int err;
173
+	
174
+	sop.sem_num = 0;
175
+	sop.sem_op = 1;
176
+	sop.sem_flg = 0;
177
+
178
+	if(semop(semid, &sop, 1) < 0)
179
+	{
180
+		err = errno;
181
+		syslog(LOG_ALERT,
182
+		       "Failed to semop before spawning a child : %s",
183
+		       strerror(errno));
184
+		clean_exit(err);
185
+	}
186
+
187
+	res = fork();
188
+	if(res == -1)
189
+	{
190
+		syslog(	LOG_ERR, "Fork fails for worker #%d : %s",
191
+			wrk_id, strerror(errno));
192
+		return -1;
193
+	}
194
+	else if(!res)
195
+	{
196
+		// Child process
197
+		exit(work(py_entrypoint, wrk_id, semid, max_reqs));
198
+	}
199
+	syslog(	LOG_INFO,
200
+		"Worker #%d spawned with PID %d", wrk_id, res);
201
+	return res;
202
+}
70 203
 
71 204
 int new_semaphore(key_t semkey)
72 205
 {

+ 14
- 0
src/responder.h View File

@@ -18,6 +18,7 @@
18 18
  */
19 19
 #ifndef _RESPONDER__H___
20 20
 #define _RESPONDER__H___
21
+#include "config.h"
21 22
 
22 23
 #include <fcgi_stdio.h> /* fcgi library; put it first*/
23 24
 
@@ -26,6 +27,7 @@
26 27
 #include <string.h>
27 28
 #include <syslog.h>
28 29
 #include <errno.h>
30
+#include <time.h>
29 31
 #include <sys/types.h>
30 32
 #include <sys/ipc.h>
31 33
 #include <sys/sem.h>
@@ -51,6 +53,18 @@ void init_context();
51 53
 int responder_loop(char *py_entrypoint, unsigned int max_reqs,
52 54
 	unsigned int min_wrk, unsigned int max_wrk);
53 55
 
56
+
57
+/**@brief Spawn a worker given an entrypoint
58
+ *
59
+ * Spawn a new worker process and prepare ENV & request forwarding
60
+ * @param char* python_entrypoint a path to a python entrypoint
61
+ * @param int worker uid
62
+ * @param int semid for FCGI access
63
+ * @param int max request before worker restart
64
+ * @return child PID
65
+ */
66
+pid_t spawn(char*, int, int, int);
67
+
54 68
 /**@brief Generate a new semaphore from given key
55 69
  * @note set pyfcgi_semid
56 70
  * @return int semid

Loading…
Cancel
Save