1 /* Unit test for W32 version of g_poll()
2  *
3  * Copyright © 2017 Руслан Ижбулатов <lrn1986@gmail.com>
4  *
5  * This is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library; if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <glib.h>
20 #include <winsock2.h>
21 
22 #define NUM_POLLEES 999
23 #define NUM_POLLFDS 1000
24 
25 #define ASYNC_CONNECT_OK(r) (r == 0 || (r < 0 && GetLastError () == WSAEWOULDBLOCK))
26 
27 #define REPEAT 1
28 
29 static void
init_networking(void)30 init_networking (void)
31 {
32   WSADATA wsadata;
33 
34   if (WSAStartup (MAKEWORD (2, 0), &wsadata) != 0)
35     g_error ("Windows Sockets could not be initialized");
36 }
37 
38 static void
prepare_fds(SOCKET sockets[],GPollFD fds[],int num_pollees)39 prepare_fds (SOCKET  sockets[],
40              GPollFD fds[],
41              int     num_pollees)
42 {
43   gint i;
44 
45   for (i = 0; i < num_pollees; i++)
46     {
47       fds[i].fd = (gintptr) WSACreateEvent ();
48       g_assert (WSAEventSelect (sockets[i], (HANDLE) fds[i].fd, FD_READ | FD_CLOSE) == 0);
49     }
50 }
51 
52 static void
reset_fds(GPollFD fds[],int num_pollees)53 reset_fds (GPollFD fds[],
54            int     num_pollees)
55 {
56   gint i;
57 
58   for (i = 0; i < num_pollees; i++)
59     {
60       WSAResetEvent ((HANDLE) fds[i].fd);
61       fds[i].events =  G_IO_IN | G_IO_OUT | G_IO_ERR;
62       fds[i].revents = 0;
63     }
64 }
65 
66 static void
reset_fds_msg(GPollFD fds[],int num_pollfds)67 reset_fds_msg (GPollFD fds[],
68                int     num_pollfds)
69 {
70   fds[num_pollfds - 1].fd = G_WIN32_MSG_HANDLE;
71   fds[num_pollfds - 1].events = G_IO_IN;
72   fds[num_pollfds - 1].revents = 0;
73 }
74 
75 static void
check_fds(SOCKET sockets[],GPollFD fds[],int num_pollees)76 check_fds (SOCKET  sockets[],
77            GPollFD fds[],
78            int     num_pollees)
79 {
80   gint i;
81 
82   for (i = 0; i < num_pollees; i++)
83     {
84       if (fds[i].revents != 0)
85         {
86           WSANETWORKEVENTS events;
87           g_assert (WSAEnumNetworkEvents (sockets[i], 0, &events) == 0);
88 
89           fds[i].revents = 0;
90           if (events.lNetworkEvents & (FD_READ | FD_ACCEPT))
91             fds[i].revents |= G_IO_IN;
92 
93           if (events.lNetworkEvents & FD_WRITE)
94             fds[i].revents |= G_IO_OUT;
95           else
96             {
97               /* We have called WSAEnumNetworkEvents() above but it didn't
98                * set FD_WRITE.
99                */
100               if (events.lNetworkEvents & FD_CONNECT)
101                 {
102                   if (events.iErrorCode[FD_CONNECT_BIT] == 0)
103                     fds[i].revents |= G_IO_OUT;
104                   else
105                     fds[i].revents |= (G_IO_HUP | G_IO_ERR);
106                 }
107               if (fds[i].revents == 0 && (events.lNetworkEvents & (FD_CLOSE)))
108                 fds[i].revents |= G_IO_HUP;
109             }
110         }
111     }
112 }
113 
114 static void
prepare_sockets(SOCKET sockets[],SOCKET opp_sockets[],GPollFD fds[],int num_pollees)115 prepare_sockets (SOCKET  sockets[],
116                  SOCKET  opp_sockets[],
117                  GPollFD fds[],
118                  int     num_pollees)
119 {
120   gint i;
121   SOCKET server;
122   struct sockaddr_in sa;
123   unsigned long ul = 1;
124   int sa_size;
125   int r;
126 
127   server = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
128   g_assert (server != INVALID_SOCKET);
129 
130   memset(&sa, 0, sizeof sa);
131 
132   sa.sin_family = AF_INET;
133   sa.sin_port = 0;
134   sa.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
135   sa_size = sizeof (sa);
136 
137   g_assert (bind (server, (const struct sockaddr *) &sa, sa_size) == 0);
138   g_assert (getsockname (server, (struct sockaddr *) &sa, &sa_size) == 0);
139   g_assert (listen (server, 1) == 0);
140 
141   for (i = 0; i < num_pollees; i++)
142     {
143       opp_sockets[i] = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
144       g_assert (opp_sockets[i] != INVALID_SOCKET);
145       g_assert (ioctlsocket (opp_sockets[i], FIONBIO, &ul) == 0);
146 
147       r = connect (opp_sockets[i], (const struct sockaddr *) &sa, sizeof (sa));
148       g_assert (ASYNC_CONNECT_OK (r));
149 
150       sockets[i] = accept (server, NULL, NULL);
151       g_assert (sockets[i] != INVALID_SOCKET);
152       g_assert (ioctlsocket (sockets[i], FIONBIO, &ul) == 0);
153     }
154 
155   closesocket (server);
156 }
157 
158 static void
cleanup_sockets(SOCKET sockets[],SOCKET opp_sockets[],int num_pollees)159 cleanup_sockets (SOCKET sockets[],
160                  SOCKET opp_sockets[],
161                  int    num_pollees)
162 {
163   gint i;
164 
165   for (i = 0; i < num_pollees; i++)
166     {
167       closesocket (sockets[i]);
168       closesocket (opp_sockets[i]);
169     }
170 }
171 
172 static void
bucketize(gint64 val,gint buckets[],gint64 bucket_limits[],gint count)173 bucketize (gint64 val,
174            gint   buckets[],
175            gint64 bucket_limits[],
176            gint   count)
177 {
178   gint i;
179 
180   if (val > bucket_limits[count - 1])
181     {
182       buckets[count - 1] += 1;
183       return;
184     }
185 
186   for (i = count - 1; i > 0; i--)
187     if (val < bucket_limits[i] && val >= bucket_limits[i - 1])
188       {
189         buckets[i] += 1;
190         return;
191       }
192 
193   buckets[0] += 1;
194 }
195 
196 static void
print_buckets(gint buckets[],gint64 bucket_limits[],gint count)197 print_buckets (gint   buckets[],
198                gint64 bucket_limits[],
199                gint   count)
200 {
201   gint i;
202 
203   for (i = 0; i < count; i++)
204     if (i < count - 1)
205       g_print ("%-4lld-%4lld|", i == 0 ? 0 : bucket_limits[i - 1], bucket_limits[i] - 1);
206     else
207       g_print ("  >= %-4lld|", bucket_limits[i - 1]);
208 
209   g_print ("\n");
210 
211   for (i = 0; i < count; i++)
212     {
213       gint len;
214       gint padding;
215       gint j;
216       if (buckets[i] < 10)
217         len = 1;
218       else if (buckets[i] < 100)
219         len = 2;
220       else if (buckets[i] < 1000)
221         len = 3;
222       else
223         len = 4;
224       padding = 9 - len;
225       for (j = 0; j < padding / 2; j++)
226         g_print (" ");
227       if (buckets[i] != 0)
228         g_print ("%*d", len, buckets[i]);
229       else
230         g_print (" ");
231       for (j = padding / 2; j < padding; j++)
232         g_print (" ");
233       g_print (" ");
234     }
235 
236   g_print ("\n\n");
237 }
238 
239 static void
test_gpoll(void)240 test_gpoll (void)
241 {
242   SOCKET sockets[NUM_POLLEES];
243   GPollFD fds[NUM_POLLFDS];
244   SOCKET opp_sockets[NUM_POLLEES];
245   gint i;
246   gint activatable;
247   gint64 times[REPEAT][2];
248 #define BUCKET_COUNT 25
249   gint64 bucket_limits[BUCKET_COUNT] = {3, 5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 100, 120, 150, 180, 220, 280, 350, 450, 600, 800, 1000};
250   gint   buckets[BUCKET_COUNT];
251   gint64 times_avg = 0, times_min = G_MAXINT64, times_max = 0;
252 
253   prepare_sockets (sockets, opp_sockets, fds, NUM_POLLEES);
254   prepare_fds (sockets, fds, NUM_POLLEES);
255 
256   times_avg = 0;
257   times_min = G_MAXINT64;
258   times_max = 0;
259   memset (buckets, 0, sizeof (gint) * BUCKET_COUNT);
260 
261   for (i = 0; i < REPEAT; i++)
262     {
263       gint r;
264       gint64 diff;
265 
266       reset_fds (fds, NUM_POLLEES);
267       reset_fds_msg (fds, NUM_POLLFDS);
268       times[i][0] = g_get_monotonic_time ();
269       r = g_poll (fds, NUM_POLLFDS, 0);
270       times[i][1] = g_get_monotonic_time ();
271       g_assert (r == 0);
272       diff = times[i][1] - times[i][0];
273       if (times_min > diff)
274         times_min = diff;
275       if (times_max < diff)
276         times_max = diff;
277       times_avg += diff;
278       bucketize (diff, buckets, bucket_limits, BUCKET_COUNT);
279     }
280 
281   times_avg /= NUM_POLLEES;
282   g_print ("\nempty poll time:\n%4lldns - %4lldns, average %4lldns\n", times_min, times_max, times_avg);
283   print_buckets (buckets, bucket_limits, BUCKET_COUNT);
284 
285   times_avg = 0;
286   times_min = G_MAXINT64;
287   times_max = 0;
288   memset (buckets, 0, sizeof (gint) * BUCKET_COUNT);
289 
290   activatable = 0;
291 
292   for (i = 0; i < REPEAT; i++)
293     {
294       gint r, s, v, t;
295       gint64 diff;
296       MSG msg;
297       gboolean found_app;
298 
299       reset_fds (fds, NUM_POLLEES);
300       reset_fds_msg (fds, NUM_POLLFDS);
301       s = send (opp_sockets[activatable], (const char *) &t, 1, 0);
302       g_assert (PostMessage (NULL, WM_APP, 1, 2));
303       /* This is to ensure that all sockets catch up, otherwise some might not poll active */
304       g_usleep (G_USEC_PER_SEC / 1000);
305 
306       times[i][0] = g_get_monotonic_time ();
307       r = g_poll (fds, NUM_POLLFDS, 1000);
308       times[i][1] = g_get_monotonic_time ();
309 
310       check_fds (sockets, fds, NUM_POLLEES);
311       v = recv (sockets[activatable], (char *) &t, 1, 0);
312       found_app = FALSE;
313       while (!found_app && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
314         if (msg.message == WM_APP && msg.wParam == 1 && msg.lParam == 2)
315           found_app = TRUE;
316       g_assert (s == 1);
317       g_assert (r == 2);
318       g_assert (v == 1);
319       g_assert (found_app);
320 
321       reset_fds (fds, NUM_POLLEES);
322       reset_fds_msg (fds, NUM_POLLFDS);
323       r = g_poll (fds, NUM_POLLFDS, 0);
324       check_fds (sockets, fds, NUM_POLLEES);
325       g_assert (r == 0);
326       diff = times[i][1] - times[i][0];
327       if (times_min > diff)
328         times_min = diff;
329       if (times_max < diff)
330         times_max = diff;
331       times_avg += diff;
332       activatable = (activatable + 1) % NUM_POLLEES;
333       bucketize (diff, buckets, bucket_limits, BUCKET_COUNT);
334     }
335 
336   times_avg /= NUM_POLLEES;
337   g_print ("1-socket + msg poll time:\n%4lldns - %4lldns, average %4lldns\n", times_min, times_max, times_avg);
338   print_buckets (buckets, bucket_limits, BUCKET_COUNT);
339 
340   times_avg = 0;
341   times_min = G_MAXINT64;
342   times_max = 0;
343   memset (buckets, 0, sizeof (gint) * BUCKET_COUNT);
344 
345   activatable = 0;
346 
347   for (i = 0; i < REPEAT; i++)
348     {
349       gint r, s, v, t;
350       gint64 diff;
351 
352       reset_fds (fds, NUM_POLLEES);
353       reset_fds_msg (fds, NUM_POLLFDS);
354       s = send (opp_sockets[activatable], (const char *) &t, 1, 0);
355 
356       g_usleep (G_USEC_PER_SEC / 1000);
357 
358       times[i][0] = g_get_monotonic_time ();
359       r = g_poll (fds, NUM_POLLFDS, 1000);
360       times[i][1] = g_get_monotonic_time ();
361 
362       check_fds (sockets, fds, NUM_POLLEES);
363       v = recv (sockets[activatable], (char *) &t, 1, 0);
364       g_assert (s == 1);
365       g_assert (r == 1);
366       g_assert (v == 1);
367 
368       reset_fds (fds, NUM_POLLEES);
369       reset_fds_msg (fds, NUM_POLLFDS);
370       r = g_poll (fds, NUM_POLLFDS, 0);
371       check_fds (sockets, fds, NUM_POLLEES);
372       g_assert (r == 0);
373 
374       diff = times[i][1] - times[i][0];
375       if (times_min > diff)
376         times_min = diff;
377       if (times_max < diff)
378         times_max = diff;
379       times_avg += diff;
380       activatable = (activatable + 1) % NUM_POLLEES;
381       bucketize (diff, buckets, bucket_limits, BUCKET_COUNT);
382     }
383 
384   times_avg /= NUM_POLLEES;
385   g_print ("1-socket poll time:\n%4lldns - %4lldns, average %4lldns\n", times_min, times_max, times_avg);
386   print_buckets (buckets, bucket_limits, BUCKET_COUNT);
387 
388   times_avg = 0;
389   times_min = G_MAXINT64;
390   times_max = 0;
391   memset (buckets, 0, sizeof (gint) * BUCKET_COUNT);
392 
393   for (i = 0; i < REPEAT; i++)
394     {
395       gint r, s, v, t;
396       gint64 diff;
397       gint j;
398 
399       reset_fds (fds, NUM_POLLEES);
400       reset_fds_msg (fds, NUM_POLLFDS);
401       s = v = 0;
402 
403       for (j = 0; j < NUM_POLLEES / 2; j++)
404         s += send (opp_sockets[j], (const char *) &t, 1, 0) == 1 ? 1 : 0;
405 
406       g_usleep (G_USEC_PER_SEC / 1000);
407 
408       times[i][0] = g_get_monotonic_time ();
409       r = g_poll (fds, NUM_POLLFDS, 1000);
410       times[i][1] = g_get_monotonic_time ();
411       check_fds (sockets, fds, NUM_POLLEES);
412       for (j = 0; j < NUM_POLLEES / 2; j++)
413         v += recv (sockets[j], (char *) &t, 1, 0) == 1 ? 1 : 0;
414       g_assert (s == NUM_POLLEES / 2);
415       g_assert (r == NUM_POLLEES / 2);
416       g_assert (v == NUM_POLLEES / 2);
417 
418       reset_fds (fds, NUM_POLLEES);
419       reset_fds_msg (fds, NUM_POLLFDS);
420       r = g_poll (fds, NUM_POLLFDS, 0);
421       check_fds (sockets, fds, NUM_POLLEES);
422       g_assert (r == 0);
423 
424       diff = times[i][1] - times[i][0];
425       if (times_min > diff)
426         times_min = diff;
427       if (times_max < diff)
428         times_max = diff;
429       times_avg += diff;
430       bucketize (diff, buckets, bucket_limits, BUCKET_COUNT);
431     }
432 
433   times_avg /= NUM_POLLEES;
434   g_print ("half-socket poll time:\n%4lldns - %4lldns, average %4lldns\n", times_min, times_max, times_avg);
435   print_buckets (buckets, bucket_limits, BUCKET_COUNT);
436 
437   times_avg = 0;
438   times_min = G_MAXINT64;
439   times_max = 0;
440   memset (buckets, 0, sizeof (gint) * BUCKET_COUNT);
441 
442   for (i = 0; i < REPEAT; i++)
443     {
444       gint r, s, v, t;
445       gint64 diff;
446       gint j;
447       MSG msg;
448       gboolean found_app;
449 
450       reset_fds (fds, NUM_POLLEES);
451       reset_fds_msg (fds, NUM_POLLFDS);
452       s = v = 0;
453 
454       for (j = 0; j < NUM_POLLEES / 2; j++)
455         s += send (opp_sockets[j], (const char *) &t, 1, 0) == 1 ? 1 : 0;
456       g_assert (PostMessage (NULL, WM_APP, 1, 2));
457 
458       /* This is to ensure that all sockets catch up, otherwise some might not poll active */
459       g_usleep (G_USEC_PER_SEC / 1000);
460 
461       times[i][0] = g_get_monotonic_time ();
462       r = g_poll (fds, NUM_POLLFDS, 1000);
463       times[i][1] = g_get_monotonic_time ();
464       check_fds (sockets, fds, NUM_POLLEES);
465       for (j = 0; j < NUM_POLLEES / 2; j++)
466         v += recv (sockets[j], (char *) &t, 1, 0) == 1 ? 1 : 0;
467       found_app = FALSE;
468       while (!found_app && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
469         if (msg.message == WM_APP && msg.wParam == 1 && msg.lParam == 2)
470           found_app = TRUE;
471       g_assert (s == NUM_POLLEES / 2);
472       g_assert (r == NUM_POLLEES / 2 + 1);
473       g_assert (v == NUM_POLLEES / 2);
474       g_assert (found_app);
475 
476       reset_fds (fds, NUM_POLLEES);
477       reset_fds_msg (fds, NUM_POLLFDS);
478       r = g_poll (fds, NUM_POLLFDS, 0);
479       check_fds (sockets, fds, NUM_POLLEES);
480       g_assert (r == 0);
481 
482       diff = times[i][1] - times[i][0];
483       if (times_min > diff)
484         times_min = diff;
485       if (times_max < diff)
486         times_max = diff;
487       times_avg += diff;
488       bucketize (diff, buckets, bucket_limits, BUCKET_COUNT);
489     }
490 
491   times_avg /= NUM_POLLEES;
492   g_print ("half-socket + msg poll time:\n%4lldns - %4lldns, average %4lldns\n", times_min, times_max, times_avg);
493   print_buckets (buckets, bucket_limits, BUCKET_COUNT);
494 
495   times_avg = 0;
496   times_min = G_MAXINT64;
497   times_max = 0;
498   memset (buckets, 0, sizeof (gint) * BUCKET_COUNT);
499 
500   for (i = 0; i < REPEAT; i++)
501     {
502       gint r, s, v, t;
503       gint64 diff;
504       gint j;
505 
506       reset_fds (fds, NUM_POLLEES);
507       reset_fds_msg (fds, NUM_POLLFDS);
508       s = v = 0;
509 
510       for (j = 0; j < NUM_POLLEES; j++)
511         s += send (opp_sockets[j], (const char *) &t, 1, 0) == 1 ? 1 : 0;
512 
513       g_usleep (G_USEC_PER_SEC / 1000);
514 
515       times[i][0] = g_get_monotonic_time ();
516       r = g_poll (fds, NUM_POLLFDS, 1000);
517       times[i][1] = g_get_monotonic_time ();
518       check_fds (sockets, fds, NUM_POLLEES);
519       for (j = 0; j < NUM_POLLEES; j++)
520         v += recv (sockets[j], (char *) &t, 1, 0) == 1 ? 1 : 0;
521       g_assert (s == NUM_POLLEES);
522       g_assert (r == NUM_POLLEES);
523       g_assert (v == NUM_POLLEES);
524 
525       reset_fds (fds, NUM_POLLEES);
526       reset_fds_msg (fds, NUM_POLLFDS);
527       r = g_poll (fds, NUM_POLLFDS, 0);
528       check_fds (sockets, fds, NUM_POLLEES);
529       g_assert (r == 0);
530 
531       diff = times[i][1] - times[i][0];
532       if (times_min > diff)
533         times_min = diff;
534       if (times_max < diff)
535         times_max = diff;
536       times_avg += diff;
537       bucketize (diff, buckets, bucket_limits, BUCKET_COUNT);
538     }
539 
540   times_avg /= NUM_POLLEES;
541   g_print ("%d-socket poll time: \n%4lldns - %4lldns, average %4lldns\n", NUM_POLLEES, times_min, times_max, times_avg);
542   print_buckets (buckets, bucket_limits, BUCKET_COUNT);
543 
544   activatable = 0;
545   times_avg = 0;
546   times_min = G_MAXINT64;
547   times_max = 0;
548   memset (buckets, 0, sizeof (gint) * BUCKET_COUNT);
549 
550   for (i = 0; i < REPEAT; i++)
551     {
552       gint r, s, v, t;
553       gint64 diff;
554       gint j;
555       MSG msg;
556       gboolean found_app;
557 
558       reset_fds (fds, NUM_POLLEES);
559       reset_fds_msg (fds, NUM_POLLFDS);
560       s = v = 0;
561 
562       for (j = 0; j < activatable; j++)
563         s += send (opp_sockets[j], (const char *) &t, 1, 0) == 1 ? 1 : 0;
564       g_assert (PostMessage (NULL, WM_APP, 1, 2));
565 
566       g_usleep (G_USEC_PER_SEC / 1000);
567 
568       times[i][0] = g_get_monotonic_time ();
569       r = g_poll (fds, NUM_POLLFDS, 1000);
570       times[i][1] = g_get_monotonic_time ();
571       check_fds (sockets, fds, NUM_POLLEES);
572       for (j = 0; j < activatable; j++)
573         v += recv (sockets[j], (char *) &t, 1, 0) == 1 ? 1 : 0;
574       found_app = FALSE;
575       while (!found_app && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
576         if (msg.message == WM_APP && msg.wParam == 1 && msg.lParam == 2)
577           found_app = TRUE;
578       g_assert (s == activatable);
579       g_assert (r == activatable + 1);
580       g_assert (v == activatable);
581       g_assert (found_app);
582 
583       reset_fds (fds, NUM_POLLEES);
584       reset_fds_msg (fds, NUM_POLLFDS);
585       r = g_poll (fds, NUM_POLLFDS, 0);
586       check_fds (sockets, fds, NUM_POLLEES);
587       g_assert (r == 0);
588 
589       diff = times[i][1] - times[i][0];
590       if (times_min > diff)
591         times_min = diff;
592       if (times_max < diff)
593         times_max = diff;
594       times_avg += diff;
595       bucketize (diff, buckets, bucket_limits, BUCKET_COUNT);
596       activatable = (activatable + 1) % NUM_POLLEES;
597     }
598 
599   times_avg /= NUM_POLLEES;
600   g_print ("variable socket number + msg poll time: \n%4lldns - %4lldns, average %4lldns\n", times_min, times_max, times_avg);
601   print_buckets (buckets, bucket_limits, BUCKET_COUNT);
602 
603   cleanup_sockets (sockets, opp_sockets, NUM_POLLEES);
604 }
605 
606 int
main(int argc,char * argv[])607 main (int   argc,
608       char *argv[])
609 {
610   int result;
611   GMainContext *ctx;
612 
613   g_test_init (&argc, &argv, NULL);
614   init_networking ();
615   ctx = g_main_context_new ();
616 
617   g_test_add_func ("/gpoll/gpoll", test_gpoll);
618 
619   result = g_test_run ();
620 
621   g_main_context_unref (ctx);
622 
623   return result;
624 }
625