comparison plugins/links/links.c @ 1164:832dbde9d495

plugin links: rewrite a thread-less version with pollable interface
author David Demelier <markand@malikania.fr>
date Mon, 28 Feb 2022 21:38:38 +0100
parents f06e9761cc90
children
comparison
equal deleted inserted replaced
1163:ad7673c59ec5 1164:832dbde9d495
17 */ 17 */
18 18
19 #include <assert.h> 19 #include <assert.h>
20 #include <ctype.h> 20 #include <ctype.h>
21 #include <errno.h> 21 #include <errno.h>
22 #include <pthread.h>
23 #include <regex.h> 22 #include <regex.h>
24 #include <stdio.h> 23 #include <stdio.h>
25 #include <stdlib.h> 24 #include <stdlib.h>
26 #include <string.h> 25 #include <string.h>
27 26
28 #include <curl/curl.h> 27 #include <curl/curl.h>
29 28
30 #include <irccd/config.h> 29 #include <irccd/config.h>
31 #include <irccd/irccd.h> 30 #include <irccd/irccd.h>
32 #include <irccd/limits.h> 31 #include <irccd/limits.h>
32 #include <irccd/log.h>
33 #include <irccd/server.h> 33 #include <irccd/server.h>
34 #include <irccd/subst.h> 34 #include <irccd/subst.h>
35 #include <irccd/util.h> 35 #include <irccd/util.h>
36 36
37 /* 37 /*
39 * analyse. Use 5MB for now. 39 * analyse. Use 5MB for now.
40 */ 40 */
41 #define PAGE_MAX 5242880 41 #define PAGE_MAX 5242880
42 42
43 struct req { 43 struct req {
44 pthread_t thr;
45 CURL *curl; 44 CURL *curl;
45 CURLM *multi;
46 struct irc_server *server; 46 struct irc_server *server;
47 struct irc_pollable pollable;
47 int status; 48 int status;
48 char *link; 49 char *link;
49 char *chan; 50 char *chan;
50 char *nickname; 51 char *nickname;
51 char *origin; 52 char *origin;
55 56
56 enum { 57 enum {
57 TPL_INFO 58 TPL_INFO
58 }; 59 };
59 60
60 static unsigned long timeout = 30; 61 static unsigned long timeout = 3;
61 62
62 static char templates[][512] = { 63 static char templates[][512] = {
63 [TPL_INFO] = "link from #{nickname}: #{title}" 64 [TPL_INFO] = "link from #{nickname}: #{title}"
64 }; 65 };
65 66
168 static char line[IRC_MESSAGE_LEN]; 169 static char line[IRC_MESSAGE_LEN];
169 170
170 struct irc_subst subst = { 171 struct irc_subst subst = {
171 .time = time(NULL), 172 .time = time(NULL),
172 .flags = IRC_SUBST_DATE | IRC_SUBST_KEYWORDS | IRC_SUBST_IRC_ATTRS, 173 .flags = IRC_SUBST_DATE | IRC_SUBST_KEYWORDS | IRC_SUBST_IRC_ATTRS,
173 .keywords = (struct irc_subst_keyword []) { 174 .keywords = (const struct irc_subst_keyword []) {
174 { "channel", req->chan }, 175 { "channel", req->chan },
175 { "nickname", req->nickname }, 176 { "nickname", req->nickname },
176 { "origin", req->origin }, 177 { "origin", req->origin },
177 { "server", req->server->name }, 178 { "server", req->server->name },
178 { "title", untitle(title) } 179 { "title", untitle(title) }
184 185
185 return line; 186 return line;
186 } 187 }
187 188
188 static void 189 static void
189 req_finish(struct req *); 190 complete(struct req *req)
190 191 {
191 static void
192 complete(void *data)
193 {
194 struct req *req = data;
195 char *title; 192 char *title;
196 193
197 if (req->status && (title = parse(req))) 194 if ((title = parse(req)))
198 irc_server_message(req->server, req->chan, fmt(req, title)); 195 irc_server_message(req->server, req->chan, fmt(req, title));
199
200 pthread_join(req->thr, NULL);
201 req_finish(req);
202 }
203
204 /*
205 * This function is running in a separate thread.
206 */
207 static void *
208 routine(void *data)
209 {
210 struct req *req = data;
211 long code = 0;
212
213 if (curl_easy_perform(req->curl) == 0) {
214 /* We only accept 200 result. */
215 curl_easy_getinfo(req->curl, CURLINFO_RESPONSE_CODE, &code);
216 req->status = code == 200;
217 }
218
219 irc_bot_post(complete, req);
220
221 return NULL;
222 } 196 }
223 197
224 static void 198 static void
225 req_finish(struct req *req) 199 req_finish(struct req *req)
226 { 200 {
227 assert(req); 201 if (!req)
202 return;
228 203
229 if (req->server) 204 if (req->server)
230 irc_server_decref(req->server); 205 irc_server_decref(req->server);
231 if (req->curl) 206
207 if (req->curl) {
208 if (req->multi)
209 curl_multi_remove_handle(req->multi, req->curl);
210
232 curl_easy_cleanup(req->curl); 211 curl_easy_cleanup(req->curl);
212 }
213 if (req->multi)
214 curl_multi_cleanup(req->multi);
215
233 if (req->fp) 216 if (req->fp)
234 fclose(req->fp); 217 fclose(req->fp);
235 218
236 free(req->link); 219 free(req->link);
237 free(req->chan); 220 free(req->chan);
238 free(req->nickname); 221 free(req->nickname);
239 free(req->origin); 222 free(req->origin);
240 free(req); 223 free(req);
241 } 224 }
242 225
243 static struct req * 226 static int
244 req_new(struct irc_server *server, const char *origin, const char *channel, char *link) 227 pollable_fd(void *data)
228 {
229 struct req *r = data;
230 fd_set in, out, exc;
231 int fd = 0;
232
233 FD_ZERO(&in);
234 FD_ZERO(&out);
235 FD_ZERO(&exc);
236
237 curl_multi_fdset(r->multi, &in, &out, &exc, &fd);
238
239 return fd;
240 }
241
242 static void
243 pollable_want(int *frecv, int *fsend, void *data)
244 {
245 struct req *r = data;
246 fd_set in, out, exc;
247 int maxfd = 0;
248
249 FD_ZERO(&in);
250 FD_ZERO(&out);
251 FD_ZERO(&exc);
252
253 curl_multi_fdset(r->multi, &in, &out, &exc, &maxfd);
254
255 if (FD_ISSET(maxfd, &in))
256 *frecv = 1;
257 if (FD_ISSET(maxfd, &out))
258 *fsend = 1;
259 }
260
261 static int
262 pollable_sync(int frecv, int fsend, void *data)
263 {
264 (void)frecv;
265 (void)fsend;
266
267 CURLMsg *msg;
268 struct req *r = data;
269 int pending, msgsz;
270
271 /*
272 * CURL does its own job reading/sending without taking action on what
273 * have been found.
274 */
275 if (curl_multi_perform(r->multi, &pending) < 0)
276 return -1;
277
278 /* We only have one handle so we can just assume 0 means complete. */
279 if (pending)
280 return 0;
281
282 while ((msg = curl_multi_info_read(r->multi, &msgsz))) {
283 if (msg->msg != CURLMSG_DONE)
284 continue;
285
286 switch (msg->data.result) {
287 case CURLE_OPERATION_TIMEDOUT:
288 irc_log_warn("links: %s timed out", r->link);
289 break;
290 case CURLE_OK:
291 complete(r);
292 break;
293 default:
294 break;
295 }
296 }
297
298 return -1;
299 }
300
301 static void
302 pollable_finish(void *data)
303 {
304 req_finish(data);
305 }
306
307 static void
308 req(struct irc_server *server, const char *origin, const char *channel, char *link)
245 { 309 {
246 assert(link); 310 assert(link);
247 311
248 struct req *req; 312 struct req *req;
249 struct irc_server_user user; 313 struct irc_server_user user;
250 314 int pending;
251 if (!(req = calloc(1, sizeof (*req) + PAGE_MAX + 1))) { 315
252 free(link); 316 if (!(req = calloc(1, sizeof (*req) + PAGE_MAX + 1)))
253 return NULL; 317 goto enomem;
254 }
255 if (!(req->curl = curl_easy_init())) 318 if (!(req->curl = curl_easy_init()))
319 goto enomem;
320 if (!(req->multi = curl_multi_init()))
256 goto enomem; 321 goto enomem;
257 if (!(req->fp = fmemopen(req->buf, PAGE_MAX, "w"))) 322 if (!(req->fp = fmemopen(req->buf, PAGE_MAX, "w")))
258 goto enomem; 323 goto enomem;
259 324
260 irc_server_incref(server); 325 irc_server_incref(server);
261 irc_server_split(origin, &user); 326 irc_server_split(origin, &user);
327
262 req->link = link; 328 req->link = link;
263 req->server = server; 329 req->server = server;
264 req->chan = strdup(channel); 330 req->chan = irc_util_strdup(channel);
265 req->nickname = strdup(user.nickname); 331 req->nickname = irc_util_strdup(user.nickname);
266 req->origin = strdup(origin); 332 req->origin = irc_util_strdup(origin);
267 333
268 curl_easy_setopt(req->curl, CURLOPT_URL, link); 334 curl_easy_setopt(req->curl, CURLOPT_URL, link);
269 curl_easy_setopt(req->curl, CURLOPT_FOLLOWLOCATION, 1L); 335 curl_easy_setopt(req->curl, CURLOPT_FOLLOWLOCATION, 1L);
270 curl_easy_setopt(req->curl, CURLOPT_WRITEFUNCTION, callback); 336 curl_easy_setopt(req->curl, CURLOPT_WRITEFUNCTION, callback);
271 curl_easy_setopt(req->curl, CURLOPT_WRITEDATA, req); 337 curl_easy_setopt(req->curl, CURLOPT_WRITEDATA, req);
272 curl_easy_setopt(req->curl, CURLOPT_TIMEOUT, timeout); 338 curl_easy_setopt(req->curl, CURLOPT_TIMEOUT, timeout);
273 339 curl_easy_setopt(req->curl, CURLOPT_NOSIGNAL, 1L);
274 return req; 340 curl_multi_add_handle(req->multi, req->curl);
341
342 /*
343 * Try immediately to create a socket, otherwise we would poll for a
344 * long time before trying to fetch data.
345 */
346 if (curl_multi_perform(req->multi, &pending) != CURLM_OK) {
347 req_finish(req);
348 return;
349 }
350
351 req->pollable.data = req;
352 req->pollable.fd = pollable_fd;
353 req->pollable.want = pollable_want;
354 req->pollable.sync = pollable_sync;
355 req->pollable.finish = pollable_finish;
356 irc_bot_pollable_add(&req->pollable);
357
358 return;
275 359
276 enomem: 360 enomem:
361 free(link);
362 req_finish(req);
363
277 errno = ENOMEM; 364 errno = ENOMEM;
278 req_finish(req);
279
280 return NULL;
281 }
282
283 static void
284 req_start(struct req *req)
285 {
286 if (pthread_create(&req->thr, NULL, routine, req) != 0)
287 req_finish(req);
288 } 365 }
289 366
290 void 367 void
291 links_event(const struct irc_event *ev) 368 links_event(const struct irc_event *ev)
292 { 369 {
293 struct req *req; 370 char *loc, *end;
294 char *loc, *link, *end;
295 371
296 if (ev->type != IRC_EVENT_MESSAGE) 372 if (ev->type != IRC_EVENT_MESSAGE)
297 return; 373 return;
298 374
299 /* Parse link. */ 375 /* Parse link. */
303 379
304 /* Compute end to allocate only what's needed. */ 380 /* Compute end to allocate only what's needed. */
305 for (end = loc; *end && !isspace(*end); ) 381 for (end = loc; *end && !isspace(*end); )
306 ++end; 382 ++end;
307 383
308 link = irc_util_strndup(loc, end - loc); 384 req(ev->server, ev->message.origin, ev->message.channel,
309 385 irc_util_strndup(loc, end - loc));
310 /* If the function fails, link is free'd anyway. */
311 if (!(req = req_new(ev->server, ev->message.origin, ev->message.channel, link)))
312 return;
313
314 req_start(req);
315 } 386 }
316 387
317 void 388 void
318 links_set_template(const char *key, const char *value) 389 links_set_template(const char *key, const char *value)
319 { 390 {