misc fixes
[oweals/gnunet.git] / src / arm / test_exponential_backoff.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file arm/test_exponential_backoff.c
22  * @brief testcase for gnunet-service-arm.c
23  */
24 #include "platform.h"
25 #include "gnunet_arm_service.h"
26 #include "gnunet_client_lib.h"
27 #include "gnunet_configuration_lib.h"
28 #include "gnunet_program_lib.h"
29 #include "gnunet_protocols.h"
30
31 #define VERBOSE GNUNET_NO
32 #define START_ARM GNUNET_YES
33 #define LOG_BACKOFF GNUNET_NO
34 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10)
35 #define SERVICE_TEST_TIMEOUT GNUNET_TIME_UNIT_FOREVER_REL
36 #define FIVE_MILLISECONDS GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 5)
37
38 static struct GNUNET_SCHEDULER_Handle *sched;
39 static const struct GNUNET_CONFIGURATION_Handle *cfg;
40 static struct GNUNET_ARM_Handle *arm;
41 static int ok = 1;
42
43 static int trialCount;
44 static struct GNUNET_TIME_Absolute startedWaitingAt;
45 struct GNUNET_TIME_Relative waitedFor;
46
47 #if LOG_BACKOFF
48 static FILE *killLogFilePtr;
49 static char *killLogFileName;
50 #endif
51
52
53 /**
54  * Context for handling the shutdown of a service.
55  */
56 struct ShutdownContext
57 {
58   /**
59    * Scheduler to be used to call continuation
60    */
61   struct GNUNET_SCHEDULER_Handle *sched;
62   /**
63    * Connection to the service that is being shutdown.
64    */
65   struct GNUNET_CLIENT_Connection *sock;
66
67   /**
68    * Time allowed for shutdown to happen.
69    */
70   struct GNUNET_TIME_Absolute timeout;
71
72   /**
73    * Task set up to cancel the shutdown request on timeout.
74    */
75   GNUNET_SCHEDULER_TaskIdentifier cancel_task;
76
77   /**
78    * Task to call once shutdown complete
79    */
80   GNUNET_CLIENT_ShutdownTask cont;
81
82   /**
83    * Closure for shutdown continuation
84    */
85   void *cont_cls;
86
87   /**
88    * We received a confirmation that the service will shut down.
89    */
90   int confirmed;
91
92 };
93
94 /**
95  * Handler receiving response to service shutdown requests.
96  * First call with NULL: service misbehaving, or something.
97  * First call with GNUNET_MESSAGE_TYPE_ARM_SHUTDOWN_ACK:
98  *   - service will shutdown
99  * Second call with NULL:
100  *   - service has now really shut down.
101  *
102  * @param cls closure
103  * @param msg NULL, indicating socket closure.
104  */
105 static void
106 service_shutdown_handler (void *cls, const struct GNUNET_MessageHeader *msg)
107 {
108   struct ShutdownContext *shutdown_ctx = cls;
109
110   if ((msg == NULL) && (shutdown_ctx->confirmed != GNUNET_YES))   
111     {
112       /* Means the other side closed the connection and never confirmed a shutdown */
113       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 
114                   "Service handle shutdown before ACK!\n");
115       if (shutdown_ctx->cont != NULL)
116         shutdown_ctx->cont(shutdown_ctx->cont_cls, GNUNET_SYSERR);      
117       GNUNET_SCHEDULER_cancel(shutdown_ctx->sched, shutdown_ctx->cancel_task);
118       GNUNET_CLIENT_disconnect (shutdown_ctx->sock, GNUNET_NO);
119       GNUNET_free(shutdown_ctx);
120     }
121   else if ((msg == NULL) && (shutdown_ctx->confirmed == GNUNET_YES))
122     {
123       GNUNET_log(GNUNET_ERROR_TYPE_WARNING, 
124                  "Service shutdown complete.\n");
125       if (shutdown_ctx->cont != NULL)
126         shutdown_ctx->cont(shutdown_ctx->cont_cls, GNUNET_NO);
127
128       GNUNET_SCHEDULER_cancel(shutdown_ctx->sched, shutdown_ctx->cancel_task);
129       GNUNET_CLIENT_disconnect (shutdown_ctx->sock, GNUNET_NO);
130       GNUNET_free(shutdown_ctx);
131     }
132   else
133     {
134       GNUNET_assert(ntohs(msg->size) == sizeof(struct GNUNET_MessageHeader));
135       switch (ntohs(msg->type))
136         {
137         case GNUNET_MESSAGE_TYPE_ARM_SHUTDOWN_ACK:
138           GNUNET_log(GNUNET_ERROR_TYPE_WARNING,
139                      "Received confirmation for service shutdown.\n");
140           shutdown_ctx->confirmed = GNUNET_YES;
141           GNUNET_CLIENT_receive (shutdown_ctx->sock, 
142                                  &service_shutdown_handler, 
143                                  shutdown_ctx, 
144                                  GNUNET_TIME_UNIT_FOREVER_REL);
145           break;
146         default: /* Fall through */
147           GNUNET_log(GNUNET_ERROR_TYPE_WARNING, 
148                      "Service shutdown refused!\n");
149           if (shutdown_ctx->cont != NULL)
150             shutdown_ctx->cont(shutdown_ctx->cont_cls, GNUNET_YES);
151           
152           GNUNET_SCHEDULER_cancel(shutdown_ctx->sched, shutdown_ctx->cancel_task);
153           GNUNET_CLIENT_disconnect (shutdown_ctx->sock, GNUNET_NO);
154           GNUNET_free(shutdown_ctx);
155           break;
156         }
157     }
158 }
159
160 /**
161  * Shutting down took too long, cancel receive and return error.
162  *
163  * @param cls closure
164  * @param tc context information (why was this task triggered now)
165  */
166 void service_shutdown_cancel (void *cls,
167                               const struct GNUNET_SCHEDULER_TaskContext * tc)
168 {
169   struct ShutdownContext *shutdown_ctx = cls;
170   GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "service_shutdown_cancel called!\n");
171   shutdown_ctx->cont(shutdown_ctx->cont_cls, GNUNET_SYSERR);
172   GNUNET_CLIENT_disconnect (shutdown_ctx->sock, GNUNET_NO);
173   GNUNET_free(shutdown_ctx);
174 }
175
176
177 /**
178  * If possible, write a shutdown message to the target
179  * buffer and destroy the client connection.
180  *
181  * @param cls the "struct GNUNET_CLIENT_Connection" to destroy
182  * @param size number of bytes available in buf
183  * @param buf NULL on error, otherwise target buffer
184  * @return number of bytes written to buf
185  */
186 static size_t
187 write_shutdown (void *cls, size_t size, void *buf)
188 {
189   struct GNUNET_MessageHeader *msg;
190   struct ShutdownContext *shutdown_ctx = cls;
191
192   if (size < sizeof (struct GNUNET_MessageHeader))
193     {
194       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
195                   _("Failed to transmit shutdown request to client.\n"));
196       shutdown_ctx->cont(shutdown_ctx->cont_cls, GNUNET_SYSERR);
197       GNUNET_CLIENT_disconnect (shutdown_ctx->sock, GNUNET_NO);
198       GNUNET_free(shutdown_ctx);
199       return 0;                 /* client disconnected */
200     }
201
202   GNUNET_CLIENT_receive (shutdown_ctx->sock,
203                          &service_shutdown_handler, shutdown_ctx, 
204                          GNUNET_TIME_UNIT_FOREVER_REL);
205   shutdown_ctx->cancel_task = GNUNET_SCHEDULER_add_delayed (shutdown_ctx->sched, 
206                                                             GNUNET_TIME_absolute_get_remaining(shutdown_ctx->timeout), 
207                                                             &service_shutdown_cancel, 
208                                                             shutdown_ctx);
209   msg = (struct GNUNET_MessageHeader *) buf;
210   msg->type = htons (GNUNET_MESSAGE_TYPE_ARM_SHUTDOWN);
211   msg->size = htons (sizeof (struct GNUNET_MessageHeader));
212   return sizeof (struct GNUNET_MessageHeader);
213 }
214
215
216 /**
217  * Request that the service should shutdown.
218  * Afterwards, the connection will automatically be
219  * disconnected.  Hence the "sock" should not
220  * be used by the caller after this call
221  * (calling this function frees "sock" after a while).
222  *
223  * @param sched the scheduler to use for calling shutdown continuation
224  * @param sock the socket connected to the service
225  * @param timeout how long to wait before giving up on transmission
226  * @param cont continuation to call once the service is really down
227  * @param cont_cls closure for continuation
228  *
229  */
230 static void
231 arm_service_shutdown (struct GNUNET_SCHEDULER_Handle *sched,
232                       struct GNUNET_CLIENT_Connection *sock,
233                       struct GNUNET_TIME_Relative timeout,
234                       GNUNET_CLIENT_ShutdownTask cont,
235                       void *cont_cls)
236 {
237   struct ShutdownContext *shutdown_ctx;
238   shutdown_ctx = GNUNET_malloc(sizeof(struct ShutdownContext));
239   shutdown_ctx->sched = sched;
240   shutdown_ctx->cont = cont;
241   shutdown_ctx->cont_cls = cont_cls;
242   shutdown_ctx->sock = sock;
243   shutdown_ctx->timeout = GNUNET_TIME_relative_to_absolute(timeout);
244   GNUNET_CLIENT_notify_transmit_ready (sock,
245                                        sizeof (struct
246                                                GNUNET_MessageHeader),
247                                        timeout,
248                                        GNUNET_NO,
249                                        &write_shutdown, shutdown_ctx);
250 }
251
252
253 static void
254 arm_notify_stop (void *cls, int success)
255 {
256   GNUNET_assert (success == GNUNET_NO);
257 #if START_ARM
258   GNUNET_ARM_stop_service (arm, "arm", TIMEOUT, NULL, NULL);
259 #endif
260 }
261
262
263 static void
264 kill_task (void *cbData,
265            const struct GNUNET_SCHEDULER_TaskContext *tc);
266
267
268 static void
269 do_nothing_notify (void *cls, int success)
270 {
271   GNUNET_assert (success == GNUNET_YES);
272   ok = 1;
273   GNUNET_SCHEDULER_add_delayed (sched, GNUNET_TIME_UNIT_SECONDS, 
274                                 &kill_task, NULL);
275 }
276
277
278 static void
279 arm_notify (void *cls, int success)
280
281   GNUNET_assert (success == GNUNET_YES);
282   GNUNET_ARM_start_service (arm, 
283                             "do-nothing", TIMEOUT, 
284                             &do_nothing_notify, NULL);
285 }
286
287
288 static void
289 kill_task (void *cbData,
290                    const struct GNUNET_SCHEDULER_TaskContext *tc);
291
292
293 static void
294 do_nothing_restarted_notify_task (void *cls,
295                                   const struct GNUNET_SCHEDULER_TaskContext *tc)
296 {       
297   static char a;
298   
299   trialCount++;
300
301 #if LOG_BACKOFF  
302   if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0) 
303     {
304       fprintf(killLogFilePtr, 
305               "%d.Reason is shutdown!\n",
306               trialCount);
307     } 
308   else if ((tc->reason & GNUNET_SCHEDULER_REASON_TIMEOUT) != 0) 
309     {
310       fprintf(killLogFilePtr, 
311               "%d.Reason is timeout!\n", 
312               trialCount);
313     }
314   else if ((tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE) != 0) 
315     {
316       fprintf(killLogFilePtr, 
317               "%d.Service is running!\n", 
318               trialCount);
319     }  
320 #endif
321   GNUNET_SCHEDULER_add_now (sched, &kill_task, &a);
322 }
323
324
325 static void
326 do_test (void *cbData,
327          const struct GNUNET_SCHEDULER_TaskContext *tc)
328 {                                     
329   GNUNET_CLIENT_service_test(sched, "do-nothing", 
330                              cfg, TIMEOUT,
331                              &do_nothing_restarted_notify_task, NULL);
332 }
333
334
335 static void
336 shutdown_cont (void *cls, int reason)
337 {
338   trialCount++;
339   startedWaitingAt = GNUNET_TIME_absolute_get();
340   GNUNET_SCHEDULER_add_delayed (sched,
341                                 waitedFor,
342                                 &do_test,
343                                 NULL);
344 }
345
346
347 static void
348 kill_task (void *cbData,
349                    const struct GNUNET_SCHEDULER_TaskContext *tc)
350 {
351   static struct GNUNET_CLIENT_Connection * doNothingConnection = NULL;
352
353   if (NULL != cbData) 
354     {
355       waitedFor = GNUNET_TIME_absolute_get_duration (startedWaitingAt);
356       
357 #if LOG_BACKOFF
358       fprintf(killLogFilePtr, 
359               "Waited for: %llu ms\n", 
360               (unsigned long long) waitedFor.value);
361 #endif
362     }
363   else
364     {
365       waitedFor.value = 0;
366     }
367   /* Connect to the doNothing task */
368   doNothingConnection = GNUNET_CLIENT_connect (sched, "do-nothing", cfg);
369 #if LOG_BACKOFF
370   if (NULL == doNothingConnection)
371     fprintf(killLogFilePtr, 
372             "Unable to connect to do-nothing process!\n");
373 #endif  
374   if (trialCount == 12) {
375     GNUNET_ARM_stop_service (arm, 
376                              "do-nothing", 
377                              TIMEOUT,
378                              &arm_notify_stop, NULL);
379     ok = 0;
380     return;
381   }
382   
383   /* Use the created connection to kill the doNothingTask */
384   arm_service_shutdown(sched,
385                        doNothingConnection, 
386                        TIMEOUT, 
387                        &shutdown_cont, NULL);
388 }
389
390        
391 static void
392 task (void *cls,
393       struct GNUNET_SCHEDULER_Handle *s,
394       char *const *args,
395       const char *cfgfile,
396       const struct GNUNET_CONFIGURATION_Handle *c)
397 {
398   cfg = c;
399   sched = s;
400   
401   arm = GNUNET_ARM_connect (cfg, sched, NULL);
402 #if START_ARM
403   GNUNET_ARM_start_service (arm, "arm", GNUNET_TIME_UNIT_ZERO, &arm_notify, NULL);
404 #else
405   arm_do_nothing (NULL, GNUNET_YES);
406 #endif
407 }
408
409 static int
410 check ()
411 {
412   char *const argv[] = {
413     "test-arm-api",
414     "-c", "test_arm_api_data.conf",
415 #if VERBOSE
416     "-L", "DEBUG",
417 #endif
418     NULL
419   };
420   struct GNUNET_GETOPT_CommandLineOption options[] = {
421     GNUNET_GETOPT_OPTION_END
422   };
423   
424   /* Running ARM  and running the do_nothing task */
425   GNUNET_assert (GNUNET_OK ==
426                  GNUNET_PROGRAM_run ((sizeof (argv) / sizeof (char *)) - 1,
427                                      argv,
428                                      "test-exponential-backoff",
429                                      "nohelp", options, &task, NULL));
430   
431   
432   return ok;
433 }
434
435 static int
436 init()
437 {
438 #if LOG_BACKOFF
439   killLogFileName = GNUNET_DISK_mktemp("exponential-backoff-waiting.log");
440   if (NULL == (killLogFilePtr = FOPEN(killLogFileName, "w"))) {
441     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "fopen", killLogFileName);
442     GNUNET_free (killLogFileName);
443     return GNUNET_SYSERR;
444   }  
445 #endif
446   return GNUNET_OK;
447 }
448
449
450 static void
451 houseKeep()
452 {
453 #if LOG_BACKOFF
454   GNUNET_assert (0 == fclose (killLogFilePtr));
455   GNUNET_free(killLogFileName);
456 #endif
457 }
458
459
460 int
461 main (int argc, char *argv[])
462 {
463   int ret;
464
465   GNUNET_log_setup ("test-exponential-backoff",
466 #if VERBOSE
467                     "DEBUG",
468 #else
469                     "WARNING",
470 #endif
471                     NULL);
472   
473   init();
474   ret = check ();
475   houseKeep();
476   return ret;
477 }
478
479 /* end of test_exponential_backoff.c */