-misc bugfixes, travel hacking
[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 3, 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 START_ARM GNUNET_YES
32
33 #define LOG_BACKOFF GNUNET_NO
34
35 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10)
36
37 #define SERVICE_TEST_TIMEOUT GNUNET_TIME_UNIT_FOREVER_REL
38
39 #define FIVE_MILLISECONDS GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 5)
40
41
42 static const struct GNUNET_CONFIGURATION_Handle *cfg;
43
44 static struct GNUNET_ARM_Handle *arm;
45
46 static int ok = 1;
47
48 static int trialCount;
49
50 static struct GNUNET_TIME_Absolute startedWaitingAt;
51
52 struct GNUNET_TIME_Relative waitedFor;
53
54 #if LOG_BACKOFF
55 static FILE *killLogFilePtr;
56
57 static char *killLogFileName;
58 #endif
59
60
61 /**
62  * Context for handling the shutdown of a service.
63  */
64 struct ShutdownContext
65 {
66   /**
67    * Connection to the service that is being shutdown.
68    */
69   struct GNUNET_CLIENT_Connection *sock;
70
71   /**
72    * Time allowed for shutdown to happen.
73    */
74   struct GNUNET_TIME_Absolute timeout;
75
76   /**
77    * Task set up to cancel the shutdown request on timeout.
78    */
79   GNUNET_SCHEDULER_TaskIdentifier cancel_task;
80
81   /**
82    * Task to call once shutdown complete
83    */
84   GNUNET_CLIENT_ShutdownTask cont;
85
86   /**
87    * Closure for shutdown continuation
88    */
89   void *cont_cls;
90
91   /**
92    * We received a confirmation that the service will shut down.
93    */
94   int confirmed;
95
96 };
97
98 /**
99  * Handler receiving response to service shutdown requests.
100  * First call with NULL: service misbehaving, or something.
101  * First call with GNUNET_MESSAGE_TYPE_ARM_SHUTDOWN:
102  *   - service will shutdown
103  * Second call with NULL:
104  *   - service has now really shut down.
105  *
106  * @param cls closure
107  * @param msg NULL, indicating socket closure.
108  */
109 static void
110 service_shutdown_handler (void *cls, const struct GNUNET_MessageHeader *msg)
111 {
112   struct ShutdownContext *shutdown_ctx = cls;
113
114   if (msg == NULL) 
115   {
116     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Service shutdown complete.\n");
117     if (shutdown_ctx->cont != NULL)
118       shutdown_ctx->cont (shutdown_ctx->cont_cls, GNUNET_NO);
119     
120     GNUNET_SCHEDULER_cancel (shutdown_ctx->cancel_task);
121     GNUNET_CLIENT_disconnect (shutdown_ctx->sock);
122     GNUNET_free (shutdown_ctx);
123     return;
124   }
125   GNUNET_assert (ntohs (msg->size) ==
126                  sizeof (struct GNUNET_MessageHeader));
127   switch (ntohs (msg->type))
128   {
129   case GNUNET_MESSAGE_TYPE_ARM_SHUTDOWN:
130     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
131                 "Received confirmation for service shutdown.\n");
132     shutdown_ctx->confirmed = GNUNET_YES;
133     GNUNET_CLIENT_receive (shutdown_ctx->sock,
134                            &service_shutdown_handler, shutdown_ctx,
135                            GNUNET_TIME_UNIT_FOREVER_REL);
136     break;
137   default:              /* Fall through */
138     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
139                 "Service shutdown refused!\n");
140     if (shutdown_ctx->cont != NULL)
141       shutdown_ctx->cont (shutdown_ctx->cont_cls, GNUNET_YES);
142     
143     GNUNET_SCHEDULER_cancel (shutdown_ctx->cancel_task);
144     GNUNET_CLIENT_disconnect (shutdown_ctx->sock);
145     GNUNET_free (shutdown_ctx);
146     break;
147   }
148 }
149
150
151 /**
152  * Shutting down took too long, cancel receive and return error.
153  *
154  * @param cls closure
155  * @param tc context information (why was this task triggered now)
156  */
157 void
158 service_shutdown_cancel (void *cls,
159                          const struct GNUNET_SCHEDULER_TaskContext *tc)
160 {
161   struct ShutdownContext *shutdown_ctx = cls;
162
163   GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "service_shutdown_cancel called!\n");
164   shutdown_ctx->cont (shutdown_ctx->cont_cls, GNUNET_SYSERR);
165   GNUNET_CLIENT_disconnect (shutdown_ctx->sock);
166   GNUNET_free (shutdown_ctx);
167 }
168
169
170 /**
171  * If possible, write a shutdown message to the target
172  * buffer and destroy the client connection.
173  *
174  * @param cls the "struct GNUNET_CLIENT_Connection" to destroy
175  * @param size number of bytes available in buf
176  * @param buf NULL on error, otherwise target buffer
177  * @return number of bytes written to buf
178  */
179 static size_t
180 write_shutdown (void *cls, size_t size, void *buf)
181 {
182   struct GNUNET_MessageHeader *msg;
183   struct ShutdownContext *shutdown_ctx = cls;
184
185   if (size < sizeof (struct GNUNET_MessageHeader))
186     {
187       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
188                   _("Failed to transmit shutdown request to client.\n"));
189       shutdown_ctx->cont (shutdown_ctx->cont_cls, GNUNET_SYSERR);
190       GNUNET_CLIENT_disconnect (shutdown_ctx->sock);
191       GNUNET_free (shutdown_ctx);
192       return 0;                 /* client disconnected */
193     }
194
195   GNUNET_CLIENT_receive (shutdown_ctx->sock, &service_shutdown_handler,
196                          shutdown_ctx, GNUNET_TIME_UNIT_FOREVER_REL);
197   shutdown_ctx->cancel_task =
198     GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining
199                                   (shutdown_ctx->timeout),
200                                   &service_shutdown_cancel, shutdown_ctx);
201   msg = (struct GNUNET_MessageHeader *) buf;
202   msg->type = htons (GNUNET_MESSAGE_TYPE_ARM_SHUTDOWN);
203   msg->size = htons (sizeof (struct GNUNET_MessageHeader));
204   return sizeof (struct GNUNET_MessageHeader);
205 }
206
207
208 /**
209  * Request that the service should shutdown.
210  * Afterwards, the connection will automatically be
211  * disconnected.  Hence the "sock" should not
212  * be used by the caller after this call
213  * (calling this function frees "sock" after a while).
214  *
215  * @param sock the socket connected to the service
216  * @param timeout how long to wait before giving up on transmission
217  * @param cont continuation to call once the service is really down
218  * @param cont_cls closure for continuation
219  *
220  */
221 static void
222 arm_service_shutdown (struct GNUNET_CLIENT_Connection *sock,
223                       struct GNUNET_TIME_Relative timeout,
224                       GNUNET_CLIENT_ShutdownTask cont, void *cont_cls)
225 {
226   struct ShutdownContext *shutdown_ctx;
227
228   shutdown_ctx = GNUNET_malloc (sizeof (struct ShutdownContext));
229   shutdown_ctx->cont = cont;
230   shutdown_ctx->cont_cls = cont_cls;
231   shutdown_ctx->sock = sock;
232   shutdown_ctx->timeout = GNUNET_TIME_relative_to_absolute (timeout);
233   GNUNET_CLIENT_notify_transmit_ready (sock,
234                                        sizeof (struct GNUNET_MessageHeader),
235                                        timeout, GNUNET_NO, &write_shutdown,
236                                        shutdown_ctx);
237 }
238
239
240 static void
241 arm_notify_stop (void *cls, enum GNUNET_ARM_ProcessStatus status)
242 {
243   GNUNET_assert ( (status == GNUNET_ARM_PROCESS_DOWN) ||
244                   (status == GNUNET_ARM_PROCESS_ALREADY_DOWN) );
245 #if START_ARM
246   GNUNET_ARM_stop_service (arm, "arm", TIMEOUT, NULL, NULL);
247 #endif
248 }
249
250
251 static void
252 kill_task (void *cbData, const struct GNUNET_SCHEDULER_TaskContext *tc);
253
254
255 static void
256 do_nothing_notify (void *cls, enum GNUNET_ARM_ProcessStatus status)
257 {
258   GNUNET_assert (status == GNUNET_ARM_PROCESS_STARTING);
259   ok = 1;
260   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &kill_task, NULL);
261 }
262
263 static void
264 arm_notify (void *cls, enum GNUNET_ARM_ProcessStatus status)
265 {
266   GNUNET_assert (status == GNUNET_ARM_PROCESS_STARTING);
267   GNUNET_ARM_start_service (arm, "do-nothing", GNUNET_OS_INHERIT_STD_OUT_AND_ERR, TIMEOUT, &do_nothing_notify,
268                             NULL);
269 }
270
271
272 static void
273 kill_task (void *cbData, const struct GNUNET_SCHEDULER_TaskContext *tc);
274
275
276 static void
277 do_nothing_restarted_notify_task (void *cls,
278                                   const struct GNUNET_SCHEDULER_TaskContext
279                                   *tc)
280 {
281   static char a;
282
283   trialCount++;
284
285 #if LOG_BACKOFF
286   if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0)
287     {
288       FPRINTF (killLogFilePtr, "%d.Reason is shutdown!\n", trialCount);
289     }
290   else if ((tc->reason & GNUNET_SCHEDULER_REASON_TIMEOUT) != 0)
291     {
292       FPRINTF (killLogFilePtr, "%d.Reason is timeout!\n", trialCount);
293     }
294   else if ((tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE) != 0)
295     {
296       FPRINTF (killLogFilePtr, "%d.Service is running!\n", trialCount);
297     }
298 #endif
299   GNUNET_SCHEDULER_add_now (&kill_task, &a);
300 }
301
302
303 static void
304 do_test (void *cbData, const struct GNUNET_SCHEDULER_TaskContext *tc)
305 {
306   GNUNET_CLIENT_service_test ("do-nothing", cfg, TIMEOUT,
307                               &do_nothing_restarted_notify_task, NULL);
308 }
309
310
311 static void
312 shutdown_cont (void *cls, int reason)
313 {
314   trialCount++;
315   startedWaitingAt = GNUNET_TIME_absolute_get ();
316   GNUNET_SCHEDULER_add_delayed (waitedFor, &do_test, NULL);
317 }
318
319
320 static void
321 kill_task (void *cbData, const struct GNUNET_SCHEDULER_TaskContext *tc)
322 {
323   static struct GNUNET_CLIENT_Connection *doNothingConnection = NULL;
324
325   if (NULL != cbData)
326     {
327       waitedFor = GNUNET_TIME_absolute_get_duration (startedWaitingAt);
328
329 #if LOG_BACKOFF
330       FPRINTF (killLogFilePtr, "Waited for: %llu ms\n",
331                (unsigned long long) waitedFor.rel_value);
332 #endif
333     }
334   else
335     {
336       waitedFor.rel_value = 0;
337     }
338   /* Connect to the doNothing task */
339   doNothingConnection = GNUNET_CLIENT_connect ("do-nothing", cfg);
340   GNUNET_assert (doNothingConnection != NULL);
341   if (trialCount == 12)
342     {
343       GNUNET_CLIENT_disconnect (doNothingConnection);
344       GNUNET_ARM_stop_service (arm, "do-nothing", TIMEOUT, &arm_notify_stop,
345                                NULL);
346       ok = 0;
347       return;
348     }
349   /* Use the created connection to kill the doNothingTask */
350   arm_service_shutdown (doNothingConnection, TIMEOUT, &shutdown_cont, NULL);
351 }
352
353
354 static void
355 task (void *cls, char *const *args, const char *cfgfile,
356       const struct GNUNET_CONFIGURATION_Handle *c)
357 {
358   cfg = c;
359
360   arm = GNUNET_ARM_connect (cfg, NULL);
361 #if START_ARM
362   GNUNET_ARM_start_service (arm, "arm", GNUNET_OS_INHERIT_STD_OUT_AND_ERR, GNUNET_TIME_UNIT_ZERO, &arm_notify,
363                             NULL);
364 #else
365   arm_do_nothing (NULL, GNUNET_YES);
366 #endif
367 }
368
369
370 static int
371 check ()
372 {
373   char *const argv[] = {
374     "test-exponential-backoff",
375     "-c", "test_arm_api_data.conf",
376     NULL
377   };
378   struct GNUNET_GETOPT_CommandLineOption options[] = {
379     GNUNET_GETOPT_OPTION_END
380   };
381
382   /* Running ARM  and running the do_nothing task */
383   GNUNET_assert (GNUNET_OK ==
384                  GNUNET_PROGRAM_run ((sizeof (argv) / sizeof (char *)) - 1,
385                                      argv, "test-exponential-backoff",
386                                      "nohelp", options, &task, NULL));
387
388
389   return ok;
390 }
391
392 static int
393 init ()
394 {
395 #if LOG_BACKOFF
396   killLogFileName = GNUNET_DISK_mktemp ("exponential-backoff-waiting.log");
397   if (NULL == (killLogFilePtr = FOPEN (killLogFileName, "w")))
398     {
399       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "fopen",
400                                 killLogFileName);
401       GNUNET_free (killLogFileName);
402       return GNUNET_SYSERR;
403     }
404 #endif
405   return GNUNET_OK;
406 }
407
408
409 static void
410 houseKeep ()
411 {
412 #if LOG_BACKOFF
413   GNUNET_assert (0 == fclose (killLogFilePtr));
414   GNUNET_free (killLogFileName);
415 #endif
416 }
417
418
419 int
420 main (int argc, char *argv[])
421 {
422   int ret;
423
424   GNUNET_log_setup ("test-exponential-backoff",
425                     "WARNING",
426                     NULL);
427
428   init ();
429   ret = check ();
430   houseKeep ();
431   return ret;
432 }
433
434 /* end of test_exponential_backoff.c */